1package com.bumptech.glide.gifdecoder;
2
3import static com.bumptech.glide.gifdecoder.GifDecoder.STATUS_FORMAT_ERROR;
4
5import android.util.Log;
6
7import java.nio.BufferUnderflowException;
8import java.nio.ByteBuffer;
9import java.nio.ByteOrder;
10import 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 */
16public 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
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
45    public void clear() {
46        rawData = null;
47        header = null;
48    }
49
50    private void reset() {
51        rawData = null;
52        Arrays.fill(block, (byte) 0);
53        header = new GifHeader();
54        blockSize = 0;
55    }
56
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
366    private int readShort() {
367        // Read 16-bit value.
368        return rawData.getShort();
369    }
370
371    private boolean err() {
372        return header.status != GifDecoder.STATUS_OK;
373    }
374}
375