I'm capturing an image from the camera which I want to pass through some processing in OpenCV. On older devices, this is failing at the first hurdle though:
public void onPictureTaken(byte[] jpeg, Camera c) {
mImageBitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length);
org.opencv.android.Utils.bitmapToMat(mImageBitmap, Utils.cameraMat);
...
}
Produces:
04-22 14:23:41.708: I/System.out(7289): Available memory (bytes): 5646168
04-22 14:23:41.718: I/dalvikvm-heap(7289): Forcing collection of SoftReferences for 51121168-byte allocation
04-22 14:23:41.758: E/dalvikvm-heap(7289): Out of memory on a 51121168-byte allocation.
Reading up about this, the advice seems to be to work with a smaller version of the image, but how do I work out roughly what dimensions to resize to given the amount of available memory left on the device?
You need to resize your image before passing it to openCV. The decode instruction consumes a lot of memory because it loads the full image in memory. Even if you don't display it.
This official tutorial will show you how to resize your image without having to load it in memory first
You can find out how much memory your application has available to use, total, with getMemoryClass. You'll have to work out how much of that you're eating up with your application's other data, and the decoded bitmap above.
Some rough estimates:
The S II has an 8 MP camera. Assuming an RGB24 Bitmap, a single decoded bitmap will take up 8 * 3 = 24 MB of memory. The OpenCV matrix will probably take up about the same, possible more depending on what its internal format is (the out-of-memory error suggests it's trying to allocate ~48 MB, which is about twice the expectation here).
That's easily above what a standard app can use for memory - the minimum baseline is only 16 MB. If you need more memory, you probably want to look into using a large heap instead of the normal-sized one. That will let you get roughly double the memory, although it's still device-dependent.
Related
after googling a lot I have not yet found a way to resize an image preserving quality.
I have my image - stored by camera in full resolution - in
String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/my_directory/my_file_name.jpg";
Now, I need to resize it preserving aspect ratio and then save to another path.
What's the best way to do this without occurring the error "Out of memory on a xxxxxxx-byte allocation."?
I continue to retrieve this error on Samsung devices, I tried in every way, even with the library Picasso.
Thanks!
1st things 1st: depending on device and bitmap size, no matter what magic code you do, it will crash! Specially cheap Samsung phones that usually have no more than 16mb of RAM to the VM.
You can use this code How to get current memory usage in android? to check on amount of memory available and deal with it properly.
When doing those calculations, remember that bitmaps are uncompressed images, that means, even thou the JPG might be 100kb, the Bitmap might take several MB.
You'll use the code shown here https://developer.android.com/training/displaying-bitmaps/load-bitmap.html to read the bitmap boundaries, and do an approximate scale down as close as possible to the size you actually need, or enough to make the device not crash. That's why it's important to properly measure the memory.
That 1st code takes virtually no RAM as it creates from the disk, making it smaller by simply skipping pixels from the image. That's why it's approximate, it only does in power of 2 the scaling.
Then you'll use the standard API to scale down to the size you actually need https://developer.android.com/reference/android/graphics/Bitmap.html#createScaledBitmap(android.graphics.Bitmap, int, int, boolean)
so the pseudo code for it, will be:
try{
Info info = getImageInfo(File);
int power2scale = calculateScale(info, w, h);
Bitmap smaller = preScaleFromDisk(File, power2scale);
Bitmap bitmap = Bitmap.createScaledBitmap(smaller, w, h, f);
} catch(OutOfMemoryError ooe){
// call GC
// sleep to let GC run
// try again with higher power2scale
}
I'm currently working on a project where I have to use RenderScript, so i started learning about it, and it's a great technology, because, just like openGL, it lets you use computational code that goes to a native level, and doesn't have to use the dalvik vm. This part of the code, being processed much faster than if you would use normal android code.
I started working with image processing and what i was wondering is:
Is it possible to resize a bitmap using RenderScript? this should be much faster then resizing an bitmap using android code. Plus, renderscript can process information that is bigger than 48mB (limit on some phones for each process).
While you could use Rendscript to do the bitmap resize, I'm not sure if that's the best choice. A quick look at the Android code base shows that Java API does go into native code to do a bitmap resize, although if the resize algorithm isn't to your needs, you'll have to implement your own.
There are a number of answers on SO for getting the bitmap scaled efficiently. My recommendation is to try those, and if they still aren't doing what your want, either as quickly or how the results appear visually to then investigate into writing your own. If you still want to write your own, do use the performance tools available to see if you really are faster or just reinventing the wheel.
You can use the below function to resize the image.
private Bitmap resize(Bitmap inBmp) {
RenderScript mRs = RenderScript.create(getApplication());
Bitmap outBmp = Bitmap.createBitmap(OUTPUT_IMAGE_WIDTH, inBmp.getHeight() * OUTPUT_IMAGE_WIDTH /inBmp.getWidth(), inBmp.getConfig());
ScriptIntrinsicResize siResize = ScriptIntrinsicResize.create(mRs);
Allocation inAlloc = Allocation.createFromBitmap(mRs, inBmp);
Allocation outAlloc = Allocation.createFromBitmap(mRs, outBmp);
siResize.setInput(inAlloc);
siResize.forEach_bicubic(outAlloc);
outAlloc.copyTo(outBmp);
inAlloc.destroy();
outAlloc.destroy();
siResize.destroy();
return outBmp;
}
OUTPUT_IMAGE is the integer value specifying the width of the output image.
NOTE: While using the RenderScript Allocation you have to be very careful as they lead to memory leakages.
Before enabling largeHeap option, I was handling large bitmaps and it's consume almost the entire memory available for the application, and recycling it over navigation and loading new ones works round on almost the full heap available. However when some operations needs a bit more memory the application crashes. So I enabled largeHeap=true to have a bit more memory.
But doing this has a unexpected behavior, it's looks like that recycle() method of bitmaps do not work most of times, and the application that worked in 58Mb of memory (and exceeds sometimes throwing a OutOfMemoryException) now consumes memory exponentially and keeps growing (for now the test I did came to 231Mb allocated memory), the expected behavior is that the memory management keeps working and the application will not use more than 60Mb.
How can I avoid that? Or efficiently recycle bitmaps?
EDIT: Actually, I made it give a OutOfMemoryError when allocating more than 390Mb of memory on the device.
Reading GC_* logs shown that only GC_FOR_ALLOC that freed 3.8Mb sometimes, but almost never other GC runs freed something.
You should probably have a look at Displaying Bitmaps Efficiently which includes several ways to handle large Bitmaps Efficiently,
Loading Large Bitmaps Efficiently
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
This will give you the size of the image before downloading and on that basis you can check the size of your device and scale it using calculateInSampleSize() and decodeSampledBitmapFromResource() given in the explanation of docs.
Calculating how much we need to scale the image,
First way
if (imageHeight > reqHeight || imageWidth > reqWidth) {
if (imageWidth > imageHeight ) {
inSampleSize = Math.round((float)imageHeight / (float)reqHeight);
} else {
inSampleSize = Math.round((float)imageWidth / (float)reqWidth);
}
}
Second way
int inSampleSize = Math.min(imageWidth / reqWidth,imageHeight / reqHeight);
The you can set the inSampleSize,
options.inSampleSize = inSampleSize;
Then finally make sure you call,
options.inJustDecodeBounds = false;
else it will return Bitmap as null
Processing Bitmaps Off the UI Thread
Processing Bitmap on UI thread is never safe so its always better to do that in a background thread and update UI after the process is completed.
Caching Bitmaps
LruCache is available from API 12 but if you are interested it using below versions it is also available in Support Library too. So caching of Images should be done efficiently using that. Also you can use DiskLruCache for images where you want then to remain for longer period in extenal storage.
Clearing the Cache
Sometimes when your image size is too large even caching the image causes OutOfMemoryError so in that case its better to clear the cache when your image is out of the scope or not used for longer period so that other images can be cached.
I had created a demo example for the same, you can download from here
Your case behaves as expected. Before Honeycomb, recycle() was unconditionally freeing the memory. But on 3.0 and above, bitmaps are part of normal garbage collected memory. You have plenty of RAM on the device, you allowed the JVM to allocate more than the 58M limit, now the garbage collector is satisfied and has no incentive to reclaim memory occupied by your bitmaps.
You can verify this by running on an emulator with controlled amount of RAM, or load some memory consuming service on your device - GC will jump to work. You can use DDMS to further investigate your memory usage.
You can try some solutions for bitmap memory management: Bitmaps in Android Bitmap memory leaks http://blog.javia.org/how-to-work-around-androids-24-mb-memory-limit/, but start with the official Android bitmap tips, as explained in #Lalit Poptani's detailed answer.
Note that moving the bitmaps to OpenGL memory as textures has some performance implications (but perfect if you will render these bitmaps through OpenGL in the end). Both textures and malloc solutions require that you explicitly free the bitmap memory which you don't use anymore.
Definitely #Lalit Poptani answer is the way to do it, you should really scale your Bitmaps if they are very large. A preferable way is that this is done server-side if this is possible, since you will also reduce NetworkOperation time.
Regarding the implementation of a MemoryCache and DiskCache this again is the best way to do it, but I would still recommend to use an existing library, which does exactly that (Ignition) and you will save yourself a lot of time, and also a lot of memory leaks, since because your Heap does not get emptied after GC I can assume that you probably have some memory leaks too.
To address your dilemma, I believe this is the expected behaviour.
If you want to free up memory you can occasionally call System.gc(), but really you should for the most part let it manage the garbage collection itself.
What I recommend is that you keep a simple cache (url/filename to bitmap) of some sort which keeps track of its own memory usage by calculating the number of bytes that each Bitmap is taking up.
/**
* Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
* #param width
* #param height
* #param config
* #return
*/
public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
long pixels=width*height;
switch(config){
case ALPHA_8: // 1 byte per pixel
return pixels;
case ARGB_4444: // 2 bytes per pixel, but depreciated
return pixels*2;
case ARGB_8888: // 4 bytes per pixel
return pixels*4;
case RGB_565: // 2 bytes per pixel
return pixels*2;
default:
return pixels;
}
}
Then you query how much memory the app is using and how much is available, maybe take half of that and try to keep the total image cache size under that, by simply removing (dereferencing) the older images from your list when your are coming up against this limit, not recycling. Let the garbage collector clean up the bitmaps when they are both derefrrenced from your cache and are not being used by any views.
/**
* Calculates and adjusts the cache size based on amount of memory available and average file size
* #return
*/
synchronized private int calculateCacheSize(){
if(this.cachedBitmaps.size()>0){
long maxMemory = this.getMaxMemory(); // Total max VM memory minus runtime memory
long maxAllocation = (long) (ImageCache.MEMORY_FRACTION*maxMemory);
long avgSize = this.bitmapCacheAllocated / this.cachedBitmaps.size();
this.bitmapCacheSize = (int) (maxAllocation/avgSize);
}
return this.bitmapCacheSize;
}
I would recommend you stay away from using recycle(), it causes a lot of intermittent exceptions (like when seemingly finalized views try to access recycled bitmaps) and in general seems buggy.
You have to be very careful with handling bitmaps on Android. Let me rephrase that: you have to watch out handling bitmaps even on a system with 4 gigs of RAM. How large are these guys and do you have a lot? You might have to chop and tile it up if it's large. Remember that you use using video RAM, which is a different animal than system RAM.
Pre-Honeycomb, the Bitmaps were allocated on the C++ layer, so that RAM usage was invisible to Java and couldn't be accessed by the garbage collector. A 3 MP uncompressed Bitmap with the RGB24 colorspace uses around 9-10 megabytes (around 2048x1512). So, larger images can easily fill up your heap. Also remember that in whatever is being used for video RAM (sometimes dedicated RAM, sometimes shared with system), the data is usually stored uncompressed.
Basically, if you are targeting pre-Honeycomb devices, you almost have to manage Bitmap object as if you were coding a C++ program. Running the bitmap recycle() onDestory() usually works if there aren't many images, but if you have a ton of images on the screen, you may have to handle them on-the-fly. Also, if you launch another activity, you may have to consider putting in logic into onPause() and onResume().
You can also cache the images using the Android file system or SQLite when they aren't in video RAM. You may be able to get away with caching it in RAM if you are using a format like .jpg or a .png with a lot of repeated data/
I have the following code which i took from another application
public static Bitmap createBitMap(DicomObject dcmObj) {
short[] image = dcmObj.getShorts(Integer.parseInt("7FE00010", 16));
float wc = dcmObj.getFloat(Integer.parseInt("00281050", 16));
float ww = dcmObj.getFloat(Integer.parseInt("00281051", 16));
Bitmap bitMap = Bitmap.createBitmap(dcmObj.getInt(Tag.Columns), dcmObj
.getInt(Tag.Rows), Bitmap.Config.ARGB_8888);
bitMap.copyPixelsFromBuffer(LocalImport.makeBuffer((localImport
.applyWindowing(image, wc, ww)), dcmObj.getInt(Tag.Columns),
dcmObj.getInt(Tag.Rows)));
return bitMap;
}
What I am trying to do is load few Dicom images from the SD Card read the attributes and display them in a grid.
The above function works fine with the application but when i integrate the same code to my application it crashes.
I tried debugging, but the values of the variables are the same.
Until changes are made to Android, the only way to allocate buffers larger than the VM limit is to allocate them in native code. In native code you can allocate as much memory as is physically available to the linux system underneath Android. I've tested this with my own applications and have allocated bitmaps larger than 150MB. Managing bitmaps in native code will then necessitate writing code which renders "views" of the bitmap into a display-sized image that is managed by Java.
the crash was because the animations in the main ui was taking up lots of memory.
the Sample application i had copied the code dint have any animations.
guess increasing the size of the memory allocated in the emulator can solve the issue(dint try it out yet).
But atleast i know its not because of any deallocations i hadnt done or logical errors..
Thanks all for the help :)
My game uses surfaceview(I know I should use GL).
I draw alot of bitmaps to my game character world controll so on. And i run into this when I opened my LogDog:
08-05 10:17:29.151: ERROR/dalvikvm(24048): Out of memory: Heap Size=5379KB, Allocated=2735KB, Bitmap Size=20576KB, Limit=32768KB
I dont know if it is leak or what.
My Allocation Tracker shows:
like 30:
138 96 char[] 9 android.content.res.AssetManager getCookieName
Then
Tons of:
32 80 android.graphics.BitmapFactory$Options 9 android.graphics.BitmapFactory decodeResource
And last:
Like 30:
141 56 android.graphics.Bitmap 9 android.graphics.BitmapFactory nativeDecodeAsset
And also some more of simular ones.
Here is some code that I think drains my memory:
player = BitmapFactory.decodeResource(getResources(), R.raw.ghostright);
world = BitmapFactory.decodeResource(getResources(), R.raw.lvl2);
thumb = BitmapFactory.decodeResource(getResources(), R.raw.thumb);
resized = Bitmap.createScaledBitmap(player, width/10, width/6, false);
player = resized;
resized = Bitmap.createScaledBitmap(world, height*10, height, false);
world = resized;
resized = Bitmap.createScaledBitmap(thumb, height/6, height/6, false);
thumb = resized;
I heard that I should use resycle but I dont know where because I always use the bitmaps
//Simon
PS: I really need help -.-
I use a lot of bitmaps on SurfaceView too and don't have this problem.
When it comes to animated sprites, you can use a sprite sheet rather than load them individually frame by frame.
You don't need to use the reference "resized" you can just say:
player = Bitmap.createScaledBitmap(player, width/10, width/6, true);
the old bitmap will lose its reference and be collected by GC. Note that I placed TRUE for bitmap filtering when rescaling to make a better quality.
On some devices onSizeChanged can happen two times which may resize bitmaps twice, if that is where you are doing your scaling.
The format of loaded bitmaps does metter whether it is ARGB_4444 or ARGB_8888 etc So you may need to explore this option and if you can use a format which requires less memory yet it has a good enough quality for your game. Of course the rule is not to load images into memory bigger than they are needed and when they are needed.
It dosen't have to be a memory leak, it could just be that you have so large bitmaps that they want to allocate to much memory. Here's a good method to determine how much memory a bitmap is going to take: W*H*8. So if you have a bitmap that's 300*300 px it's 300*300*8 = 720 kb.
Figure out how much allocated heap you have at any given time and see if it increases with time even though you know that you're not allocating new bitmaps. If so, then yes, you have a memory leak. If, however, your app crashes right on startup, then you are probably just exceeding heap limit.