04-15 02:20:34.693: ERROR/dalvikvm-heap(165): 50331648-byte external allocation too large for this process. 04-15 02:20:34.693: ERROR/(165): VM won't let us allocate 50331648 bytes 04-15 02:20:38.233: ERROR/AndroidRuntime(165): java.lang.OutOfMemoryError: bitmap size exceeds VM budgetI got it when trying to do a simple
BitmapFactory.decodeFile()
on a jpeg I had on the sd card. The jpeg was huge (~4MB) but that should exceed the 16MB heap I get, right? I found some decent help here and here, but nothing that really explained what was going on. I knew I was hitting the limit, but had no idea why.
Then, I remembered something a friend had told me a few years ago about how jpegs are compressed (duh) but that it had to first decompress to a bitmap before painting to your screen. It makes sense, after all. If I've got 1024x768 pixels on my monitor and I want to paint a picture over all of it, I'm going to need 768,432 bytes (depending on bit-depth).
I whipped out my calculator and had a facepalm moment. Needless to say 50,331,648 is 4096x6144 * 2, which corresponds to the size of my jpeg, when fully decompressed.
Fixing this was, happily, very easy. You just need to set
inSampleSize
on your BitmapFactory.Options
to something useful. It'll sample the image you get back, returning something 1/2, 1/4, ... the size. The key here is to try to keep the sample size a power of two. It isn't necessary, but it makes the processing faster and it'll make sure the image you get back keeps the same proportions.
// First, get the dimensions of the image Options options = new Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(filePath, options); // Only scale if we need to // (16384 buffer for img processing) Boolean scaleByHeight = Math.abs(options.outHeight - targetHeight) >= Math.abs(options.outWidth - targetWidth); if(options.outHeight * options.outWidth * 2 >= 16384){ // Load, scaling to smallest power of 2 that'll get it <= desired dimensions double sampleSize = scaleByHeight ? options.outHeight / targetHeight : options.outWidth / targetWidth; options.inSampleSize = (int)Math.pow(2d, Math.floor( Math.log(sampleSize)/Math.log(2d))); } // Do the actual decoding options.inJustDecodeBounds = false; options.inTempStorage = new byte[IMG_BUFFER_LEN]; Bitmap output = BitmapFactory.decodeFile(filePath, options);It's still a pretty slow operation to perform, but this at least makes it possible!
Do you think this is better than using Bitmap.createScaledBitmap() ?
ReplyDeleteYes- here's why. Bitmap.createScaledBitmap() requires that you have a source bitmap loaded already. On my droid, a full image takes nearly all the 16M I have available. The technique shown here does the sampling while the image is being loaded, so you don't have the massive memory allocation.
ReplyDeleteHi!
ReplyDeleteI've been trying do use your implementation but it doesn't seem to work with:
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
InputStream is = connection.getInputStream();
BitmapFactory.decodeStream(is, null, options);
Why do you define options.inTempStorage?
What is a reasonable IMG_BUFFER_LEN size?
as IMG_BUFFER_LEN = 16*1024 is suggested ;D
DeleteYou just made my day!!!
ReplyDeletegreetz.
Hi guys,
ReplyDeleteI have a similar problem to this with which I am struggling right now.
In my app, I am creating a bitmap from its colors code like this :
int width=getImageWidth();
int height=getImageHeight();
int[] array=getbitmap();
int[] colorsAsIntegers = new int[width*height];
int i = 0;
int j = 0;
while (i<width*height*4) {
colorsAsIntegers[j] = Color.argb(array[i], array[i+1], array[i+2], array[i+3]);
i += 4;
j++;
}
myBitmap=Bitmap.createBitmap(colorsAsIntegers,
width,
height,
Bitmap.Config.ARGB_8888);
And often I get the outofmemoryerror :(
So how can I use the BitmapFactory optimisation to avoid this problem? because I don't have an input stream or a file, I only have an array containing my pixels
Thank you
Good solution!
ReplyDeleteI was using createScaledBitmap, but that was taking considerable time and memory too.
instead your solution works best, the output bitmap is subsampled and consumes less memory.
Thanks.
My only concern is, although this solution is fine for low resolution devices where high level details of bitmaps are not required as such, how would the subsampled bitmaps look on high resolution devices like 10.1" tablets?
Why the factor in ons.outHeight * options.outWidth * 2 expression is 2 but not 3?
ReplyDeleteHi, could you tell me what is "targetHeight" and "targetWidth"??
ReplyDeleteThanks in advance