Most memory efficient way to resize bitmaps on android?

15,020

Solution 1

This answer is summarised from Loading large bitmaps Efficiently which explains how to use inSampleSize to load a down-scaled bitmap version.

In particular Pre-scaling bitmaps explains the details of various methods, how to combine them, and which are the most memory efficient.

There are three dominant ways to resize a bitmap on Android which have different memory properties:

createScaledBitmap API

This API will take in an existing bitmap, and create a NEW bitmap with the exact dimensions you’ve selected.

On the plus side, you can get exactly the image size you’re looking for (regardless of how it looks). But the downside, is that this API requires an existing bitmap in order to work. Meaning the image would have to be loaded, decoded, and a bitmap created, before being able to create a new, smaller version. This is ideal in terms of getting your exact dimensions, but horrible in terms of additional memory overhead. As such, this is kind-of a deal breaker for most app developers who tend to be memory conscious

inSampleSize flag

BitmapFactory.Options has a property noted as inSampleSize that will resize your image while decoding it, to avoid the need to decode to a temporary bitmap. This integer value used here will load an image at a 1/x reduced size. For example, setting inSampleSize to 2 returns an image that’s half the size, and Setting it to 4 returns an image that’s 1/ 4th the size. Basically image sizes will always be some power-of-two smaller than your source size.

From a memory perspective, using inSampleSize is a really fast operation. Effectively, it will only decode every Xth pixel of your image into your resulting bitmap. There’s two main issues with inSampleSize though:

  • It doesn’t give you exact resolutions. It only decreases the size of your bitmap by some power of 2.

  • It doesn’t produce the best quality resize. Most resizing filters produce good looking images by reading blocks of pixels, and then weighting them to produce the resized pixel in question. inSampleSize avoids all this by just reading every few pixels. The result is quite performant, and low memory, but quality suffers.

If you're only dealing with shrinking your image by some pow2 size, and filtering isn't an issue, then you can't find a more memory efficient (or performance efficient) method than inSampleSize.

inScaled, inDensity, inTargetDensity flags

If you need to scale an image to a dimension that’s not equal to a power of two, then you’ll need the inScaled, inDensity and inTargetDensity flags of BitmapOptions. When inScaled flag has been set, the system will derive the scaling value to apply to your bitmap by dividing the inTargetDensity by the inDensity values.

mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(), 
      mImageIDs, mBitmapOptions);

Using this method will re-size your image, and also apply a ‘resizing filter’ to it, that is, the end result will look better because some additional math has been taken into account during the resizing step. But be warned: that extra filter step, takes extra processing time, and can quickly add up for big images, resulting in slow resizes, and extra memory allocations for the filter itself.

It’s generally not a good idea to apply this technique to an image that’s significantly larger than your desired size, due to the extra filtering overhead.

Magic Combination

From a memory and performance perspective, you can combine these options for the best results. (setting the inSampleSize, inScaled, inDensity and inTargetDensity flags)

inSampleSize will first be applied to the image, getting it to the next power-of-two LARGER than your target size. Then, inDensity & inTargetDensity are used to scale the result to exact dimensions that you want, applying a filter operation to clean up the image.

Combining these two is a much faster operation, since the inSampleSize step will reduce the number of pixels that the resulting Density-based step will need to apply it’s resizing filter on.

mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth * mBitmapOptions.inSampleSize;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);

If you're needing to fit an image to specific dimensions, and some nicer filtering, then this technique is the best bridge to getting the right size, but done in a fast, low-memory footprint operation.

Getting image dimensions

Getting the image size without decoding the whole image In order to resize your bitmap, you’ll need to know the incoming dimensions. You can use the inJustDecodeBounds flag to help you get the dimensions of the image, w/o needing to actually decode the pixel data.

// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;


//now go resize the image to the size you want

You can use this flag to decode the size first, and then calculate the proper values for scaling to your target resolution.

Solution 2

As nice (and accurate) as this answer is, it's also very complicated. Rather than re-invent the wheel, consider libraries like Glide, Picasso, UIL, Ion, or any number of others that implement this complex and error prone logic for you.

Colt himself even recommends taking a look at Glide and Picasso in the Pre-scaling Bitmaps Performance Patterns Video.

By using libraries, you can get every bit of efficiency mentioned in Colt's answer, but with vastly simpler APIs that work consistently across every version of Android.

Share:
15,020

Related videos on Youtube

Colt McAnlis
Author by

Colt McAnlis

Colt McAnlis is a Developer Advocate at Google focusing on performance for Android and Cloud (while secretly having an obsession with data Compression); Before that, he was a graphics programmer in the games industry working at Blizzard, Microsoft (Ensemble), and Petroglyph. He’s been an Adjunct Professor at SMU Guildhall, a UDACITY instructor (twice), and a Book Author (twice). When he's not working with developers, Colt spends his time preparing for an invasion of giant ants from outer space.

Updated on July 16, 2022

Comments

  • Colt McAnlis
    Colt McAnlis almost 2 years

    I’m building an image-intensive social app where images are sent from the server to the device. When the device has smaller screen resolutions, I need to resize the bitmaps, on device, to match their intended display sizes.

    The problem is that using createScaledBitmap causes me to run into a lot of out-of-memory errors after resizing a horde of thumbnail images.

    What’s the most memory efficient way to resize bitmaps on Android?

    • James
      James almost 9 years
      Can your server not send the correct size so you save your customer's RAM and bandwidth!?
    • Colt McAnlis
      Colt McAnlis almost 9 years
      That's only valid if I owned the server resource, it had a compute component available with it, and in all cases, it could predict the exact dimensions of images for aspect ratios it hadn't seen yet. So if you're loading resource content from a 3rd party CDN (like I am) it doesn't work :(
  • Kosh
    Kosh almost 9 years
    it had be great if you could tell us what dstWidth is ?
  • tyczj
    tyczj almost 9 years
    @k0sh dstWIdth is the width of the ImageView for where its going to ie destination width or dstWidth for short
  • Kosh
    Kosh almost 9 years
    @tyczj thanks for the answer, i know what is it, but there are some might not know it and since Colt who did actually answer this question, perhaps he could explain it so people dont get confuse.
  • maveroid
    maveroid almost 9 years
    Nice post... didn't know about inScaled, inDensity, inTargetDensity flags previously...
  • Anis LOUNIS aka AnixPasBesoin
    Anis LOUNIS aka AnixPasBesoin almost 9 years
    I ve been watching the android performance pattern series, and I ve learned a lot!
  • kolyaseg
    kolyaseg over 8 years
    How can i use this technique if i don't have fileName, but file's Uri only (got from Google Photos)?
  • Petrakeas
    Petrakeas over 6 years
    Note that using the Bitmap methods to scale down results in aliasing because the methods just use bilinear interpolation (without prefiltering). I've written an article that explaines the problem and comes up with a solution that makes use of RenderScript to downscale an image properly.
  • Petrakeas
    Petrakeas over 6 years
    I've written a post where I compare this method to a RenderScript based one when downscaling by a power-of-2 factor or to an arbitrary size and I compare quality and performance.
  • Peter
    Peter over 3 years
    thanks for this awesome rundown of the scaling options
  • Dimezis
    Dimezis about 3 years
    on API 23, and maybe some others, inSampleSize combined with inDensity and inTargetDensity do not properly work together. The resulting resolution is neither scaled by inSampleSize, nor it's matching the required target density. Removing inSampleSize and leaving only density resolves the issue