summaryrefslogtreecommitdiff
path: root/chromium/third_party/gif_player
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-12-10 16:19:40 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-12-10 16:01:50 +0000
commit51f6c2793adab2d864b3d2b360000ef8db1d3e92 (patch)
tree835b3b4446b012c75e80177cef9fbe6972cc7dbe /chromium/third_party/gif_player
parent6036726eb981b6c4b42047513b9d3f4ac865daac (diff)
downloadqtwebengine-chromium-51f6c2793adab2d864b3d2b360000ef8db1d3e92.tar.gz
BASELINE: Update Chromium to 71.0.3578.93
Change-Id: I6a32086c33670e1b033f8b10e6bf1fd4da1d105d Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/third_party/gif_player')
-rw-r--r--chromium/third_party/gif_player/OWNERS3
-rw-r--r--chromium/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifDrawable.java959
-rw-r--r--chromium/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifImage.java204
3 files changed, 1164 insertions, 2 deletions
diff --git a/chromium/third_party/gif_player/OWNERS b/chromium/third_party/gif_player/OWNERS
index aae21dcc745..d3930d9f589 100644
--- a/chromium/third_party/gif_player/OWNERS
+++ b/chromium/third_party/gif_player/OWNERS
@@ -1,2 +1 @@
-bauerb@chromium.org
-tedchoc@chromium.org \ No newline at end of file
+tedchoc@chromium.org
diff --git a/chromium/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifDrawable.java b/chromium/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifDrawable.java
new file mode 100644
index 00000000000..02112507997
--- /dev/null
+++ b/chromium/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;
+ }
+ }
+}
diff --git a/chromium/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifImage.java b/chromium/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifImage.java
new file mode 100644
index 00000000000..fae2354d9df
--- /dev/null
+++ b/chromium/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifImage.java
@@ -0,0 +1,204 @@
+/*
+ * 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 java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * A base wrapper for GIF image data.
+ */
+public class BaseGifImage {
+ private final byte[] mData;
+ private final int mOffset;
+ private int mWidth;
+ private int mHeight;
+
+ private static final byte[] sColorTableBuffer = new byte[256 * 3];
+
+ int mHeaderSize;
+ boolean mGlobalColorTableUsed;
+ boolean mError;
+ int[] mGlobalColorTable = new int[256];
+ int mGlobalColorTableSize;
+ int mBackgroundColor;
+ int mBackgroundIndex;
+
+ public BaseGifImage(byte[] data) {
+ this(data, 0);
+ }
+
+ /**
+ * Unlike the desktop JVM, ByteBuffers created with allocateDirect() can (and since froyo, do)
+ * provide a backing array, enabling zero-copy interop with native code. However, they are
+ * aligned on a byte boundary, meaning that they often have an arrayOffset as well - in those
+ * cases, we can avoid allocating large byte arrays and a copy.
+ */
+ public BaseGifImage(ByteBuffer data) {
+ this(bufferToArray(data), bufferToOffset(data));
+ }
+
+ private static int bufferToOffset(ByteBuffer buffer) {
+ return buffer.hasArray() ? buffer.arrayOffset() : 0;
+ }
+
+ private static byte[] bufferToArray(ByteBuffer buffer) {
+ if (buffer.hasArray()) {
+ return buffer.array();
+ } else {
+ int position = buffer.position();
+ try {
+ byte[] newData = new byte[buffer.capacity()];
+ buffer.get(newData);
+ return newData;
+ } finally {
+ buffer.position(position);
+ }
+ }
+ }
+
+ public BaseGifImage(byte[] data, int offset) {
+ mData = data;
+ mOffset = offset;
+
+ GifHeaderStream stream = new GifHeaderStream(data);
+ stream.skip(offset);
+ try {
+ readHeader(stream);
+ mHeaderSize = stream.getPosition();
+ } catch (IOException e) {
+ mError = true;
+ }
+
+ try {
+ stream.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+
+ public int getDataOffset() {
+ return mOffset;
+ }
+
+ public int getWidth() {
+ return mWidth;
+ }
+
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Returns an estimate of the size of the object in bytes.
+ */
+ public int getSizeEstimate() {
+ return mData.length + mGlobalColorTable.length * 4;
+ }
+
+ /**
+ * Reads GIF file header information.
+ */
+ private void readHeader(InputStream stream) throws IOException {
+ boolean valid = stream.read() == 'G';
+ valid = valid && stream.read() == 'I';
+ valid = valid && stream.read() == 'F';
+ if (!valid) {
+ mError = true;
+ return;
+ }
+
+ // Skip the next three letter, which represent the variation of the GIF standard.
+ stream.skip(3);
+
+ readLogicalScreenDescriptor(stream);
+
+ if (mGlobalColorTableUsed && !mError) {
+ readColorTable(stream, mGlobalColorTable, mGlobalColorTableSize);
+ mBackgroundColor = mGlobalColorTable[mBackgroundIndex];
+ }
+ }
+
+ /**
+ * Reads Logical Screen Descriptor
+ */
+ private void readLogicalScreenDescriptor(InputStream stream) throws IOException {
+ // logical screen size
+ mWidth = readShort(stream);
+ mHeight = readShort(stream);
+ // packed fields
+ int packed = stream.read();
+ mGlobalColorTableUsed = (packed & 0x80) != 0; // 1 : global color table flag
+ // 2-4 : color resolution - ignore
+ // 5 : gct sort flag - ignore
+ mGlobalColorTableSize = 2 << (packed & 7); // 6-8 : gct size
+ mBackgroundIndex = stream.read();
+ stream.skip(1); // pixel aspect ratio - ignore
+ }
+
+ /**
+ * Reads color table as 256 RGB integer values
+ *
+ * @param ncolors int number of colors to read
+ */
+ static boolean readColorTable(InputStream stream, int[] colorTable, int ncolors)
+ throws IOException {
+ synchronized (sColorTableBuffer) {
+ int nbytes = 3 * ncolors;
+ int n = stream.read(sColorTableBuffer, 0, nbytes);
+ if (n < nbytes) {
+ return false;
+ } else {
+ int i = 0;
+ int j = 0;
+ while (i < ncolors) {
+ int r = sColorTableBuffer[j++] & 0xff;
+ int g = sColorTableBuffer[j++] & 0xff;
+ int b = sColorTableBuffer[j++] & 0xff;
+ colorTable[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Reads next 16-bit value, LSB first
+ */
+ private int readShort(InputStream stream) throws IOException {
+ // read 16-bit value, LSB first
+ return stream.read() | (stream.read() << 8);
+ }
+
+ private final class GifHeaderStream extends ByteArrayInputStream {
+
+ private GifHeaderStream(byte[] buf) {
+ super(buf);
+ }
+
+ public int getPosition() {
+ return pos;
+ }
+ }
+}