summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/util/GifDecoder.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/util/GifDecoder.java')
-rwxr-xr-xsrc/com/android/gallery3d/util/GifDecoder.java723
1 files changed, 723 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/util/GifDecoder.java b/src/com/android/gallery3d/util/GifDecoder.java
new file mode 100755
index 000000000..4235fc5a5
--- /dev/null
+++ b/src/com/android/gallery3d/util/GifDecoder.java
@@ -0,0 +1,723 @@
+package com.android.gallery3d.util;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+public class GifDecoder extends Thread {
+
+ public static final int STATUS_PARSING = 0;
+ public static final int STATUS_FORMAT_ERROR = 1;
+ public static final int STATUS_OPEN_ERROR = 2;
+ public static final int STATUS_FINISH = -1;
+
+ private InputStream mIS;
+ private int mStatus;
+
+ public int mWidth; // full image width
+ public int mHeight; // full image height
+ private boolean mGctFlag; // global color table used
+ private int mGctSize; // size of global color table
+ private int mLoopCount = 1; // iterations; 0 = repeat forever
+
+ private int[] mGct; // global color table
+ private int[] mLct; // local color table
+ private int[] mAct; // active color table
+
+ private int mBgIndex; // background color index
+ private int mBgColor; // background color
+ private int mLastBgColor; // previous bg color
+ private int mPixelAspect; // pixel aspect ratio
+
+ private boolean mLctFlag; // local color table flag
+ private boolean mInterlace; // interlace flag
+ private int mLctSize; // local color table size
+
+ private int mIx, mIy, mIw, mIh; // current image rectangle
+ private int mLrx, mLry, mLrw, mLrh;
+ private Bitmap mImage; // current frame
+ private Bitmap mLastImage; // previous frame
+ private GifFrame mCurrentFrame = null;
+
+ private boolean mIsShow = false;
+
+ private byte[] mBlock = new byte[256]; // current data block
+ private int mBlockSize = 0; // block size
+ private int mDispose = 0;
+ private int mLastDispose = 0;
+ private boolean mTransparency = false; // use transparent color
+ private int mDelay = 0; // delay in milliseconds
+ private int mTransIndex; // transparent color index
+
+ // max decoder pixel stack size
+ private static final int MaxStackSize = 4096;
+
+ // LZW decoder working arrays
+ private short[] mPrefix;
+ private byte[] mSuffix;
+ private byte[] mPixelStack;
+ private byte[] mPixels;
+
+ private GifFrame mGifFrame; // frames read from current file
+ private int mFrameCount;
+
+ private GifAction mGifAction = null;
+
+ private byte[] mGifData = null;
+
+ public GifDecoder(byte[] data, GifAction act) {
+ mGifData = data;
+ mGifAction = act;
+ }
+
+ public GifDecoder(InputStream is, GifAction act) {
+ mIS = is;
+ mGifAction = act;
+ }
+
+ public void run() {
+ if (mIS != null) {
+ readStream();
+ } else if (mGifData != null) {
+ readByte();
+ }
+ }
+
+ public void free() {
+ freeFrame();
+ freeIS();
+ freeImage();
+ }
+
+ public int getStatus() {
+ return mStatus;
+ }
+
+ public boolean parseOk() {
+ return mStatus == STATUS_FINISH;
+ }
+
+ public int getDelay(int n) {
+ mDelay = -1;
+ if ((n >= 0) && (n < mFrameCount)) {
+ GifFrame f = getFrame(n);
+ if (f != null) {
+ mDelay = f.mDelayInMs;
+ }
+ }
+ return mDelay;
+ }
+
+ public int[] getDelays() {
+ GifFrame f = mGifFrame;
+ int[] d = new int[mFrameCount];
+ int i = 0;
+ while (f != null && i < mFrameCount) {
+ d[i] = f.mDelayInMs;
+ f = f.mNextFrame;
+ i++;
+ }
+ return d;
+ }
+
+ public int getFrameCount() {
+ return mFrameCount;
+ }
+
+ public Bitmap getImage() {
+ return getFrameImage(0);
+ }
+
+ public int getLoopCount() {
+ return mLoopCount;
+ }
+
+ private void setPixels() {
+ int[] dest = new int[mWidth * mHeight];
+ // fill in starting image contents based on last image's dispose code
+ if (mLastDispose > 0) {
+ if (mLastDispose == 3) {
+ // use image before last
+ int n = mFrameCount - 2;
+ if (n > 0) {
+ mLastImage = getPreUndisposedImage(n - 1);
+ } else {
+ mLastImage = null;
+ }
+ }
+ if (mLastImage != null) {
+ mLastImage.getPixels(dest, 0, mWidth, 0, 0, mWidth, mHeight);
+ // copy pixels
+ if (mLastDispose == 2) {
+ // fill last image rect area with background color
+ int c = 0;
+ if (!mTransparency) {
+ c = mLastBgColor;
+ }
+ for (int i = 0; i < mLrh; i++) {
+ int n1 = (mLry + i) * mWidth + mLrx;
+ int n2 = n1 + mLrw;
+ for (int k = n1; k < n2; k++) {
+ dest[k] = c;
+ }
+ }
+ }
+ }
+ }
+
+ // 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 < mIh; i++) {
+ int line = i;
+ if (mInterlace) {
+ if (iline >= mIh) {
+ pass++;
+ switch (pass) {
+ case 2:
+ iline = 4;
+ break;
+ case 3:
+ iline = 2;
+ inc = 4;
+ break;
+ case 4:
+ iline = 1;
+ inc = 2;
+ }
+ }
+ line = iline;
+ iline += inc;
+ }
+ line += mIy;
+ if (line < mHeight) {
+ int k = line * mWidth;
+ int dx = k + mIx; // start of line in dest
+ int dlim = dx + mIw; // end of dest line
+ if ((k + mWidth) < dlim) {
+ dlim = k + mWidth; // past dest edge
+ }
+ int sx = i * mIw; // start of line in source
+ while (dx < dlim) {
+ // map color and insert in destination
+ int index = ((int) mPixels[sx++]) & 0xff;
+ int c = mAct[index];
+ if (c != 0) {
+ dest[dx] = c;
+ }
+ dx++;
+ }
+ }
+ }
+ mImage = Bitmap.createBitmap(dest, mWidth, mHeight, Config.ARGB_4444);
+ }
+
+ public Bitmap getFrameImage(int n) {
+ GifFrame frame = getFrame(n);
+ if (frame == null) {
+ return null;
+ } else {
+ return frame.mImage;
+ }
+ }
+
+ public GifFrame getCurrentFrame() {
+ return mCurrentFrame;
+ }
+
+ public GifFrame getFrame(int n) {
+ GifFrame frame = mGifFrame;
+ int i = 0;
+ while (frame != null) {
+ if (i == n) {
+ return frame;
+ } else {
+ frame = frame.mNextFrame;
+ }
+ i++;
+ }
+ return null;
+ }
+
+ private Bitmap getPreUndisposedImage(int n) {
+ Bitmap preUndisposedImage = null;
+ GifFrame frame = mGifFrame;
+ int i = 0;
+ while (frame != null && i <= n) {
+ if (frame.mDispose == 1) {
+ preUndisposedImage = frame.mImage;
+ } else {
+ frame = frame.mNextFrame;
+ }
+ i++;
+ }
+ return preUndisposedImage;
+ }
+
+ public void reset() {
+ mCurrentFrame = mGifFrame;
+ }
+
+ public GifFrame next() {
+ if (mIsShow == false) {
+ mIsShow = true;
+ return mGifFrame;
+ } else {
+ if (mStatus == STATUS_PARSING) {
+ if (mCurrentFrame.mNextFrame != null) {
+ mCurrentFrame = mCurrentFrame.mNextFrame;
+ }
+ } else {
+ mCurrentFrame = mCurrentFrame.mNextFrame;
+ if (mCurrentFrame == null) {
+ mCurrentFrame = mGifFrame;
+ }
+ }
+ return mCurrentFrame;
+ }
+ }
+
+ private int readByte() {
+ mIS = new ByteArrayInputStream(mGifData);
+ mGifData = null;
+ return readStream();
+ }
+
+ private int readStream() {
+ init();
+ if (mIS != null) {
+ readHeader();
+ if (!err()) {
+ readContents();
+ if (mFrameCount < 0) {
+ mStatus = STATUS_FORMAT_ERROR;
+ mGifAction.parseOk(false, -1);
+ } else {
+ mStatus = STATUS_FINISH;
+ mGifAction.parseOk(true, -1);
+ }
+ }
+ try {
+ mIS.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ mStatus = STATUS_OPEN_ERROR;
+ mGifAction.parseOk(false, -1);
+ }
+ return mStatus;
+ }
+
+ private void decodeImageData() {
+ int NullCode = -1;
+ int npix = mIw * mIh;
+ int available, clear, code_mask, code_size, end_of_information, in_code, old_code,
+ bits, code, count, i, datum, data_size, first, top, bi, pi;
+
+ if ((mPixels == null) || (mPixels.length < npix)) {
+ mPixels = new byte[npix]; // allocate new pixel array
+ }
+ if (mPrefix == null) {
+ mPrefix = new short[MaxStackSize];
+ }
+ if (mSuffix == null) {
+ mSuffix = new byte[MaxStackSize];
+ }
+ if (mPixelStack == null) {
+ mPixelStack = new byte[MaxStackSize + 1];
+ }
+ // Initialize GIF data stream decoder.
+ data_size = read();
+ clear = 1 << data_size;
+ end_of_information = clear + 1;
+ available = clear + 2;
+ old_code = NullCode;
+ code_size = data_size + 1;
+ code_mask = (1 << code_size) - 1;
+ for (code = 0; code < clear; code++) {
+ mPrefix[code] = 0;
+ mSuffix[code] = (byte) code;
+ }
+
+ // Decode GIF pixel stream.
+ datum = bits = count = first = top = pi = bi = 0;
+ for (i = 0; i < npix;) {
+ if (top == 0) {
+ if (bits < code_size) {
+ // Load bytes until there are enough bits for a code.
+ if (count == 0) {
+ // Read a new data block.
+ count = readBlock();
+ if (count <= 0) {
+ break;
+ }
+ bi = 0;
+ }
+ datum += (((int) mBlock[bi]) & 0xff) << bits;
+ bits += 8;
+ bi++;
+ count--;
+ continue;
+ }
+ // Get the next code.
+ code = datum & code_mask;
+ datum >>= code_size;
+ bits -= code_size;
+
+ // Interpret the code
+ if ((code > available) || (code == end_of_information)) {
+ break;
+ }
+ if (code == clear) {
+ // Reset decoder.
+ code_size = data_size + 1;
+ code_mask = (1 << code_size) - 1;
+ available = clear + 2;
+ old_code = NullCode;
+ continue;
+ }
+ if (old_code == NullCode) {
+ mPixelStack[top++] = mSuffix[code];
+ old_code = code;
+ first = code;
+ continue;
+ }
+ in_code = code;
+ if (code == available) {
+ mPixelStack[top++] = (byte) first;
+ code = old_code;
+ }
+ while (code > clear) {
+ mPixelStack[top++] = mSuffix[code];
+ code = mPrefix[code];
+ }
+ first = ((int) mSuffix[code]) & 0xff;
+ // Add a new string to the string table,
+ if (available >= MaxStackSize) {
+ break;
+ }
+ mPixelStack[top++] = (byte) first;
+ mPrefix[available] = (short) old_code;
+ mSuffix[available] = (byte) first;
+ available++;
+ if (((available & code_mask) == 0)
+ && (available < MaxStackSize)) {
+ code_size++;
+ code_mask += available;
+ }
+ old_code = in_code;
+ }
+
+ // Pop a pixel off the pixel stack.
+ top--;
+ mPixels[pi++] = mPixelStack[top];
+ i++;
+ }
+ for (i = pi; i < npix; i++) {
+ mPixels[i] = 0; // clear missing pixels
+ }
+ }
+
+ private boolean err() {
+ return mStatus != STATUS_PARSING;
+ }
+
+ private void init() {
+ mStatus = STATUS_PARSING;
+ mFrameCount = 0;
+ mGifFrame = null;
+ mGct = null;
+ mLct = null;
+ }
+
+ private int read() {
+ int curByte = 0;
+ try {
+ curByte = mIS.read();
+ } catch (Exception e) {
+ mStatus = STATUS_FORMAT_ERROR;
+ }
+ return curByte;
+ }
+
+ private int readBlock() {
+ mBlockSize = read();
+ int n = 0;
+ if (mBlockSize > 0) {
+ try {
+ int count = 0;
+ while (n < mBlockSize) {
+ count = mIS.read(mBlock, n, mBlockSize - n);
+ if (count == -1) {
+ break;
+ }
+ n += count;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ if (n < mBlockSize) {
+ mStatus = STATUS_FORMAT_ERROR;
+ }
+ }
+ return n;
+ }
+
+ private int[] readColorTable(int ncolors) {
+ int nbytes = 3 * ncolors;
+ int[] tab = null;
+ byte[] c = new byte[nbytes];
+ int n = 0;
+ try {
+ n = mIS.read(c);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ if (n < nbytes) {
+ mStatus = STATUS_FORMAT_ERROR;
+ } else {
+ tab = new int[256]; // max size to avoid bounds checks
+ int i = 0;
+ int j = 0;
+ while (i < ncolors) {
+ int r = ((int) c[j++]) & 0xff;
+ int g = ((int) c[j++]) & 0xff;
+ int b = ((int) c[j++]) & 0xff;
+ tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
+ }
+ }
+ return tab;
+ }
+
+ private void readContents() {
+ // read GIF file content blocks
+ boolean done = false;
+ while (!(done || err())) {
+ int code = read();
+ switch (code) {
+ case 0x2C: // image separator
+ readImage();
+ break;
+ case 0x21: // extension
+ code = read();
+ switch (code) {
+ case 0xf9: // graphics control extension
+ readGraphicControlExt();
+ break;
+ case 0xff: // application extension
+ readBlock();
+ String app = "";
+ for (int i = 0; i < 11; i++) {
+ app += (char) mBlock[i];
+ }
+ if (app.equals("NETSCAPE2.0")) {
+ readNetscapeExt();
+ } else {
+ skip(); // don't care
+ }
+ break;
+ default: // uninteresting extension
+ skip();
+ }
+ break;
+ case 0x3b: // terminator
+ done = true;
+ break;
+ case 0x00: // bad byte, but keep going and see what happens
+ break;
+ default:
+ mStatus = STATUS_FORMAT_ERROR;
+ }
+ }
+ }
+
+ private void readGraphicControlExt() {
+ read(); // block size
+ int packed = read(); // packed fields
+ mDispose = (packed & 0x1c) >> 2; // disposal method
+ if (mDispose == 0) {
+ mDispose = 1; // elect to keep old image if discretionary
+ }
+ mTransparency = (packed & 1) != 0;
+ mDelay = readShort() * 10; // delay in milliseconds
+ mTransIndex = read(); // transparent color index
+ read(); // block terminator
+ }
+
+ private void readHeader() {
+ String id = "";
+ for (int i = 0; i < 6; i++) {
+ id += (char) read();
+ }
+ if (!id.startsWith("GIF")) {
+ mStatus = STATUS_FORMAT_ERROR;
+ return;
+ }
+ readLSD();
+ if (mGctFlag && !err()) {
+ mGct = readColorTable(mGctSize);
+ mBgColor = mGct[mBgIndex];
+ }
+ }
+
+ private void readImage() {
+ mIx = readShort(); // (sub)image position & size
+ mIy = readShort();
+ mIw = readShort();
+ mIh = readShort();
+ int packed = read();
+ mLctFlag = (packed & 0x80) != 0; // 1 - local color table flag
+ mInterlace = (packed & 0x40) != 0; // 2 - interlace flag
+ // 3 - sort flag
+ // 4-5 - reserved
+ mLctSize = 2 << (packed & 7); // 6-8 - local color table size
+ if (mLctFlag) {
+ mLct = readColorTable(mLctSize); // read table
+ mAct = mLct; // make local table active
+ } else {
+ mAct = mGct; // make global table active
+ if (mBgIndex == mTransIndex) {
+ mBgColor = 0;
+ }
+ }
+ int save = 0;
+ if (mTransparency) {
+ save = mAct[mTransIndex];
+ mAct[mTransIndex] = 0; // set transparent color if specified
+ }
+ if (mAct == null) {
+ mStatus = STATUS_FORMAT_ERROR; // no color table defined
+ }
+ if (err()) {
+ return;
+ }
+ try {
+ decodeImageData(); // decode pixel data
+ skip();
+ if (err()) {
+ return;
+ }
+ mFrameCount++;
+ // create new image to receive frame data
+ mImage = Bitmap.createBitmap(mWidth, mHeight, Config.ARGB_4444);
+ // createImage(mWidth, mHeight);
+ setPixels(); // transfer pixel data to image
+ if (mGifFrame == null) {
+ mGifFrame = new GifFrame(mImage, mDelay, mDispose);
+ mCurrentFrame = mGifFrame;
+ } else {
+ GifFrame f = mGifFrame;
+ while (f.mNextFrame != null) {
+ f = f.mNextFrame;
+ }
+ f.mNextFrame = new GifFrame(mImage, mDelay, mDispose);
+ }
+ // frames.addElement(new GifFrame(image, delay)); // add image to
+ // frame
+ // list
+ if (mTransparency) {
+ mAct[mTransIndex] = save;
+ }
+ resetFrame();
+ mGifAction.parseOk(true, mFrameCount);
+ } catch (OutOfMemoryError e) {
+ Log.e("GifDecoder", ">>> log : " + e.toString());
+ e.printStackTrace();
+ }
+ }
+
+ private void readLSD() {
+ // logical screen size
+ mWidth = readShort();
+ mHeight = readShort();
+ // packed fields
+ int packed = read();
+ mGctFlag = (packed & 0x80) != 0; // 1 : global color table flag
+ // 2-4 : color resolution
+ // 5 : gct sort flag
+ mGctSize = 2 << (packed & 7); // 6-8 : gct size
+ mBgIndex = read(); // background color index
+ mPixelAspect = read(); // pixel aspect ratio
+ }
+
+ private void readNetscapeExt() {
+ do {
+ readBlock();
+ if (mBlock[0] == 1) {
+ // loop count sub-block
+ int b1 = ((int) mBlock[1]) & 0xff;
+ int b2 = ((int) mBlock[2]) & 0xff;
+ mLoopCount = (b2 << 8) | b1;
+ }
+ } while ((mBlockSize > 0) && !err());
+ }
+
+ private int readShort() {
+ // read 16-bit value, LSB first
+ return read() | (read() << 8);
+ }
+
+ private void resetFrame() {
+ mLastDispose = mDispose;
+ mLrx = mIx;
+ mLry = mIy;
+ mLrw = mIw;
+ mLrh = mIh;
+ mLastImage = mImage;
+ mLastBgColor = mBgColor;
+ mDispose = 0;
+ mTransparency = false;
+ mDelay = 0;
+ mLct = null;
+ }
+
+ /**
+ * Skips variable length blocks up to and including next zero length block.
+ */
+ private void skip() {
+ do {
+ readBlock();
+ } while ((mBlockSize > 0) && !err());
+ }
+
+ private void freeFrame() {
+ GifFrame fg = mGifFrame;
+ while (fg != null) {
+ if (fg.mImage != null) {
+ fg.mImage.recycle();
+ }
+ fg.mImage = null;
+ fg = null;
+ mGifFrame = mGifFrame.mNextFrame;
+ fg = mGifFrame;
+ }
+ }
+
+ private void freeIS() {
+ if (mIS != null) {
+ try {
+ mIS.close();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ mIS = null;
+ }
+ mGifData = null;
+ }
+
+ private void freeImage() {
+ if (mImage != null) {
+ mImage.recycle();
+ mImage = null;
+ }
+ if (mLastImage != null) {
+ mLastImage.recycle();
+ mLastImage = null;
+ }
+ }
+}