I would be very grateful if someone could confirm that I have solved the below problem correctly or if there is an alternative solution?
I have an app that loads a large image (e.g. 800*1720 pixels) into memory and displays it in a scrollview. The image is a floor plans for a museum and I wanted a simple map like experience of scrolling and zooming. The image loaded fine on older devices, but caused an out of memory error on a Samsung Galaxy S3.
Looking at the LogCat messages it turned out that in creaing the bitmap 22MB was being allocated for the bitmap instead of 800*1720*4 = 5.5MB. Essentially 4x as much memory was being allocated as required by other devices and pushing the memory usage over the 50MB heap size.
The recommended solution to this problem is to use the BitmapFactory.Options.inSampleSize option to reduce the resolution of the image loaded and have it require less memory. However, this reduces the quality of the image, and I actually want to display it at it's full size in the original quality as works fine on older devices.
After much head scratching I concluded that the issue was that the pixel density on the S3's WXGA screens is 2.0 and thus for each pixel in the image, the bitmap was actually allocating 4 pixels. With a bit of trial and error I discovered I could prevent this happening by setting options.inScaled = false;
http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inScaled
On the way, I also realised that I could cut my memory usage in half to 2.7MB by using a lower fidelity colour depth of 2 pixels instead of 4 pixels by setting options.inPreferredConfig = Bitmap.Config.RGB_565;. For my floorpans this didn't effect the visible image quality.
The final code was thus:
String uri = "drawable/floorplan";
int imageResource = getResources().getIdentifier(uri, null, getPackageName());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inScaled = false;
Bitmap bm = BitmapFactory.decodeResource(getResources(), imageResource, options);
IVfloorplan.setImageBitmap(bm);
When displaying this bitmap you need to scale it back up. To work out the scaling you can obtain the pixel density from:
float density = getResources().getDisplayMetrics().density;
I reduced memory usage for the bitmap from 22MB to 2.7MB, which in a 50MB heap is significant.
The screen of the S3 has a really high res, so it's quite understandable. Also, if the image is in the drawable folder, it could be getting upscaled automatically. There might be ways to turn that off. Even if your image size doesn't chance, the OS has to allocate a buffer to accommodate the image, also possibly one to accommodate showing it on the screen.
An alternative is using tiling, which is used in games like SNES games. This is also how they handled lots of graphics without running out of RAM. Evidence shows that this is how Google Maps has a map of Planet Earth. You basically chop up the large image into tiles and show the tiles on the screen as you are panned to them (of course, maybe 1 extra tile on each side).
Even though this is post-Honeycomb, where Google put in code to better manage Bitmap allocations, be careful with Bitmap allocation. It's more like a C program than a Java program, and it's a good idea to manage it like one. It's very easy to run out of heap space when using Bitmap objects in Android
Related
I have an 8MB png and when I try to load it into an Android ImageView I get an OutOfMemoryError that says it tried to allocate 32MB of memory and failed.
I'm working on changing the code to downsample the image to avoid using too much memory to avoid most of these problems, so I'm not looking for answers about downsampling. I'm trying to understand why the memory needs of the image are higher than the file size would imply.
Why is Android trying to allocate 4x the memory when loading the png?
I've set my options to tell it not to scale for pixel density:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inScaled = false;
It is 2848 x 4209 pixels
At the 4 bytes/pixel rate for ARGB_8888, that will be 47,948,928 bytes as a Bitmap.
The on-disk size of images represents a red herring. The major file formats (particularly PNG and JPEG) are compressed as files. That does not matter. What matters is the resolution and bit depth of the desired decoded image.
Also note that your image is bigger than the display resolution of most Android devices. Depending on your use case, you may wish to consider widgets like this one that can load and display portions of the image at a time.
I know there is a lot of discussion about android bitmap images out of memory but I was wondering if someone could explain it to me..
Currently in my app I have an activity which lists image thumbnail (low quality) and when I click an image it opens a new activity to view the image full screen. In my 2nd activity class I have:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
bm = BitmapFactory.decodeFile(myImagePath, options);
I then put this into an ImageView to display it. This works and displays my image to its full quality. However if i click back and then click to see that image again (and repeat this 6 times) .. on the 6th time opening the image (activity2) I get an out of memory error saying Heap size=6919KB, Allocated=3125KB, Bitmap Size = 25848KB!
How is bitmap size that big? I assumed it may be creating new instances all the time so I then decided to put a method in my 2nd activity for when the back key is pressed..and in this method I set my bitmap=null and also did System.gc() to clear the garbage collector BUT this did not fix the problem. I still get an out of memory error on the 6th time of clicking on the thumbnail to view the image in full resolution
Can anyone explain why? Thanks
There is some great information from android that explains it all in detail, and how to overcome this problem here.
Each pixel is 4 Bytes. 6M Pixel = 24MBs
One photo can use up all the Memory.
Bitmaps take up a lot of memory, especially for rich images like photographs. For example, the camera on the Galaxy Nexus takes photos up to 2592x1936 pixels (5 megapixels). If the bitmap configuration used is ARGB_8888 (the default from the Android 2.3 onward) then loading this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the per-app limit on some devices.
Again I point you to this great link I found from another SO Question that has tutorials of how to properly over come the problem.
inSample size should be set so that the image is scaled to the size of the display area (1 = full size) unless there is some reason you think you need all the bits of the image, so 2 would = 1/2 scale 4 1/4 scale etc.
Also try bm.recycle() when you are finished with the bitmap before using =null
Update
Look at the second answer what does recycle do unless you have already tried that and it didn't work. I have done similar things with loading images and never run out of memory, that's not proof that it will work for you, but it's a best practice as far as I can tell.
I am joining two images using the code below but it throws an OutOfMemory error my images are around 1MB each.
private Bitmap overlayMark(String first, String second)
{
Bitmap bmp1, bmp2;
bmp1 = BitmapFactory.decodeFile(first);
bmp2 = BitmapFactory.decodeFile(second);
if (bmp1 == null || bmp2 == null)
return bmp1;
int height = bmp1.getHeight();
if (height < bmp2.getHeight())
height = bmp2.getHeight();
Bitmap bmOverlay = Bitmap.createBitmap(bmp1.getWidth() + bmp2.getWidth(), height,
Bitmap.Config.ARGB_8888);// Out of memory
Canvas canvas = new Canvas(bmOverlay);
canvas.drawBitmap(bmp1, 0, 0, null);
canvas.drawBitmap(bmp2, bmp1.getWidth(), 0, null);
bmp1.recycle();
bmp2.recycle();
return bmOverlay;
}
Update: I tried below two answers but it still not allwoing me to create bitmap of such big size the problem is that the resultant bitmap is too large in size around 2400x3200 so its going out of memory.
How can I join large images without running out of memory?
Without loading the image into memory, you CAN get the size of the image, using inJustDecodeBounds. The Bitmap returns null, but all the parameters are set. You can scale down the image accordingly.
If your JPEG images are 1 MiB each, conversion to a BMP will take a lot of memory indeed. You can easily calculate its BMP equivalent by the dimensions of the image. Conversion of such a large image is expected to crash indeed. Android limits its apps to 16 MiB VM only.
Also use RGB_565 instead of ARGB_8888.
So your only solution is:
(a) To use BitmapFactory.Options.inSampleSize to scale down the image
or
(b) Use Android NDK where the 16 MiB limit isn't there.
I use this simple rule of the thumb:
the heavy lifting (both memory/CPU) is done on the server.
So write some servlet that takes the image, resizes it to a specified dimension (probably reduces the pixel depth too) and returns the result.
Piece of cake and it works on any mobile device you need.
Good luck!
I think a solution sort of like Sumon suggests might work.
Figure out the size of the final
image based on what will fit on the
screen.
Get the size of the first image using
the inJustDecodeBounds technique.
Figure out the size of the first
image in the final image. Calculate
re-sizing parameters.
Resize image, loading into memory.
Write resized image back to disk.
Recycle the bitmap. (This will help
when resizing the 2nd image)
Repeat for the second image, only you
can skip the writing to disk part.
Load first image.
If you only need to display, then just do that. If not then you can combine into a single bitmap at this point and write to disk. If this is the case, it may be difficult because you wil have essentially 2x the screen size in memory. In that case I would recommend resizing smaller. If you can't go smaller, then you will have to go the NDK route, thought I'm not sure how much that will help. Here's an amusing intro to the NDK and JNI. Finally, I would highly recommend developing this using a phone running Android 2.3+ since its use of heap-allocated bitmaps will make debugging much easier. More about those here.
It's not necessary that the space taken by in-memory representation of bitmaps correspond closely with file size. So even if you have 3mb memory available to jvm, you might still get OutOfMemoryException.
Your code is creating three in-memory images simultaneously. If you can find the size of both images without reading the complete files, you can modify the code in a way to have only one of the source images in memory at a time. If even that doesn't prove to be sufficient you might need some sort of streaming method of reading the images.
you may get some idea from here.
Are you trying to display this super large image or are you just trying to save it?
If your trying to display it. Cut the images into tiles. Then only display the tiles that are being viewed. If the user zooms out you need to reduce the size of the bitmap before showing the whole thing.
If your trying to save it, try saving it in sections to the same file by cutting the image up.
Loading 2 1m files in memory then creating a 2m file leaves you with 4M in memory for your images alone. Dynamically loading and unloading the memory solves this issue similar to tiles on Google maps or dynamic zooming in other map oriented solutions.
If you need to return that huge 2400x3200 bitmap as your result, there is no way to actually realize this goal. The reason is that 2400*3200*4 bytes ~ 30 Mb! How can you hope to implement this method, when even you can't even fit the return value into your limited heap space (ie 16Mb)?
And even if you used 16-bit color, it would still fail because you would end up using about 15MB, which would not leave you enough space for the language run time.
Getting this
05-25 23:55:59.145: ERROR/AndroidRuntime(3257): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
at this code of mine -
Bitmap cs = null;
cs = Bitmap.createBitmap(frameImg.getWidth(), frameImg.getHeight(), Bitmap.Config.ARGB_8888);
in activity class. How to fix this :(
I ran into a similar problem when selecting images from my EVO ... off the SD Card. The camera saves those images at over 1MB a piece, and in some cases close to 3 MB. Ironically, the size of the bitmap the Intent sends to you when "taking a picture" is only about 40 Kb.
The solution I came up with was:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; // this will cut the sampling by 50%
Bitmap bitmap = BitmapFactory.decodeFile( imageFilePath, options );
I was able to take pictures and cut them down to less than 100 Kb by increasing the factor number, and the images were still pretty good quality.
The bottom line here is it prevented OOME errors, and thus prevent me from crushing the JVM by exceeding the heap allocation. Simple - effective.
You might find this an interesting read also: BitmapFactory OOM driving me nuts
Make the bitmap smaller in resolution and potentially use a different file format..
If the image is really big then you should just display the parts you need.
Android has a relatively limited amount of spare of memory for bitmaps (which can get smaller depending on what else is going on in the device). So the other answers are correct...
scale the bitmap down (in pixels or resolution)
only display the subset of teh bitmap that is visible to the user.
Check out Displaying a bitmap of arbitrary size without running out of memory for background on what's going on under the covers - which might help you avoid the OOM.
hi #user418366 i was having the same problem when dealing with images what you can do is if you are using the emulator you can rise the max VM application Heap size so that you can get override of this problem or else as told by all try to compress the size of the image make sure that it was a loss less compression
In my app users choose images and program lets users to make changes on images. Since there are a lot of different android devices out there my program crashes on some of devices which less heap size. I want to calculate the best fit dimensions for user's phone so it won't crash because of VM budget. I have added a screenshot from "Picsay Pro" which is making exactly what i am looking for. I do know about the "BitmapFactory.Options" my only problem is to find a way to decide image dimensions which won't let crash the app because of VM budget.
Calculate the Free Space Remaining on the phone:
long freeMemory = (Runtime.getRuntime().maxMemory()) - (Debug.getNativeHeapAllocatedSize());
where,
Runtime.getRuntime().maxMemory() - returns the Total Heap Limit size (in Bytes).
Debug.getNativeHeapAllocatedSize() - returns the Amount of Data already used by your application (in Bytes).
Calculate the size of the Bitmap you are using by this formula,
long Bytes_allocated = (height of the Image) * (width of the Image) * 4;
Compare freeMemory and Bytes_allocated to select the appropiate size for your application.
I actually ended up compressing images on the phone for two reasons. One was upload speeds and another one was heap problems. You can try doing something similar, or at least post the stack trace!
Android outofmemory error bitmap size exceeds vm budget in 2.3.3
i think this compression technique could help
So far I've not found any reliable way to deal with image size vs available memory. The problem is the memory becomes fragmented very quickly, so that you could have 10 MB free, but no contiguous space for a 2 MB image. What is really needed is the size of the largest free space, but there doesn't appear to be any way to get this. Better would be a way to defragment memory, but no such function exist for this either.
Still if your available memory is less than the image, you can be sure you're going to crash if you attempt to use it, so it has some merit to at least check before you use it.
With late-2012 tablets now having 1920x1280 resolution, we now need 20MB of continuous memory for a single background image! It appears some of these tablets are now allowing heaps up to 256MB, but while tossing more VM space seemingly solves it, we really need a memory defragmenter or a way to reserve space.
There is one trick that might help if the image size is not scaled or modified, and each changed image is the same size AND you are limiting your app to Android 3.0+:
Options opt2 = new BitmapFactory.Options();
opt2.inBitmap = mBitmap; // reuse mBitmap to reduce out of memory errors
mBitmap = BitmapFactory.decodeResource(getResources(), myDrawable, opt2);
This will reuse the same memory area mBitmap for the image.