1 /* 2 * Copyright (c) 1995, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /*- 27 * Reads GIF images from an InputStream and reports the 28 * image data to an InputStreamImageSource object. 29 * 30 * The algorithm is copyright of CompuServe. 31 */ 32 package sun.awt.image; 33 34 import java.util.Vector; 35 import java.util.Hashtable; 36 import java.io.InputStream; 37 import java.io.IOException; 38 import java.awt.image.*; 39 40 /** 41 * Gif Image converter 42 * 43 * @author Arthur van Hoff 44 * @author Jim Graham 45 */ 46 public class GifImageDecoder extends ImageDecoder { 47 private static final boolean verbose = false; 48 49 private static final int IMAGESEP = 0x2c; 50 private static final int EXBLOCK = 0x21; 51 private static final int EX_GRAPHICS_CONTROL= 0xf9; 52 private static final int EX_COMMENT = 0xfe; 53 private static final int EX_APPLICATION = 0xff; 54 private static final int TERMINATOR = 0x3b; 55 private static final int TRANSPARENCYMASK = 0x01; 56 private static final int INTERLACEMASK = 0x40; 57 private static final int COLORMAPMASK = 0x80; 58 59 int num_global_colors; 60 byte[] global_colormap; 61 int trans_pixel = -1; 62 IndexColorModel global_model; 63 64 Hashtable props = new Hashtable(); 65 66 byte[] saved_image; 67 IndexColorModel saved_model; 68 69 int global_width; 70 int global_height; 71 int global_bgpixel; 72 73 GifFrame curframe; 74 75 public GifImageDecoder(InputStreamImageSource src, InputStream is) { 76 super(src, is); 77 } 78 79 /** 80 * An error has occurred. Throw an exception. 81 */ 82 private static void error(String s1) throws ImageFormatException { 83 throw new ImageFormatException(s1); 84 } 85 86 /** 87 * Read a number of bytes into a buffer. 88 * @return number of bytes that were not read due to EOF or error 89 */ 90 private int readBytes(byte buf[], int off, int len) { 91 while (len > 0) { 92 try { 93 int n = input.read(buf, off, len); 94 if (n < 0) { 95 break; 96 } 97 off += n; 98 len -= n; 99 } catch (IOException e) { 100 break; 101 } 102 } 103 return len; 104 } 105 106 private static final int ExtractByte(byte buf[], int off) { 107 return (buf[off] & 0xFF); 108 } 109 110 private static final int ExtractWord(byte buf[], int off) { 111 return (buf[off] & 0xFF) | ((buf[off + 1] & 0xFF) << 8); 112 } 113 114 /** 115 * produce an image from the stream. 116 */ 117 public void produceImage() throws IOException, ImageFormatException { 118 try { 119 readHeader(); 120 121 int totalframes = 0; 122 int frameno = 0; 123 int nloops = -1; 124 int disposal_method = 0; 125 int delay = -1; 126 boolean loopsRead = false; 127 boolean isAnimation = false; 128 129 while (!aborted) { 130 int code; 131 132 switch (code = input.read()) { 133 case EXBLOCK: 134 switch (code = input.read()) { 135 case EX_GRAPHICS_CONTROL: { 136 byte buf[] = new byte[6]; 137 if (readBytes(buf, 0, 6) != 0) { 138 return;//error("corrupt GIF file"); 139 } 140 if ((buf[0] != 4) || (buf[5] != 0)) { 141 return;//error("corrupt GIF file (GCE size)"); 142 } 143 // Get the index of the transparent color 144 delay = ExtractWord(buf, 2) * 10; 145 if (delay > 0 && !isAnimation) { 146 isAnimation = true; 147 ImageFetcher.startingAnimation(); 148 } 149 disposal_method = (buf[1] >> 2) & 7; 150 if ((buf[1] & TRANSPARENCYMASK) != 0) { 151 trans_pixel = ExtractByte(buf, 4); 152 } else { 153 trans_pixel = -1; 154 } 155 break; 156 } 157 158 case EX_COMMENT: 159 case EX_APPLICATION: 160 default: 161 boolean loop_tag = false; 162 String comment = ""; 163 while (true) { 164 int n = input.read(); 165 if (n <= 0) { 166 break; 167 } 168 byte buf[] = new byte[n]; 169 if (readBytes(buf, 0, n) != 0) { 170 return;//error("corrupt GIF file"); 171 } 172 if (code == EX_COMMENT) { 173 comment += new String(buf, 0); 174 } else if (code == EX_APPLICATION) { 175 if (loop_tag) { 176 if (n == 3 && buf[0] == 1) { 177 if (loopsRead) { 178 ExtractWord(buf, 1); 179 } 180 else { 181 nloops = ExtractWord(buf, 1); 182 loopsRead = true; 183 } 184 } else { 185 loop_tag = false; 186 } 187 } 188 if ("NETSCAPE2.0".equals(new String(buf, 0))) { 189 loop_tag = true; 190 } 191 } 192 } 193 if (code == EX_COMMENT) { 194 props.put("comment", comment); 195 } 196 if (loop_tag && !isAnimation) { 197 isAnimation = true; 198 ImageFetcher.startingAnimation(); 199 } 200 break; 201 202 case -1: 203 return; //error("corrupt GIF file"); 204 } 205 break; 206 207 case IMAGESEP: 208 if (!isAnimation) { 209 input.mark(0); // we don't need the mark buffer 210 } 211 try { 212 if (!readImage(totalframes == 0, 213 disposal_method, 214 delay)) { 215 return; 216 } 217 } catch (Exception e) { 218 if (verbose) { 219 e.printStackTrace(); 220 } 221 return; 222 } 223 frameno++; 224 totalframes++; 225 break; 226 227 default: 228 case -1: 229 if (verbose) { 230 if (code == -1) { 231 System.err.println("Premature EOF in GIF file," + 232 " frame " + frameno); 233 } else { 234 System.err.println("corrupt GIF file (parse) [" 235 + code + "]."); 236 } 237 } 238 if (frameno == 0) { 239 return; 240 } 241 // NOBREAK 242 243 case TERMINATOR: 244 if (nloops == 0 || nloops-- >= 0) { 245 try { 246 if (curframe != null) { 247 curframe.dispose(); 248 curframe = null; 249 } 250 input.reset(); 251 saved_image = null; 252 saved_model = null; 253 frameno = 0; 254 break; 255 } catch (IOException e) { 256 return; // Unable to reset input buffer 257 } 258 } 259 if (verbose && frameno != 1) { 260 System.out.println("processing GIF terminator," 261 + " frames: " + frameno 262 + " total: " + totalframes); 263 } 264 imageComplete(ImageConsumer.STATICIMAGEDONE, true); 265 return; 266 } 267 } 268 } finally { 269 close(); 270 } 271 } 272 273 /** 274 * Read Image header 275 */ 276 private void readHeader() throws IOException, ImageFormatException { 277 // Create a buffer 278 byte buf[] = new byte[13]; 279 280 // Read the header 281 if (readBytes(buf, 0, 13) != 0) { 282 throw new IOException(); 283 } 284 285 // Check header 286 if ((buf[0] != 'G') || (buf[1] != 'I') || (buf[2] != 'F')) { 287 error("not a GIF file."); 288 } 289 290 // Global width&height 291 global_width = ExtractWord(buf, 6); 292 global_height = ExtractWord(buf, 8); 293 294 // colormap info 295 int ch = ExtractByte(buf, 10); 296 if ((ch & COLORMAPMASK) == 0) { 297 // no global colormap so make up our own 298 // If there is a local colormap, it will override what we 299 // have here. If there is not a local colormap, the rules 300 // for GIF89 say that we can use whatever colormap we want. 301 // This means that we should probably put in a full 256 colormap 302 // at some point. REMIND! 303 num_global_colors = 2; 304 global_bgpixel = 0; 305 global_colormap = new byte[2*3]; 306 global_colormap[0] = global_colormap[1] = global_colormap[2] = (byte)0; 307 global_colormap[3] = global_colormap[4] = global_colormap[5] = (byte)255; 308 309 } 310 else { 311 num_global_colors = 1 << ((ch & 0x7) + 1); 312 313 global_bgpixel = ExtractByte(buf, 11); 314 315 if (buf[12] != 0) { 316 props.put("aspectratio", ""+((ExtractByte(buf, 12) + 15) / 64.0)); 317 } 318 319 // Read colors 320 global_colormap = new byte[num_global_colors * 3]; 321 if (readBytes(global_colormap, 0, num_global_colors * 3) != 0) { 322 throw new IOException(); 323 } 324 } 325 input.mark(Integer.MAX_VALUE); // set this mark in case this is an animated GIF 326 } 327 328 /** 329 * The ImageConsumer hints flag for a non-interlaced GIF image. 330 */ 331 private static final int normalflags = 332 ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES | 333 ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME; 334 335 /** 336 * The ImageConsumer hints flag for an interlaced GIF image. 337 */ 338 private static final int interlaceflags = 339 ImageConsumer.RANDOMPIXELORDER | ImageConsumer.COMPLETESCANLINES | 340 ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME; 341 342 private short prefix[] = new short[4096]; 343 private byte suffix[] = new byte[4096]; 344 private byte outCode[] = new byte[4097]; 345 346 private static native void initIDs(); 347 348 static { 349 /* ensure that the necessary native libraries are loaded */ 350 NativeLibLoader.loadLibraries(); 351 initIDs(); 352 } 353 354 private native boolean parseImage(int x, int y, int width, int height, 355 boolean interlace, int initCodeSize, 356 byte block[], byte rasline[], 357 IndexColorModel model); 358 359 private int sendPixels(int x, int y, int width, int height, 360 byte rasline[], ColorModel model) { 361 int rasbeg, rasend, x2; 362 if (y < 0) { 363 height += y; 364 y = 0; 365 } 366 if (y + height > global_height) { 367 height = global_height - y; 368 } 369 if (height <= 0) { 370 return 1; 371 } 372 // rasline[0] == pixel at coordinate (x,y) 373 // rasline[width] == pixel at coordinate (x+width, y) 374 if (x < 0) { 375 rasbeg = -x; 376 width += x; // same as (width -= rasbeg) 377 x2 = 0; // same as (x2 = x + rasbeg) 378 } else { 379 rasbeg = 0; 380 // width -= 0; // same as (width -= rasbeg) 381 x2 = x; // same as (x2 = x + rasbeg) 382 } 383 // rasline[rasbeg] == pixel at coordinate (x2,y) 384 // rasline[width] == pixel at coordinate (x+width, y) 385 // rasline[rasbeg + width] == pixel at coordinate (x2+width, y) 386 if (x2 + width > global_width) { 387 width = global_width - x2; 388 } 389 if (width <= 0) { 390 return 1; 391 } 392 rasend = rasbeg + width; 393 // rasline[rasbeg] == pixel at coordinate (x2,y) 394 // rasline[rasend] == pixel at coordinate (x2+width, y) 395 int off = y * global_width + x2; 396 boolean save = (curframe.disposal_method == GifFrame.DISPOSAL_SAVE); 397 if (trans_pixel >= 0 && !curframe.initialframe) { 398 if (saved_image != null && model.equals(saved_model)) { 399 for (int i = rasbeg; i < rasend; i++, off++) { 400 byte pixel = rasline[i]; 401 if ((pixel & 0xff) == trans_pixel) { 402 rasline[i] = saved_image[off]; 403 } else if (save) { 404 saved_image[off] = pixel; 405 } 406 } 407 } else { 408 // We have to do this the hard way - only transmit 409 // the non-transparent sections of the line... 410 // Fix for 6301050: the interlacing is ignored in this case 411 // in order to avoid artefacts in case of animated images. 412 int runstart = -1; 413 int count = 1; 414 for (int i = rasbeg; i < rasend; i++, off++) { 415 byte pixel = rasline[i]; 416 if ((pixel & 0xff) == trans_pixel) { 417 if (runstart >= 0) { 418 count = setPixels(x + runstart, y, 419 i - runstart, 1, 420 model, rasline, 421 runstart, 0); 422 if (count == 0) { 423 break; 424 } 425 } 426 runstart = -1; 427 } else { 428 if (runstart < 0) { 429 runstart = i; 430 } 431 if (save) { 432 saved_image[off] = pixel; 433 } 434 } 435 } 436 if (runstart >= 0) { 437 count = setPixels(x + runstart, y, 438 rasend - runstart, 1, 439 model, rasline, 440 runstart, 0); 441 } 442 return count; 443 } 444 } else if (save) { 445 System.arraycopy(rasline, rasbeg, saved_image, off, width); 446 } 447 int count = setPixels(x2, y, width, height, model, 448 rasline, rasbeg, 0); 449 return count; 450 } 451 452 /** 453 * Read Image data 454 */ 455 private boolean readImage(boolean first, int disposal_method, int delay) 456 throws IOException 457 { 458 if (curframe != null && !curframe.dispose()) { 459 abort(); 460 return false; 461 } 462 463 long tm = 0; 464 465 if (verbose) { 466 tm = System.currentTimeMillis(); 467 } 468 469 // Allocate the buffer 470 byte block[] = new byte[256 + 3]; 471 472 // Read the image descriptor 473 if (readBytes(block, 0, 10) != 0) { 474 throw new IOException(); 475 } 476 int x = ExtractWord(block, 0); 477 int y = ExtractWord(block, 2); 478 int width = ExtractWord(block, 4); 479 int height = ExtractWord(block, 6); 480 481 /* 482 * Majority of gif images have 483 * same logical screen and frame dimensions. 484 * Also, Photoshop and Mozilla seem to use the logical 485 * screen dimension (from the global stream header) 486 * if frame dimension is invalid. 487 * 488 * We use similar heuristic and trying to recover 489 * frame width from logical screen dimension and 490 * frame offset. 491 */ 492 if (width == 0 && global_width != 0) { 493 width = global_width - x; 494 } 495 if (height == 0 && global_height != 0) { 496 height = global_height - y; 497 } 498 499 boolean interlace = (block[8] & INTERLACEMASK) != 0; 500 501 IndexColorModel model = global_model; 502 503 if ((block[8] & COLORMAPMASK) != 0) { 504 // We read one extra byte above so now when we must 505 // transfer that byte as the first colormap byte 506 // and manually read the code size when we are done 507 int num_local_colors = 1 << ((block[8] & 0x7) + 1); 508 509 // Read local colors 510 byte[] local_colormap = new byte[num_local_colors * 3]; 511 local_colormap[0] = block[9]; 512 if (readBytes(local_colormap, 1, num_local_colors * 3 - 1) != 0) { 513 throw new IOException(); 514 } 515 516 // Now read the "real" code size byte which follows 517 // the local color table 518 if (readBytes(block, 9, 1) != 0) { 519 throw new IOException(); 520 } 521 if (trans_pixel >= num_local_colors) { 522 // Fix for 4233748: extend colormap to contain transparent pixel 523 num_local_colors = trans_pixel + 1; 524 local_colormap = grow_colormap(local_colormap, num_local_colors); 525 } 526 model = new IndexColorModel(8, num_local_colors, local_colormap, 527 0, false, trans_pixel); 528 } else if (model == null 529 || trans_pixel != model.getTransparentPixel()) { 530 if (trans_pixel >= num_global_colors) { 531 // Fix for 4233748: extend colormap to contain transparent pixel 532 num_global_colors = trans_pixel + 1; 533 global_colormap = grow_colormap(global_colormap, num_global_colors); 534 } 535 model = new IndexColorModel(8, num_global_colors, global_colormap, 536 0, false, trans_pixel); 537 global_model = model; 538 } 539 540 // Notify the consumers 541 if (first) { 542 if (global_width == 0) global_width = width; 543 if (global_height == 0) global_height = height; 544 545 setDimensions(global_width, global_height); 546 setProperties(props); 547 setColorModel(model); 548 headerComplete(); 549 } 550 551 if (disposal_method == GifFrame.DISPOSAL_SAVE && saved_image == null) { 552 saved_image = new byte[global_width * global_height]; 553 /* 554 * If height of current image is smaller than the global height, 555 * fill the gap with transparent pixels. 556 */ 557 if ((height < global_height) && (model != null)) { 558 byte tpix = (byte)model.getTransparentPixel(); 559 if (tpix >= 0) { 560 byte trans_rasline[] = new byte[global_width]; 561 for (int i=0; i<global_width;i++) { 562 trans_rasline[i] = tpix; 563 } 564 565 setPixels(0, 0, global_width, y, 566 model, trans_rasline, 0, 0); 567 setPixels(0, y+height, global_width, 568 global_height-height-y, model, trans_rasline, 569 0, 0); 570 } 571 } 572 } 573 574 int hints = (interlace ? interlaceflags : normalflags); 575 setHints(hints); 576 577 curframe = new GifFrame(this, disposal_method, delay, 578 (curframe == null), model, 579 x, y, width, height); 580 581 // allocate the raster data 582 byte rasline[] = new byte[width]; 583 584 if (verbose) { 585 System.out.print("Reading a " + width + " by " + height + " " + 586 (interlace ? "" : "non-") + "interlaced image..."); 587 } 588 int initCodeSize = ExtractByte(block, 9); 589 if (initCodeSize >= 12) { 590 if (verbose) { 591 System.out.println("Invalid initial code size: " + 592 initCodeSize); 593 } 594 return false; 595 } 596 boolean ret = parseImage(x, y, width, height, 597 interlace, initCodeSize, 598 block, rasline, model); 599 600 if (!ret) { 601 abort(); 602 } 603 604 if (verbose) { 605 System.out.println("done in " 606 + (System.currentTimeMillis() - tm) 607 + "ms"); 608 } 609 610 return ret; 611 } 612 613 public static byte[] grow_colormap(byte[] colormap, int newlen) { 614 byte[] newcm = new byte[newlen * 3]; 615 System.arraycopy(colormap, 0, newcm, 0, colormap.length); 616 return newcm; 617 } 618 } 619 620 class GifFrame { 621 private static final boolean verbose = false; 622 private static IndexColorModel trans_model; 623 624 static final int DISPOSAL_NONE = 0x00; 625 static final int DISPOSAL_SAVE = 0x01; 626 static final int DISPOSAL_BGCOLOR = 0x02; 627 static final int DISPOSAL_PREVIOUS = 0x03; 628 629 GifImageDecoder decoder; 630 631 int disposal_method; 632 int delay; 633 634 IndexColorModel model; 635 636 int x; 637 int y; 638 int width; 639 int height; 640 641 boolean initialframe; 642 643 public GifFrame(GifImageDecoder id, int dm, int dl, boolean init, 644 IndexColorModel cm, int x, int y, int w, int h) { 645 this.decoder = id; 646 this.disposal_method = dm; 647 this.delay = dl; 648 this.model = cm; 649 this.initialframe = init; 650 this.x = x; 651 this.y = y; 652 this.width = w; 653 this.height = h; 654 } 655 656 private void setPixels(int x, int y, int w, int h, 657 ColorModel cm, byte[] pix, int off, int scan) { 658 decoder.setPixels(x, y, w, h, cm, pix, off, scan); 659 } 660 661 public boolean dispose() { 662 if (decoder.imageComplete(ImageConsumer.SINGLEFRAMEDONE, false) == 0) { 663 return false; 664 } else { 665 if (delay > 0) { 666 try { 667 if (verbose) { 668 System.out.println("sleeping: "+delay); 669 } 670 Thread.sleep(delay); 671 } catch (InterruptedException e) { 672 return false; 673 } 674 } else { 675 Thread.yield(); 676 } 677 678 if (verbose && disposal_method != 0) { 679 System.out.println("disposal method: "+disposal_method); 680 } 681 682 int global_width = decoder.global_width; 683 int global_height = decoder.global_height; 684 685 if (x < 0) { 686 width += x; 687 x = 0; 688 } 689 if (x + width > global_width) { 690 width = global_width - x; 691 } 692 if (width <= 0) { 693 disposal_method = DISPOSAL_NONE; 694 } else { 695 if (y < 0) { 696 height += y; 697 y = 0; 698 } 699 if (y + height > global_height) { 700 height = global_height - y; 701 } 702 if (height <= 0) { 703 disposal_method = DISPOSAL_NONE; 704 } 705 } 706 707 switch (disposal_method) { 708 case DISPOSAL_PREVIOUS: 709 byte[] saved_image = decoder.saved_image; 710 IndexColorModel saved_model = decoder.saved_model; 711 if (saved_image != null) { 712 setPixels(x, y, width, height, 713 saved_model, saved_image, 714 y * global_width + x, global_width); 715 } 716 break; 717 case DISPOSAL_BGCOLOR: 718 byte tpix; 719 if (model.getTransparentPixel() < 0) { 720 model = trans_model; 721 if (model == null) { 722 model = new IndexColorModel(8, 1, 723 new byte[4], 0, true); 724 trans_model = model; 725 } 726 tpix = 0; 727 } else { 728 tpix = (byte) model.getTransparentPixel(); 729 } 730 byte[] rasline = new byte[width]; 731 if (tpix != 0) { 732 for (int i = 0; i < width; i++) { 733 rasline[i] = tpix; 734 } 735 } 736 737 // clear saved_image using transparent pixels 738 // this will be used as the background in the next display 739 if( decoder.saved_image != null ) { 740 for( int i = 0; i < global_width * global_height; i ++ ) 741 decoder.saved_image[i] = tpix; 742 } 743 744 setPixels(x, y, width, height, model, rasline, 0, 0); 745 break; 746 case DISPOSAL_SAVE: 747 decoder.saved_model = model; 748 break; 749 } 750 } 751 return true; 752 } 753 }