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 com.bumptech.glide.Resource;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapResource;
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 BitmapPool bitmapPool;
private Bitmap currentImage;
private byte[] data;
private GifHeader header;
private String id;
public GifDecoder(BitmapPool bitmapPool) {
this.bitmapPool = bitmapPool;
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 Resource<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 new BitmapResource(result, bitmapPool);
}
/**
* 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];