10

I have an app that displays quite a few images for the user, and we've been seeing a lot of error reports with OutOfMemoryError exception.

What we currently do is this:

// Check if image is a landscape image
if (bmp.getWidth() > bmp.getHeight()) {
    // Rotate it to show as a landscape
    Matrix m = image.getImageMatrix();
    m.postRotate(90);
    bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
}
image.setImageBitmap(bmp);

The obvious problem with this is that we have to recreate the bitmap from the image on memory and rotate the matrix, this is quite expensive for the memory.

My question is simple:

Is there a better way to rotate images without causing OutOfMemoryError?

Draiken
  • 3,805
  • 2
  • 30
  • 48

3 Answers3

6

2 methods of rotating a large image:

  1. using JNI , like on this post.

  2. using a file : it's a very slow way (depending on the input and the device , but still very slow) , which puts the decoded rotated image into the disk first , instead of putting it into the memory .

code of using a file is below:

private void rotateCw90Degrees()
  {
  Bitmap bitmap=BitmapFactory.decodeResource(getResources(),INPUT_IMAGE_RES_ID);
  // 12 => 7531
  // 34 => 8642
  // 56 =>
  // 78 =>
  final int height=bitmap.getHeight();
  final int width=bitmap.getWidth();
  try
    {
    final DataOutputStream outputStream=new DataOutputStream(new BufferedOutputStream(openFileOutput(ROTATED_IMAGE_FILENAME,Context.MODE_PRIVATE)));
    for(int x=0;x<width;++x)
      for(int y=height-1;y>=0;--y)
        {
        final int pixel=bitmap.getPixel(x,y);
        outputStream.writeInt(pixel);
        }
    outputStream.flush();
    outputStream.close();
    bitmap.recycle();
    final int newWidth=height;
    final int newHeight=width;
    bitmap=Bitmap.createBitmap(newWidth,newHeight,bitmap.getConfig());
    final DataInputStream inputStream=new DataInputStream(new BufferedInputStream(openFileInput(ROTATED_IMAGE_FILENAME)));
    for(int y=0;y<newHeight;++y)
      for(int x=0;x<newWidth;++x)
        {
        final int pixel=inputStream.readInt();
        bitmap.setPixel(x,y,pixel);
        }
    inputStream.close();
    new File(getFilesDir(),ROTATED_IMAGE_FILENAME).delete();
    saveBitmapToFile(bitmap); //for checking the output
    }
  catch(final IOException e)
    {
    e.printStackTrace();
    }
  }
Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • @android_developer Loved your file solution, I think it's worth the time to avoid all the memory issues. – vvbYWf0ugJOGNA3ACVxp Sep 17 '15 at 06:22
  • @PeterFile I think you should do it in multiple fallbacks from fastest (yet most dangerous) to sloweest (yet safest) : first try by using the normal way (using the heap). If that fails (not enough memory), use the JNI solution. If that fails (not enough memory), use the storage solution. The storage solution might also fail (not enough storage), since the file that's created is the decoded bitmap, and not compressed one, but this might be a rare thing to happen. – android developer Sep 17 '15 at 07:13
  • @androiddeveloper Does you solution keep the original image size and quality? – Yoann Hercouet Apr 26 '16 at 10:39
  • @YoannHercouet Yes, as I don't even encode it into compressed format. – android developer Apr 26 '16 at 12:30
0

you can try:

image.setImageBitmap(null);
// Check if image is a landscape image
if (bmp.getWidth() > bmp.getHeight()) {
    // Rotate it to show as a landscape
    Matrix m = image.getImageMatrix();
    m.postRotate(90);
    bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
}
BitmapDrawable bd = new BitmapDrawable(mContext.getResources(), bmp);
bmp.recycle();
bmp = null;
setImageDrawable(bd);
bd = null;
Caner
  • 57,267
  • 35
  • 174
  • 180
  • As stated on a comment up in the question, the error is happenning in the `Bitmap.createBitmap` line. – Draiken Nov 28 '11 at 11:25
0

When working with lots of Bitmaps be sure to call recycle() on them as soon as they are not needed. This call will instantly free memory associated with a particular bitmap.

In your case if you do not need the original bitmap after rotation, then recycle it. Something along the lines of:

Bitmap result = bmp;

// Check if image is a landscape image
if (bmp.getWidth() > bmp.getHeight()) {
    // Rotate it to show as a landscape
    Matrix m = image.getImageMatrix();
    m.postRotate(90);
    result = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
    // rotating done, original not needed => recycle()
    bmp.recycle();
}

image.setImageBitmap(result);
dimsuz
  • 8,969
  • 8
  • 54
  • 88
  • Nice, didn't know about that. The problem is that I need the original bitmap to rotate it and the error occurs before I can free him from memory :/ – Draiken Nov 28 '11 at 11:32
  • Actually I tried the recycle and it just shows now a black screen instead of the bitmap. I guess internally it's a pointer and I can't really mess with it. – Draiken Nov 28 '11 at 12:13
  • Hm. Bitmap.createBitmap() should create entirely new Bitmap, so freeing original one should be safe. Actually I do use this approach a lot in my own code and it works quite well. Are your sure you are recycling right bitmap and only *after* assigning reference to rotated bitmap returned by createBitmap() to other reference (like in my example). It could throw error in createBitmap() if you already used up all memory. But if each time you correctly free it with recycle() it should be ok on next invocations... – dimsuz Nov 28 '11 at 12:25
  • Of course if it crashes on *first* invocation, than my answer won't help. This would mean that when you allocate with createBitmap(), memory is *already* almost full and can hold no new objects. My answer is applicable to situations where you have to rotate many bitmaps several times one after another - then recycling will help for sure :) – dimsuz Nov 28 '11 at 12:27
  • Yeah I pretty much tried your way and it became blank. Assigned the new bitmap to a different variable and recycled the original one. – Draiken Nov 28 '11 at 13:23
  • Hmm. Strange, it really shouldn't be that way - I even checked the Bitmap class source code - it does create new Bitmap, no shallow copying in your case. And do you call setImageBitmap() with 'result' as an argument, not with 'bmp'? :) – dimsuz Nov 28 '11 at 13:36
  • of course he can't recycle the bitmap before creating a new rotated one from the old one, so at some point both bitmaps have to be loaded, he could recycle after but by that time the program crashes – Dan Levin Feb 19 '14 at 10:58