diff options
author | Sam Judd <judds@google.com> | 2014-06-15 13:31:08 -0700 |
---|---|---|
committer | Sam Judd <judds@google.com> | 2014-06-16 18:38:58 -0700 |
commit | 0f49c87b2f26f3e086f021461b7e5409a7d42be0 (patch) | |
tree | b90e854340dc5ce649fc990d9c3c7c8f799e0852 /third_party/gif_decoder/src/main/java/com/bumptech/glide | |
parent | bd01b9765954ac2b99cb5dd76ade740455ad69e9 (diff) | |
download | glide-0f49c87b2f26f3e086f021461b7e5409a7d42be0.tar.gz |
Move GifDecoder related classes into third_party.
Diffstat (limited to 'third_party/gif_decoder/src/main/java/com/bumptech/glide')
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; + } +} |