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

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

Related

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);

Dynamically creating a compressed Bitmap

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();

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

After capturing a layout screenshot, ImageView is transparent

I have a problem in Android screen capture. The captured image is transparent while the original image is solid JPEG file which is not transparent!
I tested in on many devices with different OS version and problem persists on all phones.
Here is my code for screen capture and also the final output
Code:
final Bitmap rawBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(rawBitmap);
rendererView.layout(0, 0, width, height);
rendererView.draw(canvas);
rawBitmap.compress(CompressFormat.PNG, 100, new FileOutputStream("/sdcard/test" + System.currentTimeMillis() + ".png"));
Result:
The problem was solved. When loading image source to ImageView, I used wrong setting for BitmapFactory Options.
The Code ( Before And After ):
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = false;
options.inPurgeable = true;
options.inInputShareable = true;
//options.inPreferredConfig = Config.ARGB_4444; //WRONG
options.inPreferredConfig = Config.ARGB_8888; //CORRECT
bitmap = BitmapFactory.decodeFile(result.filePath(), options);
Thanks you all.
I would try Bitmap.Config.RGB_565 in createBitmap()

OutOfMemoryException when using large images

I tried to post an image on Facebook wall from my android application.For this I created a bitmap from layout.Here is my code
System.gc();
Bitmap bitmap = Bitmap.createBitmap(
view.getMeasuredWidth(),
view.getMeasuredHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Drawable bgDrawable = newLinearLayout.getBackground();
if (bgDrawable != null)
bgDrawable.draw(canvas);
else
canvas.drawColor(Color.WHITE);
view.draw(canvas);
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(1.0f, 1.0f);
// recreate the new Bitmap
Bitmap resizedBitmap = null;
resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
//Canvas resizedCanvas = new Canvas(resizedBitmap);
if (bitmap != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
resizedBitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
Bundle parameters = new Bundle();
byte[] data = baos.toByteArray();
parameters.putByteArray("picture", data);
DataKeeper.getInstance().setBundleToPost(parameters);
}
bitmap.recycle();
It shows OutOfMemory exception.I know that this is due to imagesize exceeds its available size.How can i resize image without lossing its quality? I tried to correct this using BitmapFactory.Options. But does not work.How can solve this exception?
Regards
Asha
use this :
BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap=BitmapFactory.decodeStream(is,null,options);
for more detail see this link
link
The idea of #Zaz Gmy is correct,
however I don't think the value of inSampleSize should be constant (8) for every image.
This of course will work, but scaling different images with different resolutions on the same scale factor = 8, will cause some of images to have a visible loss of quality.
Instead, the inSimpleSize should be generated based on the target width and height of image.
Android documentation provides a good article on how to deal with this issue.
Please check this tutorial and download the sample project.This will solve your problem
http://developer.sonymobile.com/wp/2011/06/27/how-to-scale-images-for-your-android%E2%84%A2-application/

Categories

Resources