Dynamically creating a compressed Bitmap - android

I'm writing a custom printing app in Android and I'm looking for ways to save on memory. I have three basic rectangles I need to print on a full page. Currently I'm creating a base Bitmap the size of the page:
_baseBitmap = Bitmap.createBitmap(width/_scale, height/_scale, Bitmap.Config.ARGB_8888);
The print process requests a Rect portion of that page. I cannot predetermine the dimensions of this Rect.
newBitmap = Bitmap.createBitmap(fullPageBitmap, rect.left/_scale, rect.top/_scale, rect.width()/_scale, rect.height()/_scale);
return Bitmap.createScaledBitmap(newBitmap, rect.width(), rect.height(), true);
Using bitmap config ARGB_8888 _baseBitmap is about 28MB (8.5"x11" # 300dpi = 2250*3300*4bytes). Even at 50% scaling (used above), my image is over 7MB. Scaling any smaller than this and image quality is too poor.
I've attempted to create _baseBitmap using Bitmap.Config.RGB_565, which does greatly reduce the full image size, but then when I overlay an image (jpegs) I get funny results. The image is compressed in width, duplicated next to itself, and all the color is green.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap myBitmap = BitmapFactory.decodeStream(input, null, options);
input.close();
return myBitmap;
....
private static Bitmap overlay(Bitmap bmp1, Bitmap bmp2, float left, float top) {
Canvas canvas = new Canvas(bmp1);
canvas.drawBitmap(bmp2, left, top, null);
return bmp1;
}
I know I can compress an image of these dimensions down to a reasonable size. I've looked into Bitmap.compress, but for some reason beyond my understanding I'm getting the same size image back:
ByteArrayOutputStream os = new ByteArrayOutputStream();
_baseBitmap.compress(Bitmap.CompressFormat.JPEG, 3, os);
byte[] array = os.toByteArray();
Bitmap newBitmap = BitmapFactory.decodeByteArray(array, 0, array.length);
_baseBitmap.getAllocationByteCount() == newBitmap.getAllocationByteCount()
It would be better to create it compressed than to create a large one and then compress it. Is there any way to create a compressed Bitmap? Any advice is greatly appreciated.
note: Not an Android expert. I'm not necessarily familiar with the platform specific terms you may use to respond. Please be gentle.

Try something like this, if you have a target size in mind.
private static final int MAX_BYTES_IMAGE = 4194304; // 4MB
//...
ByteArrayOutputStream out;
int quality = 90;
do
{
out = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, out);
quality -= 10;
} while (out.size() > MAX_BYTES_IMAGE_FILESIZE);
out.close();

Related

Android: BitmapFactory changes dimensions of image when decoding from ByteArray

I am converting an android Image captured in my application to a bitmap. I am doing this by getting the image buffer from the pixel plane of the image and then using BitMapFactory to decode it into a Bitmap. However, doing so seems to change the resolution of the Image from 1920 x 1440 to 1800 x 1600, cropping out the top and bottom of the image in the process. The code for the method is shown here.
`protected void getImageFromBuffer(ImageReader reader){
Image image = null;
image = reader.acquireLatestImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
System.out.println("Getting Image Ready");
synchronized (this){
image_to_upload = new byte[buffer.capacity()];
buffer.get(image_to_upload);
Bitmap storedBitmap = BitmapFactory.decodeByteArray(image_to_upload, 0, image_to_upload.length, null);
Matrix mat = new Matrix();
mat.postRotate(jpegOrientation); // angle is the desired angle you wish to rotate
storedBitmap = Bitmap.createBitmap(storedBitmap, 0, 0, storedBitmap.getWidth(), storedBitmap.getHeight(), mat, true);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
storedBitmap.compress(Bitmap.CompressFormat.JPEG,70, byteArrayOutputStream);
image_to_upload = byteArrayOutputStream.toByteArray();
image_ready = true;
System.out.println("Image Ready");
}
}`
Debugging shows that the height and width of the Image are correct before the buffer is converted to a bitmap, but the bitmap dimensions are wrong immediately after decodeByteArray. Can anyone suggest why this may be? I have checked the dimensions before applying the matrix transformation.
EDIT: To add further details, I have tried using BitmapFactory.Options() to disable scaling or to set the target density and neither have any impact on the resulting Bitmap, it is always size 1800 x 1600.
You can change some options that affects the result resolution of your bitmap by using the Options param to the decodeByteArray:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDensity = DisplayMetrics.DENSITY_XXHIGH;//for example
BitmapFactory.decodeByteArray(image_to_upload, 0, image_to_upload.length, options);

Android - Formatting Bitmap to match instagram size

Here's what I'm trying to do:
Myapp calls the camera app, takes a picture, sends the pic path back to Myapp to be displayed in an ImageView and to then be shared to Instagram. I want the displayed bitmap to be of the same dimensions as what Instagram uses so no unexpected cropping will happen when going from Myapp to Instagram.
Here's what I've tried so far when returning from the camera:
(note: INSTAGRAM_FORMAT_W == INSTAGRAM_FORMAT_H == 1080)
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.outHeight = INSTAGRAM_FORMAT_H;
bmOptions.outWidth = INSTAGRAM_FORMAT_W;
bmOptions.inMutable = true;
Bitmap photo = (Bitmap) BitmapFactory.decodeFile(path, bmOptions);
photo = Bitmap.createScaledBitmap(photo, INSTAGRAM_FORMAT_H, INSTAGRAM_FORMAT_W, false);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
photo.compress(Bitmap.CompressFormat.JPEG, 40, bytes);
This distorts the image to fit the square format; not optimal
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.outHeight = INSTAGRAM_FORMAT_H;
bmOptions.outWidth = INSTAGRAM_FORMAT_W;
bmOptions.inMutable = true;
Bitmap photo = (Bitmap) BitmapFactory.decodeFile(path, bmOptions);
int baseX = (photo.getWidth() - INSTAGRAM_FORMAT_W)/2;
int baseY = (photo.getHeight() - INSTAGRAM_FORMAT_H)/2;
Bitmap photoResized = Bitmap.createBitmap(photo,baseX,baseY,INSTAGRAM_FORMAT_W,INSTAGRAM_FORMAT_H);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
photoResized.compress(Bitmap.CompressFormat.JPEG, 40, bytes);
This crops too much off what the user sees thru the camera app, it is also bigger than the Instagram size which results in additional cropping when going to Instagram; not optimal
I was digging thru BitmapFactory.Options and possible parameters for Bitmap.createBitmap but I'm pretty lost in terms of what best practice is for when/where the formatting occurs, how to deal with variable pixel density of the screen (if needed) and variable camera definition (if applicable).
I could use a helping hand folks. Thanks

Avoid OutOfMemoryError for Bitmap.createScaledBitmap in Android

I am using following code to enlarge a image.
bmp=new BitmapFactory().decodeFile(util.getPathFromUri(tempFile));
Bitmap scaledBitmap=Bitmap.createScaledBitmap(bmp, newWidth, newHeight, true); //Line 2
ByteArrayOutputStream bos=new ByteArrayOutputStream();
scaledBitmap.compress(CompressFormat.JPEG, 100, bos);
bmp.recycle();
bmp=null;
OutputStream out;
out = new FileOutputStream(util.getTempFileName());
bos.writeTo(out);
bos.flush();
But sometimes OutOfMemoryError occurs at line 2 and app crashes, I tried enclosing this code within try-catch but still my app crashes as only exceptions are caught by try-catch, also createScaledBitmap() functions only throws IllegalArgumentException.
Since, I don't want to display image therefore I don't need ot scale it down(as I saw in other Questions in SOF).
So, How can I pre-detect that OutOfMemoryError will occur if I use that (newWidth, newHeight). Is there any way to calculate bytes required by Bitmap of (newWidth, newHeight) in memory and max available memory that can be allocated to bitmap?
Please help!
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.RGB_565;
options.inPurgeable = true;
options.inSampleSize = 1;
options.inScaled = false;
options.inDensity = DisplayMetrics.DENSITY_DEFAULT;
options.inTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
options.inScreenDensity = DisplayMetrics.DENSITY_DEFAULT;
new BitmapFactory().decodeFile(util.getPathFromUri(tempFile),options);
To avoid java.lang.OutOfMemory exceptions, check the dimensions of a bitmap before decoding it, unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory. For more details see this
Try this method to resize your bitmap:
Bitmap bitmpResizeResult = ThumbnailUtils.extractThumbnail(bitmapWantToResize, width, height);

Better option then ImageMagick or Canvas for resizing images in Android

I trying to achieve good quality image resize in Android. I'm trying all methods listed here on SO and that I could find on Google, but I still can't find a good solution.
To exemplify what I'm trying to achieve and what problems I'm having, I'm posting 3 images with the different results. Basically I'm just getting a big image from SD card, resizing and cropping it.
Here is the desired result, achieved on Photoshop:
And this is when I use the tradicional method of drawing on canvas
And this result is when I use ImageMagick. It's better, but in some devices it takes minutes to resize (not cool for a mobile app)
So, here is my code using the canvas method:
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeFile(params[0].path_source, o);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inDither = false;
options.inPurgeable = true;
options.inScaled = false;
options.inPreferQualityOverSpeed = true;
options.inSampleSize = Utils.calculateInSampleSize(o, 640, 640);
Bitmap image = BitmapFactory.decodeFile(params[0].path_source, options);
... calculate right x, y for cropping ..
Matrix m = new Matrix();
m.postScale(new_width/(float)width, new_width/(float)width);
m.postRotate(rotation);
Bitmap result = Bitmap.createBitmap(640, 640, Config.ARGB_8888);
Canvas canvas = new Canvas(result);
canvas.setMatrix(m);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);
canvas.drawBitmap(image, -x, -y, paint);
FileOutputStream fos = activity.openFileOutput(Utils.NOME_ARQUIVO_FOTO, Context.MODE_PRIVATE);
result.compress(CompressFormat.JPEG, 100, fos);
fos.close();
Now here is the code using ImageMagick:
ImageInfo info = new ImageInfo(params[0].path_source);
MagickImage image = new MagickImage(info);
//I can optionally use sampleImage for better performance, but worse quality (still better then android canvas)
//image = image.sampleImage(new_sampled_width, new_sampled_height);
image = image.scaleImage(new_width, new_height);
image = image.cropImage(new Rectangle(x, y, 640, 640));
image = image.rotateImage(rotation);
byte blob[] = image.imageToBlob(info);
FileOutputStream fos = activity.openFileOutput(Utils.NOME_ARQUIVO_FOTO, Context.MODE_PRIVATE);
fos.write(blob);
fos.close();
Edit:
I'm using Android 2.3.4 on a Xperia Play for testing
Edit 2:
Saving with CompressFormat.PNG achieves near perfect result! Thanks FunkTheMonk for the tip! Only problem is that I use ImageMagick for blending this image with another later in the code, and I couldn't manage to build ImageMagick with PNG support

How to avoid "out of memory exception" when doing Bitmap processing?

In onPictureTaken, I want to do the following:
Bitmap decodedPicture = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
matrix.preScale(-1.0f, 1.0f);
Bitmap picture = Bitmap.createBitmap(decodedPicture, 0, 0, decodedPicture.getWidth(), decodedPicture.getHeight(), matrix, false);
View v1 = mainLayout.getRootView();
v1.setDrawingCacheEnabled(true);
Bitmap screenshot = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);
Bitmap scaledPicture = Bitmap.createScaledBitmap(picture, screenshot.getWidth(), screenshot.getHeight(), true);
Bitmap compos = Bitmap.createBitmap(scaledPicture.getWidth(), scaledPicture.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(compos);
canvas.drawBitmap(scaledPicture, new Matrix(), null);
canvas.drawBitmap(screenshot, new Matrix(), null);
MediaStore.Images.Media.insertImage(getContentResolver(), compos, "name" , "description");
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
My only requirement is that I'd like to save a high-quality photo... Seems I might have to sacrifice that.
On my Nexus 4 and newer devices, this code runs fine and as expected. But on older devices that have less memory, I'm running out of RAM! :(
How do I do the same image manipulation without running up against the memory limit?? I'm not trying to display these images on screen, so the solutions that have to do with a scaled down image don't really apply here...
you need to read the bitmap in with an increased sample size. the trick is finding the correct sample size that won't result in reduced resolution when you ultimately scale the image. i wrote a blog entry about it here that includes a nice utility class for scaling,
http://zerocredibility.wordpress.com/2011/01/27/android-bitmap-scaling/
you could probably simplify that class quite a bit depending on your specific needs.
the jist is to read just the size of the bitmap. calculate the optimal sample size based on your desired scaled size, read the bitmap in using that sample size, then fine-scale it to exactly the size you want.
You have so many Bitmap object lying around. try recycling/reusing some of this.
Not exactly sure what is your requirement is but i can see you can save some memory by simply doing this.
Bitmap decodedPicture = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
matrix.preScale(-1.0f, 1.0f);
Bitmap picture = Bitmap.createBitmap(decodedPicture, 0, 0, decodedPicture.getWidth(), decodedPicture.getHeight(), matrix, false);
decodedPicture.recycle();
decodedPicture=null;
View v1 = mainLayout.getRootView();
v1.setDrawingCacheEnabled(true);
Bitmap screenshot = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);
Bitmap scaledPicture = Bitmap.createScaledBitmap(picture, screenshot.getWidth(), screenshot.getHeight(), true);
picture.recycle();
picture=null;
Bitmap compos = Bitmap.createBitmap(scaledPicture.getWidth(), scaledPicture.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(compos);
canvas.drawBitmap(scaledPicture, new Matrix(), null);
canvas.drawBitmap(screenshot, new Matrix(), null);
MediaStore.Images.Media.insertImage(getContentResolver(), compos, "name" , "description");
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
Also look into your memory footprint, make sure device wise memory you are using are is not too big.
FYI, on post honycomb devices bitmap pixel image allocated on native layer. You need recycle() or finalizer() to restore memory
Considering you don't want to resize your bitmap and don't want to display it, I'd do something like this:
Load the Bitmap with inJustDecodeBounds to see its original height and width (code from here)
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
Depending on the size and memory you have, you can directly process it from there (i.e. load the Bitmap) or proceed to load a number of chunks of said Bitmap with the Bitmap.createBitmap method that allows you to only load a chunk of data. Optionally: consider converting it into a byte array (see code below) and null+ recycle() before you process the chunk.
code
ByteArrayOutputStream byteArrayBitmapStream = new ByteArrayOutputStream();
bitmapPicture.compress(Bitmap.CompressFormat.PNG, COMPRESSION_QUALITY, byteArrayBitmapStream);
byte[] b = byteArrayBitmapStream.toByteArray();
I use for work with Bitmap class WeakReference and after I always call recycle on the instance object WeakReference, the snippet code for rotate image:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inPurgeable = true;
options.inInputShareable = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
WeakReference<Bitmap> imageBitmapReference = new WeakReference<Bitmap>(BitmapFactory.decodeByteArray(params[0], 0, params[0].length, options));
Matrix mat = new Matrix();
mat.postRotate(90.0f);
imageBitmapReference = new WeakReference<Bitmap (Bitmap.createBitmap(imageBitmapReference.get(), 0, 0, resolution[0], resolution[1], mat, true));
FileOutputStream fos = new FileOutputStream(filename);
imageBitmapReference.get().compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
imageBitmapReference.get().recycle();
And second solution how work with Bitmap and don't get OutOfMemory Exception is use library Universal Image Loader
(Of course is so the third solution set in your AndroidManifest property android:largeHeap="true" and really DON'T USE THIS property).
The perfect material is on the http://developer.android.com/training/displaying-bitmaps/index.html and video https://www.youtube.com/watch?v=_CruQY55HOk

Categories

Resources