GifDrawable.java revision e7319b67364bd0ac6306bc7470a43d4a31600c1a
1package com.bumptech.glide.load.resource.gif;
2
3import android.annotation.TargetApi;
4import android.content.Context;
5import android.content.res.Resources;
6import android.graphics.Bitmap;
7import android.graphics.Canvas;
8import android.graphics.ColorFilter;
9import android.graphics.Paint;
10import android.graphics.PixelFormat;
11import android.graphics.drawable.Drawable;
12import android.os.Build;
13
14import com.bumptech.glide.gifdecoder.GifDecoder;
15import com.bumptech.glide.gifdecoder.GifHeader;
16import com.bumptech.glide.load.Transformation;
17import com.bumptech.glide.load.resource.drawable.GlideDrawable;
18
19/**
20 * An animated {@link android.graphics.drawable.Drawable} that plays the frames of an animated GIF.
21 */
22public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameCallback {
23    private final Paint paint = new Paint();
24    private final GifFrameManager frameManager;
25    private final GifState state;
26    private final GifDecoder decoder;
27
28    /** The current frame to draw, or null if no frame has been loaded yet. */
29    private Bitmap currentFrame;
30    /** True if the drawable is currently animating. */
31    private boolean isRunning;
32    /** True if the drawable should animate while visible. */
33    private boolean isStarted;
34    /** True if the drawable's resources have been recycled. */
35    private boolean isRecycled;
36    /** True if the drawable is currently visible. */
37    private boolean isVisible;
38    /** The number of times we've looped over all the frames in the gif. */
39    private int loopCount;
40    /** The number of times to loop through the gif animation. */
41    private int maxLoopCount = LOOP_FOREVER;
42
43    /**
44     * Constructor for GifDrawable.
45     *
46     * @see #setFrameTransformation(com.bumptech.glide.load.Transformation, int, int)
47     *
48     * @param context A context.
49     * @param bitmapProvider An {@link com.bumptech.glide.gifdecoder.GifDecoder.BitmapProvider} that can be used to
50     *                       retrieve re-usable {@link android.graphics.Bitmap}s.
51     * @param frameTransformation An {@link com.bumptech.glide.load.Transformation} that can be applied to each frame.
52     * @param targetFrameWidth The desired width of the frames displayed by this drawable (the width of the view or
53     *                         {@link com.bumptech.glide.request.target.Target} this drawable is being loaded into).
54     * @param targetFrameHeight The desired height of the frames displayed by this drawable (the height of the view or
55     *                          {@link com.bumptech.glide.request.target.Target} this drawable is being loaded into).
56     * @param id An id that uniquely identifies this particular gif.
57     * @param gifHeader The header data for this gif.
58     * @param data The full bytes of the gif.
59     * @param finalFrameWidth The final width of the frames displayed by this drawable after they have been transformed.
60     * @param finalFrameHeight The final height of the frames displayed by this drwaable after they have been
61     *                         transformed.
62     */
63    public GifDrawable(Context context, GifDecoder.BitmapProvider bitmapProvider,
64            Transformation<Bitmap> frameTransformation, int targetFrameWidth, int targetFrameHeight, String id,
65            GifHeader gifHeader, byte[] data, int finalFrameWidth, int finalFrameHeight) {
66        this(new GifState(id, gifHeader, data, context, frameTransformation, targetFrameWidth, targetFrameHeight,
67                bitmapProvider, finalFrameWidth, finalFrameHeight));
68    }
69
70    private GifDrawable(GifState state) {
71        this.state = state;
72        this.decoder = new GifDecoder(state.bitmapProvider);
73        decoder.setData(state.id, state.gifHeader, state.data);
74        frameManager = new GifFrameManager(state.context, decoder, state.frameTransformation, state.targetWidth,
75                state.targetHeight, state.finalFrameWidth, state.finalFrameHeight);
76    }
77
78    // For testing.
79    GifDrawable(GifDecoder decoder, GifFrameManager frameManager, int finalFrameWidth, int finalFrameHeight) {
80        this.decoder = decoder;
81        this.frameManager = frameManager;
82        this.state = new GifState(null);
83        state.finalFrameWidth = finalFrameWidth;
84        state.finalFrameHeight = finalFrameHeight;
85    }
86
87    public void setFrameTransformation(Transformation<Bitmap> frameTransformation, int finalFrameWidth,
88            int finalFrameHeight) {
89        state.frameTransformation = frameTransformation;
90        state.finalFrameWidth = finalFrameWidth;
91        state.finalFrameHeight = finalFrameHeight;
92    }
93
94    public Transformation<Bitmap> getFrameTransformation() {
95        return state.frameTransformation;
96    }
97
98    public byte[] getData() {
99        return state.data;
100    }
101
102    private void resetLoopCount() {
103        loopCount = 0;
104    }
105
106    @Override
107    public void start() {
108        isStarted = true;
109        resetLoopCount();
110        if (isVisible) {
111            startRunning();
112        }
113    }
114
115    @Override
116    public void stop() {
117        isStarted = false;
118        stopRunning();
119    }
120
121    private void startRunning() {
122        if (!isRunning) {
123            isRunning = true;
124            frameManager.getNextFrame(this);
125            invalidateSelf();
126        }
127    }
128
129    private void stopRunning() {
130        isRunning = false;
131    }
132
133    @Override
134    public boolean setVisible(boolean visible, boolean restart) {
135        isVisible = visible;
136        if (!visible) {
137            stopRunning();
138        } else if (isStarted) {
139            startRunning();
140        }
141        return super.setVisible(visible, restart);
142    }
143
144    @Override
145    public int getIntrinsicWidth() {
146        return state.finalFrameWidth;
147    }
148
149    @Override
150    public int getIntrinsicHeight() {
151        return state.finalFrameHeight;
152    }
153
154    @Override
155    public boolean isRunning() {
156        return isRunning;
157    }
158
159    // For testing.
160    void setIsRunning(boolean isRunning) {
161        this.isRunning = isRunning;
162    }
163
164    @Override
165    public void draw(Canvas canvas) {
166        if (currentFrame != null) {
167            canvas.drawBitmap(currentFrame, 0, 0, paint);
168        }
169    }
170
171    @Override
172    public void setAlpha(int i) {
173        paint.setAlpha(i);
174    }
175
176    @Override
177    public void setColorFilter(ColorFilter colorFilter) {
178        paint.setColorFilter(colorFilter);
179    }
180
181    @Override
182    public int getOpacity() {
183        return decoder.isTransparent() ? PixelFormat.TRANSPARENT : PixelFormat.OPAQUE;
184    }
185
186    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
187    @Override
188    public void onFrameRead(Bitmap frame, int frameIndex) {
189        if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT && getCallback() == null) {
190            stop();
191            return;
192        }
193        if (!isRunning) {
194            return;
195        }
196
197        if (frame != null) {
198            currentFrame = frame;
199            invalidateSelf();
200        }
201
202        if (frameIndex == decoder.getFrameCount() - 1) {
203            loopCount++;
204        }
205
206        if (maxLoopCount != LOOP_FOREVER && loopCount >= maxLoopCount) {
207            stop();
208        } else {
209            frameManager.getNextFrame(this);
210        }
211    }
212
213    @Override
214    public ConstantState getConstantState() {
215        return state;
216    }
217
218    /**
219     * Clears any resources for loading frames that are currently held on to by this object.
220     */
221    public void recycle() {
222        isRecycled = true;
223        frameManager.clear();
224    }
225
226    // For testing.
227    boolean isRecycled() {
228        return isRecycled;
229    }
230
231    @Override
232    public boolean isAnimated() {
233        return true;
234    }
235
236    @Override
237    public void setLoopCount(int loopCount) {
238        if (loopCount <= 0 && loopCount != LOOP_FOREVER && loopCount != LOOP_INTRINSIC) {
239            throw new IllegalArgumentException("Loop count must be greater than 0, or equal to "
240                    + "GlideDrawable.LOOP_FOREVER, or equal to GlideDrawable.LOOP_INTRINSIC");
241        }
242
243        if (loopCount == LOOP_INTRINSIC) {
244            maxLoopCount = decoder.getLoopCount();
245        } else {
246            maxLoopCount = loopCount;
247        }
248    }
249
250    static class GifState extends ConstantState {
251        String id;
252        GifHeader gifHeader;
253        byte[] data;
254        int finalFrameWidth;
255        int finalFrameHeight;
256        Context context;
257        Transformation<Bitmap> frameTransformation;
258        int targetWidth;
259        int targetHeight;
260        GifDecoder.BitmapProvider bitmapProvider;
261
262        public GifState(String id, GifHeader header, byte[] data, Context context,
263                Transformation<Bitmap> frameTransformation, int targetWidth, int targetHeight,
264                GifDecoder.BitmapProvider provider, int finalFrameWidth, int finalFrameHeight) {
265            this.id = id;
266            gifHeader = header;
267            this.data = data;
268            this.finalFrameWidth = finalFrameWidth;
269            this.finalFrameHeight = finalFrameHeight;
270            this.context = context.getApplicationContext();
271            this.frameTransformation = frameTransformation;
272            this.targetWidth = targetWidth;
273            this.targetHeight = targetHeight;
274            bitmapProvider = provider;
275        }
276
277        public GifState(GifState original) {
278            if (original != null) {
279                id = original.id;
280                gifHeader = original.gifHeader;
281                data = original.data;
282                context = original.context;
283                frameTransformation = original.frameTransformation;
284                targetWidth = original.targetWidth;
285                targetHeight = original.targetHeight;
286                bitmapProvider = original.bitmapProvider;
287                finalFrameWidth = original.finalFrameWidth;
288                finalFrameHeight = original.finalFrameHeight;
289            }
290        }
291
292        @Override
293        public Drawable newDrawable(Resources res) {
294            return newDrawable();
295        }
296
297        @Override
298        public Drawable newDrawable() {
299            return new GifDrawable(this);
300        }
301
302        @Override
303        public int getChangingConfigurations() {
304            return 0;
305        }
306    }
307}
308