Sunday, March 04, 2012

Loading large bitmaps in Android

Loading images in Android is simple if you'd believe the SDK documentation and samples. In most cases, I suppose it is. The only problem is that the simple way assumes you're going to load "small" bitmaps into memory.

If you try to load one of those large photos that's so common nowadays, you'll get an "out of memory" error in no time.

To get around that, there's a few tricks that can be used.

Loading parts of the bitmap

The simplest way would be to just load enough of the image to fill the screen. Images large enough to cause out of memory errors will be much larger than the screen size of any Android device.

If you only need/want to display a part of the image, it's just a matter of loading up a portion of it. This way you can't go over the memory limits.

... // Do stuff (initialize variables/objects, load things, etc.)
BitmapRegionDecoder decoder = null;
try {
    decoder = BitmapRegionDecoder.newInstance(mRes.openRawResource(resId), false);
} catch (NotFoundException e) {
} catch (IOException e) {

Bitmap image = decoder.decodeRegion(mRect, null);
... // Do more stuff (draw the Bitmap, etc.)

Sampling the bitmap

Another way to do things is to sample the bitmap you want. Basically, you want to scale down a too large image to a more manageable size.

You'll first need to get just the image dimensions (width, height).

BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
BitmapFactory.decodeBitmap(res, id, opt);

The dimensions of the bitmap will be in opt.outWidth and opt.outHeight.

That's the easy part.

After this you'll want to make use of the info you've obtained for calculating an appropriate sample size to use when decoding the bitmap for real.  I referenced this blog post for a workable formula, but find that it doesn't really work well for me in most cases.

int sampleSize = (int) Math.pow(2, Math.floor(Math.sqrt(factor)));

It's recommended to use sample sizes in powers of 2. Once you have a sample size you can decode your bitmap safely.

opt.inJustDecodeBounds = false;
opt.inSampleSize = sampleSize;
Bitmap image = BitmapFactory.decodeBitmap(res, id, opt);

That's about it. Getting a nice looking image is a matter of getting the best sample size to use for your particular image. That's the tricky part.

Just for completeness, here's a site that gives an idea just how much memory an image can consume. The large images so common these days can easily take up nearly 20MB of memory. Possibly more. Much more.

Good luck.