1 /* 2 * Copyright (c) 1995, 2018, 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.Hashtable; 35 import java.io.InputStream; 36 import java.io.IOException; 37 import java.awt.image.*; 38 39 /** 40 * Gif Image converter 41 * 42 * @author Arthur van Hoff 43 * @author Jim Graham 44 */ 45 public class GifImageDecoder extends ImageDecoder { 46 private static final boolean verbose = false; 47 48 private static final int IMAGESEP = 0x2c; 49 private static final int EXBLOCK = 0x21; 50 private static final int EX_GRAPHICS_CONTROL= 0xf9; 51 private static final int EX_COMMENT = 0xfe; 52 private static final int EX_APPLICATION = 0xff; 53 private static final int TERMINATOR = 0x3b; 54 private static final int TRANSPARENCYMASK = 0x01; 55 private static final int INTERLACEMASK = 0x40; 56 private static final int COLORMAPMASK = 0x80; 57 58 int num_global_colors; 59 byte[] global_colormap; 60 int trans_pixel = -1; 61 IndexColorModel global_model; 62 63 Hashtable<String, Object> props = new Hashtable<>(); 64 65 byte[] saved_image; 66 IndexColorModel saved_model; 67 68 int global_width; 69 int global_height; 70 int global_bgpixel; 71 72 GifFrame curframe; 73 GifImageDecoder(InputStreamImageSource src, InputStream is)74 public GifImageDecoder(InputStreamImageSource src, InputStream is) { 75 super(src, is); 76 } 77 78 /** 79 * An error has occurred. Throw an exception. 80 */ error(String s1)81 private static void error(String s1) throws ImageFormatException { 82 throw new ImageFormatException(s1); 83 } 84 85 /** 86 * Read a number of bytes into a buffer. 87 * @return number of bytes that were not read due to EOF or error 88 */ readBytes(byte[] buf, int off, int len)89 private int readBytes(byte[] buf, int off, int len) { 90 while (len > 0) { 91 try { 92 int n = input.read(buf, off, len); 93 if (n < 0) { 94 break; 95 } 96 off += n; 97 len -= n; 98 } catch (IOException e) { 99 break; 100 } 101 } 102 return len; 103 } 104 ExtractByte(byte[] buf, int off)105 private static final int ExtractByte(byte[] buf, int off) { 106 return (buf[off] & 0xFF); 107 } 108 ExtractWord(byte[] buf, int off)109 private static final int ExtractWord(byte[] buf, int off) { 110 return (buf[off] & 0xFF) | ((buf[off + 1] & 0xFF) << 8); 111 } 112 113 /** 114 * produce an image from the stream. 115 */ 116 @SuppressWarnings({"fallthrough", "deprecation"}) produceImage()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 // Fall through 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 */ readHeader()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 initIDs()346 private static native void initIDs(); 347 348 static { 349 /* ensure that the necessary native libraries are loaded */ NativeLibLoader.loadLibraries()350 NativeLibLoader.loadLibraries(); initIDs()351 initIDs(); 352 } 353 parseImage(int x, int y, int width, int height, boolean interlace, int initCodeSize, byte[] block, byte[] rasline, IndexColorModel model)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 sendPixels(int x, int y, int width, int height, byte[] rasline, ColorModel model)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 */ readImage(boolean first, int disposal_method, int delay)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 grow_colormap(byte[] colormap, int newlen)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 623 static final int DISPOSAL_NONE = 0x00; 624 static final int DISPOSAL_SAVE = 0x01; 625 static final int DISPOSAL_BGCOLOR = 0x02; 626 static final int DISPOSAL_PREVIOUS = 0x03; 627 628 GifImageDecoder decoder; 629 630 int disposal_method; 631 int delay; 632 633 IndexColorModel model; 634 635 int x; 636 int y; 637 int width; 638 int height; 639 640 boolean initialframe; 641 GifFrame(GifImageDecoder id, int dm, int dl, boolean init, IndexColorModel cm, int x, int y, int w, int h)642 public GifFrame(GifImageDecoder id, int dm, int dl, boolean init, 643 IndexColorModel cm, int x, int y, int w, int h) { 644 this.decoder = id; 645 this.disposal_method = dm; 646 this.delay = dl; 647 this.model = cm; 648 this.initialframe = init; 649 this.x = x; 650 this.y = y; 651 this.width = w; 652 this.height = h; 653 } 654 setPixels(int x, int y, int w, int h, ColorModel cm, byte[] pix, int off, int scan)655 private void setPixels(int x, int y, int w, int h, 656 ColorModel cm, byte[] pix, int off, int scan) { 657 decoder.setPixels(x, y, w, h, cm, pix, off, scan); 658 } 659 dispose()660 public boolean dispose() { 661 if (decoder.imageComplete(ImageConsumer.SINGLEFRAMEDONE, false) == 0) { 662 return false; 663 } else { 664 if (delay > 0) { 665 try { 666 if (verbose) { 667 System.out.println("sleeping: "+delay); 668 } 669 Thread.sleep(delay); 670 } catch (InterruptedException e) { 671 return false; 672 } 673 } else { 674 Thread.yield(); 675 } 676 677 if (verbose && disposal_method != 0) { 678 System.out.println("disposal method: "+disposal_method); 679 } 680 681 int global_width = decoder.global_width; 682 int global_height = decoder.global_height; 683 684 if (x < 0) { 685 width += x; 686 x = 0; 687 } 688 if (x + width > global_width) { 689 width = global_width - x; 690 } 691 if (width <= 0) { 692 disposal_method = DISPOSAL_NONE; 693 } else { 694 if (y < 0) { 695 height += y; 696 y = 0; 697 } 698 if (y + height > global_height) { 699 height = global_height - y; 700 } 701 if (height <= 0) { 702 disposal_method = DISPOSAL_NONE; 703 } 704 } 705 706 switch (disposal_method) { 707 case DISPOSAL_PREVIOUS: 708 byte[] saved_image = decoder.saved_image; 709 IndexColorModel saved_model = decoder.saved_model; 710 if (saved_image != null) { 711 setPixels(x, y, width, height, 712 saved_model, saved_image, 713 y * global_width + x, global_width); 714 } 715 break; 716 case DISPOSAL_BGCOLOR: 717 byte tpix; 718 if (model.getTransparentPixel() < 0) { 719 tpix = 0; 720 } else { 721 tpix = (byte) model.getTransparentPixel(); 722 } 723 byte[] rasline = new byte[width]; 724 if (tpix != 0) { 725 for (int i = 0; i < width; i++) { 726 rasline[i] = tpix; 727 } 728 } 729 730 // clear saved_image using transparent pixels 731 // this will be used as the background in the next display 732 if( decoder.saved_image != null ) { 733 for( int i = 0; i < global_width * global_height; i ++ ) 734 decoder.saved_image[i] = tpix; 735 } 736 737 setPixels(x, y, width, height, model, rasline, 0, 0); 738 break; 739 case DISPOSAL_SAVE: 740 decoder.saved_model = model; 741 break; 742 } 743 } 744 return true; 745 } 746 } 747