Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bitmap Out Of Memory Issues

My Problem

I take a picture with my android device. I then decode that picture from file.

        Bitmap photo = BitmapFactory.decodeFile(EXTERNAL_IMAGE_PATH+File.separator+this._currentPhotoName+JPEG_FILE_SUFFIX);
        if (photo == null && data != null) 
            photo = (Bitmap) data.getExtras().get("data");
        else if (data == null && photo == null)
            Log.e("CCPhotoManager","Can't find image from file or from intent data.");

I then check that picture and see whether it needs to be rotated to the correct orientation.

             try {
                ExifInterface exif = new ExifInterface(EXTERNAL_IMAGE_PATH+File.separator+this._currentPhotoName+JPEG_FILE_SUFFIX);
                int rotation = CCDataUtils.exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL));
                Log.v("CCPhotoManager", "Rotation:"+rotation);
                if (rotation > 0) {
                    photo = this.convertSavedImageToCorrectOrientation(EXTERNAL_IMAGE_PATH+File.separator+this._currentPhotoName+JPEG_FILE_SUFFIX, photo, rotation);
                }
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }

If it does need rotating I call this method.

public Bitmap convertSavedImageToCorrectOrientation(String filePath,Bitmap photo,int rotation) {
        Log.d("CCPhotoManager", "Changing Orientation of photo located at: "+filePath+" Rotating by:"+rotation);
        int width = photo.getWidth();
        int height = photo.getHeight();


        Matrix matrix = new Matrix();
        matrix.preRotate(rotation);

        Bitmap adjusted = Bitmap.createBitmap(photo, 0, 0, width, height, matrix, true);

        try {
               FileOutputStream out = new FileOutputStream(filePath);
               adjusted.compress(Bitmap.CompressFormat.JPEG, 100, out);
        } catch (Exception e) {
               e.printStackTrace();
        }

        return adjusted;
    }

I am getting Out of Memory complaints if the convertSavedImageToCorrectOrientation is called on the line Bitmap adjusted = Bitmap.createBitmap(photo,0,0,width,height,matrix,true);

This is only the case on the Samsung Galaxy S3. It works fine on the Samsung Galaxy Ace, HTC Hero and the Sony Xperia U.

Here is the error.

10-17 14:33:33.950: E/AndroidRuntime(12556): java.lang.OutOfMemoryError
10-17 14:33:33.950: E/AndroidRuntime(12556):    at android.graphics.Bitmap.nativeCreate(Native Method)
10-17 14:33:33.950: E/AndroidRuntime(12556):    at android.graphics.Bitmap.createBitmap(Bitmap.java:605)
10-17 14:33:33.950: E/AndroidRuntime(12556):    at android.graphics.Bitmap.createBitmap(Bitmap.java:551)

It's a massive amount of memory too.

10-17 14:33:33.945: E/dalvikvm-heap(12556): Out of memory on a 31961104-byte allocation.

I think its something to do with the amount of Bitmaps around but I'm not sure how to stop this error from happening.

I know you can call .recycle(); on them but it doesn't seem to work.

My Question

How do I correctly handle my Bitmaps so I don't have this OOM problem?

Thanks in advance

like image 876
StuStirling Avatar asked Oct 17 '12 13:10

StuStirling


People also ask

Why do I get an outofmemory exception with bitmap decoding?

These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemoryexception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Optionsclass.

What happens when you decode a bitmap in Java?

So every time you decode a bitmap, it allocates memory outside of VM heap which is never reclaimed by GC To help you better appreciate this, imagine you have kept ur image in the drawable folder. You just get the image by doing a getResources().getDrwable(R.drawable.).

How much memory does it take to encode a 2048x1536 image?

For example, an image with resolution 2048x1536 that is decoded with an inSampleSizeof 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration of ARGB_8888).

Is it my responsibility to cache the decoded bitmap image?

Now since your image is in a file somewhere (or may even be coming from an external server), it is YOUR responsibility to cache the decoded bitmap instance to be reused any where it is needed. Hope this helps. Share Improve this answer Follow


Video Answer


3 Answers

Here is a more complete example of how to resize/rotate, taken in part from the Android Developers guide (change REQ_WIDTH and REQ_HEIGHT):

private static final int REQ_WIDTH = 450;
private static final int REQ_HEIGHT = 450;

/**
 * Resize, crop, rotate and Inserts the picture on the layout.
 * 
 * @param mImageView to insert the bitmap.
 * @param imageURI from wich to obtain the bitmap.
 * 
 */
private void setPic(ImageView mImageView, String imageURI) {
    // Get the original bitmap dimensions
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imageURI, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, REQ_HEIGHT, REQ_WIDTH);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    Bitmap bitmap = BitmapFactory.decodeFile(imageURI, options);

    //need rotation?
    float rotation = rotationForImage(getActivity(), Uri.fromFile(new File(imageURI)));

    if (rotation != 0) {
        //rotate
        Matrix matrix = new Matrix();
        matrix.preRotate(rotation);
        mImageView.setImageBitmap(Bitmap.createBitmap(bitmap, 0, 0, REQ_HEIGHT, REQ_WIDTH, matrix, true));
    } else {
        //use the original
        mImageView.setImageBitmap(BitmapFactory.decodeFile(imageURI, options));
    }
}

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
        if (width > height) {
            inSampleSize = Math.round((float) height / (float) reqHeight);
        } else {
            inSampleSize = Math.round((float) width / (float) reqWidth);
        }
    }
    return inSampleSize;
}

public static float rotationForImage(Context context, Uri uri) {
    try {
        if (uri.getScheme().equals("content")) {
            String[] projection = { Images.ImageColumns.ORIENTATION };
            Cursor c = context.getContentResolver().query(uri, projection, null, null, null);
            if (c.moveToFirst()) {
                return c.getInt(0);
            }
        } else if (uri.getScheme().equals("file")) {
            ExifInterface exif = new ExifInterface(uri.getPath());
            int rotation = (int) exifOrientationToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL));
            return rotation;
        }
        return 0;

    } catch (IOException e) {
        Log.e(TAG, "Error checking exif", e);
        return 0;
    }
}

private static float exifOrientationToDegrees(int exifOrientation) {
    if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
        return 90;
    } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {
        return 180;
    } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {
        return 270;
    }
    return 0;
}
like image 39
nsemeniuk Avatar answered Sep 21 '22 23:09

nsemeniuk


For out of memory issue

//decodes image and scales it to reduce memory consumption

private Bitmap decodeFile(File f){

try {
    //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(new FileInputStream(f),null,o);

    //The new size we want to scale to
    final int REQUIRED_SIZE=70;

    //Find the correct scale value. It should be the power of 2.
    int scale=1;
    while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
        scale*=2;

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize=scale;
    return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
like image 198
Amit Hooda Avatar answered Sep 20 '22 23:09

Amit Hooda


I had the exact same issue, doing the exact same thing on the exact same device! Unfortunately in my case the image needed to be submitted to a webservice, using the full size, original. What worked for me was turning on largeHeap in the application element of the manifest.

This will not solve the issue permanently - its possible that a device will come along with an even larger camera and the images will not fit in memory even with largeHeap enabled. To catch this extreme edge case I also put a try catch around the code that rotates the image, and just displayed a nice error to the user.

A fuller solution would be to write your own jpeg manipulation code that can rotate a jpeg using a stream based approach so that the image never needs to be loaded into memory.

like image 38
Luke Sleeman Avatar answered Sep 22 '22 23:09

Luke Sleeman