Android 是否可以让新的ImageDecoder类手动一帧接一帧地返回位图? 背景
我正在尝试手动(逐帧)浏览动画GIF和WEBP文件的位图,这样它不仅适用于视图,也适用于其他情况(如实时壁纸) 问题 只有Android p使用ImageDecoder API支持动画GIF/WEBP文件(示例) 对于GIF,我想尝试使用Glide来完成任务,但我失败了,所以我尝试通过使用允许加载它们的库(,解决方案)来克服这一点。我认为它很好用 对于WebP,我认为我已经找到了另一个可以在较旧的Android版本(,made fork)上工作的库,但在某些情况下它似乎不能很好地处理WebP文件(报告)。我试图找出问题所在以及解决方法,但没有成功 因此,假设有一天谷歌将通过支持库(他们编写的)为旧版Android版本支持GIF和WEBP动画,我决定尝试使用ImageDecoder来完成这项任务 问题是,从ImageDecoder的整个API来看,我们应该如何使用它是非常有限的。我不知道如何克服它的局限性 我发现了什么 这就是如何使用ImageDecoder在ImageView上显示动画WebP(当然,只是一个示例):Android 是否可以让新的ImageDecoder类手动一帧接一帧地返回位图? 背景,android,animated-gif,imagedecoder,animated-webp,Android,Animated Gif,Imagedecoder,Animated Webp,我正在尝试手动(逐帧)浏览动画GIF和WEBP文件的位图,这样它不仅适用于视图,也适用于其他情况(如实时壁纸) 问题 只有Android p使用ImageDecoder API支持动画GIF/WEBP文件(示例) 对于GIF,我想尝试使用Glide来完成任务,但我失败了,所以我尝试通过使用允许加载它们的库(,解决方案)来克服这一点。我认为它很好用 对于WebP,我认为我已经找到了另一个可以在较旧的Android版本(,made fork)上工作的库,但在某些情况下它似乎不能很好地处理WebP文件
class MainActivity:AppCompatActivity(){
@SuppressLint(“StaticFieldLeak”)
重写创建时的乐趣(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val source=ImageDecoder.createSource(参考资料,R.raw.test)
对象:AsyncTask(){
覆盖有趣的背景(vararg参数:Void?):可绘制{
回击{
ImageDecoder.decodeDrawable(源)
}捕获(e:例外){
无效的
}
}
重写onPostExecute(结果:可绘制?){
super.onPostExecute(结果)
imageView.setImageDrawable(结果)
如果(结果为动画可绘制){
result.start()
}
}
}.execute()
}
}
我试着阅读了和的所有文档,也查看了它的代码,但我不知道如何手动检查每一帧,并在它们之间留出需要等待的时间
问题
编辑: 因为我想我得到了我想要的,对于第三方库和ImageDecoder,能够从动画WebP中获取位图,我仍然想知道如何使用ImageDecoder获取帧计数和当前帧,如果可能的话。我尝试使用
ImageDecoder.decodeDrawable(来源,对象:ImageDecoder.OnHeaderDecodeListener…
,但它不提供帧计数信息,而且在API中我无法看到我可以转到特定的帧索引并从那里开始,或者知道特定帧需要多长时间才能转到下一帧。因此我对这些进行了重新测试
遗憾的是,我也找不到谷歌有适用于较旧Android版本的ImageDecoder
同样有趣的是,如果有什么方法可以像我对相对较新的HEIC动画文件所做的那样。目前它只在Android p上受支持。好的,我得到了一个可能的解决方案,使用,和 我不确定这是否是最好的方法,但我认为它应该工作得很好。下一段代码显示了如何在我创建的位图实例中为动画需要显示的每个帧绘制可绘制的绘图。这不完全是我要求的,但它可能会帮助其他人 以下是代码(项目可用): 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) {}
}
@GlideModule
class MyAppGlideModule : AppGlideModule()
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()
}
}
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()
}
}
MyAppGlideModule.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) {}
}
@GlideModule
class MyAppGlideModule : AppGlideModule()
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()
}
}
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()
}
}
MainActivity.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) {}
}
@GlideModule
class MyAppGlideModule : AppGlideModule()
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()
}
}
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()
}
}
看
首先需要创建一个源,包括:
// source from file
val source = ImageDecoder.createSource(file)
// source from byte buffer
val source = ImageDecoder.createSource(byteBuffer)
// source from resource
val source = ImageDecoder.createSource(resources, resId)
// source from URI
val source = ImageDecoder.createSource(contentResolver, uri)
// source from asset file
val source = ImageDecoder.createSource(assetManager, assetFileName)
// create bitmap
val bitmap = ImageDecoder.decodeBitmap(source)
// create drawable
val drawable = ImageDecoder.decodeDrawable(source)
然后用以下任一方法进行解码:
// source from file
val source = ImageDecoder.createSource(file)
// source from byte buffer
val source = ImageDecoder.createSource(byteBuffer)
// source from resource
val source = ImageDecoder.createSource(resources, resId)
// source from URI
val source = ImageDecoder.createSource(contentResolver, uri)
// source from asset file
val source = ImageDecoder.createSource(assetManager, assetFileName)
// create bitmap
val bitmap = ImageDecoder.decodeBitmap(source)
// create drawable
val drawable = ImageDecoder.decodeDrawable(source)
更新:问题是,结果没有两种方法:正如AnimationDrawable
所指出的那样。正如@androiddeveloper所指出的……我搞乱了两个不同的类。我仔细检查了文档,似乎没有办法。使用gifimagerreader
仍然可以提取():
ArrayList getFrames(文件gif)引发IOException{
ArrayList frames=新的ArrayList();
ImageReader ir=新的GIFImageReader(新的GIFImageReaderSpi());
ir.setInput(ImageIO.createImageInputStream(gif));
for(int i=0;i
刚刚尝试将其转换为Kotlin,但Android上没有
javax.imageio.imageio
。几年前我玩过GIF图像。我的想法是将GIF图像解码为帧,将帧转换为位图添加创建可从位图绘制的动画并延迟下注