32

I have a bunch of image URLs. I have to download these images and display them in my application one-by-one. I am saving the images in a Collection using SoftReferences and also on Sdcard to avoid refetches and improve user experience.

The problem is I dont know anything about the size of the bitmaps. And as it turns out, I am getting OutOfMemoryExceptions sporadically, when I am using BitmapFactory.decodeStream(InputStream) method. So, I chose to downsample the images using BitmapFactory Options(sample size=2). This gave a better output: no OOMs, but this affects the quality of smaller images.

How should I handle such cases? Is there a way to selectively downsample only high resolution images?

tshepang
  • 12,111
  • 21
  • 91
  • 136
Samuh
  • 36,316
  • 26
  • 109
  • 116
  • The official [Android Training](http://developer.android.com/training/index.html) site now has a class called [Displaying Bitmaps Efficiently](http://developer.android.com/training/displaying-bitmaps/index.html) with a lesson, [Loading Large Bitmaps Efficiently](http://developer.android.com/training/displaying-bitmaps/load-bitmap.html) that goes over this. The [Caching Bitmaps](http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html) lesson also covers some information on caching using a hard reference memory cache rather than a WeakReference or SoftReference. – AdamK Apr 10 '12 at 09:58

3 Answers3

55

There is an option in BitmapFactory.Options class (one I overlooked) named inJustDecodeBounds, javadoc of which reads:

If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.

I used it to find out the actual size of the Bitmap and then chose to down sample it using inSampleSize option. This at least avoids any OOM errors while decoding the file.

Reference:
1. Handling larger Bitmaps
2. How do I get Bitmap info before I decode

Samuh
  • 36,316
  • 26
  • 109
  • 116
  • 10
    +1 for answering your own question when you found out the answer; think of those who google and find your question, but no answer. – Will Feb 09 '10 at 13:19
  • Its really works fine if i want to get the dimensions, but i am getting OOM error when i want to decode a file to bitmap and set that bitmap to ImageView. Please tell me how to optimize it or any other way to fix this issue. – Suresh Nov 27 '12 at 07:37
14

After a few days struggling to avoid all OutOfMemory errors that I was getting with different devices, I create this:

private Bitmap getDownsampledBitmap(Context ctx, Uri uri, int targetWidth, int targetHeight) {
    Bitmap bitmap = null;
    try {
        BitmapFactory.Options outDimens = getBitmapDimensions(uri);

        int sampleSize = calculateSampleSize(outDimens.outWidth, outDimens.outHeight, targetWidth, targetHeight);

        bitmap = downsampleBitmap(uri, sampleSize);

    } catch (Exception e) {
        //handle the exception(s)
    }

    return bitmap;
}

private BitmapFactory.Options getBitmapDimensions(Uri uri) throws FileNotFoundException, IOException {
    BitmapFactory.Options outDimens = new BitmapFactory.Options();
    outDimens.inJustDecodeBounds = true; // the decoder will return null (no bitmap)

    InputStream is= getContentResolver().openInputStream(uri);
    // if Options requested only the size will be returned
    BitmapFactory.decodeStream(is, null, outDimens);
    is.close();

    return outDimens;
}

private int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) {
    int inSampleSize = 1;

    if (height > targetHeight || width > targetWidth) {

        // Calculate ratios of height and width to requested height and
        // width
        final int heightRatio = Math.round((float) height
                / (float) targetHeight);
        final int widthRatio = Math.round((float) width / (float) targetWidth);

        // Choose the smallest ratio as inSampleSize value, this will
        // guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
    return inSampleSize;
}

private Bitmap downsampleBitmap(Uri uri, int sampleSize) throws FileNotFoundException, IOException {
    Bitmap resizedBitmap;
    BitmapFactory.Options outBitmap = new BitmapFactory.Options();
    outBitmap.inJustDecodeBounds = false; // the decoder will return a bitmap
    outBitmap.inSampleSize = sampleSize;

    InputStream is = getContentResolver().openInputStream(uri);
    resizedBitmap = BitmapFactory.decodeStream(is, null, outBitmap);
    is.close();

    return resizedBitmap;
}

This method works with all devices I tested, but I think the quality can be better using other process that I'm not aware.

I hope my code can help other developers in the same situation. I also appreciate if a senior developer can help, giving a suggestion about other process to avoid lose (less) quality in the process.

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Ryan Amaral
  • 4,059
  • 1
  • 41
  • 39
12

What I've done myself is :

  • use inJustDecodeBounds to get the original size of the image
  • have a fixed maximum Surface for loading the Bitmap (say 1Mpixels)
  • check if the image surface is below the limit, if yes, then load it directly
  • if not compute the ideal width and height at which you should load the Bitmap to stay below the max surface (there is some simple maths to do here). This give you a float ratio that you want to apply when loading the bitmap
  • now you want to translate this ratio to a suitable inSampleSize (a power of 2 that doesn't degrade the image quality). I use this function :
int k = Integer.highestOneBit((int)Math.floor(ratio));
if(k==0) return 1;
else return k;
  • then because the Bitmap will have been loaded with a slightly higher resolution than the max surface (because you had to use a smaller power of 2), you'll have to resize the Bitmap, but it will be much faster.
Greg
  • 196
  • 1
  • 3
  • thanks for adding the routine to calculate sample size. It augments the thread with a piece of important information. – Samuh Sep 15 '10 at 07:52