背景

我正在尝试手动(逐帧)遍历动画GIF&WEBP文件的位图,以便它不仅适用于“ View ”,而且还适用于其他情况(例如动态壁纸)。

问题

动画GIF/WEBP文件仅通过使用ImageDecoder API(例如here)从Android P支持。

对于GIF,我想尝试使用Glide来完成该任务,但是失败了,所以我尝试通过使用允许加载它们的库(here,解决方案here)来克服这一问题。我认为效果很好。

对于WebP,我认为我已经找到了另一个可以在较旧的Android版本上运行的库(here,制成fork here),但是似乎在某些情况下它不能很好地处理WebP文件(报告为here)。我试图找出问题所在以及如何解决,但没有成功。

因此,假设有一天Google将通过支持库(他们将其编写为here)支持较旧的Android版本的GIF&WEBP动画,我决定尝试使用ImageDecoder来完成该任务。

问题是,查看ImageDecoder的整个API时,我们使用它的方式受到了很大的限制。我看不到如何克服其局限性。

我发现了什么

这就是ImageDecoder可用于在ImageView上显示动画WebP的方式(当然,只是示例,可用here):

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()

    }
}

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

问题
  • 是否可以使用ImageDecoder API手动遍历每个帧,绘制位图并知道在帧之间等待需要多少时间?有任何解决方法?甚至可以使用AnimatedImageDrawable吗?
  • 我想在较旧的Android版本上执行相同的操作。可能吗?如果可以,怎么办?也许在不同的API/库上? Google编写了一种在较旧的Android版本上使用ImageDecoder的方式,但是我看不到它在任何地方都被提及(我提供的链接除外)。可能尚未准备好... Android P甚至还没有达到0.1%的用户... Fresco可以做到吗?我也尝试过在那儿检查它,但是我也看不到它有这种能力,并且它是一个庞大的库,仅用于此任务,因此我宁愿使用其他库。我也知道libwebp是可用的,但是它在C/C++中,并且不确定它是否适合Android,以及在Java/Kotlin for Android上是否有用于它的端口。


  • 编辑:

    由于我认为我已经有了想要的东西,无论是对于第三方库还是对于ImageDecoder,都能够从动画WebP中获取位图,所以我仍然想知道如何使用ImageDecoder获取帧数和当前帧,如果那是可能的。我尝试使用ImageDecoder.decodeDrawable(source, object : ImageDecoder.OnHeaderDecodedListener...,但是它不提供帧计数信息,并且在API中没有办法可以看到我可以转到特定的帧索引并从那里开始,或者知道一个特定的帧需要多长时间它需要转到下一帧。因此,我对这些here提出了要求。

    遗憾的是,我也没有找到Google的ImageDecoder适用于较旧的Android版本。

    如果有某种方法可以像我对相对较新的HEIC动画文件所做的一样,这也很有趣。目前仅在Android P上受支持。

    最佳答案

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

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

    这是代码(项目可用here):

    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) {}
    }
    

    MyAppGlideModule.kt
    @GlideModule
    class MyAppGlideModule : AppGlideModule()
    

    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()
        }
    
    }
    

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

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

    这是代码:

    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()
        }
    
    }
    

    关于android - 是否可以获取新的ImageDecoder类以手动返回一帧又一帧的位图?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53024304/

    10-12 01:19