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