@sjudd
I want to using android-gif-drawable to decode gif.
Should i do like this ?
Gradle (Android Studio)
Insert the following dependency to build.gradle file of your project.
dependencies {
compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.10'
}
@GlideModule
public class MyGifModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide,
@NonNull Registry registry) {
/* GIFs */
registry.prepend(
Registry.BUCKET_GIF,
InputStream.class,
pl.droidsonroids.gif.GifDrawable.class, // <-------------- here
new MyStreamGifDecoder(registry.getImageHeaderParsers(), glide.getArrayPool()));
}
// Disable manifest parsing to avoid adding similar modules twice.
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
import android.support.annotation.NonNull;
import com.bumptech.glide.load.engine.Initializable;
import com.bumptech.glide.load.resource.drawable.DrawableResource;
import pl.droidsonroids.gif.GifDrawable; // <------------------- here
/**
* @author Administrator
* @date 2018/1/12 14:49
*/
public class MyGifDrawableResource extends DrawableResource<GifDrawable>
implements Initializable {
// Public API.
@SuppressWarnings("WeakerAccess")
public MyGifDrawableResource(GifDrawable drawable) {
super(drawable);
}
@NonNull
@Override
public Class<GifDrawable> getResourceClass() {
return GifDrawable.class;
}
@Override
public int getSize() {
return drawable.getFrameByteCount() * drawable.getNumberOfFrames();
}
@Override
public void recycle() {
drawable.stop();
drawable.recycle();
}
@Override
public void initialize() {
drawable.seekToFrameAndGet(0).prepareToDraw();
}
}
import android.support.annotation.NonNull;
import com.bumptech.glide.load.ImageHeaderParser;
import com.bumptech.glide.load.ImageHeaderParserUtils;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
import com.bumptech.glide.load.resource.gif.GifOptions;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import pl.droidsonroids.gif.GifDrawable; // <------------------- here
/**
* @author Administrator
* @date 2018/1/12 14:39
*/
public class MyStreamGifDecoder implements ResourceDecoder<InputStream, GifDrawable> {
private static final String TAG = "MyStreamGifDecoder";
private final List<ImageHeaderParser> parsers;
private final ArrayPool byteArrayPool;
public MyStreamGifDecoder(List<ImageHeaderParser> parsers, ArrayPool byteArrayPool) {
this.parsers = parsers;
this.byteArrayPool = byteArrayPool;
}
@Override
public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException {
return !options.get(GifOptions.DISABLE_ANIMATION)
&& ImageHeaderParserUtils.getType(parsers, source, byteArrayPool) == ImageHeaderParser.ImageType.GIF;
}
@Override
public Resource<GifDrawable> decode(@NonNull InputStream source, int width, int height,
@NonNull Options options) throws IOException {
GifDrawable gifDrawable = new GifDrawable(source);
return new MyGifDrawableResource(gifDrawable);
}
}
But have something wrong in below
public class MyStreamGifDecoder implements ResourceDecoder<InputStream, GifDrawable> {
@Override
public Resource<GifDrawable> decode(@NonNull InputStream source, int width, int height,
@NonNull Options options) throws IOException {
GifDrawable gifDrawable = new GifDrawable(source); //<-------- get wrong
return new MyGifDrawableResource(gifDrawable);
}
}
So I debug into the class com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream that the source code of Glide.
Then i wrap RecyclableBufferedInputStream as BufferedInputStream like below:
public class MyStreamGifDecoder implements ResourceDecoder<InputStream, GifDrawable> {
@Override
public Resource<GifDrawable> decode(@NonNull InputStream source, int width, int height,
@NonNull Options options) throws IOException {
RecyclableBufferedInputStream source2 = (RecyclableBufferedInputStream) source;
source = new BufferedInputStream(source2);
GifDrawable gifDrawable2 = new GifDrawable(source);
return new MyGifDrawableResource(gifDrawable2);
}
}
It's OK, but little flaw happen in gif render! -_-|||,
I think has something wrong between RecyclableBufferedInputStream and pl.droidsonroids.gif.GifDrawable.
Can you help me fix it? Thank you? My demo is here
Can you explain more about what you mean by a flaw happens in the gif render? What error do you see specifically if you do not use BufferedInputStream?
I don't see anything obviously wrong with your implementation, though I haven't used the library you're referencing. If GifDrawable accepts byte[], you could also try buffering the image data in memory, similar to: https://github.com/bumptech/glide/blob/bfa237c19a7d742a4911e2744d174ae82f420ac0/library/src/main/java/com/bumptech/glide/load/resource/gif/StreamGifDecoder.java#L46
@sjudd
Sorry, forget provide the apk and snapshot to explain the flaw and error...
svg-debug-not-use-BufferedInputStream.zip
The gif not display, the error holder display

svg-debug-use-BufferedInputStream.zip

I also try below, but not work. And the effect is same as not use BufferedInputStream
If GifDrawable accepts byte[], you could also try buffering the image data in memory, similar to:
https://github.com/bumptech/glide/blob/bfa237c19a7d742a4911e2744d174ae82f420ac0/library/src/main/java/com/bumptech/glide/load/resource/gif/StreamGifDecoder.java#L46
>
see this issue https://github.com/bumptech/glide/issues/805
```java
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int count;
while ((count = source.read(buffer)) != -1) {
bytes.write(buffer, 0, count);
}
return new MyGifDrawableResource(new GifDrawable(bytes.toByteArray()));
@ooeo
I know how to combime android-gif-drawable in Glide V4,
But my problem is gif render not well with **android-gif-drawable** in Glide V4,
The #805 may not be what I want, thanks you help!
@Guang1234567
I mean use those code, android-gif-drawable can be render well
@sjudd @ooeoo
I find the solution, but i don not know why -_-||| ...
ByteBuffer, not working
public class MyStreamGifDecoder implements ResourceDecoder<InputStream, GifDrawable> {
@Override
public Resource<GifDrawable> decode(@NonNull InputStream source, int width, int height,
@NonNull Options options) throws IOException {
byte[] data = inputStreamToBytes(source);
if (data == null) {
return null;
}
// can not use byteBuffer, works not well
ByteBuffer byteBuffer = ByteBuffer.wrap(data); //<-------------- here
GifDrawable gifDrawable = new GifDrawable(byteBuffer);
return new MyGifDrawableResource(gifDrawable);
}
private static byte[] inputStreamToBytes(InputStream is) {
final int bufferSize = 16384;
ByteArrayOutputStream buffer = new ByteArrayOutputStream(bufferSize);
try {
int nRead;
byte[] data = new byte[bufferSize];
while ((nRead = is.read(data)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Error reading data from stream", e);
}
return null;
}
return buffer.toByteArray();
}
}
Byte[], it wroks well why?public class MyStreamGifDecoder implements ResourceDecoder<InputStream, GifDrawable> {
@Override
public Resource<GifDrawable> decode(@NonNull InputStream source, int width, int height,
@NonNull Options options) throws IOException {
byte[] data = inputStreamToBytes(source);
if (data == null) {
return null;
}
/*
// can not use byteBuffer, works not well
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
GifDrawable gifDrawable = new GifDrawable(byteBuffer);
*/
// byte[] works well, but i don not know why
GifDrawable gifDrawable = new GifDrawable(data); // <---- here
return new MyGifDrawableResource(gifDrawable);
}
private static byte[] inputStreamToBytes(InputStream is) {
final int bufferSize = 16384;
ByteArrayOutputStream buffer = new ByteArrayOutputStream(bufferSize);
try {
int nRead;
byte[] data = new byte[bufferSize];
while ((nRead = is.read(data)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Error reading data from stream", e);
}
return null;
}
return buffer.toByteArray();
}
}
https://github.com/koral--/android-gif-drawable/blob/dev/android-gif-drawable/src/main/c/gif.c#L198-L206
when you use ByteBuffer you need pass direct byte buffer or GetDirectBufferAddress cannot get address.
// allocates direct byte buffer
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(capacity);
byteBuffer.put(src);
@ooeoo
thanks. it is ok if using ByteBuffer.allocateDirect(capacity);
BTW, do you know the error reason of using InputStream source directly? It is the problem about marksupport implement of RecyclableBufferedInputStream?
public class MyStreamGifDecoder implements ResourceDecoder<InputStream, GifDrawable> {
@Override
public Resource<GifDrawable> decode(@NonNull InputStream source, int width, int height,
@NonNull Options options) throws IOException {
GifDrawable gifDrawable = new GifDrawable(source); //<-------- get wrong, gif render not well
return new MyGifDrawableResource(gifDrawable);
}
}
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.
It seems that glide closes InputStream after decode is called.
@TWiStErRob sorry for disturbing you. Is there a way to prevent of releasing RecyclableBufferedInputStream?
After decoding InputStreamRewinder calls cleanup and release RecyclableBufferedInputStream. But pl.droidsonroids.gif.GifDrawable needs opened stream to proper work.
Not sure if it is a proper fix, but it solves this issue: https://github.com/bumptech/glide/pull/2892
@futurobot you can always grab contents into memory and forget about using the stream. @koral--'s fix also seems reasonable and I think @sjudd will accept it.
@TWiStErRob Yes, for sure i can. But sometimes i need to show large gif's ≈ 50 MB long. I can get OOM on old devices.
Basically i need an input stream to display image and after that i should close it manually. I don't think that glide is fit into this use case. Only in case of using it as downloader.asFile().
@TWiStErRob do you think that there is any other way to fix this issue on the glide side?
At the time of writing, glide does not support progressive images and the only reliable solution is to buffer entire GIF in byte array or ByteBuffer.
More details: https://github.com/bumptech/glide/pull/2892#issuecomment-365303079
Question: Glide does not have transcoder for Model String (web url) -> Data File.
What is the proper way to register load path URL -> File, with using Glides native DiskCache implemention?
My goal is to implement load path Web url -> File -> GifDrawable Resource
Just use DiskCacheStrategy.SOURCE or DiskCacheStrategy.AUTOMATIC with a remote data source. Those download the image into Glide's disk cache and then load the image from the corresponding File.
@sjudd Thank you, work like a charm!
So this is my approach of implementing loading Korals GifDrawable with Glide.
GlideModule.java
@com.bumptech.glide.annotation.GlideModule
public class GlideModule extends AppGlideModule {
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setLogLevel(Log.VERBOSE); // For debug build only
}
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide,
@NonNull Registry registry) {
final KoralFileGifDecoder koralFileGifDecoder = new KoralFileGifDecoder(
registry.getImageHeaderParsers(), glide.getArrayPool());
registry.prepend(/*Data:*/File.class, /*Resource:*/GifDrawable.class, koralFileGifDecoder);
}
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
KoralFileGifDecoder.java
public class KoralFileGifDecoder implements ResourceDecoder<File, GifDrawable> {
private final List<ImageHeaderParser> imageHeaderParsers;
private final ArrayPool arrayPool;
public KoralFileGifDecoder(List<ImageHeaderParser> imageHeaderParsers,
ArrayPool arrayPool) {
this.imageHeaderParsers = imageHeaderParsers;
this.arrayPool = arrayPool;
}
@Override
public boolean handles(@NonNull File source, @NonNull Options options) throws IOException {
// TODO: Maybe it is too expensive to open stream each time?
InputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(source);
return ImageHeaderParserUtils.getType(imageHeaderParsers, fileInputStream, arrayPool)
== ImageType.GIF;
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException ignore) {
}
}
}
}
@Nullable
@Override
public Resource<GifDrawable> decode(@NonNull File source, int width, int height,
@NonNull Options options) throws IOException {
return new KoralGifDrawableResource(new GifDrawable(source));
}
}
KoralGifDrawableResource.java
public class KoralGifDrawableResource implements Resource<GifDrawable>, Initializable {
private GifDrawable gifDrawable;
public KoralGifDrawableResource(GifDrawable gifDrawable) {
this.gifDrawable = gifDrawable;
}
@NonNull
@Override
public Class<GifDrawable> getResourceClass() {
return GifDrawable.class;
}
@NonNull
@Override
public GifDrawable get() {
return gifDrawable;
}
@Override
public int getSize() {
return gifDrawable.getFrameByteCount() * gifDrawable.getNumberOfFrames();
}
@Override
public void recycle() {
gifDrawable.stop();
gifDrawable.recycle();
}
@Override
public void initialize() {
gifDrawable.seekToFrameAndGet(0).prepareToDraw();
}
}
Loading gif into ImageView
RequestOptions requestOptions = new RequestOptions()
.placeholder(R.drawable.gray)
.error(R.drawable.red)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.skipMemoryCache(true);
Glide.with(GifActivity.this)
.as(GifDrawable.class)
.load(/*Gif url*/)
.apply(requestOptions)
.into(imageView);