diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-12-10 16:19:40 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-12-10 16:01:50 +0000 |
commit | 51f6c2793adab2d864b3d2b360000ef8db1d3e92 (patch) | |
tree | 835b3b4446b012c75e80177cef9fbe6972cc7dbe /chromium/third_party/gif_player | |
parent | 6036726eb981b6c4b42047513b9d3f4ac865daac (diff) | |
download | qtwebengine-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')
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; + } + } +} |