android better save image option? - java

Currently I am using the compress method to save an image taken with the camera hardware on the android phone to the SD card.
try {
BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize = 10;
Bitmap myImage = BitmapFactory.decodeByteArray(imageData, 0,
imageData.length,options);
fileOutputStream = new FileOutputStream(
sdImageMainDirectory.toString() +"/"+fileName+".png");
BufferedOutputStream bos = new BufferedOutputStream(
fileOutputStream);
myImage.compress(CompressFormat.PNG, 100, bos);
bos.flush();
bos.close();
Now this works perfectly fine, however, the quality of image it saves hardly makes it worth taking the picture in the first place. I'm looking for a better way to save the picture at a higher quality.

options.inSampleSize = 10;
here is your loss of quality: This will create an image of 1/10 in heigth and width
From the doc:
If set to a value > 1, requests the decoder to subsample the original
image, returning a smaller image to save memory. The sample size is
the number of pixels in either dimension that correspond to a single
pixel in the decoded bitmap. For example, inSampleSize == 4 returns an
image that is 1/4 the width/height of the original, and 1/16 the
number of pixels. Any value <= 1 is treated the same as 1. Note: the
decoder will try to fulfill this request, but the resulting bitmap may
have different dimensions that precisely what has been requested.
Also, powers of 2 are often faster/easier for the decoder to honor.

Related

Java Image BufferedImage loosing aspect ratio of upright image

I am storing an Image in the filesystem like:
FileImageOutputStream fos = new FileImageOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
And all images are stored correctly in the filesystem. (Also with correct aspect ratio with width and height!)
However, later, I am loading my image like:
File imgFile ...
FileImageInputStream stream = new FileImageInputStream(imgFile);
BufferedImage srcImage = ImageIO.read(stream);
And I want to get the width and height for my images like:
int actualHeight = srcImage.getHeight();
int actualWidth = srcImage.getWidth();
This works totally fine for images in landscape format.
However, for images in upfront format, the width and height is always swapped, so that the width is the height of the orginial image. So, e.g. I have an image that is 200x500 (width x height) and the two integers above are actualHeight = 200 and actualWidth = 500.
Am I missing something?
Most likely, your images are Exif images ("Exif-in-JPEG") from a digital camera/phone. For such images, the pixel data is often stored in the "natural" orientation of the sensor, which is always the same (usually landscape). For portrait images, the orientation is only stored in the Exif metadata, and the ImageIO JPEG plugin doesn't take this orientation into account.
Some cameras, like my Canon DSLR, has an option to do in-camera rotation to match the orientation, but this feature is typically disabled by default. This is obviously only a possible fix if you control the input images.
To fix this in the Java side, you hava some options. You already mentioned using Thumbnailator:
BufferedImage srcImage = Thumbnails.of(new FileInputStream(imgFile))
.scale(1)
.asBufferedImage();
Another option is to use EXIFUtilities from TwelveMonkeys (I'm the author of that library):
IIOImage image = EXIFUtilities.readWithOrientation(imgFile);
BufferedImage srcImage = (BufferedImage) image.getRenderedImage();
Or, if you don't need the other metadata for anything:
BufferedImage srcImage = (BufferedImage) EXIFUtilities.readWithOrientation(imgFile)
.getRenderedImage();

Size prediction of a scaled down image

I have a JPEG file of 2550x3300 size (this file was created with quality level 90). The physical size of the file is 2.5 MB. I would like to scale down this image to 1288x1864 (50% of the original dimension) and save with same quality 90. But I want to know the physical size of the down sampled image in advance, even before doing the actual scale down.
Any help is appreciated!
Here is the code I am using,
`
//Decodes the existing file to bitmap
Bitmap srcBP = BitmapFactory.decodeFile(filePath, options);
//Calculates required width and height
int reqWidth = options.outWidth * .50;
int reqHeight = options.outHeight * .50;
//Creates scaled image
Bitmap outBP = Bitmap.createScaledBitmap(srcBP, reqWidth, reqHeight, false);
//Save modified as JPEG
File tempFile = new File(imageOutPath);
FileOutputStream out = new FileOutputStream(tempFile);
outBP.compress(Bitmap.CompressFormat.JPEG, compression, out);
out.close();
`
Is hard to predict the size of the image after compression because compression depends on the actual content of the image, some images compress to smaller size than others even they have the same dimensions. The approach I would suggest is to try and compress the image in memory like a byte array, then get the size of this array and that would be the size of the file give or take a few bytes. This code taken from another answer and modified a little bit to your needs:
Java BufferedImage JPG compression without writing to file
ByteArrayOutputStream compressed = new ByteArrayOutputStream();
ImageOutputStream outputStream =
ImageIO.createImageOutputStream(compressed);
ImageWriter jpgWriter =
ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(0.9f);
jpgWriter.setOutput(outputStream);
int reqWidth = options.outWidth * .50;
int reqHeight = options.outHeight * .50;
BufferedImage img = new BufferedImage(reqWidth , reqHeight, BufferedImage.TYPE_INT_ARGB);;
try {
img = ImageIO.read(new File(filepath));
} catch (IOException e) {
}
jpgWriter.write(null, new IIOImage(img, null, null), jpgWriteParam);
jpgWriter.dispose();
byte[] jpegData = compressed.toByteArray()
Now jpegData.size() will be very close to the size of your image in bytes.

Convert GIF images to PNG format, 32 bits depth

Here is a solution to convert GIF images to PNG format. The problem is that the generated PNG images are in 8 bits depth (in my case at least).
Is there a way to force them to be in 32 bits depth? The best would be using the javax.imageio.ImageIO library.
Just convert the BufferedImage, redrawing a copy with the desidered colour model. Not extremely efficient, but practical:
File input = new File("/tmp/input.gif");
File output = new File("/tmp/output.png");
BufferedImage im1 = ImageIO.read( input );
BufferedImage im2 = new BufferedImage(im1.getWidth(), im1.getHeight(),
BufferedImage.TYPE_4BYTE_ABGR);
im2.getGraphics().drawImage(im1, 0, 0, null);
ImageIO.write(im2 , "png", output);

New Bitmap Changed on Copy Using Buffer

When I am using copyPixelsFromBuffer and copyPixelsToBuffer, the bitmap is not displaying as the same one, I have tried below code:
Bitmap bm = BitmapFactory.decodeByteArray(a, 0, a.length);
int[] pixels = new int[bm.getWidth() * bm.getHeight()];
bm.getPixels(pixels, 0, bm.getWidth(), 0, 0,bm.getWidth(),bm.getHeight());
ByteBuffer buffer = ByteBuffer.allocate(bm.getRowBytes()*bm.getHeight());
bm.copyPixelsToBuffer(buffer);//I copy the pixels from Bitmap bm to the buffer
ByteBuffer buffer1 = ByteBuffer.wrap(buffer.array());
newbm = Bitmap.createBitmap(160, 160,Config.RGB_565);
newbm.copyPixelsFromBuffer(buffer1);//I read pixels from the Buffer and put the pixels to the Bitmap newbm.
imageview1.setImageBitmap(newbm);
imageview2.setImageBitmap(bm);
Why the Bitmap bm and newbm did not display the same content?
In your code, you are copying the pixels into a bitmap with RGB_565 format, whereas the original bitmap from which you got the pixels must be in a different format.
The problem is clear from the documentation of copyPixelsFromBuffer():
The data in the buffer is not changed in any way (unlike setPixels(),
which converts from unpremultipled 32bit to whatever the bitmap's
native format is.
So either use the same bitmap format, or use setPixels() or draw the original bitmap onto the new one using a Canvas.drawBitmap() call.
Also use bm.getWidth() & bm.getHeight() to specify the size of the new bitmap instead of hard-coding as 160.

How to save jpeg Image file size after read--rotate-write operations in Java?

Im trying to read a JPEG image as BufferedImage, rotate and save it as another jpeg image from file system. But there is a problem : after these operations I cannot proceed same file size.
Here the code
//read Image
BufferedImage img = ImageIO.read(new File(path));
//rotate Image
BufferedImage rotatedImage = new BufferedImage(image.getHeight(),
image.getWidth(), BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g2d = (Graphics2D) rotatedImage.getGraphics();
g2d.rotate(Math.toRadians(PhotoConstants.ROTATE_LEFT));
int height=-rotatedImage.getHeight(null);
g2d.drawImage(image, height, 0, null);
g2d.dispose();
//Write Image
Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");
ImageWriter writer = (ImageWriter)iter.next();
// instantiate an ImageWriteParam object with default compression options
ImageWriteParam iwp = writer.getDefaultWriteParam();
try {
FileImageOutputStream output = null;
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(0.98f); // an integer between 0 and 1
// 1 specifies minimum compression and maximum quality
File file = new File(path);
output = new FileImageOutputStream(file);
writer.setOutput(output);
IIOImage iioImage = new IIOImage(image, null, null);
writer.write(null, iioImage, iwp);
output.flush();
output.close();
writer.dispose();
Is it possible to access compressionQuality parameter of original jpeg image in the beginning. when I set 1 to compression quality, the image gets bigger size. Otherwise I set 0.9 or less the image gets smaller size. How can i proceed the image size after these operations?
Thank you,
What do you mean - do you want to read the compression rate of the original file?
Please note that even with the same compression settings, the filesize might vary, due to the JPEG compression algorithm. So a rotated image does not always have the exact same size as the unrotated/original version, even if all options (like compression rate, quality settings etc.) are the same.
If there is really now way to read the compression quality from the metadata, as a last resort, what you could do is use a binary search for the quality. Start with 0.5, if the file is too small try 0.75 and so on (up to 5 tries or so). This is a bit slow of course, but depending on your use case it might be OK.

Categories