glide
glide copied to clipboard
Request: allow to extract Bitmaps from animated GIF/WEBP, including how long each should last
Currently there are still only workarounds for this, but even then, I couldn't find the missing information, of how long each frame I get should last.
Here's one way: https://medium.com/@bogomazartem/extract-bitmap-frames-from-gif-file-android-9e06d8b709f8
And another, which I'm not sure if it's ok, as it actually plays the animation :
private fun testWebp() {
val drawable = GlideApp.with(applicationContext).load(R.raw.test_webp).skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.submit().get() as WebpDrawable
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.loopCount = 1
val callback = object : CallbackEx() {
override fun invalidateDrawable(who: Drawable) {
val webp = who as WebpDrawable
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
who.draw(canvas)
//image is available here on the bitmap object
Log.d("AppLog", "frameIndex:${webp.frameIndex} frameCount:${webp.frameCount} firstFrame:${webp.firstFrame}")
}
}
drawable.callback = callback
drawable.start()
}
And for WEBP (using this library with Glide), something similar:
private fun testGif() {
val drawable = GlideApp.with(applicationContext).load(R.raw.test_gif).skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE).submit().get() as GifDrawable
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(1)
val callback = object : CallbackEx() {
override fun invalidateDrawable(who: Drawable) {
super.invalidateDrawable(who)
val gif = who as GifDrawable
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
who.draw(canvas)
//image is available here on the bitmap object
Log.d("AppLog", "frameIndex:${gif.frameIndex} frameCount:${gif.frameCount} firstFrame:${gif.firstFrame}")
}
}
drawable.callback = callback
drawable.start()
}
But there is no official way to do it. Please offer an official way using Glide, to extract the frames and check how long each frame should last.
I know it's open sourced and technically I could dig the code to find it (I can see it's in ByteBufferGifDecoder, using GifDecoder), but I'd like to request it still.
Please do consider adding this functionality, and if there is one already, please show me a link about this.
Another workaround, again with reflection, but this time with the duration of the frames:
private fun testGif2() {
val drawable = GlideApp.with(applicationContext).load(R.raw.test_gif).skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE).submit().get() as GifDrawable
val state = drawable.constantState as Drawable.ConstantState
val frameLoader: Field = state::class.java.getDeclaredField("frameLoader")
frameLoader.isAccessible = true
val gifFrameLoader: Any = frameLoader.get(state)
val gifDecoder: Field = gifFrameLoader.javaClass.getDeclaredField("gifDecoder")
gifDecoder.isAccessible = true
val standardGifDecoder = gifDecoder.get(gifFrameLoader) as StandardGifDecoder
Log.d("AppLog", "got ${standardGifDecoder.frameCount} frames:")
val parent = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "gifFrames")
parent.mkdirs()
for (i in 0 until standardGifDecoder.frameCount) {
val file = File(parent, "${String.format("%07d", i)}.png")
val delay = standardGifDecoder.nextDelay
val bitmap = standardGifDecoder.nextFrame
if (bitmap == null) {
Log.d("AppLog", "error getting frame")
break
}
bitmap.compress(Bitmap.CompressFormat.PNG, 100, FileOutputStream(file))
Log.d("AppLog", "${standardGifDecoder.currentFrameIndex} - $delay ${bitmap?.width}x${bitmap?.height}")
standardGifDecoder.advance()
}
Log.d("AppLog", "done")
}
private fun testWebp2() {
val drawable = GlideApp.with(applicationContext).load(R.raw.test_webp).skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.submit().get() as WebpDrawable
drawable.constantState
val state = drawable.constantState as Drawable.ConstantState
val frameLoader: Field = state::class.java.getDeclaredField("frameLoader")
frameLoader.isAccessible = true
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
val webpFrameLoader = frameLoader.get(state) as WebpFrameLoader
val webpDecoder: Field = webpFrameLoader.javaClass.getDeclaredField("webpDecoder")
webpDecoder.isAccessible = true
val standardGifDecoder = webpDecoder.get(webpFrameLoader) as GifDecoder
Log.d("AppLog", "got ${standardGifDecoder.frameCount} frames:")
for (i in 0 until standardGifDecoder.frameCount) {
val delay = standardGifDecoder.nextDelay
val bitmap = standardGifDecoder.nextFrame
Log.d("AppLog", "${standardGifDecoder.currentFrameIndex} - $delay ${bitmap?.width}x${bitmap?.height}")
standardGifDecoder.advance()
}
Log.d("AppLog", "done")
}
Anyway, I think I got it, but used too much things of Glide even though I don't need caching of anything:
For GIF:
private fun testGif3() {
val data = resources.openRawResource(R.raw.test_gif).readBytes()
val byteBuffer = ByteBuffer.wrap(data)
val memorySizeCalculator = MemorySizeCalculator.Builder(this).build()
val bitmapPoolSize = memorySizeCalculator.bitmapPoolSize.toLong()
val bitmapPool = if (bitmapPoolSize > 0) LruBitmapPool(bitmapPoolSize) else BitmapPoolAdapter()
val arrayPool = LruArrayPool(memorySizeCalculator.arrayPoolSizeInBytes)
val gifBitmapProvider = GifBitmapProvider(bitmapPool, arrayPool)
val header = GifHeaderParser().setData(byteBuffer).parseHeader()
val standardGifDecoder = StandardGifDecoder(gifBitmapProvider, header, byteBuffer, 1)
//alternative, without getting header and needing sample size:
// val standardGifDecoder = StandardGifDecoder(gifBitmapProvider)
// standardGifDecoder.read(data)
val frameCount = standardGifDecoder.frameCount
standardGifDecoder.advance()
for (i in 0 until frameCount) {
val delay = standardGifDecoder.nextDelay
val bitmap = standardGifDecoder.nextFrame
//bitmap ready here
standardGifDecoder.advance()
}
}
For WEBP:
private fun testWebP3() {
val data = resources.openRawResource(R.raw.test_webp).readBytes()
val byteBuffer = ByteBuffer.wrap(data)
val webp = WebpImage.create(data)
val sampleSize = 1
val cacheStrategy: WebpFrameCacheStrategy? = Options().get(WebpFrameLoader.FRAME_CACHE_STRATEGY)
val memorySizeCalculator = MemorySizeCalculator.Builder(this).build()
val bitmapPoolSize = memorySizeCalculator.bitmapPoolSize.toLong()
val bitmapPool = if (bitmapPoolSize > 0) LruBitmapPool(bitmapPoolSize) else BitmapPoolAdapter()
val arrayPool = LruArrayPool(memorySizeCalculator.arrayPoolSizeInBytes)
val gifBitmapProvider = GifBitmapProvider(bitmapPool, arrayPool)
val webpDecoder = WebpDecoder(gifBitmapProvider, webp, byteBuffer, sampleSize, cacheStrategy)
val frameCount = webpDecoder.frameCount
webpDecoder.advance()
for (i in 0 until frameCount) {
val delay = webpDecoder.nextDelay
val bitmap = webpDecoder.nextFrame
//bitmap ready here
webpDecoder.advance()
}
}
If anyone knows of a shorter way, please let me know.
This issue has been automatically marked as stale because it has not had activity in the last seven days. It will be closed if no further activity occurs within the next seven days. Thank you for your contributions.
Can't you at least consider?
Anyway, I think I got it, but used too much things of Glide even though I don't need caching of anything:
For GIF:
private fun testGif3() { val data = resources.openRawResource(R.raw.test_gif).readBytes() val byteBuffer = ByteBuffer.wrap(data) val memorySizeCalculator = MemorySizeCalculator.Builder(this).build() val bitmapPoolSize = memorySizeCalculator.bitmapPoolSize.toLong() val bitmapPool = if (bitmapPoolSize > 0) LruBitmapPool(bitmapPoolSize) else BitmapPoolAdapter() val arrayPool = LruArrayPool(memorySizeCalculator.arrayPoolSizeInBytes) val gifBitmapProvider = GifBitmapProvider(bitmapPool, arrayPool) val header = GifHeaderParser().setData(byteBuffer).parseHeader() val standardGifDecoder = StandardGifDecoder(gifBitmapProvider, header, byteBuffer, 1) //alternative, without getting header and needing sample size: // val standardGifDecoder = StandardGifDecoder(gifBitmapProvider) // standardGifDecoder.read(data) val frameCount = standardGifDecoder.frameCount standardGifDecoder.advance() for (i in 0 until frameCount) { val delay = standardGifDecoder.nextDelay val bitmap = standardGifDecoder.nextFrame //bitmap ready here standardGifDecoder.advance() } }
For WEBP:
private fun testWebP3() { val data = resources.openRawResource(R.raw.test_webp).readBytes() val byteBuffer = ByteBuffer.wrap(data) val webp = WebpImage.create(data) val sampleSize = 1 val cacheStrategy: WebpFrameCacheStrategy? = Options().get(WebpFrameLoader.FRAME_CACHE_STRATEGY) val memorySizeCalculator = MemorySizeCalculator.Builder(this).build() val bitmapPoolSize = memorySizeCalculator.bitmapPoolSize.toLong() val bitmapPool = if (bitmapPoolSize > 0) LruBitmapPool(bitmapPoolSize) else BitmapPoolAdapter() val arrayPool = LruArrayPool(memorySizeCalculator.arrayPoolSizeInBytes) val gifBitmapProvider = GifBitmapProvider(bitmapPool, arrayPool) val webpDecoder = WebpDecoder(gifBitmapProvider, webp, byteBuffer, sampleSize, cacheStrategy) val frameCount = webpDecoder.frameCount webpDecoder.advance() for (i in 0 until frameCount) { val delay = webpDecoder.nextDelay val bitmap = webpDecoder.nextFrame //bitmap ready here webpDecoder.advance() } }
If anyone knows of a shorter way, please let me know.
Hello, WebpImage.create(data) throws me "Failed to create demuxer" exception. Do you by any chance know why it throws that?
@Luosoha Please create a sample project and then share it. Also, here's a sample webp file that should work fine: https://mathiasbynens.be/demo/animated-webp https://mathiasbynens.be/demo/animated-webp-supported.webp
@Luosoha Please create a sample project and then share it. Also, here's a sample webp file that should work fine: https://mathiasbynens.be/demo/animated-webp https://mathiasbynens.be/demo/animated-webp-supported.webp
I double checked again and my webp file was actually a gif file. After making sure that it is a webp file all worked fine. Thanks for the quick reply :D