5

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

StuStirling
  • 15,601
  • 23
  • 93
  • 150
  • This is a fairly commonly asked question with lots of duplicates. Most of them are specific to whatever situation the developer cornered himself into, but chances are you could get somewhere by reading what techniques were suggested to them in order to keep bitmap size down. – Wug Oct 17 '12 at 13:49
  • You need to read, modify and save the entire image? No sample size? Maybe is happening on the S3 because it has better camera, and therefore you load a bigger picture on memory. – nsemeniuk Oct 17 '12 at 13:51
  • 1
    @nsemeniuk I need to rotate the entire image to its correct orientation and save it again because the camera always saves it in its default orientation which is not always correct. I'm fairly certain the size of the image due to better camera is the reason like you said – StuStirling Oct 17 '12 at 13:54
  • ok, the camera saves it that way agreed, but then if you need to use that pic on an app, you always have the EXIF data to display it properly on your app... smaller of course. – nsemeniuk Oct 17 '12 at 13:57
  • Thats a good suggestion and a good alternative. The issue I have with this method is when I need the actual file in the correct orientation ie. attaching it to an email? – StuStirling Oct 17 '12 at 14:00
  • Yup, each image takes 32 MB of memory, so to do the rotation you'd need 64 which apparently goes over what the system can allocate (varies from device to device). – dmon Oct 17 '12 at 14:10
  • Maybe not sending attached an original, but a smaller version in case the picture is too big? That's all i can think of... – nsemeniuk Oct 17 '12 at 14:13
  • My answer posted in another similar thread might help you: http://stackoverflow.com/questions/477572/strange-out-of-memory-issue-while-loading-an-image-to-a-bitmap-object/18544069#18544069 – Bruce Sep 16 '13 at 02:06

3 Answers3

4

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;
}
Amit Hooda
  • 2,133
  • 3
  • 23
  • 37
  • If I scale the image and then save it again its going to be smaller than the original? – StuStirling Oct 17 '12 at 13:55
  • it might make it somewhat blur , so u can check its size it is much higher then apply this and notify the user – Amit Hooda Oct 17 '12 at 13:58
  • What is the size variable here? Is that 70% of the originals size? – StuStirling Oct 17 '12 at 14:45
  • So its the percentage of the orginals size? I am trying to implement this at the moment – StuStirling Oct 17 '12 at 14:52
  • REQUIRED_SIZE is the new size you want to scale to. – Amit Hooda Oct 17 '12 at 14:57
  • Thanks for this method. Very helpful. Just need to find a way of getting the desired scale where it still looks ok – StuStirling Oct 17 '12 at 15:04
  • Sorry adding onto my previous comment. Is there a way to find the exact size I want to scale to, for example the width of my phone screen? What if my image isn't square also – StuStirling Oct 17 '12 at 15:07
  • Display display = getWindowManager().getDefaultDisplay(); Point size = new Point(); display.getSize(size); int width = size.x; int height = size.y; , you can get the width and height at run time and then you can enter the required size accordingly – Amit Hooda Oct 17 '12 at 15:18
4

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.

Luke Sleeman
  • 1,636
  • 1
  • 16
  • 27
3

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;
}
nsemeniuk
  • 1,171
  • 1
  • 13
  • 24