Bitmap / ImageDecoder

ImageDecoder – Basics

The Android P developer preview contains a new ImageDecoder which is quite interesting and can do some useful things. At present it only appears within the Framework so we can only use it on devices running P or later, and I understand that if consists of a chunk of native code and so is unlikely to appear as a support library. However, it is a really nice, powerful yet simple API. In this series we’ll take a look at how to use it, and see some of the cool things that we can achieve with it.

The ImageDecoder API is actually pretty small, at the time of writing it only has 19 methods of which 7 are static factory methods. Of course this may change before the final release, but those methods are actually pretty powerful.

To demonstrate the different things that we can do with ImageDecoder, I created a simple app which will display images within a ViewPager, and different pages can implement different techniques of ImageDecoder. I won’t bother with a full explanation of the app itself, but we’ll start with the PagerAdapter implementation which is responsible for creating the individual pages for the ViewPager:

class ImageDecoderPagerAdapter(
        private val context: Context,
        fragmentManager: FragmentManager
) : FragmentStatePagerAdapter(fragmentManager) {

    private val items = arrayOf(
            Item("Logo", SimpleBitmapFragment::class.java)
    )

    override fun getItem(position: Int): ImageDecoderFragment =
            items[position].let { item ->
                createFragment(item.fragmentClass)
            }

    private fun createFragment(cls: Class<out ImageDecoderFragment>) : ImageDecoderFragment =
            Fragment.instantiate(context, cls.name) as ImageDecoderFragment

    override fun getCount(): Int = items.size

    override fun getPageTitle(position: Int): CharSequence? {
        return items[position].title
    }

    private data class Item(
            val title: String,
            val fragmentClass: Class<out ImageDecoderFragment>
    )
}

This currently has a single page which will contain a Fragment of type SimpleBitmapFragment, which is a subclass of ImageDecoderFragment. ImageDecoderFragment is the abstract base class:

abstract class ImageDecoderFragment : Fragment() {

    protected abstract val assetName: String

    protected val cacheAsset: CacheAsset by lazy {
        CacheAsset(context as Context)
    }

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? = inflater.inflate(R.layout.fragment_image_decoder, container, false)

    protected abstract fun updateAsset(context: Context)

    override fun onAttach(context: Context) {
        super.onAttach(context)
        updateAsset(context)
    }
}

This will call the abstract method updateAsset() as soon as the Fragment is attached to a valid Context. An instance of CacheAsset is also created lazily. This is a simple utility class which will unpack images packaged in the APK in the assets folder, and save them to the apps cache folder:

class CacheAsset(
        context: Context,
        private val cacheDir: File = context.cacheDir,
        private val assetManager: AssetManager = context.assets
) {
    
    fun file(assetName: String): File =
            File(cacheDir, assetName).apply {
                if (!exists()) {
                    createCache(assetName, this)
                }
            }

    private fun createCache(assetName: String, output: File) {
        val buffer = ByteArray(1024)
        assetManager.open(assetName).use { inputStream ->
            FileOutputStream(output).use { outputStream ->
                var length = inputStream.read(buffer)
                while (length > 0) {
                    outputStream.write(buffer, 0, length)
                    length = inputStream.read(buffer)
                }
            }
        }
    }
}

The first concrete subclass of ImageDecoderFragment is SimpleBitmapFragment which shows how to do the very basic operation of loading a Bitmap from a file. APologies that it has taken a while to get to the part where we actually see ImageDecoder in action, but this framework will allow us to explore a few different aspects of how we can use ImageDecoder.

class SimpleBitmapFragment : ImageDecoderFragment() {

    override val assetName: String = "StylingAndroid.png"

    override fun updateAsset(context: Context) {
        launch(CommonPool) {
            ImageDecoder.createSource(cacheAsset.file(assetName)).also { source ->
                ImageDecoder.decodeBitmap(source).also { bitmap ->
                    launch(UI) {
                        image.setImageBitmap(bitmap)
                    }
                }
            }
        }
    }
}

All of the interesting stuff happens in updateAsset(). We run everything inside a Kotlin coroutine running in a CommonPool context – essentially we take things off the main UI thread. The first thing we need to do is create an ImageDecoder.Source instance which we do by calling the static method createSource() with an argument of the File object that we retrieve from CacheAsset (line 7). While we’re creating the ImageDecoder.Source from a file, there are other static methods which enable an instance to be created from a ByteArray, and from a ContentResolver / Uri pair. The obvious mechanism missing here to make it truly flexible is to be able to create an InputDecoder.Source instance from an arbitrary InputStream, but perhaps there are technical constraints which prevent that, or maybe that will be added before a full release.

Next we call another static method decodeBitmap() with the ImageDecoder.Source instance as the argument, and this returns a Bitmap. Finally we launch another coroutine, this time back on the UI thread, and invoke setImageBitmap() on an ImageView to display the Bitmap:

So that’s the basics of how to use ImageDecoder and, while it is simple enough, what we have seen thus far does not appear to provide any meaningful benefit over, for example, BitmapFactory which allows us to do the same thing with a single static method call. However, the official advice with the P developer preview is to use ImageDecoder in preference to BitmapFactory, presumably for performance reasons. But also, there are some additional features of ImageDecoder that make it far more flexible than BitmapFactory, and we’ll begin to explore some of these in the next article in this series.

The source code for this article is available here.

I should also give a brief mention to the 12 others with whom I got stuck in an elevator at Chicago Roboto. While it wasn’t an experience I would be keen to repeat any time soon, the gallows humour made it rather less of an ordeal that it otherwise might have been. I’m sure that the Fire Department mistook all the laughter for screams of anguish and endeavoured to release us much faster. I’m grateful for the good humour shown by my fellow detainees, and even more grateful that nobody broke wind while we were trapped in there.

Free the #Chicago13

© 2018, Mark Allison. All rights reserved.

Copyright © 2018 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.