Home | History | Annotate | Download | only in gifdecoder
      1 package com.bumptech.glide.gifdecoder;
      2 
      3 
      4 /**
      5  * Copyright (c) 2013 Xcellent Creations, Inc.
      6  *
      7  * Permission is hereby granted, free of charge, to any person obtaining
      8  * a copy of this software and associated documentation files (the
      9  * "Software"), to deal in the Software without restriction, including
     10  * without limitation the rights to use, copy, modify, merge, publish,
     11  * distribute, sublicense, and/or sell copies of the Software, and to
     12  * permit persons to whom the Software is furnished to do so, subject to
     13  * the following conditions:
     14  *
     15  * The above copyright notice and this permission notice shall be
     16  * included in all copies or substantial portions of the Software.
     17  *
     18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     19  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     21  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
     22  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
     23  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     24  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     25  */
     26 
     27 import android.graphics.Bitmap;
     28 import android.graphics.Color;
     29 import android.util.Log;
     30 
     31 import java.io.ByteArrayOutputStream;
     32 import java.io.IOException;
     33 import java.io.InputStream;
     34 import java.nio.ByteBuffer;
     35 import java.nio.ByteOrder;
     36 
     37 /**
     38  * Reads frame data from a GIF image source and decodes it into individual frames
     39  * for animation purposes.  Image data can be read from either and InputStream source
     40  * or a byte[].
     41  *
     42  * This class is optimized for running animations with the frames, there
     43  * are no methods to get individual frame images, only to decode the next frame in the
     44  * animation sequence.  Instead, it lowers its memory footprint by only housing the minimum
     45  * data necessary to decode the next frame in the animation sequence.
     46  *
     47  * The animation must be manually moved forward using {@link #advance()} before requesting the next
     48  * frame.  This method must also be called before you request the first frame or an error will
     49  * occur.
     50  *
     51  * Implementation adapted from sample code published in Lyons. (2004). <em>Java for Programmers</em>,
     52  * republished under the MIT Open Source License
     53  */
     54 public class GifDecoder {
     55     private static final String TAG = GifDecoder.class.getSimpleName();
     56 
     57     /**
     58      * File read status: No errors.
     59      */
     60     public static final int STATUS_OK = 0;
     61     /**
     62      * File read status: Error decoding file (may be partially decoded)
     63      */
     64     public static final int STATUS_FORMAT_ERROR = 1;
     65     /**
     66      * File read status: Unable to open source.
     67      */
     68     public static final int STATUS_OPEN_ERROR = 2;
     69     /**
     70      * max decoder pixel stack size
     71      */
     72     private static final int MAX_STACK_SIZE = 4096;
     73 
     74     /**
     75      * GIF Disposal Method meaning take no action
     76      */
     77     private static final int DISPOSAL_UNSPECIFIED = 0;
     78     /**
     79      * GIF Disposal Method meaning leave canvas from previous frame
     80      */
     81     private static final int DISPOSAL_NONE = 1;
     82     /**
     83      * GIF Disposal Method meaning clear canvas to background color
     84      */
     85     private static final int DISPOSAL_BACKGROUND = 2;
     86     /**
     87      * GIF Disposal Method meaning clear canvas to frame before last
     88      */
     89     private static final int DISPOSAL_PREVIOUS = 3;
     90 
     91     //Global File Header values and parsing flags
     92     private int[] act; // active color table
     93 
     94     // Raw GIF data from input source
     95     private ByteBuffer rawData;
     96 
     97     // Raw data read working array
     98     private byte[] block = new byte[256]; // current data block
     99     // LZW decoder working arrays
    100     private short[] prefix;
    101     private byte[] suffix;
    102     private byte[] pixelStack;
    103     private byte[] mainPixels;
    104     private int[] mainScratch;
    105 
    106     private int framePointer = -1;
    107     private byte[] data;
    108     private GifHeader header;
    109     private String id;
    110     private BitmapProvider bitmapProvider;
    111 
    112     public interface BitmapProvider {
    113         public Bitmap obtain(int width, int height, Bitmap.Config config);
    114     }
    115 
    116     public GifDecoder(BitmapProvider provider) {
    117         this.bitmapProvider = provider;
    118         header = new GifHeader();
    119     }
    120 
    121     public int getWidth() {
    122         return header.width;
    123     }
    124 
    125     public int getHeight() {
    126         return header.height;
    127     }
    128 
    129     public boolean isTransparent() {
    130         return header.isTransparent;
    131     }
    132 
    133     public int getGifByteSize() {
    134         return data.length;
    135     }
    136 
    137     public byte[] getData() {
    138         return data;
    139     }
    140 
    141     public int getDecodedFramesByteSizeSum() {
    142         // 4 == ARGB_8888, 2 == RGB_565
    143         return header.frameCount * header.width * header.height * (header.isTransparent ? 4 : 2);
    144     }
    145 
    146     /**
    147      * Move the animation frame counter forward
    148      */
    149     public void advance() {
    150         framePointer = (framePointer + 1) % header.frameCount;
    151     }
    152 
    153     /**
    154      * Gets display duration for specified frame.
    155      *
    156      * @param n int index of frame
    157      * @return delay in milliseconds
    158      */
    159     public int getDelay(int n) {
    160         int delay = -1;
    161         if ((n >= 0) && (n < header.frameCount)) {
    162             delay = header.frames.get(n).delay;
    163         }
    164         return delay;
    165     }
    166 
    167     /**
    168      * Gets display duration for the upcoming frame
    169      */
    170     public int getNextDelay() {
    171         if (header.frameCount <= 0 || framePointer < 0) {
    172             return -1;
    173         }
    174 
    175         return getDelay(framePointer);
    176     }
    177 
    178     /**
    179      * Gets the number of frames read from file.
    180      *
    181      * @return frame count
    182      */
    183     public int getFrameCount() {
    184         return header.frameCount;
    185     }
    186 
    187     /**
    188      * Gets the current index of the animation frame, or -1 if animation hasn't not yet started
    189      *
    190      * @return frame index
    191      */
    192     public int getCurrentFrameIndex() {
    193         return framePointer;
    194     }
    195 
    196     /**
    197      * Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitiely.
    198      *
    199      * @return iteration count if one was specified, else 1.
    200      */
    201     public int getLoopCount() {
    202         return header.loopCount;
    203     }
    204 
    205     public String getId() {
    206         return id;
    207     }
    208 
    209     /**
    210      * Get the next frame in the animation sequence.
    211      *
    212      * @return Bitmap representation of frame
    213      */
    214     public Bitmap getNextFrame() {
    215         if (header.frameCount <= 0 || framePointer < 0 ) {
    216             return null;
    217         }
    218 
    219         GifFrame frame = header.frames.get(framePointer);
    220 
    221         //Set the appropriate color table
    222         if (frame.lct == null) {
    223             act = header.gct;
    224         } else {
    225             act = frame.lct;
    226             if (header.bgIndex == frame.transIndex) {
    227                 header.bgColor = 0;
    228             }
    229         }
    230 
    231         int save = 0;
    232         if (frame.transparency) {
    233             save = act[frame.transIndex];
    234             act[frame.transIndex] = 0; // set transparent color if specified
    235         }
    236         if (act == null) {
    237             Log.w(TAG, "No Valid Color Table");
    238             header.status = STATUS_FORMAT_ERROR; // no color table defined
    239             return null;
    240         }
    241 
    242         Bitmap result = setPixels(framePointer); // transfer pixel data to image
    243 
    244         // Reset the transparent pixel in the color table
    245         if (frame.transparency) {
    246             act[frame.transIndex] = save;
    247         }
    248 
    249         return result;
    250     }
    251 
    252     /**
    253      * Reads GIF image from stream
    254      *
    255      * @param is containing GIF file.
    256      * @return read status code (0 = no errors)
    257      */
    258     public int read(InputStream is, int contentLength) {
    259         if (is != null) {
    260             try {
    261                 int capacity = (contentLength > 0) ? (contentLength + 4096) : 16384;
    262                 ByteArrayOutputStream buffer = new ByteArrayOutputStream(capacity);
    263                 int nRead;
    264                 byte[] data = new byte[16384];
    265                 while ((nRead = is.read(data, 0, data.length)) != -1) {
    266                     buffer.write(data, 0, nRead);
    267                 }
    268                 buffer.flush();
    269 
    270                 read(buffer.toByteArray());
    271             } catch (IOException e) {
    272                 Log.w(TAG, "Error reading data from stream", e);
    273             }
    274         } else {
    275             header.status = STATUS_OPEN_ERROR;
    276         }
    277 
    278         try {
    279             if (is != null) {
    280                 is.close();
    281             }
    282         } catch (IOException e) {
    283             Log.w(TAG, "Error closing stream", e);
    284         }
    285 
    286         return header.status;
    287     }
    288 
    289     public void setData(String id, GifHeader header, byte[] data) {
    290         this.id = id;
    291         this.header = header;
    292         this.data = data;
    293         //Initialize the raw data buffer
    294         rawData = ByteBuffer.wrap(data);
    295         rawData.rewind();
    296         rawData.order(ByteOrder.LITTLE_ENDIAN);
    297 
    298         //Now that we know the size, init scratch arrays
    299         mainPixels = new byte[header.width * header.height];
    300         mainScratch = new int[header.width * header.height];
    301     }
    302 
    303     /**
    304      * Reads GIF image from byte array
    305      *
    306      * @param data containing GIF file.
    307      * @return read status code (0 = no errors)
    308      */
    309     public int read(byte[] data) {
    310         this.data = data;
    311         this.header = new GifHeaderParser(data).parseHeader();
    312         if (data != null) {
    313             //Initialize the raw data buffer
    314             rawData = ByteBuffer.wrap(data);
    315             rawData.rewind();
    316             rawData.order(ByteOrder.LITTLE_ENDIAN);
    317 
    318             //Now that we know the size, init scratch arrays
    319             mainPixels = new byte[header.width * header.height];
    320             mainScratch = new int[header.width * header.height];
    321         }
    322 
    323         return header.status;
    324     }
    325 
    326     /**
    327      * Creates new frame image from current data (and previous frames as specified by their disposition codes).
    328      */
    329     private Bitmap setPixels(int frameIndex) {
    330         GifFrame currentFrame = header.frames.get(frameIndex);
    331         GifFrame previousFrame = null;
    332         int previousIndex = frameIndex - 1;
    333         if (previousIndex >= 0) {
    334             previousFrame = header.frames.get(previousIndex);
    335         }
    336 
    337         // final location of blended pixels
    338         final int[] dest = mainScratch;
    339 
    340         // fill in starting image contents based on last image's dispose code
    341         if (previousFrame != null && previousFrame.dispose > DISPOSAL_UNSPECIFIED) {
    342 //            if (previousFrame.dispose == DISPOSAL_NONE) {
    343 //                We don't need to do anything for this case, mainScratch should already have the pixels of the
    344 //                previous image.
    345 //                currentImage.getPixels(dest, 0, header.width, 0, 0, header.width, header.height);
    346 //            }
    347             if (previousFrame.dispose == DISPOSAL_BACKGROUND) {
    348                 // Start with a canvas filled with the background color
    349                 int c = 0;
    350                 if (!currentFrame.transparency) {
    351                     c = header.bgColor;
    352                 }
    353                 for (int i = 0; i < previousFrame.ih; i++) {
    354                     int n1 = (previousFrame.iy + i) * header.width + previousFrame.ix;
    355                     int n2 = n1 + previousFrame.iw;
    356                     for (int k = n1; k < n2; k++) {
    357                         dest[k] = c;
    358                     }
    359                 }
    360             }
    361         } else {
    362             int c = 0;
    363             if (!currentFrame.transparency) {
    364                 c = header.bgColor;
    365             }
    366             for (int i = 0; i < dest.length; i++) {
    367                 dest[i] = c;
    368             }
    369         }
    370 
    371         // Decode pixels for this frame  into the global pixels[] scratch
    372         decodeBitmapData(currentFrame, mainPixels); // decode pixel data
    373 
    374         // copy each source line to the appropriate place in the destination
    375         int pass = 1;
    376         int inc = 8;
    377         int iline = 0;
    378         for (int i = 0; i < currentFrame.ih; i++) {
    379             int line = i;
    380             if (currentFrame.interlace) {
    381                 if (iline >= currentFrame.ih) {
    382                     pass++;
    383                     switch (pass) {
    384                         case 2:
    385                             iline = 4;
    386                             break;
    387                         case 3:
    388                             iline = 2;
    389                             inc = 4;
    390                             break;
    391                         case 4:
    392                             iline = 1;
    393                             inc = 2;
    394                             break;
    395                         default:
    396                             break;
    397                     }
    398                 }
    399                 line = iline;
    400                 iline += inc;
    401             }
    402             line += currentFrame.iy;
    403             if (line < header.height) {
    404                 int k = line * header.width;
    405                 int dx = k + currentFrame.ix; // start of line in dest
    406                 int dlim = dx + currentFrame.iw; // end of dest line
    407                 if ((k + header.width) < dlim) {
    408                     dlim = k + header.width; // past dest edge
    409                 }
    410                 int sx = i * currentFrame.iw; // start of line in source
    411                 while (dx < dlim) {
    412                     // map color and insert in destination
    413                     int index = ((int) mainPixels[sx++]) & 0xff;
    414                     int c = act[index];
    415                     if (c != 0) {
    416                         dest[dx] = c;
    417                     }
    418                     dx++;
    419                 }
    420             }
    421         }
    422 
    423         //Set pixels for current image
    424         Bitmap result = getNextBitmap();
    425         result.setPixels(dest, 0, header.width, 0, 0, header.width, header.height);
    426         return result;
    427     }
    428 
    429     /**
    430      * Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
    431      */
    432     private void decodeBitmapData(GifFrame frame, byte[] dstPixels) {
    433         if (frame != null) {
    434             //Jump to the frame start position
    435             rawData.position(frame.bufferFrameStart);
    436         }
    437 
    438         int nullCode = -1;
    439         int npix = (frame == null) ? header.width * header.height : frame.iw * frame.ih;
    440         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;
    441 
    442         if (dstPixels == null || dstPixels.length < npix) {
    443             dstPixels = new byte[npix]; // allocate new pixel array
    444         }
    445         if (prefix == null) {
    446             prefix = new short[MAX_STACK_SIZE];
    447         }
    448         if (suffix == null) {
    449             suffix = new byte[MAX_STACK_SIZE];
    450         }
    451         if (pixelStack == null) {
    452             pixelStack = new byte[MAX_STACK_SIZE + 1];
    453         }
    454 
    455         // Initialize GIF data stream decoder.
    456         data_size = read();
    457         clear = 1 << data_size;
    458         end_of_information = clear + 1;
    459         available = clear + 2;
    460         old_code = nullCode;
    461         code_size = data_size + 1;
    462         code_mask = (1 << code_size) - 1;
    463         for (code = 0; code < clear; code++) {
    464             prefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
    465             suffix[code] = (byte) code;
    466         }
    467 
    468         // Decode GIF pixel stream.
    469         datum = bits = count = first = top = pi = bi = 0;
    470         for (i = 0; i < npix; ) {
    471             if (top == 0) {
    472                 if (bits < code_size) {
    473                     // Load bytes until there are enough bits for a code.
    474                     if (count == 0) {
    475                         // Read a new data block.
    476                         count = readBlock();
    477                         if (count <= 0) {
    478                             break;
    479                         }
    480                         bi = 0;
    481                     }
    482                     datum += (((int) block[bi]) & 0xff) << bits;
    483                     bits += 8;
    484                     bi++;
    485                     count--;
    486                     continue;
    487                 }
    488                 // Get the next code.
    489                 code = datum & code_mask;
    490                 datum >>= code_size;
    491                 bits -= code_size;
    492                 // Interpret the code
    493                 if ((code > available) || (code == end_of_information)) {
    494                     break;
    495                 }
    496                 if (code == clear) {
    497                     // Reset decoder.
    498                     code_size = data_size + 1;
    499                     code_mask = (1 << code_size) - 1;
    500                     available = clear + 2;
    501                     old_code = nullCode;
    502                     continue;
    503                 }
    504                 if (old_code == nullCode) {
    505                     pixelStack[top++] = suffix[code];
    506                     old_code = code;
    507                     first = code;
    508                     continue;
    509                 }
    510                 in_code = code;
    511                 if (code == available) {
    512                     pixelStack[top++] = (byte) first;
    513                     code = old_code;
    514                 }
    515                 while (code > clear) {
    516                     pixelStack[top++] = suffix[code];
    517                     code = prefix[code];
    518                 }
    519                 first = ((int) suffix[code]) & 0xff;
    520                 // Add a new string to the string table,
    521                 if (available >= MAX_STACK_SIZE) {
    522                     break;
    523                 }
    524                 pixelStack[top++] = (byte) first;
    525                 prefix[available] = (short) old_code;
    526                 suffix[available] = (byte) first;
    527                 available++;
    528                 if (((available & code_mask) == 0) && (available < MAX_STACK_SIZE)) {
    529                     code_size++;
    530                     code_mask += available;
    531                 }
    532                 old_code = in_code;
    533             }
    534             // Pop a pixel off the pixel stack.
    535             top--;
    536             dstPixels[pi++] = pixelStack[top];
    537             i++;
    538         }
    539 
    540         for (i = pi; i < npix; i++) {
    541             dstPixels[i] = 0; // clear missing pixels
    542         }
    543     }
    544 
    545     /**
    546      * Reads a single byte from the input stream.
    547      */
    548     private int read() {
    549         int curByte = 0;
    550         try {
    551             curByte = (rawData.get() & 0xFF);
    552         } catch (Exception e) {
    553             header.status = STATUS_FORMAT_ERROR;
    554         }
    555         return curByte;
    556     }
    557 
    558     /**
    559      * Reads next variable length block from input.
    560      *
    561      * @return number of bytes stored in "buffer"
    562      */
    563     private int readBlock() {
    564         int blockSize = read();
    565         int n = 0;
    566         if (blockSize > 0) {
    567             try {
    568                 int count;
    569                 while (n < blockSize) {
    570                     count = blockSize - n;
    571                     rawData.get(block, n, count);
    572 
    573                     n += count;
    574                 }
    575             } catch (Exception e) {
    576                 Log.w(TAG, "Error Reading Block", e);
    577                 header.status = STATUS_FORMAT_ERROR;
    578             }
    579         }
    580         return n;
    581     }
    582 
    583     private Bitmap getNextBitmap() {
    584         Bitmap.Config targetConfig = header.isTransparent ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
    585         Bitmap result = bitmapProvider.obtain(header.width, header.height, targetConfig);
    586         if (result == null) {
    587             result = Bitmap.createBitmap(header.width, header.height, targetConfig);
    588         } else {
    589             // If we're reusing a bitmap it may have other things drawn in it which we need to remove.
    590             result.eraseColor(Color.TRANSPARENT);
    591         }
    592         return result;
    593     }
    594 }
    595