Using Glide, how can I go over each frame of GifDrawable, as Bitmap?
I had a similar requirement when I wanted to display a preview instead of the animation while loading a gif in Glide.
My solution was to take the first frame from the GifDrawable and to present this as the entire drawable. The same approach can be adapted to get the other frames to display (or to export etc.)
DrawableRequestBuilder builder = Glide.with(ctx).load(someUrl);
builder.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
if (resource.isAnimated()) {
target.onResourceReady(new GlideBitmapDrawable(null, ((GifDrawable) resource).getFirstFrame()), null);
}
return handled;
}
});
builder.into(mImageView);
You can either progress the animation to get the keyframes or get them by index within the callback by directly accessing the decoder
attached to GifDrawable. Alternatively set a Callback
(actual class name) on the drawable when it is ready. It will be called by onFrameReady
(Giving you the current frame in the drawable each time). The gif drawable class already manages the bitmap pool.
Once the GifDrawable is ready, loop through frames with the following method:
GifDrawable gd = (GifDrawable) resource;
Bitmap b = gd.getDecoder().getNextFrame();
Note that if you are using the decoder you should really do it from the onResourceReady
callback I mentioned above. I had intermittent issues when I tried to do it earlier.
If you let the decoder run automatically, you can get callbacks for frames
gifDrawable.setCallback(new Drawable.Callback() {
@Override
public void invalidateDrawable(@NonNull Drawable who) {
//NOTE: this method is called each time the GifDrawable updates itself with a new frame
//who.draw(canvas); //if you already have a canvas
//https://stackoverflow.com/questions/3035692/how-to-convert-a-drawable-to-a-bitmap //if you really want a bitmap
}
@Override public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { /* ignore */ }
@Override public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { /* ignore */ }
});
At the time this was the best approach available. As it has been over a year, I cannot guarantee there is not a more efficient way to do this now.
The version of the library I use is Glide 3.7.0. Access is restricted in the latest version 4.7.+, but I'm not sure how far back you need to go to use my approach.
Anyway, we will use non-documented methods from Glide and i hope in one day Glide team will make it public. You will need to have a bit experience with Java Reflection :) Here is bench of code to extract Bitmap from GIF file:
ArrayList bitmaps = new ArrayList<>();
Glide.with(AppObj.getContext())
.asGif()
.load(GIF_PATH)
.into(new SimpleTarget<GifDrawable>() {
@Override
public void onResourceReady(@NonNull GifDrawable resource, @Nullable Transition<? super GifDrawable> transition) {
try {
Object GifState = resource.getConstantState();
Field frameLoader = GifState.getClass().getDeclaredField("frameLoader");
frameLoader.setAccessible(true);
Object gifFrameLoader = frameLoader.get(GifState);
Field gifDecoder = gifFrameLoader.getClass().getDeclaredField("gifDecoder");
gifDecoder.setAccessible(true);
StandardGifDecoder standardGifDecoder = (StandardGifDecoder) gifDecoder.get(gifFrameLoader);
for (int i = 0; i < standardGifDecoder.getFrameCount(); i++) {
standardGifDecoder.advance();
bitmaps.add(standardGifDecoder.getNextFrame());
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
}