aboutsummaryrefslogtreecommitdiff
path: root/third_party/gif_decoder/src/main/java/com/bumptech/glide
diff options
context:
space:
mode:
authorSam Judd <judds@google.com>2014-06-15 13:31:08 -0700
committerSam Judd <judds@google.com>2014-06-16 18:38:58 -0700
commit0f49c87b2f26f3e086f021461b7e5409a7d42be0 (patch)
treeb90e854340dc5ce649fc990d9c3c7c8f799e0852 /third_party/gif_decoder/src/main/java/com/bumptech/glide
parentbd01b9765954ac2b99cb5dd76ade740455ad69e9 (diff)
downloadglide-0f49c87b2f26f3e086f021461b7e5409a7d42be0.tar.gz
Move GifDecoder related classes into third_party.
Diffstat (limited to 'third_party/gif_decoder/src/main/java/com/bumptech/glide')
-rw-r--r--third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java591
-rw-r--r--third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java21
-rw-r--r--third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java31
-rw-r--r--third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java411
4 files changed, 1054 insertions, 0 deletions
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java
new file mode 100644
index 00000000..131a9683
--- /dev/null
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java
@@ -0,0 +1,591 @@
+package com.bumptech.glide.gifdecoder;
+
+
+/**
+ * Copyright (c) 2013 Xcellent Creations, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Reads frame data from a GIF image source and decodes it into individual frames
+ * for animation purposes. Image data can be read from either and InputStream source
+ * or a byte[].
+ *
+ * This class is optimized for running animations with the frames, there
+ * are no methods to get individual frame images, only to decode the next frame in the
+ * animation sequence. Instead, it lowers its memory footprint by only housing the minimum
+ * data necessary to decode the next frame in the animation sequence.
+ *
+ * The animation must be manually moved forward using {@link #advance()} before requesting the next
+ * frame. This method must also be called before you request the first frame or an error will
+ * occur.
+ *
+ * Implementation adapted from sample code published in Lyons. (2004). <em>Java for Programmers</em>,
+ * republished under the MIT Open Source License
+ */
+public class GifDecoder {
+ private static final String TAG = GifDecoder.class.getSimpleName();
+
+ /**
+ * File read status: No errors.
+ */
+ public static final int STATUS_OK = 0;
+ /**
+ * File read status: Error decoding file (may be partially decoded)
+ */
+ public static final int STATUS_FORMAT_ERROR = 1;
+ /**
+ * File read status: Unable to open source.
+ */
+ public static final int STATUS_OPEN_ERROR = 2;
+ /**
+ * max decoder pixel stack size
+ */
+ private static final int MAX_STACK_SIZE = 4096;
+
+ /**
+ * GIF Disposal Method meaning take no action
+ */
+ private static final int DISPOSAL_UNSPECIFIED = 0;
+ /**
+ * GIF Disposal Method meaning leave canvas from previous frame
+ */
+ private static final int DISPOSAL_NONE = 1;
+ /**
+ * GIF Disposal Method meaning clear canvas to background color
+ */
+ private static final int DISPOSAL_BACKGROUND = 2;
+ /**
+ * GIF Disposal Method meaning clear canvas to frame before last
+ */
+ private static final int DISPOSAL_PREVIOUS = 3;
+
+ //Global File Header values and parsing flags
+ private int[] act; // active color table
+
+ // Raw GIF data from input source
+ private ByteBuffer rawData;
+
+ // Raw data read working array
+ private byte[] block = new byte[256]; // current data block
+ // LZW decoder working arrays
+ private short[] prefix;
+ private byte[] suffix;
+ private byte[] pixelStack;
+ private byte[] mainPixels;
+ private int[] mainScratch;
+
+ private int framePointer = -1;
+ private Bitmap currentImage;
+ private byte[] data;
+ private GifHeader header;
+ private String id;
+ private BitmapProvider bitmapProvider;
+
+ public interface BitmapProvider {
+ public Bitmap obtain(int width, int height, Bitmap.Config config);
+ }
+
+ public GifDecoder(BitmapProvider provider) {
+ this.bitmapProvider = provider;
+ header = new GifHeader();
+ }
+
+ public int getWidth() {
+ return header.width;
+ }
+
+ public int getHeight() {
+ return header.height;
+ }
+
+ public boolean isTransparent() {
+ return header.isTransparent;
+ }
+
+ public int getGifByteSize() {
+ return data.length;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public int getDecodedFramesByteSizeSum() {
+ // 4 == ARGB_8888, 2 == RGB_565
+ return header.frameCount * header.width * header.height * (header.isTransparent ? 4 : 2);
+ }
+
+ /**
+ * Move the animation frame counter forward
+ */
+ public void advance() {
+ framePointer = (framePointer + 1) % header.frameCount;
+ }
+
+ /**
+ * Gets display duration for specified frame.
+ *
+ * @param n int index of frame
+ * @return delay in milliseconds
+ */
+ public int getDelay(int n) {
+ int delay = -1;
+ if ((n >= 0) && (n < header.frameCount)) {
+ delay = header.frames.get(n).delay;
+ }
+ return delay;
+ }
+
+ /**
+ * Gets display duration for the upcoming frame
+ */
+ public int getNextDelay() {
+ if (header.frameCount <= 0 || framePointer < 0) {
+ return -1;
+ }
+
+ return getDelay(framePointer);
+ }
+
+ /**
+ * Gets the number of frames read from file.
+ *
+ * @return frame count
+ */
+ public int getFrameCount() {
+ return header.frameCount;
+ }
+
+ /**
+ * Gets the current index of the animation frame, or -1 if animation hasn't not yet started
+ *
+ * @return frame index
+ */
+ public int getCurrentFrameIndex() {
+ return framePointer;
+ }
+
+ /**
+ * Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitiely.
+ *
+ * @return iteration count if one was specified, else 1.
+ */
+ public int getLoopCount() {
+ return header.loopCount;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Get the next frame in the animation sequence.
+ *
+ * @return Bitmap representation of frame
+ */
+ public Bitmap getNextFrame() {
+ if (header.frameCount <= 0 || framePointer < 0 ) {
+ return null;
+ }
+
+ GifFrame frame = header.frames.get(framePointer);
+
+ //Set the appropriate color table
+ if (frame.lct == null) {
+ act = header.gct;
+ } else {
+ act = frame.lct;
+ if (header.bgIndex == frame.transIndex) {
+ header.bgColor = 0;
+ }
+ }
+
+ int save = 0;
+ if (frame.transparency) {
+ save = act[frame.transIndex];
+ act[frame.transIndex] = 0; // set transparent color if specified
+ }
+ if (act == null) {
+ Log.w(TAG, "No Valid Color Table");
+ header.status = STATUS_FORMAT_ERROR; // no color table defined
+ return null;
+ }
+
+ Bitmap result = setPixels(framePointer); // transfer pixel data to image
+ currentImage = result;
+
+ // Reset the transparent pixel in the color table
+ if (frame.transparency) {
+ act[frame.transIndex] = save;
+ }
+
+ return result;
+ }
+
+ /**
+ * Reads GIF image from stream
+ *
+ * @param is containing GIF file.
+ * @return read status code (0 = no errors)
+ */
+ public int read(InputStream is, int contentLength) {
+ if (is != null) {
+ try {
+ int capacity = (contentLength > 0) ? (contentLength + 4096) : 16384;
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream(capacity);
+ int nRead;
+ byte[] data = new byte[16384];
+ while ((nRead = is.read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, nRead);
+ }
+ buffer.flush();
+
+ read(buffer.toByteArray());
+ } catch (IOException e) {
+ Log.w(TAG, "Error reading data from stream", e);
+ }
+ } else {
+ header.status = STATUS_OPEN_ERROR;
+ }
+
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Error closing stream", e);
+ }
+
+ return header.status;
+ }
+
+ public void setData(String id, GifHeader header, byte[] data) {
+ this.id = id;
+ this.header = header;
+ this.data = data;
+ //Initialize the raw data buffer
+ rawData = ByteBuffer.wrap(data);
+ rawData.rewind();
+ rawData.order(ByteOrder.LITTLE_ENDIAN);
+
+ //Now that we know the size, init scratch arrays
+ mainPixels = new byte[header.width * header.height];
+ mainScratch = new int[header.width * header.height];
+ }
+
+ /**
+ * Reads GIF image from byte array
+ *
+ * @param data containing GIF file.
+ * @return read status code (0 = no errors)
+ */
+ public int read(byte[] data) {
+ this.data = data;
+ this.header = new GifHeaderParser(data).parseHeader();
+ if (data != null) {
+ //Initialize the raw data buffer
+ rawData = ByteBuffer.wrap(data);
+ rawData.rewind();
+ rawData.order(ByteOrder.LITTLE_ENDIAN);
+
+ //Now that we know the size, init scratch arrays
+ mainPixels = new byte[header.width * header.height];
+ mainScratch = new int[header.width * header.height];
+ }
+
+ return header.status;
+ }
+
+ /**
+ * Creates new frame image from current data (and previous frames as specified by their disposition codes).
+ */
+ private Bitmap setPixels(int frameIndex) {
+ GifFrame currentFrame = header.frames.get(frameIndex);
+ GifFrame previousFrame = null;
+ int previousIndex = frameIndex - 1;
+ if (previousIndex >= 0) {
+ previousFrame = header.frames.get(previousIndex);
+ }
+
+ // final location of blended pixels
+ final int[] dest = mainScratch;
+
+ // fill in starting image contents based on last image's dispose code
+ if (previousFrame != null && previousFrame.dispose > DISPOSAL_UNSPECIFIED) {
+ if (previousFrame.dispose == DISPOSAL_NONE && currentImage != null) {
+ // Start with the current image
+ currentImage.getPixels(dest, 0, header.width, 0, 0, header.width, header.height);
+ }
+ if (previousFrame.dispose == DISPOSAL_BACKGROUND) {
+ // Start with a canvas filled with the background color
+ int c = 0;
+ if (!currentFrame.transparency) {
+ c = header.bgColor;
+ }
+ for (int i = 0; i < previousFrame.ih; i++) {
+ int n1 = (previousFrame.iy + i) * header.width + previousFrame.ix;
+ int n2 = n1 + previousFrame.iw;
+ for (int k = n1; k < n2; k++) {
+ dest[k] = c;
+ }
+ }
+ }
+ } else {
+ int c = 0;
+ if (!currentFrame.transparency) {
+ c = header.bgColor;
+ }
+ for (int i = 0; i < dest.length; i++) {
+ dest[i] = c;
+ }
+ }
+
+ //Decode pixels for this frame into the global pixels[] scratch
+ decodeBitmapData(currentFrame, mainPixels); // decode pixel data
+
+ // 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 < currentFrame.ih; i++) {
+ int line = i;
+ if (currentFrame.interlace) {
+ if (iline >= currentFrame.ih) {
+ 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 += currentFrame.iy;
+ if (line < header.height) {
+ int k = line * header.width;
+ int dx = k + currentFrame.ix; // start of line in dest
+ int dlim = dx + currentFrame.iw; // end of dest line
+ if ((k + header.width) < dlim) {
+ dlim = k + header.width; // past dest edge
+ }
+ int sx = i * currentFrame.iw; // start of line in source
+ while (dx < dlim) {
+ // map color and insert in destination
+ int index = ((int) mainPixels[sx++]) & 0xff;
+ int c = act[index];
+ if (c != 0) {
+ dest[dx] = c;
+ }
+ dx++;
+ }
+ }
+ }
+
+ //Set pixels for current image
+ Bitmap result = getNextBitmap();
+ result.setPixels(dest, 0, header.width, 0, 0, header.width, header.height);
+ return result;
+ }
+
+ /**
+ * Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
+ */
+ private void decodeBitmapData(GifFrame frame, byte[] dstPixels) {
+ if (frame != null) {
+ //Jump to the frame start position
+ rawData.position(frame.bufferFrameStart);
+ }
+
+ int nullCode = -1;
+ int npix = (frame == null) ? header.width * header.height : frame.iw * frame.ih;
+ 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 (dstPixels == null || dstPixels.length < npix) {
+ dstPixels = new byte[npix]; // allocate new pixel array
+ }
+ if (prefix == null) {
+ prefix = new short[MAX_STACK_SIZE];
+ }
+ if (suffix == null) {
+ suffix = new byte[MAX_STACK_SIZE];
+ }
+ if (pixelStack == null) {
+ pixelStack = new byte[MAX_STACK_SIZE + 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++) {
+ prefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
+ suffix[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) block[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) {
+ pixelStack[top++] = suffix[code];
+ old_code = code;
+ first = code;
+ continue;
+ }
+ in_code = code;
+ if (code == available) {
+ pixelStack[top++] = (byte) first;
+ code = old_code;
+ }
+ while (code > clear) {
+ pixelStack[top++] = suffix[code];
+ code = prefix[code];
+ }
+ first = ((int) suffix[code]) & 0xff;
+ // Add a new string to the string table,
+ if (available >= MAX_STACK_SIZE) {
+ break;
+ }
+ pixelStack[top++] = (byte) first;
+ prefix[available] = (short) old_code;
+ suffix[available] = (byte) first;
+ available++;
+ if (((available & code_mask) == 0) && (available < MAX_STACK_SIZE)) {
+ code_size++;
+ code_mask += available;
+ }
+ old_code = in_code;
+ }
+ // Pop a pixel off the pixel stack.
+ top--;
+ dstPixels[pi++] = pixelStack[top];
+ i++;
+ }
+
+ for (i = pi; i < npix; i++) {
+ dstPixels[i] = 0; // clear missing pixels
+ }
+ }
+
+ /**
+ * Reads a single byte from the input stream.
+ */
+ private int read() {
+ int curByte = 0;
+ try {
+ curByte = (rawData.get() & 0xFF);
+ } catch (Exception e) {
+ header.status = STATUS_FORMAT_ERROR;
+ }
+ return curByte;
+ }
+
+ /**
+ * Reads next variable length block from input.
+ *
+ * @return number of bytes stored in "buffer"
+ */
+ private int readBlock() {
+ int blockSize = read();
+ int n = 0;
+ if (blockSize > 0) {
+ try {
+ int count;
+ while (n < blockSize) {
+ count = blockSize - n;
+ rawData.get(block, n, count);
+
+ n += count;
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Error Reading Block", e);
+ header.status = STATUS_FORMAT_ERROR;
+ }
+ }
+ return n;
+ }
+
+ private Bitmap getNextBitmap() {
+ Bitmap.Config targetConfig = header.isTransparent ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+ Bitmap result = bitmapProvider.obtain(header.width, header.height, targetConfig);
+ if (result == null) {
+ result = Bitmap.createBitmap(header.width, header.height, targetConfig);
+ }
+ return result;
+ }
+}
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java
new file mode 100644
index 00000000..a315350c
--- /dev/null
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java
@@ -0,0 +1,21 @@
+package com.bumptech.glide.gifdecoder;
+
+/**
+ * Inner model class housing metadata for each frame
+ */
+class GifFrame {
+ public int ix, iy, iw, ih;
+ /* Control Flags */
+ public boolean interlace;
+ public boolean transparency;
+ /* Disposal Method */
+ public int dispose;
+ /* Transparency Index */
+ public int transIndex;
+ /* Delay, in ms, to next frame */
+ public int delay;
+ /* Index in the raw buffer where we need to start reading to decode */
+ public int bufferFrameStart;
+ /* Local Color Table */
+ public int[] lct;
+}
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java
new file mode 100644
index 00000000..4c062088
--- /dev/null
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java
@@ -0,0 +1,31 @@
+package com.bumptech.glide.gifdecoder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GifHeader {
+
+ public int[] gct = null;
+ /**
+ * Global status code of GIF data parsing
+ */
+ public int status = GifDecoder.STATUS_OK;
+ public int frameCount = 0;
+
+ public GifFrame currentFrame;
+ public List<GifFrame> frames = new ArrayList<GifFrame>();
+ // logical screen size
+ public int width; // full image width
+ public int height; // full image height
+
+ public boolean gctFlag; // 1 : global color table flag
+ // 2-4 : color resolution
+ // 5 : gct sort flag
+ public int gctSize; // 6-8 : gct size
+ public int bgIndex; // background color index
+ public int pixelAspect; // pixel aspect ratio
+ //TODO: this is set both during reading the header and while decoding frames...
+ public int bgColor;
+ public boolean isTransparent;
+ public int loopCount;
+}
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java
new file mode 100644
index 00000000..6237c3cf
--- /dev/null
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java
@@ -0,0 +1,411 @@
+package com.bumptech.glide.gifdecoder;
+
+import android.util.Log;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static com.bumptech.glide.gifdecoder.GifDecoder.STATUS_FORMAT_ERROR;
+
+public class GifHeaderParser {
+ public static final String TAG = "GifHeaderParser";
+ /**
+ * max decoder pixel stack size
+ */
+ private static final int MAX_STACK_SIZE = 4096;
+
+ private final ByteBuffer rawData;
+ private GifHeader header = new GifHeader();
+
+ // Raw data read working array
+ protected byte[] block = new byte[256]; // current data block
+ protected int blockSize = 0; // block size last graphic control extension info
+ protected boolean lctFlag; // local color table flag
+ protected int lctSize; // local color table size
+ private short[] prefix;
+ private byte[] suffix;
+ private byte[] pixelStack;
+
+ public GifHeaderParser(byte[] data) {
+ if (data != null) {
+ rawData = ByteBuffer.wrap(data);
+ rawData.rewind();
+ rawData.order(ByteOrder.LITTLE_ENDIAN);
+ } else {
+ rawData = null;
+ header.status = GifDecoder.STATUS_OPEN_ERROR;
+ }
+ }
+
+ public GifHeader parseHeader() {
+ if (err()) {
+ return header;
+ }
+
+ readHeader();
+ if (!err()) {
+ readContents();
+ if (header.frameCount < 0) {
+ header.status = STATUS_FORMAT_ERROR;
+ }
+ }
+
+ return header;
+ }
+
+ /**
+ * Main file parser. Reads GIF content blocks.
+ */
+ protected void readContents() {
+ // read GIF file content blocks
+ boolean done = false;
+ while (!(done || err())) {
+ int code = read();
+ switch (code) {
+ case 0x2C: // image separator
+ readBitmap();
+ break;
+ case 0x21: // extension
+ code = read();
+ switch (code) {
+ case 0xf9: // graphics control extension
+ //Start a new frame
+ header.currentFrame = new GifFrame();
+ readGraphicControlExt();
+ break;
+ case 0xff: // application extension
+ readBlock();
+ String app = "";
+ for (int i = 0; i < 11; i++) {
+ app += (char) block[i];
+ }
+ if (app.equals("NETSCAPE2.0")) {
+ readNetscapeExt();
+ } 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 0x3b: // terminator
+ done = true;
+ break;
+ case 0x00: // bad byte, but keep going and see what happens break;
+ default:
+ header.status = STATUS_FORMAT_ERROR;
+ }
+ }
+ }
+
+ /**
+ * Reads Graphics Control Extension values
+ */
+ protected void readGraphicControlExt() {
+ read(); // block size
+ int packed = read(); // packed fields
+ header.currentFrame.dispose = (packed & 0x1c) >> 2; // disposal method
+ if (header.currentFrame.dispose == 0) {
+ header.currentFrame.dispose = 1; // elect to keep old image if discretionary
+ }
+ header.currentFrame.transparency = (packed & 1) != 0;
+ header.isTransparent |= header.currentFrame.transparency;
+ header.currentFrame.delay = readShort() * 10; // delay in milliseconds
+ header.currentFrame.transIndex = read(); // transparent color index
+ read(); // block terminator
+ }
+
+ /**
+ * Reads next frame image
+ */
+ protected void readBitmap() {
+ header.currentFrame.ix = readShort(); // (sub)image position & size
+ header.currentFrame.iy = readShort();
+ header.currentFrame.iw = readShort();
+ header.currentFrame.ih = readShort();
+
+ int packed = read();
+ lctFlag = (packed & 0x80) != 0; // 1 - local color table flag interlace
+ lctSize = (int) Math.pow(2, (packed & 0x07) + 1);
+ // 3 - sort flag
+ // 4-5 - reserved lctSize = 2 << (packed & 7); // 6-8 - local color
+ // table size
+ header.currentFrame.interlace = (packed & 0x40) != 0;
+ if (lctFlag) {
+ header.currentFrame.lct = readColorTable(lctSize); // read table
+ } else {
+ header.currentFrame.lct = null; //No local color table
+ }
+
+ header.currentFrame.bufferFrameStart = rawData.position(); //Save this as the decoding position pointer
+
+ skipBitmapData(); // false decode pixel data to advance buffer.
+
+ skip();
+ if (err()) {
+ return;
+ }
+
+ header.frameCount++;
+ header.frames.add(header.currentFrame); // add image to frame
+ }
+ /**
+ * Reads Netscape extenstion to obtain iteration count
+ */
+ protected void readNetscapeExt() {
+ do {
+ readBlock();
+ if (block[0] == 1) {
+ // loop count sub-block
+ int b1 = ((int) block[1]) & 0xff;
+ int b2 = ((int) block[2]) & 0xff;
+ header.loopCount = (b2 << 8) | b1;
+ }
+ } while ((blockSize > 0) && !err());
+ }
+
+
+ /**
+ * Reads GIF file header information.
+ */
+ private void readHeader() {
+ String id = "";
+ for (int i = 0; i < 6; i++) {
+ id += (char) read();
+ }
+ if (!id.startsWith("GIF")) {
+ header.status = STATUS_FORMAT_ERROR;
+ return;
+ }
+ readLSD();
+ if (header.gctFlag && !err()) {
+ header.gct = readColorTable(header.gctSize);
+ header.bgColor = header.gct[header.bgIndex];
+ }
+ }
+ /**
+ * Reads Logical Screen Descriptor
+ */
+ protected void readLSD() {
+ // logical screen size
+ header.width = readShort();
+ header.height = readShort();
+ // packed fields
+ int packed = read();
+ header.gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
+ // 2-4 : color resolution
+ // 5 : gct sort flag
+ header.gctSize = 2 << (packed & 7); // 6-8 : gct size
+ header.bgIndex = read(); // background color index
+ header.pixelAspect = read(); // pixel aspect ratio
+
+ //Now that we know the size, init scratch arrays
+ //TODO: these shouldn't go here.
+// mainPixels = new byte[header.width * header.height];
+// mainScratch = new int[header.width * header.height];
+ }
+ /**
+ * Reads color table as 256 RGB integer values
+ *
+ * @param ncolors int number of colors to read
+ * @return int array containing 256 colors (packed ARGB with full alpha)
+ */
+ protected int[] readColorTable(int ncolors) {
+ int nbytes = 3 * ncolors;
+ int[] tab = null;
+ byte[] c = new byte[nbytes];
+
+ try {
+ rawData.get(c);
+
+ 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;
+ }
+ } catch (BufferUnderflowException e) {
+ Log.w(TAG, "Format Error Reading Color Table", e);
+ header.status = STATUS_FORMAT_ERROR;
+ }
+
+ return tab;
+ }
+
+ /**
+ * Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
+ */
+ protected void skipBitmapData() {
+ int nullCode = -1;
+ int npix = header.width * header.height;
+ 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 (prefix == null) {
+ prefix = new short[MAX_STACK_SIZE];
+ }
+ if (suffix == null) {
+ suffix = new byte[MAX_STACK_SIZE];
+ }
+ if (pixelStack == null) {
+ pixelStack = new byte[MAX_STACK_SIZE + 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;
+ long start = System.currentTimeMillis();
+ for (code = 0; code < clear; code++) {
+ prefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
+ suffix[code] = (byte) code;
+ }
+
+ start = System.currentTimeMillis();
+ // Decode GIF pixel stream.
+ datum = bits = count = first = top = pi = bi = 0;
+ int iterations = 0;
+ for (i = 0; i < npix; ) {
+ iterations++;
+ 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) block[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) {
+ pixelStack[top++] = suffix[code];
+ old_code = code;
+ first = code;
+ continue;
+ }
+ in_code = code;
+ if (code == available) {
+ pixelStack[top++] = (byte) first;
+ code = old_code;
+ }
+ while (code > clear) {
+ pixelStack[top++] = suffix[code];
+ code = prefix[code];
+ }
+ first = ((int) suffix[code]) & 0xff;
+ // Add a new string to the string table,
+ if (available >= MAX_STACK_SIZE) {
+ break;
+ }
+ pixelStack[top++] = (byte) first;
+ prefix[available] = (short) old_code;
+ suffix[available] = (byte) first;
+ available++;
+ if (((available & code_mask) == 0) && (available < MAX_STACK_SIZE)) {
+ code_size++;
+ code_mask += available;
+ }
+ old_code = in_code;
+ }
+ // Pop a pixel off the pixel stack.
+ top--;
+ i++;
+ }
+ }
+
+ /**
+ * Skips variable length blocks up to and including next zero length block.
+ */
+ protected void skip() {
+ do {
+ readBlock();
+ } while ((blockSize > 0) && !err());
+ }
+
+ /**
+ * Reads next variable length block from input.
+ *
+ * @return number of bytes stored in "buffer"
+ */
+ protected int readBlock() {
+ blockSize = read();
+ int n = 0;
+ if (blockSize > 0) {
+ try {
+ int count;
+ while (n < blockSize) {
+ count = blockSize - n;
+ rawData.get(block, n, count);
+
+ n += count;
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Error Reading Block", e);
+ header.status = STATUS_FORMAT_ERROR;
+ }
+ }
+ return n;
+ }
+
+ /**
+ * Reads a single byte from the input stream.
+ */
+ private int read() {
+ int curByte = 0;
+ try {
+ curByte = (rawData.get() & 0xFF);
+ } catch (Exception e) {
+ header.status = STATUS_FORMAT_ERROR;
+ }
+ return curByte;
+ }
+
+ /**
+ * Reads next 16-bit value, LSB first
+ */
+ protected int readShort() {
+ // read 16-bit value
+ return rawData.getShort();
+ }
+
+ private boolean err() {
+ return header.status != GifDecoder.STATUS_OK;
+ }
+}