是否有可能让新的ImageDecoder类手动返回一个接一个的Bitmaps?

and*_*per 17 android animated-gif imagedecoder animated-webp

背景

我试图手动(逐帧)查看动画GIF和WEBP文件的位图,这样它不仅可以用于视图,还可以用于其他情况(例如动态壁纸).

问题

仅使用ImageDecoder API(此处为示例)从Android P支持动画GIF/WEBP文件.

对于GIF,我想尝试滑翔的任务,但我失败了,所以我试图克服这一,通过使用一个库,允许(加载它们在这里,解决方案在这里).我认为它运作正常.

对于WebP,我以为我找到了另一个可以在较旧的Android版本上运行的库(这里,在这里制作了fork ),但似乎在某些情况下它无法很好地处理WebP文件(在此报道).我试图弄清楚问题是什么以及如何解决,但我没有成功.

因此,假设有一天谷歌将通过支持库支持旧版Android的GIF和WEBP动画(他们在这里),我决定尝试使用ImageDecoder来完成任务.

事实上,在ImageDecoder的整个API中,我们应该如何使用它.我不知道如何克服其局限性.

我发现了什么

这就是ImageDecoder如何用于在ImageView上显示动画WebP(当然,这里只有一个示例):

class MainActivity : AppCompatActivity() {
    @SuppressLint("StaticFieldLeak")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val source = ImageDecoder.createSource(resources, R.raw.test)
        object : AsyncTask<Void, Void, Drawable?>() {
            override fun doInBackground(vararg params: Void?): Drawable? {
                return try {
                    ImageDecoder.decodeDrawable(source)
                } catch (e: Exception) {
                    null
                }
            }

            override fun onPostExecute(result: Drawable?) {
                super.onPostExecute(result)
                imageView.setImageDrawable(result)
                if (result is AnimatedImageDrawable) {
                    result.start()
                }
            }

        }.execute()

    }
}
Run Code Online (Sandbox Code Playgroud)

我试图阅读ImageDecoderAnimatedImageDrawable的所有文档,并查看其代码,但我不知道如何手动遍历每个帧,并有时间需要在它们之间等待.

问题

  1. 有没有办法使用ImageDecoder API手动遍历每个帧,获取一个位图来绘制并知道在帧之间需要等待多长时间?有可用的解决方法吗?也许甚至使用AnimatedImageDrawable?

  2. 我想在较旧的Android版本上做同样的事情.可能吗?如果是这样的话?也许在不同的API /库上?谷歌写道,它的工作方式是在较旧的Android版本上使用ImageDecoder,但我没有看到它被提到任何地方(除了我提供的链接).可能尚未准备好...... Android P甚至没有达到0.1%的用户......也许Fresco能做到吗?我也试过在那里检查它,但我也没有看到它能够做到这一点,并且它只是一个巨大的库,只用于这个任务,所以我更喜欢使用不同的库而不是..我也知道libwebp是可用的,但它是用C/C++而不确定它是否适合Android,以及它是否适用于Android/Java/Kotlin上的端口.


编辑:

因为我认为我得到了我想要的东西,对于第三方库和ImageDecoder,能够从动画WebP中获取位图,我仍然想知道如何使用ImageDecoder获取帧数和当前帧,如果这是可能的.我尝试过使用ImageDecoder.decodeDrawable(source, object : ImageDecoder.OnHeaderDecodedListener...,但是它没有提供帧数信息,并且我无法在API中看到我可以转到特定的帧索引并从那里开始,或者知道特定的帧有多长需要进入下一帧.所以我对这里的那些人做了一些了解.

可悲的是,我也发现谷歌的ImageDecoder也适用于较旧的Android版本.

如果有一种方法可以像我对HEIC相对较新的动画文件那样做,那也很有趣.目前仅支持Android P.

and*_*per 5

好的,使用Glide库GlideWebpDecoder库,我得到了一个可能的解决方案。

我不确定这是否是最好的方法,但是我认为它应该可以正常工作。接下来的代码显示了如何针对动画需要显示的每个帧,将可绘制的绘制放入我创建的Bitmap实例中。这不完全是我的要求,但可能会帮助其他人。

这是代码(可在此处找到项目):

CallbackEx.kt

abstract class CallbackEx : Drawable.Callback {
    override fun unscheduleDrawable(who: Drawable, what: Runnable) {}
    override fun invalidateDrawable(who: Drawable) {}
    override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {}
}
Run Code Online (Sandbox Code Playgroud)

MyAppGlideModule.kt

@GlideModule
class MyAppGlideModule : AppGlideModule()
Run Code Online (Sandbox Code Playgroud)

MainActivity.kt

class MainActivity : AppCompatActivity() {
    var webpDrawable: WebpDrawable? = null
    var gifDrawable: GifDrawable? = null
    var callback: Drawable.Callback? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        useFrameByFrameDecoding()
//        useNormalDecoding()
    }

    fun useNormalDecoding() {
        //webp url : https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp
        Glide.with(this)
                //                .load(R.raw.test)
                //                .load(R.raw.fast)
                .load(R.raw.example2)

                //                .load("https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp")
                .into(object : SimpleTarget<Drawable>() {
                    override fun onResourceReady(drawable: Drawable, transition: Transition<in Drawable>?) {
                        imageView.setImageDrawable(drawable)
                        when (drawable) {
                            is GifDrawable -> {
                                drawable.start()
                            }
                            is WebpDrawable -> {
                                drawable.start()
                            }
                        }
                    }
                })
    }

    fun useFrameByFrameDecoding() {
        //webp url : https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp
        Glide.with(this)
                .load(R.raw.test)
                //                .load(R.raw.fast)
                //                .load(R.raw.example2)
                //                .load("https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp")
                .into(object : SimpleTarget<Drawable>() {
                    override fun onResourceReady(drawable: Drawable, transition: Transition<in Drawable>?) {
                        //                        val callback
                        when (drawable) {
                            is GifDrawable -> {
                                gifDrawable = drawable
                                val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
                                val canvas = Canvas(bitmap)
                                drawable.setBounds(0, 0, bitmap.width, bitmap.height)
                                drawable.setLoopCount(GifDrawable.LOOP_FOREVER)
                                callback = object : CallbackEx() {
                                    override fun invalidateDrawable(who: Drawable) {
                                        who.draw(canvas)
                                        imageView.setImageBitmap(bitmap)
                                        Log.d("AppLog", "invalidateDrawable ${drawable.toString().substringAfter('@')} ${drawable.frameIndex}/${drawable.frameCount}")
                                    }
                                }
                                drawable.callback = callback
                                drawable.start()
                            }
                            is WebpDrawable -> {
                                webpDrawable = drawable
                                val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
                                val canvas = Canvas(bitmap)
                                drawable.setBounds(0, 0, bitmap.width, bitmap.height)
                                drawable.setLoopCount(WebpDrawable.LOOP_FOREVER)
                                callback = object : CallbackEx() {
                                    override fun invalidateDrawable(who: Drawable) {
                                        who.draw(canvas)
                                        imageView.setImageBitmap(bitmap)
                                        Log.d("AppLog", "invalidateDrawable ${drawable.toString().substringAfter('@')} ${drawable.frameIndex}/${drawable.frameCount}")
                                    }
                                }
                                drawable.callback = callback
                                drawable.start()
                            }
                        }
                    }
                })
    }

    override fun onStart() {
        super.onStart()
        gifDrawable?.start()
        gifDrawable?.start()
    }

    override fun onStop() {
        super.onStop()
        Log.d("AppLog", "onStop")
        webpDrawable?.stop()
        gifDrawable?.stop()
    }

}
Run Code Online (Sandbox Code Playgroud)

不知道为什么SimpleTarget将其标记为已弃用,但是我应该使用什么代替。

使用类似的技术,我还发现了如何使用ImageDecoder进行操作,但是由于某种原因,它们却没有相同的功能。这里有一个示例项目。

这是代码:

MainActivity.kt

class MainActivity : AppCompatActivity() {
    var webpDrawable: AnimatedImageDrawable? = null

    @SuppressLint("StaticFieldLeak")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val source = ImageDecoder.createSource(resources, R.raw.test)
        object : AsyncTask<Void, Void, Drawable?>() {
            override fun doInBackground(vararg params: Void?): Drawable? {
                return try {
                    ImageDecoder.decodeDrawable(source)
                } catch (e: Exception) {
                    null
                }
            }

            override fun onPostExecute(drawable: Drawable?) {
                super.onPostExecute(drawable)
//                imageView.setImageDrawable(result)
                if (drawable is AnimatedImageDrawable) {
                    webpDrawable = drawable
                    val bitmap =
                        Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
                    val canvas = Canvas(bitmap)
                    drawable.setBounds(0, 0, bitmap.width, bitmap.height)
                    drawable.repeatCount = AnimatedImageDrawable.REPEAT_INFINITE
                    drawable.callback = object : Drawable.Callback {
                        val handler = Handler()
                        override fun unscheduleDrawable(who: Drawable, what: Runnable) {
                            Log.d("AppLog", "unscheduleDrawable")
                        }

                        override fun invalidateDrawable(who: Drawable) {
                            who.draw(canvas)
                            imageView.setImageBitmap(bitmap)
                            Log.d("AppLog", "invalidateDrawable")
                        }

                        override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {
                            Log.d("AppLog", "scheduleDrawable next frame in ${`when` - SystemClock.uptimeMillis()} ms")
                            handler.postAtTime(what, `when`)
                        }
                    }
                    drawable.start()
                }
            }
        }.execute()
    }

    override fun onStart() {
        super.onStart()
        webpDrawable?.start()
    }

    override fun onStop() {
        super.onStop()
        webpDrawable?.stop()
    }

}
Run Code Online (Sandbox Code Playgroud)