Understanding ImageDecoder API in Android

Understanding ImageDecoder API in Android

In Android, we work with a lot of Bitmaps and drawables. Handling bitmap conversions requires us to write a lot of code and quite a few times we get our favorite error, " Out of Memory " exception.

Manipulating Bitmap happens using the BitmapFactory , but with Android P, we got something called ImageDecoder which helps us convert images like PNG, JPEG, etc to Drawables or Bitmaps.

Welcome to MindOrks, in this blog, we are going to learn about how to use ImageDecoder to efficiently convert images.

While understanding ImageDecoder We are going to cover,

  • Understanding the source and loading Images
  • Decoding from Drawable folder and URI
  • Overriding the default setting of the source
  • Decoding GIFs and WebP
  • Handling Errors

Understanding the source and loading Images

To decode anything, we first need to map the image source. The source is like the accepted path required by the ImageDecoder. To create a source we use,

val source = ImageDecoder.createSource(file_path)

and to use it as a drawable we just use it as,

val drawable: Drawable = ImageDecoder.decodeDrawable(source)

Here, generating a source can happen at any thread. But the decoding is recommended to do in the background thread .

Now, to set the image in an ImageView we use,

imageView.setImageDrawable(drawable)

Here, we are getting drawable using the decodeDrawable method but if we want to get a bitmap from the defined source we will use,

val bitmap:Bitmap = ImageDecoder.decodeBitmap(source)

And to set the bitmap in ImageView we use,

imageView.setImageBitmap(bitmap)

This above use-case was to create a source from the file path and decode it to Drawable of Bitmap.

Similarly, we can create a source from ByteBuffer like,

val source = ImageDecoder.createSource(byte_buffer)

Decoding from Drawable folder and URI

Now, consider a use case where we have PNGs or JPEGs in the drawable folder of our project. Then we can also create the source from the resource folder like,

val source = ImageDecoder.createSource(resources, R.drawable.ic_location)

Here, we are pulling up the PNG icon, ic_location from the drawable, and creating a source for it using ImageDecoder .

And now we can decode the source to drawable or bitmap using,

val drawable: Drawable = ImageDecoder.decodeDrawable(source)

and,

val bitmap:Bitmap = ImageDecoder.decodeBitmap(source)

And these can be set to ImageView using setImageDrawable and setImageBitmap .

Similarly, if we have a URI and want to create a source from it, we use content resolver to create source like,

val source = ImageDecoder.createSource(contentResolver, image_uri)

and finally, if we have to create a source from file from the asset, then we use,

val source = ImageDecoder.createSource(assetManager, file_from_asset)

and like the above steps we can decode the source to bitmap and drawable.

Overriding the default settings of the source

While creating a source, we can also override the default settings we get from the Image. To override the default setting we use,

OnHeaderDecodedListener

To add the onHeaderDecodedListener we use,

val listener: OnHeaderDecodedListener = object : OnHeaderDecodedListener {

    override fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source: ImageDecoder.Source) {
     // do something here
    }
}
val drawable = ImageDecoder.decodeDrawable(source, listener)

We can do a lot of transformation using the listeners.

decoder here let us do the transformations, info contains all the details of the original image like Mime type, size, or if it is animating or not and the source .

Consider, if we want to resize the image, then inside the onHeaderDecoded, we will write,

decoder.setTargetSize(100,100)

It will transform the original image to a size of 100 in a square shape.

Decoding GIFs and WebP

If we have GIFs and WebP, then we can load them using ImageDecoder itself with all the animations and transitions of the frames without using any third-party library.

Let's say we have a source from the assets folder which is a Gif file. So, to decode it in Drawable and start the animation we use,

val source = ImageDecoder.createSource(assetManager, file_from_asset)

Then,

val drawable = ImageDecoder.decodeDrawable(source)
if (drawable is AnimatedImageDrawable) {
    drawable.start()
}

Here, while decoding it as drawable it is decoded as AnimatedImageDrawable .

And to start the animation we call start() .

Handling Errors

While decoding the source, we might get errors. To detect the errors we need to set the setOnPartialImageListener to the decoder parameter in OnHeaderDecodedListener like,

val listener: OnHeaderDecodedListener = object : OnHeaderDecodedListener {

    override fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source: ImageDecoder.Source) {
        decoder.setOnPartialImageListener {exception->
            Log.d("ImageDecoder",exception.error.toString()))
            true
        }
    }
}

Here, inside setOnPartialImageListener we get the exception and that is where we can log the error.

When we want to log the error, exception.error might return the possible errors as following,

Here, we are returning true, which tells the listeners to only display the generated image until the exception occurred. But if it returns false, it will stop executing and will return an exception.

Good to know

If you want to do processing after loading the Image like setting some custom background etc, we can do it in,

OnHeaderDecodedListener

To do processing we use it like,

val listener: OnHeaderDecodedListener = object : OnHeaderDecodedListener {

    override fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source: ImageDecoder.Source) {
        decoder.setPostProcessor { canvas ->
            
        }
    }
}

Here, inside setOnProcessor we get the canvas on which we will do our transformations and will apply custom effects that will be applied to the canvas after the Image is decoded and loaded.

This is how you can use ImageDecoder in your application. It requires Android Pie and above to run in your project.

Happy learning.

Team MindOrks :)