GifHeaderParser.java revision e4ac1f593a328f35ca17d8603a54db3826da2475
1package com.bumptech.glide.gifdecoder; 2 3import android.util.Log; 4 5import java.nio.BufferUnderflowException; 6import java.nio.ByteBuffer; 7import java.nio.ByteOrder; 8import java.util.Arrays; 9 10import static com.bumptech.glide.gifdecoder.GifDecoder.STATUS_FORMAT_ERROR; 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 Log.w(TAG, "Format Error Reading Color Table", e); 294 header.status = STATUS_FORMAT_ERROR; 295 } 296 297 return tab; 298 } 299 300 /** 301 * Skips LZW image data for a single frame to advance buffer. 302 */ 303 private void skipImageData() { 304 // lzwMinCodeSize 305 read(); 306 // data sub-blocks 307 skip(); 308 } 309 310 /** 311 * Skips variable length blocks up to and including next zero length block. 312 */ 313 private void skip() { 314 int blockSize; 315 do { 316 blockSize = read(); 317 rawData.position(rawData.position() + blockSize); 318 } while (blockSize > 0); 319 } 320 321 /** 322 * Reads next variable length block from input. 323 * 324 * @return number of bytes stored in "buffer" 325 */ 326 private int readBlock() { 327 blockSize = read(); 328 int n = 0; 329 if (blockSize > 0) { 330 int count = 0; 331 try { 332 while (n < blockSize) { 333 count = blockSize - n; 334 rawData.get(block, n, count); 335 336 n += count; 337 } 338 } catch (Exception e) { 339 Log.w(TAG, "Error Reading Block n: " + n + " count: " + count + " blockSize: " + blockSize, e); 340 header.status = STATUS_FORMAT_ERROR; 341 } 342 } 343 return n; 344 } 345 346 /** 347 * Reads a single byte from the input stream. 348 */ 349 private int read() { 350 int curByte = 0; 351 try { 352 curByte = rawData.get() & 0xFF; 353 } catch (Exception e) { 354 header.status = STATUS_FORMAT_ERROR; 355 } 356 return curByte; 357 } 358 359 /** 360 * Reads next 16-bit value, LSB first. 361 */ 362 private int readShort() { 363 // Read 16-bit value. 364 return rawData.getShort(); 365 } 366 367 private boolean err() { 368 return header.status != GifDecoder.STATUS_OK; 369 } 370} 371