Background
I'm trying to go over bitmaps of animated GIF&WEBP files manually (frame by frame), so that it would work not just for Views, but on other cases too (such as a live wallpaper).
The problem
Animated GIF/WEBP files are supported only from Android P, using ImageDecoder API (example here) .
For GIF, I wanted to try Glide for the task, but I've failed, so I've tried overcoming this, by using a library that allows to load them (here, solution here). I think it works fine.
For WebP, I thought I've found another library that could work on older Android versions (here, made fork here), but it seems that it can't handle WebP files well in some cases (reported here). I tried to figure out what's the issue and how to solve it, but I didn't succeed.
So, assuming that some day Google will support GIF&WEBP animation for older Android versions via the support library (they wrote it here), I've decided to try to use ImageDecoder for the task.
Thing is, looking in the entire API of ImageDecoder , it's quite restricted in how we should use it. I don't see how I can overcome its limitations.
What I've found
This is how ImageDecoder can be used to show an animated WebP on an ImageView (just a sample, of course, available 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()
}
}
I've tried to read all of the documentations of ImageDecoder and AnimatedImageDrawable, and also look at its code, but I don't see how it's possible to manually go over each frame, and have the time that needs to be waited between them.
The questions
Is there a way to use ImageDecoder API to go over each frame manually, getting a Bitmap to draw and knowing how much time it's needed to wait between frames? Any workaround available? Maybe even using AnimatedImageDrawable ?
I'd like to do the same on older Android versions. Is it possible? If so how? Maybe on a different API/library? Google wrote it works on a way to use ImageDecoder on older Android versions, but I don't see it being mentioned anywhere (except for the link I've provided). Probably not ready yet... Android P didn't even reach 0.1% of users yet... Maybe Fresco can do it? I've tried to check it there too, but I don't see that it's capable of such a thing either, and it's a huge library to use just for this task, so I'd prefer to use a different library instead... I also know that libwebp is available, but it's in C/C++ and not sure if it's suited for Android, and whether there is a port for it on Java/Kotlin for Android.
EDIT:
Since I think I got what I wanted, for both a third party library and for ImageDecoder, to be able to get bitmaps out of animated WebP, I'd still want to know how to get the frame count and current frame using ImageDecoder, if that's possible. I tried using ImageDecoder.decodeDrawable(source, object : ImageDecoder.OnHeaderDecodedListener...
, but it doesn't provide frame count information, and there is no way in the API that I can see that I can go to a specific frame index and start from there, or to know for a specific frame how long it needs to go to the next frame. So I made a reuqest about those here.
Sadly I also could not find that Google has ImageDecoder available for older Android versions, either.
It's also interesting if there is some kind of way to do the same as I did for the relatively new animation file of HEIC. Currently it's supported only on Android P.
OK, I got a possible solution, using Glide library, together with GlideWebpDecoder library .
I'm not sure if that's the best way to do it, but I think it should work fine. The next code shows how it's possible to make the drawable draw into the Bitmap instance that I create, for each frame that the animation needs to show. It's not exactly what I asked, but it might help others.
Here's the code (project available here) :
CallbackEx.kt
MyAppGlideModule.kt
MainActivity.kt
Not sure why
SimpleTarget
is marked as deprecated, and what I should use instead, though.Using a similar technique, I've also found out how to do it using ImageDecoder, but not with the same functionality for some reason. A sample project available here.
Here's the code:
MainActivity.kt
EDIT: In actually implementing this, I encountered a couple unexpected problems, but nothing insurmountable:
AnimatedImageDrawable
seems to ignore its configured bounds. I scaled the canvas instead.AnimatedImageDrawable.draw()
occasionally neglects to schedule the next frame. I decided to call the function twice. The second time, I translate the canvas so that all drawing is out of bounds, which should allow most of the work to be optimized away.Here's the sample code.
I played with GIF images a few years ago. My idea is decode GIF images to frames, convert frames to bitmaps add create Animated Drawable from bitmaps and delay between frames. This is decode class:
}
I upload the full demo source here. Hope it can help you.
see ImageDecoder.Source ...
one needs to first create a source, with either:
and then decode, with either:
update: the problem is, that the resulting AnimatedImageDrawable doesn't have the two methods: getNumberOfFrames() and getFrame(int) as an
AnimationDrawable
has. as @androiddeveloper pointed out ...I've messed up two different classes. I've double-checked the documentation and there seems to be no way. with theGIFImageReader
it still can be extracted (source):just tried to convert it to Kotlin, but
javax.imageio.ImageIO
is not available on Android.