WhatsApp developers recently improved the image loading in which immediately loading some portion of the image (getting its dimension and some pixels of the images) and then after loading the entire image, replace the placeholder with the full image:
My question is, how did they implement it? Do they read the dimension of the image by reading its header (meta-data)? How about the image content? Or do they have two versions of the image at the server-side, a smaller one with low-quality which is loaded first and a bigger one which is the full image? Note that if it's the second approach then they still need to extract the smaller version of the image at the server side once receiving the image from the sender. Any other approaches?
There is another alternative to the colored placeholder solution, which is to show a thumbnail image (may be only a 100 X 100 px) as the placeholder until loading the real image, which is away more cooler than having only a colored placeholder :) .
I did it in Zingoo and made a blog post about it. Using Picasso, you can do it like this:
Transformation blurTransformation = new Transformation() {
#Override
public Bitmap transform(Bitmap source) {
Bitmap blurred = Blur.fastblur(LiveImageView.this.context, source, 10);
source.recycle();
return blurred;
}
#Override
public String key() {
return "blur()";
}
};
Picasso.with(context)
.load(thumbUrl) // thumbnail url goes here
.placeholder(R.drawable.placeholder)
.resize(imageViewWidth, imageViewHeight)
.transform(blurTransformation)
.into(imageView, new Callback() {
#Override
public void onSuccess() {
Picasso.with(context)
.load(url) // image url goes here
.resize(imageViewWidth, imageViewHeight)
.placeholder(imageView.getDrawable())
.into(imageView);
}
#Override
public void onError() {
}
});
more details in the post itself including the Blur class.
Yet another up-to-date answer for an older post. You could try the progressive JPEG streaming feature of the Fresco library to achieve that effect.
Basically all you'd need to do is calling .setProgressiveRenderingEnabled(true) while creating an ImageRequest. I have included a complete example for Fresco's progressive JPEG streaming into my demo application mentioned in this answer, you might want to try it out to see how it works.
For the lazy ones: when working with Fresco, create a DraweeController as following:
ImageRequest imgReq = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
.setProgressiveRenderingEnabled(true)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(imgReq)
.setOldController(yourDrawee.getController())
.build();
yourDrawee.setController(controller);
Note: this approach has some restrictions as explained in the docs.
After several experiments, I got the dimensions without downloading the entire image:
String address = "image_url";
URL url = new URL(address);
URLConnection urlC = url.openConnection();
urlC.connect();
InputStream is = urlC.getInputStream();
for(int i = 0; i < 92; i++) is.read();
nt byte1 = is.read();
int byte2 = is.read();
int width = (byte1 << 8) + byte2;
for(int i = 0; i < 10; i++) is.read();
byte1 = is.read();
byte2 = is.read();
int height = (byte1 << 8) + byte2;
System.out.println("width = " + width + " | height = " + height);
is.close();
Using Glide lib:
Glide.with(context)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView);
Related
We want to know how we can share multiple files (image and txt file) with the Gluon ShareService. Especially how to share an image which was previously taken and stored (in gallery) with the PictureService.
But we need to create a file first with the path and image name. Unfortunately, the PictureService saves the image with the image title consisting of date and time at the moment the picture was taken.
We tried to get the image name with the loadImageFromGallery method but this returns void and opens the recent-screen.
Here what we've tried to share an image:
public void sharePicture() {
Services.get(PicturesService.class).ifPresent(picturesService -> {
Image image = picturesService.loadImageFromGallery().get();
File file= new File("Pictures", image.toString());
Services.get(ShareService.class).ifPresent(service -> {
service.share("image/jpg", file);
});
});
}
How can we store the image where we want with a title we want?
How can we share a file and an image together?
You are on the right track, combining different services from Charm Down, in order to select an image from the gallery and share it.
There is a major problem in this approach, though: You can't convert easily a JavaFX Image into a File.
So far the PicturesService returns only a JavaFX Image, and not a File, so we need a way to save that image into a file that we can read and share.
And the process is not easy since on mobile we don't have SwingUtilities.
The initial approach of using a PixelReader to read the image and get a byte array doesn't really work, as it will give you a big raw file that can't be read or share.
I've used this solution that makes use of a PNG encoder to get the byte array of a png from a JavaFX image:
PngEncoderFX encoder = new PngEncoderFX(image, true);
byte[] bytes = encoder.pngEncode();
Then I'll save that byte array into a file in the public storage folder (so it can be shared), that I can retrieve using the `StorageService:
private File getImageFile(Image image) {
if (image == null) {
return null;
}
// 1. Encode image to png
PngEncoderFX encoder = new PngEncoderFX(image, true);
byte[] bytes = encoder.pngEncode();
// 2.Write byte array to a file in public storage
File root = Services.get(StorageService.class)
.flatMap(storage -> storage.getPublicStorage("Pictures"))
.orElse(null);
if (root != null) {
File file = new File(root, "Image-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuuMMdd-HHmmss")) + ".png");
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(bytes);
return file;
} catch (IOException ex) {
System.out.println("Error: " + ex);
}
}
return null;
}
Now, you can call the PicturesService, retrieve the image, save it to the file and finally share it:
Services.get(PicturesService.class).ifPresent(pictures -> {
// 1. Retrieve picture from gallery
pictures.loadImageFromGallery().ifPresent(image -> {
// 2. Convert image to file
File imageFile = getImageFile(image);
// 3. Share file
if (imageFile != null) {
Services.get(ShareService.class).ifPresent(share -> {
share.share("image/png", imageFile);
});
}
});
});
Note that you may run into memory issues if you try to encode big images.
Anyway, all the process could be simplified if the PicturesService will return a file in the first place. If you want to file an issue, you can do it here.
EDIT
A possible solution to avoid memory issues, and to reduce the size of the shared file, and based on this solution, is scaling down the original image, if it exceeds certain size, like it is already done in the iOS implementation of the PicturesService:
private Image scaleImage(Image source) {
// Possible limit based on memory limitations
double maxResolution = 1280;
double width = source.getWidth();
double height = source.getHeight();
double targetWidth = width;
double targetHeight = height;
if (width > maxResolution || height > maxResolution) {
double ratio = width/height;
if (ratio > 1) {
targetWidth = maxResolution;
targetHeight = targetWidth/ ratio;
}
else {
targetHeight = maxResolution;
targetWidth = targetHeight * ratio;
}
}
ImageView imageView = new ImageView(source);
imageView.setPreserveRatio(true);
imageView.setFitWidth(targetWidth);
imageView.setFitHeight(targetHeight);
return imageView.snapshot(null, null);
}
This method can be used now in getImageFile():
// 1 Scale image to avoid memory issues
Image scaledImage = scaleImage(image);
// 2. Encode image to png
PngEncoderFX encoder = new PngEncoderFX(scaledImage, true);
byte[] bytes = encoder.pngEncode();
// 3. Write byte array to a file in public storage
...
I looked through the internet but I could not find an answer. I have a pgm file which I use as a BufferedImage to do a convolution (I use JAI for that) but I am having trouble in saving it back to a pgm file.
So far I used following code to save:
JAI.create("filestore", newImage, outputFileName);
With that I get a pgm file but when I open the image IfranView tells me that it is a TIF file with incorrect extension. What do I need to change?
I appreciate any help! Please provide code examples if possible. Thanks everyone.
Kind regards,
Staniel
BufferedImage bufferedImage = ImageIO.read(new File("input file directory...image.png"));
ImageIO.write(bufferedImage, "pgm", new File("output file directory.....image.pgm"));
This should take a buffered image (jpeg, png...etc) and convert it properly to a pgm.
EDIT: The JAI Plugin which allows for .pgm files to be used with ImageIO can be found at http://www.oracle.com/technetwork/java/index.html
Here's an example I found. Not tested.
// Create the OutputStream.
OutputStream out = new FileOutputStream(fileToWriteTo);
// Create the ParameterBlock.
PNMEncodeParam param = new PNMEncodeParam();
param.setRaw(true.equals("raw"));
//Create the PNM image encoder.
ImageEncoder encoder = ImageCodec.createImageEncoder("PNM", out, param);
See Writing PNM Files.
I found the answer. I already added the external JAI imageio to my library.
ImageIO.write(bufferedImage, "pnm", new File("output file directory.....image.pgm"));
Instead of "pgm" it should say "pnm". The new file will automatically have the pgm extension.
Maybe late, but I just wrote one. A simple PGM writer taking a 2d-double array with values in range [0.0,1.0].
public static void WritePGM(string fileName, double[,] bitmap)
{
var width = bitmap.GetLength(0);
var height = bitmap.GetLength(1);
var header = "P5\n" + width + " " + height + "\n255\n";
var writer = new BinaryWriter(new FileStream(fileName, FileMode.Create));
writer.Write(System.Text.Encoding.ASCII.GetBytes(header));
for (var j = 0; j < height; j++)
{
for (var i = width-1; i >= 0; i--)
{
var c = (byte)(System.Math.Max(System.Math.Min(1.0, bitmap[i, j]), 0.0) * 255.0);
writer.Write(c);
}
}
writer.Close();
}
First of all, im asking this specific question, because i've already read many examples around this topic, but none of them really helped.
My problem is that i'd like to convert and display a jpg image. I don't have the original image, it is on a server. The data comes through a stream (socket) and it is given in a byte array. In this byte array, every 4 bytes represents information about a pixel. I managed to get the RGBs and convert/save them into a bmp image, however i couldn't find a working solution to make a jpg.
On the other hand, i have a BMP :) how can i display it in a JLabel or JPanel? I also read abou imageicon ImageIO etc. but it doesn't work for me. I use ScheduleExecutorSystem btw. Maybe that is the problem?
Here's the encoder code (Copyright (C) 2013 Philipp C. Heckel ):
public static void encodeToBitmap(byte[] srcBytes, OutputStream destStream)
throws IOException {
int imageWidth = 1024;
int imageHeight = 1080;
int imageBytes = imageWidth * imageHeight * 3;
int filesizeBytes = imageBytes + BMP_SIZE_HEADER;
byte[] header = BMP_HEADER.clone(); // Clone bitmap header template, and
// overwrite with fields
header = writeIntLE(header, BMP_OFFSET_FILESIZE_BYTES, filesizeBytes);
header = writeIntLE(header, BMP_OFFSET_IMAGE_WIDTH, imageWidth);
header = writeIntLE(header, BMP_OFFSET_IMAGE_HEIGHT, imageHeight);
header = writeIntLE(header, BMP_OFFSET_IMAGE_DATA_BYTES, 0);
header = writeIntLE(header, BMP_OFFSET_PAYLOAD_LENGTH,
(int) srcBytes.length);
// WRITE TO STREAM
// Add payload
destStream.write(header, 0, header.length);
for (int offset = imageBytes - imageWidth * 3; offset >= 0; offset -= imageWidth * 3) {
for (int i = 0; i < (imageWidth) * 3; i++) {
destStream.write(srcBytes[offset + i]);
}
}
destStream.close();
}
And the code, how i use it:
BitmapEncoder.encodeToBitmap(RGBvalues, new FileOutputStream("path to file"));
RGBvalues - bytes of 3, with the RGB values (i don't say!! :P )
image width and Height is fix for debug purpose
I have a JPanel and a JLabel within, and i added a default ImageIcon to the JLabel (a jpg image), and it works fine.
After i saved the image, i try :
ImageIcon icon = new ImageIcon("path to file");
pictureLabel.setIcon(icon);
It only makes the default disappear. Am i forgetting to set something?
Is this the fastest way, to do it?
The ImageIcon class only directly supports reading PNG, GIF and JPEG types. You need to use ImageIO to read your bitmap into a generic Image object (which is supported by ImageIcon) and then pass that to ImageIcon. Here's how I'd modify your code to work:
File imgFile = new File("path to file");
Image image = javax.imageio.ImageIO.read(imgFile);
ImageIcon icon = new ImageIcon(image);
pictureLabel.setIcon(icon);
Note that you can also use ImageIO to convert your bitmap to JPEG format.
I am trying to read 2D Data matrix barcode using zxing library(GenericMultipleBarcodeReader). I have multiple barcodes on a single image.
The problem is that the efficiency of the zing reader is very low, it
recognizes 1 barcode from image 1.png and no barcode from image 2.png which has 48 barcodes. Is there
any way to get 100% efficiency or any other library which results 100%
My code to read barcode is:
public static void main(String[] args) throws Exception {
BufferedImage image = ImageIO.read(new File("1.png"));
if (image != null) {
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
DataMatrixReader dataMatrixReader = new DataMatrixReader();
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>();
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
GenericMultipleBarcodeReader reader = new GenericMultipleBarcodeReader(
dataMatrixReader);
Result[] results = reader.decodeMultiple(bitmap, hints);
for (Result result : results) {
System.out.println(result.toString());
}
}
}
And images I used are:
Please help to resolve this issue.
Thanks
It doesn't quite work this way. It will not read barcodes in a grid, as it makes an assumption that it can cut up the image in a certain way that won't be compatible with grids. You will have to write your own method to cut up the image into scannable regions.
It is also the case that the Data Matrix decoder assumes the center of the image is inside the barcode. This is another reason you need to pre-chop the image into squares around the cylinders and then scan. It ought to work fairly well then.
An alternative solution is to consider a barcode engine that can detect multiple barcodes in various orientations on one document. If you're running on Windows, the ClearImage Barcode SDK has a Java API and should be able to handle your needs without pre-processing. You can test if their engine can read your image using their Online Barcode Reader.
Some sample code:
public static void testDataMatrix () {
try {
String filename = "1.png ";
CiServer objCi = new CiServer();
Ci = objCi.getICiServer();
ICiDataMatrix reader = Ci.CreateDataMatrix(); // read DataMatrix Barcode
reader.getImage().Open(filename, 1);
int n = reader.Find(0); // find all the barcodes in the doc
for (i = 1; i <= n; i++) {
ICiBarcode Bc = reader.getBarcodes().getItem(i); // getItem is 1-based
System.out.println("Barcode " + i + " has Text: " + Bc.getText());
}
} catch (Exception ex) {System.out.println(ex.getMessage());}
}
Disclaimer: I've done some work for Inlite in the past.
I am trying to implement a simple class that will allow a user to crop an image to be used for their profile picture. This is a java web application.
I have done some searching and found that java.awt has a BufferedImage class, and this appears (at first glance) to be perfect for what I need. However, it seems that there is a bug in this (or perhaps java, as I have seen suggested) that means that the cropping does not always work correctly.
Here is the code I am using to try to crop my image:
BufferedImage profileImage = getProfileImage(form, modelMap);
if (profileImage != null) {
BufferedImage croppedImage = profileImage
.getSubimage(form.getStartX(), form.getStartY(), form.getWidth(), form.getHeight());
System.err.println(form.getStartX());
System.err.println(form.getStartY());
File finalProfileImage = new File(form.getProfileImage());
try {
String imageType = getImageType(form.getProfileImage());
ImageIO.write(croppedImage, imageType, finalProfileImage);
}
catch (Exception e) {
logger.error("Unable to write cropped image", e);
}
}
return modelAndView;
}
protected BufferedImage getProfileImage(CropImageForm form, Map<String, Object> modelMap) {
String profileImageFileName = form.getProfileImage();
if (validImage(profileImageFileName) && imageExists(profileImageFileName)) {
BufferedImage image = null;
try {
image = getCroppableImage(form, ImageIO.read(new File(profileImageFileName)), modelMap);
}
catch (IOException e) {
logger.error("Unable to crop image, could not read profile image: [" + profileImageFileName + "]");
modelMap.put("errorMessage", "Unable to crop image. Please try again");
return null;
}
return image;
}
modelMap.put("errorMessage", "Unable to crop image. Please try again.");
return null;
}
private boolean imageExists(String profileImageFileName) {
return new File(profileImageFileName).exists();
}
private BufferedImage getCroppableImage(CropImageForm form, BufferedImage image, Map<String, Object> modelMap) {
int cropHeight = form.getHeight();
int cropWidth = form.getWidth();
if (cropHeight <= image.getHeight() && cropWidth <= image.getWidth()) {
return image;
}
modelMap.put("errorMessage", "Unable to crop image. Crop size larger than image.");
return null;
}
private boolean validImage(String profileImageFileName) {
String extension = getImageType(profileImageFileName);
return (extension.equals("jpg") || extension.equals("gif") || extension.equals("png"));
}
private String getImageType(String profileImageFileName) {
int indexOfSeparator = profileImageFileName.lastIndexOf(".");
return profileImageFileName.substring(indexOfSeparator + 1);
}
The form referred to in this code snippet is a simple POJO which contains integer values of the upper left corner to start cropping (startX and startY) and the width and height to make the new image.
What I end up with, however, is a cropped image that always starts at 0,0 rather than the startX and startY position. I have inspected the code to make sure the proper values are being passed in to the getSubimage method, and they appear to be.
Are there simple alternatives to using BufferedImage for cropping an image. I have taken a brief look at JAI. I would rather add a jar to my application than update the jdk installed on all of the production boxes, as well as any development/testing servers and local workstations.
My criteria for selecting an alternative are:
1) simple to use to crop an image as this is all I will be using it for
2) if not built into java or spring, the jar should be small and easily deployable in a web-app
Any suggestions?
Note: The comment above that there is an issue with bufferedImage or Java was something I saw in this posting: Guidance on the BufferedImage.getSubimage(int x, int y, int w, int h) method?
I have used getSubimage() numerous times before without any problems. Have you added a System.out.println(form.getStartX() + " " + form.getStartY()) before that call to make sure they're not both 0?
Also, are you at least getting an image that is form.getWidth() x form.getHeight()?
Do make sure you are not modifying/disposing profileImage in any way since the returned BufferedImage shares the same data array as the parent.
The best way is to just simply draw it across if you want a completely new and independent BufferedImage:
BufferedImage croppedImage = new BufferedImage(form.getWidth(),form.getHeight(),BufferedImage.TYPE_INT_ARGB);
Graphics g = croppedImage.getGraphics();
g.drawImage(profileImage,0,0,form.getWidth(),form.getHeight(),form.getStartX(),form.getStartY(),form.getWidth(),form.getHeight(),null);
g.dispose();
You can do it in this manner as well (code is not 100% tested as I adopted for example from an existing app i did):
import javax.imageio.*;
import java.awt.image.*;
import java.awt.geom.*;
...
BufferedImage img = ImageIO.read(imageStream);
...
/*
* w = image width, h = image height, l = crop left, t = crop top
*/
ColorModel dstCM = img.getColorModel();
BufferedImage dst = new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(w, h), dstCM.isAlphaPremultiplied(), null);
Graphics2D g = dst.createGraphics();
g.drawRenderedImage(img, AffineTransform.getTranslateInstance(-l,-t));
g.dispose();
java.io.File outputfile = new java.io.File(sessionScope.get('absolutePath') + java.io.File.separator + sessionScope.get('lastUpload'));
ImageIO.write(dst, 'png', outputfile);
Thanks for all who replied. It turns out that the problem was not in the cropping code at all.
When I displayed the image to be cropped, I resized it to fit into my layout nicely, then used a javascript cropping tool to figure out the coordinates to crop.
Since I had resized my image, but didn't take the resizing into account when I was determining the cropping coordinates, I ended up with coordinates that appeared to coincide with the top left corner.
I have changed the display to no longer resize the image, and now cropping is working beautifully.