Index: third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifDrawable.java |
diff --git a/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifDrawable.java b/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifDrawable.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..02112507997c4d23ca687dfd465e05a0e25aa1aa |
--- /dev/null |
+++ b/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifDrawable.java |
@@ -0,0 +1,959 @@ |
+/* |
+ * 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; |
+ } |
+ } |
+} |