• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.bumptech.glide.gifdecoder;
2 
3 import static com.bumptech.glide.gifdecoder.GifDecoder.STATUS_FORMAT_ERROR;
4 
5 import android.util.Log;
6 
7 import java.nio.BufferUnderflowException;
8 import java.nio.ByteBuffer;
9 import java.nio.ByteOrder;
10 import java.util.Arrays;
11 
12 /**
13  * A class responsible for creating {@link com.bumptech.glide.gifdecoder.GifHeader}s from data representing animated
14  * gifs.
15  */
16 public class GifHeaderParser {
17     public static final String TAG = "GifHeaderParser";
18 
19     // The minimum frame delay in hundredths of a second.
20     static final int MIN_FRAME_DELAY = 3;
21     // The default frame delay in hundredths of a second for GIFs with frame delays less than the minimum.
22     static final int DEFAULT_FRAME_DELAY = 10;
23 
24     private static final int MAX_BLOCK_SIZE = 256;
25     // Raw data read working array.
26     private final byte[] block = new byte[MAX_BLOCK_SIZE];
27 
28     private ByteBuffer rawData;
29     private GifHeader header;
30     private int blockSize = 0;
31 
setData(byte[] data)32     public GifHeaderParser setData(byte[] data) {
33         reset();
34         if (data != null) {
35             rawData = ByteBuffer.wrap(data);
36             rawData.rewind();
37             rawData.order(ByteOrder.LITTLE_ENDIAN);
38         } else {
39             rawData = null;
40             header.status = GifDecoder.STATUS_OPEN_ERROR;
41         }
42         return this;
43     }
44 
clear()45     public void clear() {
46         rawData = null;
47         header = null;
48     }
49 
reset()50     private void reset() {
51         rawData = null;
52         Arrays.fill(block, (byte) 0);
53         header = new GifHeader();
54         blockSize = 0;
55     }
56 
parseHeader()57     public GifHeader parseHeader() {
58         if (rawData == null) {
59             throw new IllegalStateException("You must call setData() before parseHeader()");
60         }
61         if (err()) {
62             return header;
63         }
64 
65         readHeader();
66         if (!err()) {
67             readContents();
68             if (header.frameCount < 0) {
69                 header.status = STATUS_FORMAT_ERROR;
70             }
71         }
72 
73         return header;
74     }
75 
76     /**
77      * Main file parser. Reads GIF content blocks.
78      */
readContents()79     private void readContents() {
80         // Read GIF file content blocks.
81         boolean done = false;
82         while (!(done || err())) {
83             int code = read();
84             switch (code) {
85                 // Image separator.
86                 case 0x2C:
87                     // The graphics control extension is optional, but will always come first if it exists. If one did
88                     // exist, there will be a non-null current frame which we should use. However if one did not exist,
89                     // the current frame will be null and we must create it here. See issue #134.
90                     if (header.currentFrame == null) {
91                         header.currentFrame = new GifFrame();
92                     }
93                     readBitmap();
94                     break;
95                 // Extension.
96                 case 0x21:
97                     code = read();
98                     switch (code) {
99                         // Graphics control extension.
100                         case 0xf9:
101                             // Start a new frame.
102                             header.currentFrame = new GifFrame();
103                             readGraphicControlExt();
104                             break;
105                         // Application extension.
106                         case 0xff:
107                             readBlock();
108                             String app = "";
109                             for (int i = 0; i < 11; i++) {
110                                 app += (char) block[i];
111                             }
112                             if (app.equals("NETSCAPE2.0")) {
113                                 readNetscapeExt();
114                             } else {
115                                 // Don't care.
116                                 skip();
117                             }
118                             break;
119                         // Comment extension.
120                         case 0xfe:
121                             skip();
122                             break;
123                         // Plain text extension.
124                         case 0x01:
125                             skip();
126                             break;
127                         // Uninteresting extension.
128                         default:
129                             skip();
130                     }
131                     break;
132                 // Terminator.
133                 case 0x3b:
134                     done = true;
135                     break;
136                 // Bad byte, but keep going and see what happens break;
137                 case 0x00:
138                 default:
139                     header.status = STATUS_FORMAT_ERROR;
140             }
141         }
142     }
143 
144     /**
145      * Reads Graphics Control Extension values.
146      */
readGraphicControlExt()147     private void readGraphicControlExt() {
148         // Block size.
149         read();
150         // Packed fields.
151         int packed = read();
152         // Disposal method.
153         header.currentFrame.dispose = (packed & 0x1c) >> 2;
154         if (header.currentFrame.dispose == 0) {
155             // Elect to keep old image if discretionary.
156             header.currentFrame.dispose = 1;
157         }
158         header.currentFrame.transparency = (packed & 1) != 0;
159         // Delay in milliseconds.
160         int delayInHundredthsOfASecond = readShort();
161         // TODO: consider allowing -1 to indicate show forever.
162         if (delayInHundredthsOfASecond < MIN_FRAME_DELAY) {
163             delayInHundredthsOfASecond = DEFAULT_FRAME_DELAY;
164         }
165         header.currentFrame.delay = delayInHundredthsOfASecond * 10;
166         // Transparent color index
167         header.currentFrame.transIndex = read();
168         // Block terminator
169         read();
170     }
171 
172     /**
173      * Reads next frame image.
174      */
readBitmap()175     private void readBitmap() {
176         // (sub)image position & size.
177         header.currentFrame.ix = readShort();
178         header.currentFrame.iy = readShort();
179         header.currentFrame.iw = readShort();
180         header.currentFrame.ih = readShort();
181 
182         int packed = read();
183         // 1 - local color table flag interlace
184         boolean lctFlag = (packed & 0x80) != 0;
185         int lctSize = (int) Math.pow(2, (packed & 0x07) + 1);
186         // 3 - sort flag
187         // 4-5 - reserved lctSize = 2 << (packed & 7); // 6-8 - local color
188         // table size
189         header.currentFrame.interlace = (packed & 0x40) != 0;
190         if (lctFlag) {
191             // Read table.
192             header.currentFrame.lct = readColorTable(lctSize);
193         } else {
194             // No local color table.
195             header.currentFrame.lct = null;
196         }
197 
198         // Save this as the decoding position pointer.
199         header.currentFrame.bufferFrameStart = rawData.position();
200 
201         // False decode pixel data to advance buffer.
202         skipImageData();
203 
204         if (err()) {
205             return;
206         }
207 
208         header.frameCount++;
209         // Add image to frame.
210         header.frames.add(header.currentFrame);
211     }
212     /**
213      * Reads Netscape extension to obtain iteration count.
214      */
readNetscapeExt()215     private void readNetscapeExt() {
216         do {
217             readBlock();
218             if (block[0] == 1) {
219                 // Loop count sub-block.
220                 int b1 = ((int) block[1]) & 0xff;
221                 int b2 = ((int) block[2]) & 0xff;
222                 header.loopCount = (b2 << 8) | b1;
223             }
224         } while ((blockSize > 0) && !err());
225     }
226 
227 
228     /**
229      * Reads GIF file header information.
230      */
readHeader()231     private void readHeader() {
232         String id = "";
233         for (int i = 0; i < 6; i++) {
234             id += (char) read();
235         }
236         if (!id.startsWith("GIF")) {
237             header.status = STATUS_FORMAT_ERROR;
238             return;
239         }
240         readLSD();
241         if (header.gctFlag && !err()) {
242             header.gct = readColorTable(header.gctSize);
243             header.bgColor = header.gct[header.bgIndex];
244         }
245     }
246     /**
247      * Reads Logical Screen Descriptor.
248      */
readLSD()249     private void readLSD() {
250         // Logical screen size.
251         header.width = readShort();
252         header.height = readShort();
253         // Packed fields
254         int packed = read();
255         // 1 : global color table flag.
256         header.gctFlag = (packed & 0x80) != 0;
257         // 2-4 : color resolution.
258         // 5 : gct sort flag.
259         // 6-8 : gct size.
260         header.gctSize = 2 << (packed & 7);
261         // Background color index.
262         header.bgIndex = read();
263         // Pixel aspect ratio
264         header.pixelAspect = read();
265     }
266 
267     /**
268      * Reads color table as 256 RGB integer values.
269      *
270      * @param ncolors int number of colors to read.
271      * @return int array containing 256 colors (packed ARGB with full alpha).
272      */
readColorTable(int ncolors)273     private int[] readColorTable(int ncolors) {
274         int nbytes = 3 * ncolors;
275         int[] tab = null;
276         byte[] c = new byte[nbytes];
277 
278         try {
279             rawData.get(c);
280 
281             // TODO: what bounds checks are we avoiding if we know the number of colors?
282             // Max size to avoid bounds checks.
283             tab = new int[MAX_BLOCK_SIZE];
284             int i = 0;
285             int j = 0;
286             while (i < ncolors) {
287                 int r = ((int) c[j++]) & 0xff;
288                 int g = ((int) c[j++]) & 0xff;
289                 int b = ((int) c[j++]) & 0xff;
290                 tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
291             }
292         } catch (BufferUnderflowException e) {
293             if (Log.isLoggable(TAG, Log.DEBUG)) {
294                 Log.d(TAG, "Format Error Reading Color Table", e);
295             }
296             header.status = STATUS_FORMAT_ERROR;
297         }
298 
299         return tab;
300     }
301 
302     /**
303      * Skips LZW image data for a single frame to advance buffer.
304      */
skipImageData()305     private void skipImageData() {
306         // lzwMinCodeSize
307         read();
308         // data sub-blocks
309         skip();
310     }
311 
312     /**
313      * Skips variable length blocks up to and including next zero length block.
314      */
skip()315     private void skip() {
316         int blockSize;
317         do {
318             blockSize = read();
319             rawData.position(rawData.position() + blockSize);
320         } while (blockSize > 0);
321     }
322 
323     /**
324      * Reads next variable length block from input.
325      *
326      * @return number of bytes stored in "buffer"
327      */
readBlock()328     private int readBlock() {
329         blockSize = read();
330         int n = 0;
331         if (blockSize > 0) {
332             int count = 0;
333             try {
334                 while (n < blockSize) {
335                     count = blockSize - n;
336                     rawData.get(block, n, count);
337 
338                     n += count;
339                 }
340             } catch (Exception e) {
341                 if (Log.isLoggable(TAG, Log.DEBUG)) {
342                     Log.d(TAG, "Error Reading Block n: " + n + " count: " + count + " blockSize: " + blockSize, e);
343                 }
344                 header.status = STATUS_FORMAT_ERROR;
345             }
346         }
347         return n;
348     }
349 
350     /**
351      * Reads a single byte from the input stream.
352      */
read()353     private int read() {
354         int curByte = 0;
355         try {
356             curByte = rawData.get() & 0xFF;
357         } catch (Exception e) {
358             header.status = STATUS_FORMAT_ERROR;
359         }
360         return curByte;
361     }
362 
363     /**
364      * Reads next 16-bit value, LSB first.
365      */
readShort()366     private int readShort() {
367         // Read 16-bit value.
368         return rawData.getShort();
369     }
370 
err()371     private boolean err() {
372         return header.status != GifDecoder.STATUS_OK;
373     }
374 }
375