redwarp / gifdecoder Goto Github PK
View Code? Open in Web Editor NEWAn implementation of a gif decoder written 100% in Kotlin, plus an associated Drawable for Android
License: Apache License 2.0
An implementation of a gif decoder written 100% in Kotlin, plus an associated Drawable for Android
License: Apache License 2.0
Hi,
As I'm trying to use this library in a commercial project, can you please clarify a few things regarding licenses?
Are these benchmark files actually included in the library?
benchmark/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.kt: * Copyright 2014 Google, Inc. All rights reserved.
benchmark/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.kt: * Copyright 2014 Google, Inc. All rights reserved.
benchmark/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.kt: * Copyright 2014 Google, Inc. All rights reserved.
benchmark/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.kt: * Copyright 2014 Google, Inc. All rights reserved.
benchmark/src/main/java/com/bumptech/glide/gifdecoder/StandardGifDecoder.java: * Copyright (c) 2013 Xcellent Creations, Inc.
benchmark/src/main/java/com/bumptech/glide/gifdecoder/StandardGifDecoder.java: * Copyright 2014 Google, Inc. All rights reserved.
Is this bitmap.h file from AOSP actually used in the library?
giflzwdecoder/ndkheaders/bitmap.h: * Copyright (C) 2009 The Android Open Source Project
Any other thing I should take into account regarding the license?
Could also be helpful to clarify this in the README.md file ;)
Thank you so much!
or it may dead loop decode and refresh the one frame
class GifDrawable {
var loopCount: LoopCount
get() = state.loopCount ?: state.gif.loopCount
}
I change to :
get() = if (gif.isAnimated) gifState.loopCount ?: gif.loopCount else LoopCount.Fixed(1)
HI there, found another little bug :)
I was testing with lots of different gifs and downloaded some from Giphy, I found some stickers are not rendered correctly. For example this one (and others): https://media0.giphy.com/media/kayGhRlOKbJHCqnj9m/giphy.gif ends up in including the first frame when rendering the second:
I debugged a bit, this gif has 2 frames with different disposal method, the first frame being RESTORE_TO_BACKGROUND and the second DO_NOT_DISPOSE. Not sure whether that's common or not, but other tools render it correctly.
I have created a branch on my fork so you can quickly test it https://github.com/PauGuillamon/gifdecoder/commits/PGJ/car_gunna_incorrect_disposal I'm not sure what would be the best fix here, since I don't know the specs of GIF.
Let me know if I can help in any way. Thank you!
the Drawable#setCallback() saved the instance via WeakReference, that won't last much longer, after several GC, the instance probably drop, which cause the playback stop unexpectedly.
so I make it strong reference myself.
class GifDrawable {
private fun nextFrame(unschedule: Boolean) {
// invalidateSelf()
// turn to :
strongCallback?.invalidateDrawable(this)
// and so on
}
private var strongCallback: Callback? = null
fun setStrongCallback(strongCallback: Callback) {
this.strongCallback = strongCallback
}
override fun getCallback(): Callback? {
return strongCallback
}
}
the adjustments I mentioned in other issue is to allow user pick the playback speed, for example 1X 2X 3X 4X, but your preload work scheduling by UI thread, can't satisfy my purpose when speed turn to fast, that's why I give up the BitmapCache and preload Executor.
after I implement this in a Thread, it work but no perfect, the highest speed will cause UI busy, suspending to response subsequence touch events, must kill the process to solve, it cannot accept for user, also for me.
so I turn to SurfaceView, also I noticed you offer a simple demo for drawing gif into Surface, get rid of the UI busy problem eventually.
but still can't satisfy the speed, because decode next frame sometime slow for those large resolution frame, most time wasting in the lzw data decode, so I plan to have a preloader which step forward only to pre decode next frame's lzw data, via Coroutines I think, I want to ask if you have some advices to achive this, thanks
With 0.4.0 comes reading the gif from a random access file. Cool. But what happens if the file gets deleted?
It crashes.
It might be cool to create or reuse an exception for:
in case of a broken gif
fun ReplayInputStream.readImageData(): ImageData {
val position = getPosition()
var length = 1
skip(1)
while (true) {
val blockSize = readUByte().toInt()
length++
if (blockSize == 0) {
// never reach
break
}
length += blockSize
skip(blockSize.toLong())
}
return ImageData(position, length)
}
The decoder sometimes throws exception on multipl IndexOutOfBoundsExceptions:
java.lang.ArrayIndexOutOfBoundsException:
at net.redwarp.gif.decoder.lzw.LzwDecoder.decode (LzwDecoder.java:100)
at net.redwarp.gif.decoder.Gif.getFrame (Gif.java:98)
at net.redwarp.gif.decoder.Gif.getCurrentFrame (Gif.java:24)
java.lang.IndexOutOfBoundsException:
at java.util.ArrayList.get (ArrayList.java:437)
at net.redwarp.gif.decoder.Gif.getFrame (Gif.java:17)
at net.redwarp.gif.decoder.Gif.getCurrentFrame (Gif.java:24)
java.lang.ArrayIndexOutOfBoundsException:
at net.redwarp.gif.decoder.Gif.fillPixelsInterlaced (Gif.java:79)
at net.redwarp.gif.decoder.Gif.fillPixels (Gif.java:6)
at net.redwarp.gif.decoder.Gif.getFrame (Gif.java:114)
at net.redwarp.gif.decoder.Gif.getCurrentFrame (Gif.java:24)
java.lang.ArrayIndexOutOfBoundsException:
at net.redwarp.gif.decoder.Gif.fillPixelsSimple (Gif.java:75)
at net.redwarp.gif.decoder.Gif.fillPixels (Gif.java:10)
at net.redwarp.gif.decoder.Gif.getFrame (Gif.java:114)
at net.redwarp.gif.decoder.Gif.getCurrentFrame (Gif.java:24)
hi, it's been a long time to come here, I upgrade my app's targetSdkVersion to 34, now I can't access the media resource by File api, only Uri, I can get the absolute path from it, but when I try to turn it as RandomAccessFile, I got a FileNotFoundException, so the problem is we can't have a RandomAccessFile easily any more, only take the return from ContentResolver#openFileDescriptor(android.net.Uri) which cannot turn to RandomAccessFile directly
Turns out that for maven central, I need to own the domain name corresponding to the group.
Which is fine. But as some rando already bought redwarp.net, I have to change to a new group.
I could get the domain name redwarp.app, so I guess we will go for that.
When advancing multiple frames without calling getCurrentFrame()
, the pixels contain artifacts. I have changed the sample app to call advance()
twice before getting the frame, I got this:
The use case for this would be integrating Gif into a rendering engine where advancing frames is calculated on each frame update. If the frame rate of the engine is slower than the gif's, then advance() needs to be called multiple times in a frame resulting in this issue. Temporary workaround would be to explicitly call getCurrentFrame()
after each advance()
call, but I'm sure we can improve this :)
I am not sure if calling getCurrentFrame()
internally when advance()
is called would be the best solution, but it would fix the problem. I am not into the details of decoding, but it sounds like we need to decode frame by frame?
Because why not?
To create some xmp tags on the gif for testing purposes: https://superuser.com/questions/556315/gif-image-exif-tags
Hi, the decode step would cause ArrayIndexOutOfBoundsException sometime
private const val MAX_STACK_SIZE = 4096
private val prefix = ShortArray(MAX_STACK_SIZE)
fun decode(imageData: ByteArray, destination: ByteArray, pixelCount: Int) {
val lzwMinimumCodeSize = imageData[dataIndex]
val clear: Int = 1.shl(lzwMinimumCodeSize.toInt())
for (code in 0 until clear) {
prefix[code] = 0
suffix[code] = code.toByte()
}
once lzwMinimumCodeSize's value greater than 12, I can't make sure that gif file is corrupted or not due to it happened from my user's devices, but it is infrequent, the last report was two months ago, about 20 times totally, probably the files was broken.
just to ask if you have any idea.
Similar to libjpeg-turbo/libjpeg-turbo#560
If we modify the header of the gif to declare a size much bigger than the gif actually is (so, corrupted gif), we will allocate the theoritical maximum for no reason.
We could try to find a heuristic to exit early:
A typical compression ratio of the gif seems to range between 4:1 to 10:1 (http://ist.uwaterloo.ca/~anderson/images/GIFvsJPEG/compression_rates.html)
So, if the size of the inputstream is available (might not be the case), we could exist early if we detect that the size declared of the gif is more than 15 time bigger than the data stream)
For exemple, if we read the header and the gif is 100x100pixel, meaning 10000 pixels, but the gif size is only 100 bytes, then it's really fishy.
A theoritical max gif is 65535x65535, meaning 4_294_836_225 pixels. The code creates an array to store these pixels. But in java, the max size of an array is around Integer.MAX_VALUE (apparently depends on the VM, can be minus 2 or minus 8), so around 2_147_483_648.
It means that the library is actually unable to decode gifs that size ๐ค, and will throw an exception.
should we default the ARGB_8888 to wield space for time? especially I give up your BitmapCache, instead used only one bitmap instance through the whole decode procedure, I will explain in another issue
It was not a great idea in the end: messes up with proguard
the BitmapCache is good for preload next frame, but it allocating two more bitmaps in memory, for me, I turn to single bitmap, that means I can't benefit from the prepareNextFrame work, but it doesn't matter as I have some other adjustments make me discard your preload job.
below are the key code, omit some unimportant lines
class GifDrawable {
private val frameBitmap = Bitmap.createBitmap(gifWidth, gifHeight, Bitmap.Config.ARGB_8888)
fun renderNextFrame(): Long {
val nextIndex = (++gifState.frameIndex) % gif.frameCount
val result = gif.getFrame(nextIndex, frameBitmap)
if (result.isSuccess) {
frameBitmap.prepareToDraw()
invalidateSelf()
}
}
}
class Gif {
fun getFrame(index: Int, frameBitmap: Bitmap): Result<Any?> {
while (currentIndex != index) {
advance().onFailure { return Result.failure(it) }
}
return getCurrentFrame(frameBitmap)
}
private fun getCurrentFrame(frameBitmap: Bitmap): Result<Any?> {
val width = frameBitmap.width
frameBitmap.setPixels(framePixels, 0, width, 0, 0, width, frameBitmap.height)
return Result.success(null)
}
}
Yup, should get that done.
See https://jeroenmols.com/blog/2021/02/04/migratingjcenter/ and https://proandroiddev.com/publishing-your-first-android-library-to-mavencentral-be2c51330b88, I guess it might help with taking care of it.
As a third option, would be cool to compile the decoder as a kotlin native library.
I should check this sample: https://github.com/JetBrains/kotlin-native/tree/master/samples/androidNativeActivity
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.