1 /*
2  * Copyright (C) 2015 The Gifplayer Authors. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package jp.tomorrowkey.android.gifplayer;
18 
19 import android.graphics.Bitmap;
20 import android.graphics.Canvas;
21 import android.graphics.ColorFilter;
22 import android.graphics.Paint;
23 import android.graphics.PixelFormat;
24 import android.graphics.Rect;
25 import android.graphics.drawable.Animatable;
26 import android.graphics.drawable.Drawable;
27 import android.os.Handler;
28 import android.os.HandlerThread;
29 import android.os.Looper;
30 import android.os.Message;
31 import android.os.SystemClock;
32 import android.util.Log;
33 
34 /**
35  * A base GIF Drawable with support for animations.
36  *
37  * Inspired by http://code.google.com/p/android-gifview/
38  */
39 public class BaseGifDrawable extends Drawable implements Runnable, Animatable,
40     android.os.Handler.Callback {
41 
42     private static final String TAG = "GifDrawable";
43 
44     // Max decoder pixel stack size
45     private static final int MAX_STACK_SIZE = 4096;
46     private static final int MAX_BITS = 4097;
47 
48     // Frame disposal methods
49     private static final int DISPOSAL_METHOD_UNKNOWN = 0;
50     private static final int DISPOSAL_METHOD_LEAVE = 1;
51     private static final int DISPOSAL_METHOD_BACKGROUND = 2;
52     private static final int DISPOSAL_METHOD_RESTORE = 3;
53 
54     // Message types
55     private static final int READ_FRAME_REQ = 10;
56     private static final int READ_FRAME_RESP = 11;
57     private static final int RESET_DECODER = 12;
58 
59     // Specifies the minimum amount of time before a subsequent frame will be rendered.
60     private static final int MIN_FRAME_SCHEDULE_DELAY_MS = 5;
61 
62     private static final byte[] NETSCAPE2_0 = "NETSCAPE2.0".getBytes();
63 
64     private static Paint sPaint;
65     private static Paint sScalePaint;
66 
67     protected final BaseGifImage mGifImage;
68     private final byte[] mData;
69 
70     private int mPosition;
71     protected int mIntrinsicWidth;
72     protected int mIntrinsicHeight;
73 
74     private int mWidth;
75     private int mHeight;
76 
77     protected Bitmap mBitmap;
78     protected int[] mColors;
79     private boolean mScale;
80     private float mScaleFactor;
81 
82     // The following are marked volatile because they are read/written in the background decoder
83     // thread and read from the UI thread.  No further synchronization is needed because their
84     // values will only ever change from at most once, and it is safe to lazily detect the change
85     // in the UI thread.
86     private volatile boolean mError;
87     private volatile boolean mDone;
88     private volatile boolean mAnimateOnLoad = true;
89 
90     private int mBackgroundColor;
91     private boolean mLocalColorTableUsed;
92     private int mLocalColorTableSize;
93     private int[] mLocalColorTable;
94     private int[] mActiveColorTable;
95     private boolean mInterlace;
96 
97     // Each frame specifies a sub-region of the image that should be updated.  The values are
98     // clamped to the GIF dimensions if they exceed the intrinsic dimensions.
99     private int mFrameX, mFrameY, mFrameWidth, mFrameHeight;
100 
101     // This specifies the width of the actual data within a GIF frame.  It will be equal to
102     // mFrameWidth unless the frame sub-region was clamped to prevent exceeding the intrinsic
103     // dimensions.
104     private int mFrameStep;
105 
106     private byte[] mBlock = new byte[256];
107     private int mDisposalMethod = DISPOSAL_METHOD_BACKGROUND;
108     private boolean mTransparency;
109     private int mTransparentColorIndex;
110 
111     // LZW decoder working arrays
112     private short[] mPrefix = new short[MAX_STACK_SIZE];
113     private byte[] mSuffix = new byte[MAX_STACK_SIZE];
114     private byte[] mPixelStack = new byte[MAX_STACK_SIZE + 1];
115     private byte[] mPixels;
116 
117     private boolean mBackupSaved;
118     private int[] mBackup;
119 
120     private int mFrameCount;
121 
122     private long mLastFrameTime;
123 
124     private boolean mRunning;
125     protected int mFrameDelay;
126     private int mNextFrameDelay;
127     protected boolean mScheduled;
128     private boolean mAnimationEnabled = true;
129     private final Handler mHandler = new Handler(Looper.getMainLooper(), this);
130     private static DecoderThread sDecoderThread;
131     private static Handler sDecoderHandler;
132 
133     private boolean mRecycled;
134     protected boolean mFirstFrameReady;
135     private boolean mEndOfFile;
136     private int mLoopCount = 0; // 0 to repeat endlessly.
137     private int mLoopIndex = 0;
138 
139     private final Bitmap.Config mBitmapConfig;
140     private boolean mFirstFrame = true;
141 
BaseGifDrawable(BaseGifImage gifImage, Bitmap.Config bitmapConfig)142     public BaseGifDrawable(BaseGifImage gifImage, Bitmap.Config bitmapConfig) {
143         this.mBitmapConfig = bitmapConfig;
144 
145         // Create the background decoder thread, if necessary.
146         if (sDecoderThread == null) {
147             sDecoderThread = new DecoderThread();
148             sDecoderThread.start();
149             sDecoderHandler = new Handler(sDecoderThread.getLooper(), sDecoderThread);
150         }
151 
152         if (sPaint == null) {
153             sPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
154             sScalePaint = new Paint(Paint.FILTER_BITMAP_FLAG);
155             sScalePaint.setFilterBitmap(true);
156         }
157 
158         mGifImage = gifImage;
159         mData = gifImage.getData();
160         mPosition = mGifImage.mHeaderSize;
161         mFrameWidth = mFrameStep = mIntrinsicWidth = gifImage.getWidth();
162         mFrameHeight = mIntrinsicHeight = gifImage.getHeight();
163         mBackgroundColor = mGifImage.mBackgroundColor;
164         mError = mGifImage.mError;
165 
166         if (!mError) {
167             try {
168                 mBitmap = Bitmap.createBitmap(mIntrinsicWidth, mIntrinsicHeight, mBitmapConfig);
169                 if (mBitmap == null) {
170                     throw new OutOfMemoryError("Cannot allocate bitmap");
171                 }
172 
173                 int pixelCount = mIntrinsicWidth * mIntrinsicHeight;
174                 mColors = new int[pixelCount];
175                 mPixels = new byte[pixelCount];
176 
177                 mWidth = mIntrinsicHeight;
178                 mHeight = mIntrinsicHeight;
179 
180                 // Read the first frame
181                 sDecoderHandler.sendMessage(sDecoderHandler.obtainMessage(READ_FRAME_REQ, this));
182             } catch (OutOfMemoryError e) {
183                 mError = true;
184             }
185         }
186     }
187 
188     /**
189      * Sets the loop count for multi-frame animation.
190      */
setLoopCount(int loopCount)191     public void setLoopCount(int loopCount) {
192         mLoopCount = loopCount;
193     }
194 
195     /**
196      * Returns the loop count for multi-frame animation.
197      */
getLoopCount()198     public int getLoopCount() {
199         return mLoopCount;
200     }
201 
202     /**
203      * Sets whether to start animation on load or not.
204      */
setAnimateOnLoad(boolean animateOnLoad)205     public void setAnimateOnLoad(boolean animateOnLoad) {
206         mAnimateOnLoad = animateOnLoad;
207     }
208 
209     /**
210      * Returns {@code true} if the GIF is valid and {@code false} otherwise.
211      */
isValid()212     public boolean isValid() {
213         return !mError && mFirstFrameReady;
214     }
215 
onRecycle()216     public void onRecycle() {
217         if (mBitmap != null) {
218             mBitmap.recycle();
219         }
220         mBitmap = null;
221         mRecycled = true;
222     }
223 
224     /**
225      * Enables or disables the GIF from animating. GIF animations are enabled by default.
226      */
setAnimationEnabled(boolean animationEnabled)227     public void setAnimationEnabled(boolean animationEnabled) {
228         if (mAnimationEnabled == animationEnabled) {
229             return;
230         }
231 
232         mAnimationEnabled = animationEnabled;
233         if (mAnimationEnabled) {
234             start();
235         } else {
236             stop();
237         }
238     }
239 
240     @Override
onBoundsChange(Rect bounds)241     protected void onBoundsChange(Rect bounds) {
242         super.onBoundsChange(bounds);
243         mWidth = bounds.width();
244         mHeight =  bounds.height();
245         mScale = mWidth != mIntrinsicWidth && mHeight != mIntrinsicHeight;
246         if (mScale) {
247             mScaleFactor = Math.max((float) mWidth / mIntrinsicWidth,
248                     (float) mHeight / mIntrinsicHeight);
249         }
250 
251         if (!mError && !mRecycled) {
252             // Request that the decoder reset itself
253             sDecoderHandler.sendMessage(sDecoderHandler.obtainMessage(RESET_DECODER, this));
254         }
255     }
256 
257     @Override
setVisible(boolean visible, boolean restart)258     public boolean setVisible(boolean visible, boolean restart) {
259         boolean changed = super.setVisible(visible, restart);
260         if (visible) {
261             if (changed || restart) {
262                 start();
263             }
264         } else {
265             stop();
266         }
267         return changed;
268     }
269 
270     @Override
draw(Canvas canvas)271     public void draw(Canvas canvas) {
272         if (mError || mWidth == 0 || mHeight == 0 || mRecycled || !mFirstFrameReady) {
273             return;
274         }
275 
276         if (mScale) {
277             canvas.save();
278             canvas.scale(mScaleFactor, mScaleFactor, 0, 0);
279             canvas.drawBitmap(mBitmap, 0, 0, sScalePaint);
280             canvas.restore();
281         } else {
282             canvas.drawBitmap(mBitmap, 0, 0, sPaint);
283         }
284 
285         if (mRunning) {
286             if (!mScheduled) {
287                 // Schedule the next frame at mFrameDelay milliseconds from the previous frame or
288                 // the minimum sceduling delay from now, whichever is later.
289                 mLastFrameTime = Math.max(
290                     mLastFrameTime + mFrameDelay,
291                     SystemClock.uptimeMillis() + MIN_FRAME_SCHEDULE_DELAY_MS);
292                 scheduleSelf(this, mLastFrameTime);
293             }
294         } else if (!mDone) {
295             start();
296         } else {
297             unscheduleSelf(this);
298         }
299     }
300 
301     @Override
getIntrinsicWidth()302     public int getIntrinsicWidth() {
303         return mIntrinsicWidth;
304     }
305 
306     @Override
getIntrinsicHeight()307     public int getIntrinsicHeight() {
308         return mIntrinsicHeight;
309     }
310 
311     @Override
getOpacity()312     public int getOpacity() {
313         return PixelFormat.UNKNOWN;
314     }
315 
316     @Override
setAlpha(int alpha)317     public void setAlpha(int alpha) {
318     }
319 
320     @Override
setColorFilter(ColorFilter cf)321     public void setColorFilter(ColorFilter cf) {
322     }
323 
324     @Override
isRunning()325     public boolean isRunning() {
326         return mRunning;
327     }
328 
329     @Override
start()330     public void start() {
331         if (!isRunning()) {
332             mRunning = true;
333             if (!mAnimateOnLoad) {
334                 mDone = true;
335             }
336             mLastFrameTime = SystemClock.uptimeMillis();
337             run();
338         }
339     }
340 
341     @Override
stop()342     public void stop() {
343         if (isRunning()) {
344             unscheduleSelf(this);
345         }
346     }
347 
348     @Override
scheduleSelf(Runnable what, long when)349     public void scheduleSelf(Runnable what, long when) {
350         if (mAnimationEnabled) {
351             super.scheduleSelf(what, when);
352             mScheduled = true;
353         }
354     }
355 
356     @Override
unscheduleSelf(Runnable what)357     public void unscheduleSelf(Runnable what) {
358         super.unscheduleSelf(what);
359         mRunning = false;
360     }
361 
362     /**
363      * Moves to the next frame.
364      */
365     @Override
run()366     public void run() {
367         if (mRecycled) {
368             return;
369         }
370 
371         // Send request to decoder to read the next frame
372         if (!mDone) {
373             sDecoderHandler.sendMessage(sDecoderHandler.obtainMessage(READ_FRAME_REQ, this));
374         }
375     }
376 
377     /**
378      * Restarts decoding the image from the beginning.  Called from the background thread.
379      */
reset()380     private void reset() {
381         // Return to the position of the first image frame in the stream.
382         mPosition = mGifImage.mHeaderSize;
383         mBackupSaved = false;
384         mFrameCount = 0;
385         mDisposalMethod = DISPOSAL_METHOD_UNKNOWN;
386     }
387 
388     /**
389      * Restarts animation if a limited number of loops of animation have been previously done.
390      */
restartAnimation()391     public void restartAnimation() {
392         if (mDone && mLoopCount > 0) {
393             reset();
394             mDone = false;
395             mLoopIndex = 0;
396             run();
397         }
398     }
399 
400     /**
401      * Reads color table as 256 RGB integer values.  Called from the background thread.
402      *
403      * @param ncolors int number of colors to read
404      */
readColorTable(int[] colorTable, int ncolors)405     private void readColorTable(int[] colorTable, int ncolors) {
406         for (int i = 0; i < ncolors; i++) {
407             int r = mData[mPosition++] & 0xff;
408             int g = mData[mPosition++] & 0xff;
409             int b = mData[mPosition++] & 0xff;
410             colorTable[i] = 0xff000000 | (r << 16) | (g << 8) | b;
411         }
412     }
413 
414     /**
415      * Reads GIF content blocks.  Called from the background thread.
416      *
417      * @return true if the next frame has been parsed successfully, false if EOF
418      *         has been reached
419      */
readNextFrame()420     private void readNextFrame() {
421         // Don't clear the image if it is a terminator.
422         if ((mData[mPosition] & 0xff) == 0x3b) {
423           mEndOfFile = true;
424           return;
425         }
426         disposeOfLastFrame();
427 
428         mDisposalMethod = DISPOSAL_METHOD_UNKNOWN;
429         mTransparency = false;
430 
431         mEndOfFile = false;
432         mNextFrameDelay = 100;
433         mLocalColorTable = null;
434 
435         while (true) {
436             int code = mData[mPosition++] & 0xff;
437             switch (code) {
438                 case 0:     // Empty block, ignore
439                     break;
440                 case 0x21: // Extension.  Extensions precede the corresponding image.
441                     code = mData[mPosition++] & 0xff;
442                     switch (code) {
443                         case 0xf9: // graphics control extension
444                             readGraphicControlExt();
445                             break;
446                         case 0xff: // application extension
447                             readBlock();
448                             boolean netscape = true;
449                             for (int i = 0; i < NETSCAPE2_0.length; i++) {
450                                 if (mBlock[i] != NETSCAPE2_0[i]) {
451                                     netscape = false;
452                                     break;
453                                 }
454                             }
455                             if (netscape) {
456                                 readNetscapeExtension();
457                             } else {
458                                 skip(); // don't care
459                             }
460                             break;
461                         case 0xfe:// comment extension
462                             skip();
463                             break;
464                         case 0x01:// plain text extension
465                             skip();
466                             break;
467                         default: // uninteresting extension
468                             skip();
469                     }
470                     break;
471 
472                 case 0x2C: // Image separator
473                     readBitmap();
474                     return;
475 
476                 case 0x3b: // Terminator
477                     mEndOfFile = true;
478                     return;
479 
480                 default:  // We don't know what this is. Just skip it.
481                     break;
482             }
483         }
484     }
485 
486     /**
487      * Disposes of the previous frame.  Called from the background thread.
488      */
disposeOfLastFrame()489     private void disposeOfLastFrame() {
490         if (mFirstFrame) {
491           mFirstFrame = false;
492           return;
493         }
494         switch (mDisposalMethod) {
495             case DISPOSAL_METHOD_UNKNOWN:
496             case DISPOSAL_METHOD_LEAVE: {
497                 mBackupSaved = false;
498                 break;
499             }
500             case DISPOSAL_METHOD_RESTORE: {
501                 if (mBackupSaved) {
502                     System.arraycopy(mBackup, 0, mColors, 0, mBackup.length);
503                 }
504                 break;
505             }
506             case DISPOSAL_METHOD_BACKGROUND: {
507                 mBackupSaved = false;
508 
509                 // Fill last image rect area with background color
510                 int color = 0;
511                 if (!mTransparency) {
512                     color = mBackgroundColor;
513                 }
514                 for (int i = 0; i < mFrameHeight; i++) {
515                     int n1 = (mFrameY + i) * mIntrinsicWidth + mFrameX;
516                     int n2 = n1 + mFrameWidth;
517                     for (int k = n1; k < n2; k++) {
518                         mColors[k] = color;
519                     }
520                 }
521                 break;
522             }
523         }
524     }
525 
526     /**
527      * Reads Graphics Control Extension values.  Called from the background thread.
528      */
readGraphicControlExt()529     private void readGraphicControlExt() {
530         mPosition++; // Block size, fixed
531 
532         int packed = mData[mPosition++] & 0xff; // Packed fields
533 
534         mDisposalMethod = (packed & 0x1c) >> 2;  // Disposal method
535         mTransparency = (packed & 1) != 0;
536         mNextFrameDelay = readShort() * 10; // Delay in milliseconds
537 
538         // It seems that there are broken tools out there that set a 0ms or 10ms
539         // timeout when they really want a "default" one.
540         // Following WebKit's lead (http://trac.webkit.org/changeset/73295)
541         // we use 10 frames per second as the default frame rate.
542         if (mNextFrameDelay <= 10) {
543             mNextFrameDelay = 100;
544         }
545 
546         mTransparentColorIndex = mData[mPosition++] & 0xff;
547 
548         mPosition++; // Block terminator - ignore
549     }
550 
551     /**
552      * Reads Netscape extension to obtain iteration count.  Called from the background thread.
553      */
readNetscapeExtension()554     private void readNetscapeExtension() {
555         int count;
556         do {
557             count = readBlock();
558         } while ((count > 0) && !mError);
559     }
560 
561     /**
562      * Reads next frame image.  Called from the background thread.
563      */
readBitmap()564     private void readBitmap() {
565         mFrameX = readShort(); // (sub)image position & size
566         mFrameY = readShort();
567 
568         int width = readShort();
569         int height = readShort();
570 
571         // Clamp the frame dimensions to the intrinsic dimensions.
572         mFrameWidth = Math.min(width, mIntrinsicWidth - mFrameX);
573         mFrameHeight = Math.min(height, mIntrinsicHeight - mFrameY);
574 
575         // The frame step is set to the specfied frame width before clamping.
576         mFrameStep = width;
577 
578         // Increase the size of the decoding buffer if necessary.
579         int framePixelCount = width * height;
580         if (framePixelCount > mPixels.length) {
581             mPixels = new byte[framePixelCount];
582         }
583 
584         int packed = mData[mPosition++] & 0xff;
585         // 3 - sort flag
586         // 4-5 - reserved lctSize = 2 << (packed & 7);
587         // 6-8 - local color table size
588         mInterlace = (packed & 0x40) != 0;
589         mLocalColorTableUsed = (packed & 0x80) != 0; // 1 - local color table flag interlace
590         mLocalColorTableSize = (int) Math.pow(2, (packed & 0x07) + 1);
591 
592         if (mLocalColorTableUsed) {
593             if (mLocalColorTable == null) {
594                 mLocalColorTable = new int[256];
595             }
596             readColorTable(mLocalColorTable, mLocalColorTableSize);
597             mActiveColorTable = mLocalColorTable;
598         } else {
599             mActiveColorTable = mGifImage.mGlobalColorTable;
600             if (mGifImage.mBackgroundIndex == mTransparentColorIndex) {
601                 mBackgroundColor = 0;
602             }
603         }
604         int savedColor = 0;
605         if (mTransparency) {
606             savedColor = mActiveColorTable[mTransparentColorIndex];
607             mActiveColorTable[mTransparentColorIndex] = 0;
608         }
609 
610         if (mActiveColorTable == null) {
611             mError = true;
612         }
613 
614         if (mError) {
615             return;
616         }
617 
618         decodeBitmapData();
619 
620         skip();
621 
622         if (mError) {
623             return;
624         }
625 
626         if (mDisposalMethod == DISPOSAL_METHOD_RESTORE) {
627             backupFrame();
628         }
629 
630         populateImageData();
631 
632         if (mTransparency) {
633             mActiveColorTable[mTransparentColorIndex] = savedColor;
634         }
635 
636         mFrameCount++;
637     }
638 
639     /**
640      * Stores the relevant portion of the current frame so that it can be restored
641      * before the next frame is rendered.  Called from the background thread.
642      */
backupFrame()643     private void backupFrame() {
644         if (mBackupSaved) {
645             return;
646         }
647 
648         if (mBackup == null) {
649             mBackup = null;
650             try {
651                 mBackup = new int[mColors.length];
652             } catch (OutOfMemoryError e) {
653                 Log.e(TAG, "GifDrawable.backupFrame threw an OOME", e);
654             }
655         }
656 
657         if (mBackup != null) {
658             System.arraycopy(mColors, 0, mBackup, 0, mColors.length);
659             mBackupSaved = true;
660         }
661     }
662 
663     /**
664      * Decodes LZW image data into pixel array.  Called from the background thread.
665      */
decodeBitmapData()666     private void decodeBitmapData() {
667         int npix = mFrameWidth * mFrameHeight;
668 
669         // Initialize GIF data stream decoder.
670         int dataSize = mData[mPosition++] & 0xff;
671         int clear = 1 << dataSize;
672         int endOfInformation = clear + 1;
673         int available = clear + 2;
674         int oldCode = -1;
675         int codeSize = dataSize + 1;
676         int codeMask = (1 << codeSize) - 1;
677         for (int code = 0; code < clear; code++) {
678             mPrefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
679             mSuffix[code] = (byte) code;
680         }
681 
682         // Decode GIF pixel stream.
683         int datum = 0;
684         int bits = 0;
685         int first = 0;
686         int top = 0;
687         int pi = 0;
688         while (pi < npix) {
689             int blockSize = mData[mPosition++] & 0xff;
690             if (blockSize == 0) {
691                 break;
692             }
693 
694             int blockEnd = mPosition + blockSize;
695             while (mPosition < blockEnd) {
696                 datum += (mData[mPosition++] & 0xff) << bits;
697                 bits += 8;
698 
699                 while (bits >= codeSize) {
700                     // Get the next code.
701                     int code = datum & codeMask;
702                     datum >>= codeSize;
703                     bits -= codeSize;
704 
705                     // Interpret the code
706                     if (code == clear) {
707                         // Reset decoder.
708                         codeSize = dataSize + 1;
709                         codeMask = (1 << codeSize) - 1;
710                         available = clear + 2;
711                         oldCode = -1;
712                         continue;
713                     }
714 
715                     // Check for explicit end-of-stream
716                     if (code == endOfInformation) {
717                         mPosition = blockEnd;
718                         return;
719                     }
720 
721                     if (oldCode == -1) {
722                         mPixels[pi++] = mSuffix[code];
723                         oldCode = code;
724                         first = code;
725                         continue;
726                     }
727 
728                     int inCode = code;
729                     if (code >= available) {
730                         mPixelStack[top++] = (byte) first;
731                         code = oldCode;
732                         if (top == MAX_BITS) {
733                             mError =  true;
734                             return;
735                         }
736                     }
737 
738                     while (code >= clear) {
739                         if (code >= MAX_BITS || code == mPrefix[code]) {
740                             mError =  true;
741                             return;
742                         }
743 
744                         mPixelStack[top++] = mSuffix[code];
745                         code = mPrefix[code];
746 
747                         if (top == MAX_BITS) {
748                             mError =  true;
749                             return;
750                         }
751                     }
752 
753                     first = mSuffix[code];
754                     mPixelStack[top++] = (byte) first;
755 
756                     // Add new code to the dictionary
757                     if (available < MAX_STACK_SIZE) {
758                         mPrefix[available] = (short) oldCode;
759                         mSuffix[available] = (byte) first;
760                         available++;
761 
762                         if (((available & codeMask) == 0) && (available < MAX_STACK_SIZE)) {
763                             codeSize++;
764                             codeMask += available;
765                         }
766                     }
767 
768                     oldCode = inCode;
769 
770                     // Drain the pixel stack.
771                     do {
772                         mPixels[pi++] = mPixelStack[--top];
773                     } while (top > 0);
774                 }
775             }
776         }
777 
778         while (pi < npix) {
779             mPixels[pi++] = 0; // clear missing pixels
780         }
781     }
782 
783     /**
784      * Populates the color array with pixels for the next frame.
785      */
populateImageData()786     private void populateImageData() {
787 
788         // Copy each source line to the appropriate place in the destination
789         int pass = 1;
790         int inc = 8;
791         int iline = 0;
792         for (int i = 0; i < mFrameHeight; i++) {
793             int line = i;
794             if (mInterlace) {
795                 if (iline >= mFrameHeight) {
796                     pass++;
797                     switch (pass) {
798                         case 2:
799                             iline = 4;
800                             break;
801                         case 3:
802                             iline = 2;
803                             inc = 4;
804                             break;
805                         case 4:
806                             iline = 1;
807                             inc = 2;
808                             break;
809                         default:
810                             break;
811                     }
812                 }
813                 line = iline;
814                 iline += inc;
815             }
816             line += mFrameY;
817             if (line < mIntrinsicHeight) {
818                 int k = line * mIntrinsicWidth;
819                 int dx = k + mFrameX; // start of line in dest
820                 int dlim = dx + mFrameWidth; // end of dest line
821 
822                 // It is unnecesary to test if dlim is beyond the edge of the destination line,
823                 // since mFrameWidth is clamped to a maximum of mIntrinsicWidth - mFrameX.
824 
825                 int sx = i * mFrameStep; // start of line in source
826                 while (dx < dlim) {
827                     // map color and insert in destination
828                     int index = mPixels[sx++] & 0xff;
829                     int c = mActiveColorTable[index];
830                     if (c != 0) {
831                         mColors[dx] = c;
832                     }
833                     dx++;
834                 }
835             }
836         }
837     }
838 
839     /**
840      * Reads next variable length block from input.  Called from the background thread.
841      *
842      * @return number of bytes stored in "buffer"
843      */
readBlock()844     private int readBlock() {
845         int blockSize = mData[mPosition++] & 0xff;
846         if (blockSize > 0) {
847             System.arraycopy(mData, mPosition, mBlock, 0, blockSize);
848             mPosition += blockSize;
849         }
850         return blockSize;
851     }
852 
853     /**
854      * Reads next 16-bit value, LSB first.  Called from the background thread.
855      */
readShort()856     private int readShort() {
857         // read 16-bit value, LSB first
858         int byte1 = mData[mPosition++] & 0xff;
859         int byte2 = mData[mPosition++] & 0xff;
860         return byte1 | (byte2 << 8);
861     }
862 
863     /**
864      * Skips variable length blocks up to and including next zero length block.
865      * Called from the background thread.
866      */
skip()867     private void skip() {
868         int blockSize;
869         do {
870             blockSize = mData[mPosition++] & 0xff;
871             mPosition += blockSize;
872         } while (blockSize > 0);
873     }
874 
875     @Override
handleMessage(Message msg)876     public boolean handleMessage(Message msg) {
877         if (msg.what == BaseGifDrawable.READ_FRAME_RESP) {
878             mFrameDelay = msg.arg1;
879             if (mBitmap != null) {
880                 mBitmap.setPixels(mColors, 0, mIntrinsicWidth,
881                         0, 0, mIntrinsicWidth, mIntrinsicHeight);
882                 postProcessFrame(mBitmap);
883                 mFirstFrameReady = true;
884                 mScheduled = false;
885                 invalidateSelf();
886             }
887             return true;
888         }
889 
890         return false;
891     }
892 
893     /**
894      * Gives a subclass a chance to apply changes to the mutable bitmap
895      * before showing the frame.
896      */
postProcessFrame(Bitmap bitmap)897     protected void postProcessFrame(Bitmap bitmap) {
898     }
899 
900     /**
901      * Background thread that handles reading and decoding frames of GIF images.
902      */
903     private static class DecoderThread extends HandlerThread
904             implements android.os.Handler.Callback {
905         private static final String DECODER_THREAD_NAME = "GifDecoder";
906 
DecoderThread()907         public DecoderThread() {
908             super(DECODER_THREAD_NAME);
909         }
910 
911         @Override
handleMessage(Message msg)912         public boolean handleMessage(Message msg) {
913             BaseGifDrawable gif = (BaseGifDrawable) msg.obj;
914             if (gif == null || gif.mBitmap == null || gif.mRecycled) {
915                 return true;
916             }
917 
918             switch (msg.what) {
919 
920                 case READ_FRAME_REQ:
921                     // Processed on background thread
922                     do {
923                         try {
924                             gif.readNextFrame();
925                         } catch (ArrayIndexOutOfBoundsException e) {
926                             gif.mEndOfFile = true;
927                         }
928 
929                         // Check for EOF
930                         if (gif.mEndOfFile) {
931                             if (gif.mFrameCount == 0) {
932                                 // could not read first frame
933                                 gif.mError = true;
934                             } else if (gif.mFrameCount > 1) {
935                                 if (gif.mLoopCount == 0 || ++gif.mLoopIndex < gif.mLoopCount) {
936                                     // Repeat the animation
937                                     gif.reset();
938                                 } else {
939                                     gif.mDone = true;
940                                 }
941                             } else {
942                                 // Only one frame.  Mark as done.
943                                 gif.mDone = true;
944                             }
945                         }
946                     } while (gif.mEndOfFile && !gif.mError && !gif.mDone);
947                     gif.mHandler.sendMessage(gif.mHandler.obtainMessage(READ_FRAME_RESP,
948                             gif.mNextFrameDelay, 0));
949                     return true;
950 
951                 case RESET_DECODER:
952                     gif.reset();
953                     return true;
954             }
955 
956             return false;
957         }
958     }
959 }
960