Never keep in memory more than you actually need

That’s the most fundamental rule you should always keep in mind. Luckily, you are safe if you use some image libraries like Picasso or Glide since they take care of all the optimizations out of the box. On the other hand, if you happen to manipulate bitmaps manually, you are on your own to implement them.

Scaling

In the perfect world, your bitmap has to be the same size as your preview canvas. In this way, you provide the best quality possible with minimal resources required. Therefore, scaling is the first option to consider while optimizing your code. You are to use BitmapFactory API with a special option called inSampleSize which can be used to scale your bitmap on the decoding step.

Note: If you support zooming, you might want to show a full-size image without any scaling.

According to the documentation, inSampleSize value should be a power of 2 and any other value will be rounded down to the nearest power of 2. However, during some testing, I figured out that other values are also supported on the latest android versions (or devices). Looks like underlying image decoders behavior has been changed without the documentation being updated. For instance, inSampleSize == 4 returns an image that is 1/4 the width/height of the original.

Obviously, such scaling is not accurate and doesn’t let you decode an image with precise size. This is where inDensity and inTargetDensity options come in. You can combine them to get an image with any size you want.

Consider the following code snippet:

Using the code above you can get a bitmap scaled to an arbitrary size in a single step.

The thing to note here is that you avoid decoding an original bitmap just to make a scaled copy. Omitting this step helps you minimize the possibility of getting the OutOfMemoryError .

Decode a specific region

Another trick is decoding a specific part of an image that you are interested in using BitmapRegionDecoder API. You can use inSampleSize option here as well to perform an additional scaling. Unfortunately, inDensity / inTargetDensity options are not supported in BitmapRegionDecoder . That’s why you can only achieve rough scaling and cropping in one step. Luckily, you can create a scaled bitmap from an existing one in the second step using Bitmap.createScaledBitmap() method.

For instance, let’s try to decode a half of an image:

Getting image size

You can get an image size without decoding a full bitmap in the memory using the following code:

ImageDecoder

The new ImageDecoder class is introduced in Android 9. According to the documentation, it provides a modernized approach for decoding images. Google recommends using it over BitmapFactory and BitmapFactory.Options APIs. However, there is no backward compatibility for versions below.

There are a few things I found interesting which bring some benefits for the techniques discussed in the post:

A bit simpler API for image scaling. You can just use setTargetSize instead of the combination of inSampleSize , inDensity and inTargetDensity . In contrast to BitmapFactory , scaling and precise cropping can be done in one step. But I was not able to achieve that due to the bug. Also, cropping capability is not intended as a replacement BitmapRegionDecoder.decodeRegion according to docs. I’m not aware of the reason, but I guess that ImageDecoder is just not optimal for that. EXIF orientation tags are handled automatically during decoding which is a real plus (which is not true for BitmapFactory ).

Looks like Google is planning a support library for ImageDecoder , but there have been no updates about it for a long time already.

Here is an example for ImageDecoder :

Sample project

You can find sample code on Github: