Preventing out of memory with high res photo
Hi,
I notice in my app when I take a picture and process it the app crashes if I take the picture in full resolution (8M on my phone). My code is basically trying to do the following:
- Take a picture
- Check if it was in portrait or landscape
- Resize to no bigger than the screen
- Save to disk
Functionally it all works, but the act of putting the image into an imageview (which I think I need to get the size) causes OOM about 50% of the time.
02-11 10:22:14.039: E/dalvikvm-heap(6131): 24023040-byte external allocation too large for this process.
02-11 10:22:14.039: E/dalvikvm(6131): Out of memory: Heap Size=8263KB, Allocated=4617KB, Bitmap Size=23478KB, Limit=32768KB
02-11 10:22:14.039: E/dalvikvm(6131): Trim info: Footprint=8263KB, Allowed Footprint=8263KB, Trimmed=1624KB
02-11 10:22:14.079: E/GraphicsJNI(6131): VM won't let us allocate 24023040 bytes
02-11 10:22:14.079: D/dalvikvm(6131): GC_FOR_MALLOC freed <1K, 45% free 4617K/8263K, external 23478K/25526K, paused 26ms
02-11 10:22:14.079: D/skia(6131): --- decoder->decode returned false
02-11 10:22:14.079: E/TiDrawableReference(6131): (main) [82,70664] Unable to load bitmap. Not enough memory: bitmap size exceeds VM budget(Heap Size=8839KB, Allocated=4617KB, Bitmap Size=23478KB)
02-11 10:22:14.079: E/TiDrawableReference(6131): java.lang.OutOfMemoryError: bitmap size exceeds VM budget(Heap Size=8839KB, Allocated=4617KB, Bitmap Size=23478KB)
02-11 10:22:14.079: E/TiDrawableReference(6131): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
02-11 10:22:14.079: E/TiDrawableReference(6131): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:694)
02-11 10:22:14.079: E/TiDrawableReference(6131): at org.appcelerator.titanium.view.TiDrawableReference.getBitmap(TiDrawableReference.java:253)
02-11 10:22:14.079: E/TiDrawableReference(6131): at org.appcelerator.titanium.view.TiDrawableReference.getBitmap(TiDrawableReference.java:483)
02-11 10:22:14.079: E/TiDrawableReference(6131): at ti.modules.titanium.ui.widget.TiUIImageView.setImage(TiUIImageView.java:738)
Here is my code:
Ti.API.info("creating picture image view");
var iView = Ti.UI.createImageView({
image:event.media,
visible:false,
height:'auto',
width:'auto'
});
Ti.API.info("image view created");
//parentWin.add(iView);
var img = iView.toImage();
Ti.API.info("Size "+img.height+' '+img.width);
if (img.width>img.height){
//do stuff
}
else {
var factor = AppUtil.ui.screenWidth/img.width;
var newH = Math.round(img.height*factor);
var newW = Math.round(img.width*factor);
Ti.API.info("Resizing Factor "+factor+" New Height "+newH+" New Width "+newW);
var f = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory,fname);
if (AppUtil.isAndroid()){
newImg = ImageFactory.imageAsResized(event.media, { width:newW, height:newH});
ImageFactory.compressToFile(newImg,.5,f.getNativePath());
} else {
//alert("Resizing Factor "+factor+" New Height "+newH+" New Width "+newW);
// newImg = ImageFactory.imageAsResized(event.media, { width:640, height:960});
//alert("resizing complete");
newImg2 = ImageFactory.compress(event.media.imageAsResized(640,960),.5);
f.write(newImg2);
}
I haven't seen the issue on iOS. A few questions:
- It appears that its trying to allocate 24megs but the image is only 8 - any idea why?
- ANything I can do in this area of the code to be more memory efficient?
3 Answers
-
I've created a standlone app.js that recreates the issue. The OOM happens when I take a full resolution (8m) picture. It can happen in a few different spots:
// this sets the background color of the master UIView (when there are no windows/tab groups on it) Titanium.UI.setBackgroundColor('#000'); // create tab group var tabGroup = Titanium.UI.createTabGroup(); var win1 = Titanium.UI.createWindow({ title:'Tab 1', backgroundColor:'#fff' }); var tab1 = Titanium.UI.createTab({ icon:'KS_nav_views.png', title:'Tab 1', window:win1 }); // // add tabs // tabGroup.addTab(tab1); // open tab group tabGroup.open(); // Create a Button. var aButton = Ti.UI.createButton({ title : 'take pic', height : 100, width : 250, top : 20, left : 20 }); // Listen for click events. aButton.addEventListener('click', function() { addPictures(1,win1); }); // Add to the parent view. win1.add(aButton); var ImageFactory = require('ti.imagefactory'); function addPictures(mid,parentWin){ Titanium.Media.showCamera({ success:function(event) { // called when media returned from the camera Ti.API.debug('Our type was: '+event.mediaType); if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO) { var fname = 'test.jpg'; Ti.API.debug("Writing file "+fname + JSON.stringify(event.media)); Ti.API.info("creating picture image view"); Ti.API.info("memory = "+Ti.Platform.availableMemory); var iView = Ti.UI.createImageView({ image:event.media, visible:false, height:'auto', width:'auto' }); Ti.API.info("image view created"); Ti.API.info("memory = "+Ti.Platform.availableMemory); //this sometimes causes OOM var img = iView.toImage(); var ih = img.height; var iw = img.width; var len = img.length; //img = null; //iView = null; Ti.API.info("Size "+ih+' '+iw); Ti.API.info("Img Info: "+JSON.stringify(img)); if (iw>ih){ alert("landscape"); } else { //already in portrait savePic(fname,mid,1,event.media,ih,iw,parentWin); } } else { alert("got the wrong type back ="+event.mediaType); } }, cancel:function() { alert('cancel'); }, error:function(error) { // called when there's an error var a = Titanium.UI.createAlertDialog({title:'Camera'}); if (error.code == Titanium.Media.NO_CAMERA) { a.setMessage('No Camera Found'); } else { a.setMessage('Unexpected error: ' + error.code); } a.show(); }, saveToPhotoGallery:false, allowEditing:false, mediaTypes:[Ti.Media.MEDIA_TYPE_PHOTO] }); } function savePic(fname,mid,thisImgIdx,media,ih,iw,parentWin){ var factor = Ti.Platform.displayCaps.platformWidth/iw; var newH = Math.round(ih*factor); var newW = Math.round(iw*factor); Ti.API.info("Resizing Factor "+factor+" New Height "+newH+" New Width "+newW); Ti.API.info("memory = "+Ti.Platform.availableMemory); var f = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory,fname); //sometimes either of these lines cause OOM newImg = ImageFactory.imageAsResized(media, { width:newW, height:newH}); ImageFactory.compressToFile(newImg,.5,f.getNativePath()); Ti.API.debug("Writing file, complete "+fname); Ti.API.info("memory = "+Ti.Platform.availableMemory); alert('done'); }
-
you have multiple instances of the image object in memory, it is no wonder the thing is crashing.
this is a phone with limited memory, you cannot keep all copies of the image around.
you have the event.media, the imageView you created, then the image from the toImage call and then you load a module to manipulate another copy of the image.
-
The solution I'm using, compliments of iotashan, is to use this module which handles the images natively. https://github.com/yagitoshiro/ImageAsResized Thanks to the author as well, it's a life saver.