/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifDrawable.java
Java | 959 lines | 679 code | 133 blank | 147 comment | 149 complexity | 8b8808054b9e0b214270ca5c935eb1cc MD5 | raw file
- /*
- * Copyright (C) 2015 The Gifplayer Authors. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package jp.tomorrowkey.android.gifplayer;
- import android.graphics.Bitmap;
- import android.graphics.Canvas;
- import android.graphics.ColorFilter;
- import android.graphics.Paint;
- import android.graphics.PixelFormat;
- import android.graphics.Rect;
- import android.graphics.drawable.Animatable;
- import android.graphics.drawable.Drawable;
- import android.os.Handler;
- import android.os.HandlerThread;
- import android.os.Looper;
- import android.os.Message;
- import android.os.SystemClock;
- import android.util.Log;
- /**
- * A base GIF Drawable with support for animations.
- *
- * Inspired by http://code.google.com/p/android-gifview/
- */
- public class BaseGifDrawable extends Drawable implements Runnable, Animatable,
- android.os.Handler.Callback {
- private static final String TAG = "GifDrawable";
- // Max decoder pixel stack size
- private static final int MAX_STACK_SIZE = 4096;
- private static final int MAX_BITS = 4097;
- // Frame disposal methods
- private static final int DISPOSAL_METHOD_UNKNOWN = 0;
- private static final int DISPOSAL_METHOD_LEAVE = 1;
- private static final int DISPOSAL_METHOD_BACKGROUND = 2;
- private static final int DISPOSAL_METHOD_RESTORE = 3;
- // Message types
- private static final int READ_FRAME_REQ = 10;
- private static final int READ_FRAME_RESP = 11;
- private static final int RESET_DECODER = 12;
- // Specifies the minimum amount of time before a subsequent frame will be rendered.
- private static final int MIN_FRAME_SCHEDULE_DELAY_MS = 5;
- private static final byte[] NETSCAPE2_0 = "NETSCAPE2.0".getBytes();
- private static Paint sPaint;
- private static Paint sScalePaint;
- protected final BaseGifImage mGifImage;
- private final byte[] mData;
- private int mPosition;
- protected int mIntrinsicWidth;
- protected int mIntrinsicHeight;
- private int mWidth;
- private int mHeight;
- protected Bitmap mBitmap;
- protected int[] mColors;
- private boolean mScale;
- private float mScaleFactor;
- // The following are marked volatile because they are read/written in the background decoder
- // thread and read from the UI thread. No further synchronization is needed because their
- // values will only ever change from at most once, and it is safe to lazily detect the change
- // in the UI thread.
- private volatile boolean mError;
- private volatile boolean mDone;
- private volatile boolean mAnimateOnLoad = true;
- private int mBackgroundColor;
- private boolean mLocalColorTableUsed;
- private int mLocalColorTableSize;
- private int[] mLocalColorTable;
- private int[] mActiveColorTable;
- private boolean mInterlace;
- // Each frame specifies a sub-region of the image that should be updated. The values are
- // clamped to the GIF dimensions if they exceed the intrinsic dimensions.
- private int mFrameX, mFrameY, mFrameWidth, mFrameHeight;
- // This specifies the width of the actual data within a GIF frame. It will be equal to
- // mFrameWidth unless the frame sub-region was clamped to prevent exceeding the intrinsic
- // dimensions.
- private int mFrameStep;
- private byte[] mBlock = new byte[256];
- private int mDisposalMethod = DISPOSAL_METHOD_BACKGROUND;
- private boolean mTransparency;
- private int mTransparentColorIndex;
- // LZW decoder working arrays
- private short[] mPrefix = new short[MAX_STACK_SIZE];
- private byte[] mSuffix = new byte[MAX_STACK_SIZE];
- private byte[] mPixelStack = new byte[MAX_STACK_SIZE + 1];
- private byte[] mPixels;
- private boolean mBackupSaved;
- private int[] mBackup;
- private int mFrameCount;
- private long mLastFrameTime;
- private boolean mRunning;
- protected int mFrameDelay;
- private int mNextFrameDelay;
- protected boolean mScheduled;
- private boolean mAnimationEnabled = true;
- private final Handler mHandler = new Handler(Looper.getMainLooper(), this);
- private static DecoderThread sDecoderThread;
- private static Handler sDecoderHandler;
- private boolean mRecycled;
- protected boolean mFirstFrameReady;
- private boolean mEndOfFile;
- private int mLoopCount = 0; // 0 to repeat endlessly.
- private int mLoopIndex = 0;
- private final Bitmap.Config mBitmapConfig;
- private boolean mFirstFrame = true;
- public BaseGifDrawable(BaseGifImage gifImage, Bitmap.Config bitmapConfig) {
- this.mBitmapConfig = bitmapConfig;
- // Create the background decoder thread, if necessary.
- if (sDecoderThread == null) {
- sDecoderThread = new DecoderThread();
- sDecoderThread.start();
- sDecoderHandler = new Handler(sDecoderThread.getLooper(), sDecoderThread);
- }
- if (sPaint == null) {
- sPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
- sScalePaint = new Paint(Paint.FILTER_BITMAP_FLAG);
- sScalePaint.setFilterBitmap(true);
- }
- mGifImage = gifImage;
- mData = gifImage.getData();
- mPosition = mGifImage.mHeaderSize;
- mFrameWidth = mFrameStep = mIntrinsicWidth = gifImage.getWidth();
- mFrameHeight = mIntrinsicHeight = gifImage.getHeight();
- mBackgroundColor = mGifImage.mBackgroundColor;
- mError = mGifImage.mError;
- if (!mError) {
- try {
- mBitmap = Bitmap.createBitmap(mIntrinsicWidth, mIntrinsicHeight, mBitmapConfig);
- if (mBitmap == null) {
- throw new OutOfMemoryError("Cannot allocate bitmap");
- }
- int pixelCount = mIntrinsicWidth * mIntrinsicHeight;
- mColors = new int[pixelCount];
- mPixels = new byte[pixelCount];
- mWidth = mIntrinsicHeight;
- mHeight = mIntrinsicHeight;
- // Read the first frame
- sDecoderHandler.sendMessage(sDecoderHandler.obtainMessage(READ_FRAME_REQ, this));
- } catch (OutOfMemoryError e) {
- mError = true;
- }
- }
- }
- /**
- * Sets the loop count for multi-frame animation.
- */
- public void setLoopCount(int loopCount) {
- mLoopCount = loopCount;
- }
- /**
- * Returns the loop count for multi-frame animation.
- */
- public int getLoopCount() {
- return mLoopCount;
- }
- /**
- * Sets whether to start animation on load or not.
- */
- public void setAnimateOnLoad(boolean animateOnLoad) {
- mAnimateOnLoad = animateOnLoad;
- }
- /**
- * Returns {@code true} if the GIF is valid and {@code false} otherwise.
- */
- public boolean isValid() {
- return !mError && mFirstFrameReady;
- }
- public void onRecycle() {
- if (mBitmap != null) {
- mBitmap.recycle();
- }
- mBitmap = null;
- mRecycled = true;
- }
- /**
- * Enables or disables the GIF from animating. GIF animations are enabled by default.
- */
- public void setAnimationEnabled(boolean animationEnabled) {
- if (mAnimationEnabled == animationEnabled) {
- return;
- }
- mAnimationEnabled = animationEnabled;
- if (mAnimationEnabled) {
- start();
- } else {
- stop();
- }
- }
- @Override
- protected void onBoundsChange(Rect bounds) {
- super.onBoundsChange(bounds);
- mWidth = bounds.width();
- mHeight = bounds.height();
- mScale = mWidth != mIntrinsicWidth && mHeight != mIntrinsicHeight;
- if (mScale) {
- mScaleFactor = Math.max((float) mWidth / mIntrinsicWidth,
- (float) mHeight / mIntrinsicHeight);
- }
- if (!mError && !mRecycled) {
- // Request that the decoder reset itself
- sDecoderHandler.sendMessage(sDecoderHandler.obtainMessage(RESET_DECODER, this));
- }
- }
- @Override
- public boolean setVisible(boolean visible, boolean restart) {
- boolean changed = super.setVisible(visible, restart);
- if (visible) {
- if (changed || restart) {
- start();
- }
- } else {
- stop();
- }
- return changed;
- }
- @Override
- public void draw(Canvas canvas) {
- if (mError || mWidth == 0 || mHeight == 0 || mRecycled || !mFirstFrameReady) {
- return;
- }
- if (mScale) {
- canvas.save();
- canvas.scale(mScaleFactor, mScaleFactor, 0, 0);
- canvas.drawBitmap(mBitmap, 0, 0, sScalePaint);
- canvas.restore();
- } else {
- canvas.drawBitmap(mBitmap, 0, 0, sPaint);
- }
- if (mRunning) {
- if (!mScheduled) {
- // Schedule the next frame at mFrameDelay milliseconds from the previous frame or
- // the minimum sceduling delay from now, whichever is later.
- mLastFrameTime = Math.max(
- mLastFrameTime + mFrameDelay,
- SystemClock.uptimeMillis() + MIN_FRAME_SCHEDULE_DELAY_MS);
- scheduleSelf(this, mLastFrameTime);
- }
- } else if (!mDone) {
- start();
- } else {
- unscheduleSelf(this);
- }
- }
- @Override
- public int getIntrinsicWidth() {
- return mIntrinsicWidth;
- }
- @Override
- public int getIntrinsicHeight() {
- return mIntrinsicHeight;
- }
- @Override
- public int getOpacity() {
- return PixelFormat.UNKNOWN;
- }
- @Override
- public void setAlpha(int alpha) {
- }
- @Override
- public void setColorFilter(ColorFilter cf) {
- }
- @Override
- public boolean isRunning() {
- return mRunning;
- }
- @Override
- public void start() {
- if (!isRunning()) {
- mRunning = true;
- if (!mAnimateOnLoad) {
- mDone = true;
- }
- mLastFrameTime = SystemClock.uptimeMillis();
- run();
- }
- }
- @Override
- public void stop() {
- if (isRunning()) {
- unscheduleSelf(this);
- }
- }
- @Override
- public void scheduleSelf(Runnable what, long when) {
- if (mAnimationEnabled) {
- super.scheduleSelf(what, when);
- mScheduled = true;
- }
- }
- @Override
- public void unscheduleSelf(Runnable what) {
- super.unscheduleSelf(what);
- mRunning = false;
- }
- /**
- * Moves to the next frame.
- */
- @Override
- public void run() {
- if (mRecycled) {
- return;
- }
- // Send request to decoder to read the next frame
- if (!mDone) {
- sDecoderHandler.sendMessage(sDecoderHandler.obtainMessage(READ_FRAME_REQ, this));
- }
- }
- /**
- * Restarts decoding the image from the beginning. Called from the background thread.
- */
- private void reset() {
- // Return to the position of the first image frame in the stream.
- mPosition = mGifImage.mHeaderSize;
- mBackupSaved = false;
- mFrameCount = 0;
- mDisposalMethod = DISPOSAL_METHOD_UNKNOWN;
- }
- /**
- * Restarts animation if a limited number of loops of animation have been previously done.
- */
- public void restartAnimation() {
- if (mDone && mLoopCount > 0) {
- reset();
- mDone = false;
- mLoopIndex = 0;
- run();
- }
- }
- /**
- * Reads color table as 256 RGB integer values. Called from the background thread.
- *
- * @param ncolors int number of colors to read
- */
- private void readColorTable(int[] colorTable, int ncolors) {
- for (int i = 0; i < ncolors; i++) {
- int r = mData[mPosition++] & 0xff;
- int g = mData[mPosition++] & 0xff;
- int b = mData[mPosition++] & 0xff;
- colorTable[i] = 0xff000000 | (r << 16) | (g << 8) | b;
- }
- }
- /**
- * Reads GIF content blocks. Called from the background thread.
- *
- * @return true if the next frame has been parsed successfully, false if EOF
- * has been reached
- */
- private void readNextFrame() {
- // Don't clear the image if it is a terminator.
- if ((mData[mPosition] & 0xff) == 0x3b) {
- mEndOfFile = true;
- return;
- }
- disposeOfLastFrame();
- mDisposalMethod = DISPOSAL_METHOD_UNKNOWN;
- mTransparency = false;
- mEndOfFile = false;
- mNextFrameDelay = 100;
- mLocalColorTable = null;
- while (true) {
- int code = mData[mPosition++] & 0xff;
- switch (code) {
- case 0: // Empty block, ignore
- break;
- case 0x21: // Extension. Extensions precede the corresponding image.
- code = mData[mPosition++] & 0xff;
- switch (code) {
- case 0xf9: // graphics control extension
- readGraphicControlExt();
- break;
- case 0xff: // application extension
- readBlock();
- boolean netscape = true;
- for (int i = 0; i < NETSCAPE2_0.length; i++) {
- if (mBlock[i] != NETSCAPE2_0[i]) {
- netscape = false;
- break;
- }
- }
- if (netscape) {
- readNetscapeExtension();
- } else {
- skip(); // don't care
- }
- break;
- case 0xfe:// comment extension
- skip();
- break;
- case 0x01:// plain text extension
- skip();
- break;
- default: // uninteresting extension
- skip();
- }
- break;
- case 0x2C: // Image separator
- readBitmap();
- return;
- case 0x3b: // Terminator
- mEndOfFile = true;
- return;
- default: // We don't know what this is. Just skip it.
- break;
- }
- }
- }
- /**
- * Disposes of the previous frame. Called from the background thread.
- */
- private void disposeOfLastFrame() {
- if (mFirstFrame) {
- mFirstFrame = false;
- return;
- }
- switch (mDisposalMethod) {
- case DISPOSAL_METHOD_UNKNOWN:
- case DISPOSAL_METHOD_LEAVE: {
- mBackupSaved = false;
- break;
- }
- case DISPOSAL_METHOD_RESTORE: {
- if (mBackupSaved) {
- System.arraycopy(mBackup, 0, mColors, 0, mBackup.length);
- }
- break;
- }
- case DISPOSAL_METHOD_BACKGROUND: {
- mBackupSaved = false;
- // Fill last image rect area with background color
- int color = 0;
- if (!mTransparency) {
- color = mBackgroundColor;
- }
- for (int i = 0; i < mFrameHeight; i++) {
- int n1 = (mFrameY + i) * mIntrinsicWidth + mFrameX;
- int n2 = n1 + mFrameWidth;
- for (int k = n1; k < n2; k++) {
- mColors[k] = color;
- }
- }
- break;
- }
- }
- }
- /**
- * Reads Graphics Control Extension values. Called from the background thread.
- */
- private void readGraphicControlExt() {
- mPosition++; // Block size, fixed
- int packed = mData[mPosition++] & 0xff; // Packed fields
- mDisposalMethod = (packed & 0x1c) >> 2; // Disposal method
- mTransparency = (packed & 1) != 0;
- mNextFrameDelay = readShort() * 10; // Delay in milliseconds
- // It seems that there are broken tools out there that set a 0ms or 10ms
- // timeout when they really want a "default" one.
- // Following WebKit's lead (http://trac.webkit.org/changeset/73295)
- // we use 10 frames per second as the default frame rate.
- if (mNextFrameDelay <= 10) {
- mNextFrameDelay = 100;
- }
- mTransparentColorIndex = mData[mPosition++] & 0xff;
- mPosition++; // Block terminator - ignore
- }
- /**
- * Reads Netscape extension to obtain iteration count. Called from the background thread.
- */
- private void readNetscapeExtension() {
- int count;
- do {
- count = readBlock();
- } while ((count > 0) && !mError);
- }
- /**
- * Reads next frame image. Called from the background thread.
- */
- private void readBitmap() {
- mFrameX = readShort(); // (sub)image position & size
- mFrameY = readShort();
- int width = readShort();
- int height = readShort();
- // Clamp the frame dimensions to the intrinsic dimensions.
- mFrameWidth = Math.min(width, mIntrinsicWidth - mFrameX);
- mFrameHeight = Math.min(height, mIntrinsicHeight - mFrameY);
- // The frame step is set to the specfied frame width before clamping.
- mFrameStep = width;
- // Increase the size of the decoding buffer if necessary.
- int framePixelCount = width * height;
- if (framePixelCount > mPixels.length) {
- mPixels = new byte[framePixelCount];
- }
- int packed = mData[mPosition++] & 0xff;
- // 3 - sort flag
- // 4-5 - reserved lctSize = 2 << (packed & 7);
- // 6-8 - local color table size
- mInterlace = (packed & 0x40) != 0;
- mLocalColorTableUsed = (packed & 0x80) != 0; // 1 - local color table flag interlace
- mLocalColorTableSize = (int) Math.pow(2, (packed & 0x07) + 1);
- if (mLocalColorTableUsed) {
- if (mLocalColorTable == null) {
- mLocalColorTable = new int[256];
- }
- readColorTable(mLocalColorTable, mLocalColorTableSize);
- mActiveColorTable = mLocalColorTable;
- } else {
- mActiveColorTable = mGifImage.mGlobalColorTable;
- if (mGifImage.mBackgroundIndex == mTransparentColorIndex) {
- mBackgroundColor = 0;
- }
- }
- int savedColor = 0;
- if (mTransparency) {
- savedColor = mActiveColorTable[mTransparentColorIndex];
- mActiveColorTable[mTransparentColorIndex] = 0;
- }
- if (mActiveColorTable == null) {
- mError = true;
- }
- if (mError) {
- return;
- }
- decodeBitmapData();
- skip();
- if (mError) {
- return;
- }
- if (mDisposalMethod == DISPOSAL_METHOD_RESTORE) {
- backupFrame();
- }
- populateImageData();
- if (mTransparency) {
- mActiveColorTable[mTransparentColorIndex] = savedColor;
- }
- mFrameCount++;
- }
- /**
- * Stores the relevant portion of the current frame so that it can be restored
- * before the next frame is rendered. Called from the background thread.
- */
- private void backupFrame() {
- if (mBackupSaved) {
- return;
- }
- if (mBackup == null) {
- mBackup = null;
- try {
- mBackup = new int[mColors.length];
- } catch (OutOfMemoryError e) {
- Log.e(TAG, "GifDrawable.backupFrame threw an OOME", e);
- }
- }
- if (mBackup != null) {
- System.arraycopy(mColors, 0, mBackup, 0, mColors.length);
- mBackupSaved = true;
- }
- }
- /**
- * Decodes LZW image data into pixel array. Called from the background thread.
- */
- private void decodeBitmapData() {
- int npix = mFrameWidth * mFrameHeight;
- // Initialize GIF data stream decoder.
- int dataSize = mData[mPosition++] & 0xff;
- int clear = 1 << dataSize;
- int endOfInformation = clear + 1;
- int available = clear + 2;
- int oldCode = -1;
- int codeSize = dataSize + 1;
- int codeMask = (1 << codeSize) - 1;
- for (int code = 0; code < clear; code++) {
- mPrefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
- mSuffix[code] = (byte) code;
- }
- // Decode GIF pixel stream.
- int datum = 0;
- int bits = 0;
- int first = 0;
- int top = 0;
- int pi = 0;
- while (pi < npix) {
- int blockSize = mData[mPosition++] & 0xff;
- if (blockSize == 0) {
- break;
- }
- int blockEnd = mPosition + blockSize;
- while (mPosition < blockEnd) {
- datum += (mData[mPosition++] & 0xff) << bits;
- bits += 8;
- while (bits >= codeSize) {
- // Get the next code.
- int code = datum & codeMask;
- datum >>= codeSize;
- bits -= codeSize;
- // Interpret the code
- if (code == clear) {
- // Reset decoder.
- codeSize = dataSize + 1;
- codeMask = (1 << codeSize) - 1;
- available = clear + 2;
- oldCode = -1;
- continue;
- }
- // Check for explicit end-of-stream
- if (code == endOfInformation) {
- mPosition = blockEnd;
- return;
- }
- if (oldCode == -1) {
- mPixels[pi++] = mSuffix[code];
- oldCode = code;
- first = code;
- continue;
- }
- int inCode = code;
- if (code >= available) {
- mPixelStack[top++] = (byte) first;
- code = oldCode;
- if (top == MAX_BITS) {
- mError = true;
- return;
- }
- }
- while (code >= clear) {
- if (code >= MAX_BITS || code == mPrefix[code]) {
- mError = true;
- return;
- }
- mPixelStack[top++] = mSuffix[code];
- code = mPrefix[code];
- if (top == MAX_BITS) {
- mError = true;
- return;
- }
- }
- first = mSuffix[code];
- mPixelStack[top++] = (byte) first;
- // Add new code to the dictionary
- if (available < MAX_STACK_SIZE) {
- mPrefix[available] = (short) oldCode;
- mSuffix[available] = (byte) first;
- available++;
- if (((available & codeMask) == 0) && (available < MAX_STACK_SIZE)) {
- codeSize++;
- codeMask += available;
- }
- }
- oldCode = inCode;
- // Drain the pixel stack.
- do {
- mPixels[pi++] = mPixelStack[--top];
- } while (top > 0);
- }
- }
- }
- while (pi < npix) {
- mPixels[pi++] = 0; // clear missing pixels
- }
- }
- /**
- * Populates the color array with pixels for the next frame.
- */
- private void populateImageData() {
- // Copy each source line to the appropriate place in the destination
- int pass = 1;
- int inc = 8;
- int iline = 0;
- for (int i = 0; i < mFrameHeight; i++) {
- int line = i;
- if (mInterlace) {
- if (iline >= mFrameHeight) {
- pass++;
- switch (pass) {
- case 2:
- iline = 4;
- break;
- case 3:
- iline = 2;
- inc = 4;
- break;
- case 4:
- iline = 1;
- inc = 2;
- break;
- default:
- break;
- }
- }
- line = iline;
- iline += inc;
- }
- line += mFrameY;
- if (line < mIntrinsicHeight) {
- int k = line * mIntrinsicWidth;
- int dx = k + mFrameX; // start of line in dest
- int dlim = dx + mFrameWidth; // end of dest line
- // It is unnecesary to test if dlim is beyond the edge of the destination line,
- // since mFrameWidth is clamped to a maximum of mIntrinsicWidth - mFrameX.
- int sx = i * mFrameStep; // start of line in source
- while (dx < dlim) {
- // map color and insert in destination
- int index = mPixels[sx++] & 0xff;
- int c = mActiveColorTable[index];
- if (c != 0) {
- mColors[dx] = c;
- }
- dx++;
- }
- }
- }
- }
- /**
- * Reads next variable length block from input. Called from the background thread.
- *
- * @return number of bytes stored in "buffer"
- */
- private int readBlock() {
- int blockSize = mData[mPosition++] & 0xff;
- if (blockSize > 0) {
- System.arraycopy(mData, mPosition, mBlock, 0, blockSize);
- mPosition += blockSize;
- }
- return blockSize;
- }
- /**
- * Reads next 16-bit value, LSB first. Called from the background thread.
- */
- private int readShort() {
- // read 16-bit value, LSB first
- int byte1 = mData[mPosition++] & 0xff;
- int byte2 = mData[mPosition++] & 0xff;
- return byte1 | (byte2 << 8);
- }
- /**
- * Skips variable length blocks up to and including next zero length block.
- * Called from the background thread.
- */
- private void skip() {
- int blockSize;
- do {
- blockSize = mData[mPosition++] & 0xff;
- mPosition += blockSize;
- } while (blockSize > 0);
- }
- @Override
- public boolean handleMessage(Message msg) {
- if (msg.what == BaseGifDrawable.READ_FRAME_RESP) {
- mFrameDelay = msg.arg1;
- if (mBitmap != null) {
- mBitmap.setPixels(mColors, 0, mIntrinsicWidth,
- 0, 0, mIntrinsicWidth, mIntrinsicHeight);
- postProcessFrame(mBitmap);
- mFirstFrameReady = true;
- mScheduled = false;
- invalidateSelf();
- }
- return true;
- }
- return false;
- }
- /**
- * Gives a subclass a chance to apply changes to the mutable bitmap
- * before showing the frame.
- */
- protected void postProcessFrame(Bitmap bitmap) {
- }
- /**
- * Background thread that handles reading and decoding frames of GIF images.
- */
- private static class DecoderThread extends HandlerThread
- implements android.os.Handler.Callback {
- private static final String DECODER_THREAD_NAME = "GifDecoder";
- public DecoderThread() {
- super(DECODER_THREAD_NAME);
- }
- @Override
- public boolean handleMessage(Message msg) {
- BaseGifDrawable gif = (BaseGifDrawable) msg.obj;
- if (gif == null || gif.mBitmap == null || gif.mRecycled) {
- return true;
- }
- switch (msg.what) {
- case READ_FRAME_REQ:
- // Processed on background thread
- do {
- try {
- gif.readNextFrame();
- } catch (ArrayIndexOutOfBoundsException e) {
- gif.mEndOfFile = true;
- }
- // Check for EOF
- if (gif.mEndOfFile) {
- if (gif.mFrameCount == 0) {
- // could not read first frame
- gif.mError = true;
- } else if (gif.mFrameCount > 1) {
- if (gif.mLoopCount == 0 || ++gif.mLoopIndex < gif.mLoopCount) {
- // Repeat the animation
- gif.reset();
- } else {
- gif.mDone = true;
- }
- } else {
- // Only one frame. Mark as done.
- gif.mDone = true;
- }
- }
- } while (gif.mEndOfFile && !gif.mError && !gif.mDone);
- gif.mHandler.sendMessage(gif.mHandler.obtainMessage(READ_FRAME_RESP,
- gif.mNextFrameDelay, 0));
- return true;
- case RESET_DECODER:
- gif.reset();
- return true;
- }
- return false;
- }
- }
- }