001 package railo.runtime.img.gif; 002 003 import java.awt.AlphaComposite; 004 import java.awt.Color; 005 import java.awt.Dimension; 006 import java.awt.Graphics2D; 007 import java.awt.Rectangle; 008 import java.awt.image.BufferedImage; 009 import java.awt.image.DataBufferInt; 010 import java.io.BufferedInputStream; 011 import java.io.FileInputStream; 012 import java.io.IOException; 013 import java.io.InputStream; 014 import java.net.URL; 015 import java.util.ArrayList; 016 017 public class GifDecoder { 018 019 /** 020 * File read status: No errors. 021 */ 022 public static final int STATUS_OK = 0; 023 024 /** 025 * File read status: Error decoding file (may be partially decoded) 026 */ 027 public static final int STATUS_FORMAT_ERROR = 1; 028 029 /** 030 * File read status: Unable to open source. 031 */ 032 public static final int STATUS_OPEN_ERROR = 2; 033 034 protected BufferedInputStream in; 035 protected int status; 036 037 protected int width; // full image width 038 protected int height; // full image height 039 protected boolean gctFlag; // global color table used 040 protected int gctSize; // size of global color table 041 protected int loopCount = 1; // iterations; 0 = repeat forever 042 043 protected int[] gct; // global color table 044 protected int[] lct; // local color table 045 protected int[] act; // active color table 046 047 protected int bgIndex; // background color index 048 protected int bgColor; // background color 049 protected int lastBgColor; // previous bg color 050 protected int pixelAspect; // pixel aspect ratio 051 052 protected boolean lctFlag; // local color table flag 053 protected boolean interlace; // interlace flag 054 protected int lctSize; // local color table size 055 056 protected int ix, iy, iw, ih; // current image rectangle 057 protected Rectangle lastRect; // last image rect 058 protected BufferedImage image; // current frame 059 protected BufferedImage lastImage; // previous frame 060 061 protected byte[] block = new byte[256]; // current data block 062 protected int blockSize = 0; // block size 063 064 // last graphic control extension info 065 protected int dispose = 0; 066 // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev 067 protected int lastDispose = 0; 068 protected boolean transparency = false; // use transparent color 069 protected int delay = 0; // delay in milliseconds 070 protected int transIndex; // transparent color index 071 072 protected static final int MaxStackSize = 4096; 073 // max decoder pixel stack size 074 075 // LZW decoder working arrays 076 protected short[] prefix; 077 protected byte[] suffix; 078 protected byte[] pixelStack; 079 protected byte[] pixels; 080 081 protected ArrayList frames; // frames read from current file 082 protected int frameCount; 083 084 static class GifFrame { 085 public GifFrame(BufferedImage im, int del) { 086 image = im; 087 delay = del; 088 } 089 public BufferedImage image; 090 public int delay; 091 } 092 093 /** 094 * Gets display duration for specified frame. 095 * 096 * @param n int index of frame 097 * @return delay in milliseconds 098 */ 099 public int getDelay(int n) { 100 // 101 delay = -1; 102 if ((n >= 0) && (n < frameCount)) { 103 delay = ((GifFrame) frames.get(n)).delay; 104 } 105 return delay; 106 } 107 108 /** 109 * Gets the number of frames read from file. 110 * @return frame count 111 */ 112 public int getFrameCount() { 113 return frameCount; 114 } 115 116 /** 117 * Gets the first (or only) image read. 118 * 119 * @return BufferedImage containing first frame, or null if none. 120 */ 121 public BufferedImage getImage() { 122 return getFrame(0); 123 } 124 125 /** 126 * Gets the "Netscape" iteration count, if any. 127 * A count of 0 means repeat indefinitiely. 128 * 129 * @return iteration count if one was specified, else 1. 130 */ 131 public int getLoopCount() { 132 return loopCount; 133 } 134 135 /** 136 * Creates new frame image from current data (and previous 137 * frames as specified by their disposition codes). 138 */ 139 protected void setPixels() { 140 // expose destination image's pixels as int array 141 int[] dest = 142 ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); 143 144 // fill in starting image contents based on last image's dispose code 145 if (lastDispose > 0) { 146 if (lastDispose == 3) { 147 // use image before last 148 int n = frameCount - 2; 149 if (n > 0) { 150 lastImage = getFrame(n - 1); 151 } else { 152 lastImage = null; 153 } 154 } 155 156 if (lastImage != null) { 157 int[] prev = 158 ((DataBufferInt) lastImage.getRaster().getDataBuffer()).getData(); 159 System.arraycopy(prev, 0, dest, 0, width * height); 160 // copy pixels 161 162 if (lastDispose == 2) { 163 // fill last image rect area with background color 164 Graphics2D g = image.createGraphics(); 165 Color c = null; 166 if (transparency) { 167 c = new Color(0, 0, 0, 0); // assume background is transparent 168 } else { 169 c = new Color(lastBgColor); // use given background color 170 } 171 g.setColor(c); 172 g.setComposite(AlphaComposite.Src); // replace area 173 g.fill(lastRect); 174 g.dispose(); 175 } 176 } 177 } 178 179 // copy each source line to the appropriate place in the destination 180 int pass = 1; 181 int inc = 8; 182 int iline = 0; 183 for (int i = 0; i < ih; i++) { 184 int line = i; 185 if (interlace) { 186 if (iline >= ih) { 187 pass++; 188 switch (pass) { 189 case 2 : 190 iline = 4; 191 break; 192 case 3 : 193 iline = 2; 194 inc = 4; 195 break; 196 case 4 : 197 iline = 1; 198 inc = 2; 199 } 200 } 201 line = iline; 202 iline += inc; 203 } 204 line += iy; 205 if (line < height) { 206 int k = line * width; 207 int dx = k + ix; // start of line in dest 208 int dlim = dx + iw; // end of dest line 209 if ((k + width) < dlim) { 210 dlim = k + width; // past dest edge 211 } 212 int sx = i * iw; // start of line in source 213 while (dx < dlim) { 214 // map color and insert in destination 215 int index = pixels[sx++] & 0xff; 216 int c = act[index]; 217 if (c != 0) { 218 dest[dx] = c; 219 } 220 dx++; 221 } 222 } 223 } 224 } 225 226 /** 227 * Gets the image contents of frame n. 228 * 229 * @return BufferedImage representation of frame, or null if n is invalid. 230 */ 231 public BufferedImage getFrame(int n) { 232 BufferedImage im = null; 233 if ((n >= 0) && (n < frameCount)) { 234 im = ((GifFrame) frames.get(n)).image; 235 } 236 return im; 237 } 238 239 /** 240 * Gets image size. 241 * 242 * @return GIF image dimensions 243 */ 244 public Dimension getFrameSize() { 245 return new Dimension(width, height); 246 } 247 248 /** 249 * Reads GIF image from stream 250 * 251 * @param BufferedInputStream containing GIF file. 252 * @return read status code (0 = no errors) 253 */ 254 public int read(BufferedInputStream is) { 255 init(); 256 if (is != null) { 257 in = is; 258 readHeader(); 259 if (!err()) { 260 readContents(); 261 if (frameCount < 0) { 262 status = STATUS_FORMAT_ERROR; 263 } 264 } 265 } else { 266 status = STATUS_OPEN_ERROR; 267 } 268 try { 269 is.close(); 270 } catch (IOException e) { 271 } 272 return status; 273 } 274 275 /** 276 * Reads GIF image from stream 277 * 278 * @param InputStream containing GIF file. 279 * @return read status code (0 = no errors) 280 */ 281 public int read(InputStream is) { 282 init(); 283 if (is != null) { 284 if (!(is instanceof BufferedInputStream)) 285 is = new BufferedInputStream(is); 286 in = (BufferedInputStream) is; 287 readHeader(); 288 if (!err()) { 289 readContents(); 290 if (frameCount < 0) { 291 status = STATUS_FORMAT_ERROR; 292 } 293 } 294 } else { 295 status = STATUS_OPEN_ERROR; 296 } 297 try { 298 is.close(); 299 } catch (IOException e) { 300 } 301 return status; 302 } 303 304 /** 305 * Reads GIF file from specified file/URL source 306 * (URL assumed if name contains ":/" or "file:") 307 * 308 * @param name String containing source 309 * @return read status code (0 = no errors) 310 */ 311 public int read(String name) { 312 status = STATUS_OK; 313 try { 314 name = name.trim().toLowerCase(); 315 if ((name.indexOf("file:") >= 0) || 316 (name.indexOf(":/") > 0)) { 317 URL url = new URL(name); 318 in = new BufferedInputStream(url.openStream()); 319 } else { 320 in = new BufferedInputStream(new FileInputStream(name)); 321 } 322 status = read(in); 323 } catch (IOException e) { 324 status = STATUS_OPEN_ERROR; 325 } 326 327 return status; 328 } 329 330 /** 331 * Decodes LZW image data into pixel array. 332 * Adapted from John Cristy's ImageMagick. 333 */ 334 protected void decodeImageData() { 335 int NullCode = -1; 336 int npix = iw * ih; 337 int available, 338 clear, 339 code_mask, 340 code_size, 341 end_of_information, 342 in_code, 343 old_code, 344 bits, 345 code, 346 count, 347 i, 348 datum, 349 data_size, 350 first, 351 top, 352 bi, 353 pi; 354 355 if ((pixels == null) || (pixels.length < npix)) { 356 pixels = new byte[npix]; // allocate new pixel array 357 } 358 if (prefix == null) prefix = new short[MaxStackSize]; 359 if (suffix == null) suffix = new byte[MaxStackSize]; 360 if (pixelStack == null) pixelStack = new byte[MaxStackSize + 1]; 361 362 // Initialize GIF data stream decoder. 363 364 data_size = read(); 365 clear = 1 << data_size; 366 end_of_information = clear + 1; 367 available = clear + 2; 368 old_code = NullCode; 369 code_size = data_size + 1; 370 code_mask = (1 << code_size) - 1; 371 for (code = 0; code < clear; code++) { 372 prefix[code] = 0; 373 suffix[code] = (byte) code; 374 } 375 376 // Decode GIF pixel stream. 377 378 datum = bits = count = first = top = pi = bi = 0; 379 380 for (i = 0; i < npix;) { 381 if (top == 0) { 382 if (bits < code_size) { 383 // Load bytes until there are enough bits for a code. 384 if (count == 0) { 385 // Read a new data block. 386 count = readBlock(); 387 if (count <= 0) 388 break; 389 bi = 0; 390 } 391 datum += (block[bi] & 0xff) << bits; 392 bits += 8; 393 bi++; 394 count--; 395 continue; 396 } 397 398 // Get the next code. 399 400 code = datum & code_mask; 401 datum >>= code_size; 402 bits -= code_size; 403 404 // Interpret the code 405 406 if ((code > available) || (code == end_of_information)) 407 break; 408 if (code == clear) { 409 // Reset decoder. 410 code_size = data_size + 1; 411 code_mask = (1 << code_size) - 1; 412 available = clear + 2; 413 old_code = NullCode; 414 continue; 415 } 416 if (old_code == NullCode) { 417 pixelStack[top++] = suffix[code]; 418 old_code = code; 419 first = code; 420 continue; 421 } 422 in_code = code; 423 if (code == available) { 424 pixelStack[top++] = (byte) first; 425 code = old_code; 426 } 427 while (code > clear) { 428 pixelStack[top++] = suffix[code]; 429 code = prefix[code]; 430 } 431 first = suffix[code] & 0xff; 432 433 // Add a new string to the string table, 434 435 if (available >= MaxStackSize) 436 break; 437 pixelStack[top++] = (byte) first; 438 prefix[available] = (short) old_code; 439 suffix[available] = (byte) first; 440 available++; 441 if (((available & code_mask) == 0) 442 && (available < MaxStackSize)) { 443 code_size++; 444 code_mask += available; 445 } 446 old_code = in_code; 447 } 448 449 // Pop a pixel off the pixel stack. 450 451 top--; 452 pixels[pi++] = pixelStack[top]; 453 i++; 454 } 455 456 for (i = pi; i < npix; i++) { 457 pixels[i] = 0; // clear missing pixels 458 } 459 460 } 461 462 /** 463 * Returns true if an error was encountered during reading/decoding 464 */ 465 protected boolean err() { 466 return status != STATUS_OK; 467 } 468 469 /** 470 * Initializes or re-initializes reader 471 */ 472 protected void init() { 473 status = STATUS_OK; 474 frameCount = 0; 475 frames = new ArrayList(); 476 gct = null; 477 lct = null; 478 } 479 480 /** 481 * Reads a single byte from the input stream. 482 */ 483 protected int read() { 484 int curByte = 0; 485 try { 486 curByte = in.read(); 487 } catch (IOException e) { 488 status = STATUS_FORMAT_ERROR; 489 } 490 return curByte; 491 } 492 493 /** 494 * Reads next variable length block from input. 495 * 496 * @return number of bytes stored in "buffer" 497 */ 498 protected int readBlock() { 499 blockSize = read(); 500 int n = 0; 501 if (blockSize > 0) { 502 try { 503 int count = 0; 504 while (n < blockSize) { 505 count = in.read(block, n, blockSize - n); 506 if (count == -1) 507 break; 508 n += count; 509 } 510 } catch (IOException e) { 511 } 512 513 if (n < blockSize) { 514 status = STATUS_FORMAT_ERROR; 515 } 516 } 517 return n; 518 } 519 520 /** 521 * Reads color table as 256 RGB integer values 522 * 523 * @param ncolors int number of colors to read 524 * @return int array containing 256 colors (packed ARGB with full alpha) 525 */ 526 protected int[] readColorTable(int ncolors) { 527 int nbytes = 3 * ncolors; 528 int[] tab = null; 529 byte[] c = new byte[nbytes]; 530 int n = 0; 531 try { 532 n = in.read(c); 533 } catch (IOException e) { 534 } 535 if (n < nbytes) { 536 status = STATUS_FORMAT_ERROR; 537 } else { 538 tab = new int[256]; // max size to avoid bounds checks 539 int i = 0; 540 int j = 0; 541 while (i < ncolors) { 542 int r = c[j++] & 0xff; 543 int g = c[j++] & 0xff; 544 int b = c[j++] & 0xff; 545 tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b; 546 } 547 } 548 return tab; 549 } 550 551 /** 552 * Main file parser. Reads GIF content blocks. 553 */ 554 protected void readContents() { 555 // read GIF file content blocks 556 boolean done = false; 557 while (!(done || err())) { 558 int code = read(); 559 switch (code) { 560 561 case 0x2C : // image separator 562 readImage(); 563 break; 564 565 case 0x21 : // extension 566 code = read(); 567 switch (code) { 568 case 0xf9 : // graphics control extension 569 readGraphicControlExt(); 570 break; 571 572 case 0xff : // application extension 573 readBlock(); 574 String app = ""; 575 for (int i = 0; i < 11; i++) { 576 app += (char) block[i]; 577 } 578 if (app.equals("NETSCAPE2.0")) { 579 readNetscapeExt(); 580 } 581 else 582 skip(); // don't care 583 break; 584 585 default : // uninteresting extension 586 skip(); 587 } 588 break; 589 590 case 0x3b : // terminator 591 done = true; 592 break; 593 594 case 0x00 : // bad byte, but keep going and see what happens 595 break; 596 597 default : 598 status = STATUS_FORMAT_ERROR; 599 } 600 } 601 } 602 603 /** 604 * Reads Graphics Control Extension values 605 */ 606 protected void readGraphicControlExt() { 607 read(); // block size 608 int packed = read(); // packed fields 609 dispose = (packed & 0x1c) >> 2; // disposal method 610 if (dispose == 0) { 611 dispose = 1; // elect to keep old image if discretionary 612 } 613 transparency = (packed & 1) != 0; 614 delay = readShort() * 10; // delay in milliseconds 615 transIndex = read(); // transparent color index 616 read(); // block terminator 617 } 618 619 /** 620 * Reads GIF file header information. 621 */ 622 protected void readHeader() { 623 String id = ""; 624 for (int i = 0; i < 6; i++) { 625 id += (char) read(); 626 } 627 if (!id.startsWith("GIF")) { 628 status = STATUS_FORMAT_ERROR; 629 return; 630 } 631 632 readLSD(); 633 if (gctFlag && !err()) { 634 gct = readColorTable(gctSize); 635 bgColor = gct[bgIndex]; 636 } 637 } 638 639 /** 640 * Reads next frame image 641 */ 642 protected void readImage() { 643 ix = readShort(); // (sub)image position & size 644 iy = readShort(); 645 iw = readShort(); 646 ih = readShort(); 647 648 int packed = read(); 649 lctFlag = (packed & 0x80) != 0; // 1 - local color table flag 650 interlace = (packed & 0x40) != 0; // 2 - interlace flag 651 // 3 - sort flag 652 // 4-5 - reserved 653 lctSize = 2 << (packed & 7); // 6-8 - local color table size 654 655 if (lctFlag) { 656 lct = readColorTable(lctSize); // read table 657 act = lct; // make local table active 658 } else { 659 act = gct; // make global table active 660 if (bgIndex == transIndex) 661 bgColor = 0; 662 } 663 int save = 0; 664 if (transparency) { 665 save = act[transIndex]; 666 act[transIndex] = 0; // set transparent color if specified 667 } 668 669 if (act == null) { 670 status = STATUS_FORMAT_ERROR; // no color table defined 671 } 672 673 if (err()) return; 674 675 decodeImageData(); // decode pixel data 676 skip(); 677 678 if (err()) return; 679 680 frameCount++; 681 682 // create new image to receive frame data 683 image = 684 new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); 685 686 setPixels(); // transfer pixel data to image 687 688 frames.add(new GifFrame(image, delay)); // add image to frame list 689 690 if (transparency) { 691 act[transIndex] = save; 692 } 693 resetFrame(); 694 695 } 696 697 /** 698 * Reads Logical Screen Descriptor 699 */ 700 protected void readLSD() { 701 702 // logical screen size 703 width = readShort(); 704 height = readShort(); 705 706 // packed fields 707 int packed = read(); 708 gctFlag = (packed & 0x80) != 0; // 1 : global color table flag 709 // 2-4 : color resolution 710 // 5 : gct sort flag 711 gctSize = 2 << (packed & 7); // 6-8 : gct size 712 713 bgIndex = read(); // background color index 714 pixelAspect = read(); // pixel aspect ratio 715 } 716 717 /** 718 * Reads Netscape extenstion to obtain iteration count 719 */ 720 protected void readNetscapeExt() { 721 do { 722 readBlock(); 723 if (block[0] == 1) { 724 // loop count sub-block 725 int b1 = block[1] & 0xff; 726 int b2 = block[2] & 0xff; 727 loopCount = (b2 << 8) | b1; 728 } 729 } while ((blockSize > 0) && !err()); 730 } 731 732 /** 733 * Reads next 16-bit value, LSB first 734 */ 735 protected int readShort() { 736 // read 16-bit value, LSB first 737 return read() | (read() << 8); 738 } 739 740 /** 741 * Resets frame state for reading next image. 742 */ 743 protected void resetFrame() { 744 lastDispose = dispose; 745 lastRect = new Rectangle(ix, iy, iw, ih); 746 lastImage = image; 747 lastBgColor = bgColor; 748 int dispose = 0; 749 boolean transparency = false; 750 int delay = 0; 751 lct = null; 752 } 753 754 /** 755 * Skips variable length blocks up to and including 756 * next zero length block. 757 */ 758 protected void skip() { 759 do { 760 readBlock(); 761 } while ((blockSize > 0) && !err()); 762 } 763 }