1 /* 2 * Copyright (c) 2000, 2020, 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 package com.sun.imageio.plugins.gif; 27 28 import java.awt.Point; 29 import java.awt.Rectangle; 30 import java.awt.image.BufferedImage; 31 import java.awt.image.DataBuffer; 32 import java.awt.image.WritableRaster; 33 import java.io.EOFException; 34 import java.io.IOException; 35 import java.nio.ByteOrder; 36 import java.util.ArrayList; 37 import java.util.Iterator; 38 import java.util.List; 39 import javax.imageio.IIOException; 40 import javax.imageio.ImageReader; 41 import javax.imageio.ImageReadParam; 42 import javax.imageio.ImageTypeSpecifier; 43 import javax.imageio.metadata.IIOMetadata; 44 import javax.imageio.spi.ImageReaderSpi; 45 import javax.imageio.stream.ImageInputStream; 46 import com.sun.imageio.plugins.common.ReaderUtil; 47 import java.awt.image.ColorModel; 48 import java.awt.image.IndexColorModel; 49 import java.awt.image.MultiPixelPackedSampleModel; 50 import java.awt.image.PixelInterleavedSampleModel; 51 import java.awt.image.SampleModel; 52 53 public class GIFImageReader extends ImageReader { 54 55 // The current ImageInputStream source. 56 ImageInputStream stream = null; 57 58 // Per-stream settings 59 60 // True if the file header including stream metadata has been read. 61 boolean gotHeader = false; 62 63 // Global metadata, read once per input setting. 64 GIFStreamMetadata streamMetadata = null; 65 66 // The current image index 67 int currIndex = -1; 68 69 // Metadata for image at 'currIndex', or null. 70 GIFImageMetadata imageMetadata = null; 71 72 // A List of Longs indicating the stream positions of the 73 // start of the metadata for each image. Entries are added 74 // as needed. 75 List<Long> imageStartPosition = new ArrayList<>(); 76 77 // Length of metadata for image at 'currIndex', valid only if 78 // imageMetadata != null. 79 int imageMetadataLength; 80 81 // The number of images in the stream, if known, otherwise -1. 82 int numImages = -1; 83 84 // Variables used by the LZW decoding process 85 byte[] block = new byte[255]; 86 int blockLength = 0; 87 int bitPos = 0; 88 int nextByte = 0; 89 int initCodeSize; 90 int clearCode; 91 int eofCode; 92 93 // 32-bit lookahead buffer 94 int next32Bits = 0; 95 96 // Try if the end of the data blocks has been found, 97 // and we are simply draining the 32-bit buffer 98 boolean lastBlockFound = false; 99 100 // The image to be written. 101 BufferedImage theImage = null; 102 103 // The image's tile. 104 WritableRaster theTile = null; 105 106 // The image dimensions (from the stream). 107 int width = -1, height = -1; 108 109 // The pixel currently being decoded (in the stream's coordinates). 110 int streamX = -1, streamY = -1; 111 112 // The number of rows decoded 113 int rowsDone = 0; 114 115 // The current interlace pass, starting with 0. 116 int interlacePass = 0; 117 118 private byte[] fallbackColorTable = null; 119 120 // End per-stream settings 121 122 // Constants used to control interlacing. 123 static final int[] interlaceIncrement = { 8, 8, 4, 2, -1 }; 124 static final int[] interlaceOffset = { 0, 4, 2, 1, -1 }; 125 126 public GIFImageReader(ImageReaderSpi originatingProvider) { 127 super(originatingProvider); 128 } 129 130 // Take input from an ImageInputStream 131 public void setInput(Object input, 132 boolean seekForwardOnly, 133 boolean ignoreMetadata) { 134 super.setInput(input, seekForwardOnly, ignoreMetadata); 135 if (input != null) { 136 if (!(input instanceof ImageInputStream)) { 137 throw new IllegalArgumentException 138 ("input not an ImageInputStream!"); 139 } 140 this.stream = (ImageInputStream)input; 141 } else { 142 this.stream = null; 143 } 144 145 // Clear all values based on the previous stream contents 146 resetStreamSettings(); 147 } 148 149 public int getNumImages(boolean allowSearch) throws IIOException { 150 if (stream == null) { 151 throw new IllegalStateException("Input not set!"); 152 } 153 if (seekForwardOnly && allowSearch) { 154 throw new IllegalStateException 155 ("seekForwardOnly and allowSearch can't both be true!"); 156 } 157 158 if (numImages > 0) { 159 return numImages; 160 } 161 if (allowSearch) { 162 this.numImages = locateImage(Integer.MAX_VALUE) + 1; 163 } 164 return numImages; 165 } 166 167 // Throw an IndexOutOfBoundsException if index < minIndex, 168 // and bump minIndex if required. 169 private void checkIndex(int imageIndex) { 170 if (imageIndex < minIndex) { 171 throw new IndexOutOfBoundsException("imageIndex < minIndex!"); 172 } 173 if (seekForwardOnly) { 174 minIndex = imageIndex; 175 } 176 } 177 178 public int getWidth(int imageIndex) throws IIOException { 179 checkIndex(imageIndex); 180 181 int index = locateImage(imageIndex); 182 if (index != imageIndex) { 183 throw new IndexOutOfBoundsException(); 184 } 185 readMetadata(); 186 return imageMetadata.imageWidth; 187 } 188 189 public int getHeight(int imageIndex) throws IIOException { 190 checkIndex(imageIndex); 191 192 int index = locateImage(imageIndex); 193 if (index != imageIndex) { 194 throw new IndexOutOfBoundsException(); 195 } 196 readMetadata(); 197 return imageMetadata.imageHeight; 198 } 199 200 // We don't check all parameters as ImageTypeSpecifier.createIndexed do 201 // since this method is private and we pass consistent data here 202 private ImageTypeSpecifier createIndexed(byte[] r, byte[] g, byte[] b, 203 int bits) { 204 ColorModel colorModel; 205 if (imageMetadata.transparentColorFlag) { 206 // Some files erroneously have a transparent color index 207 // of 255 even though there are fewer than 256 colors. 208 int idx = Math.min(imageMetadata.transparentColorIndex, 209 r.length - 1); 210 colorModel = new IndexColorModel(bits, r.length, r, g, b, idx); 211 } else { 212 colorModel = new IndexColorModel(bits, r.length, r, g, b); 213 } 214 215 SampleModel sampleModel; 216 if (bits == 8) { 217 int[] bandOffsets = {0}; 218 sampleModel = 219 new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, 220 1, 1, 1, 1, 221 bandOffsets); 222 } else { 223 sampleModel = 224 new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, 225 1, 1, bits); 226 } 227 return new ImageTypeSpecifier(colorModel, sampleModel); 228 } 229 230 public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) 231 throws IIOException { 232 checkIndex(imageIndex); 233 234 int index = locateImage(imageIndex); 235 if (index != imageIndex) { 236 throw new IndexOutOfBoundsException(); 237 } 238 readMetadata(); 239 240 List<ImageTypeSpecifier> l = new ArrayList<>(1); 241 242 byte[] colorTable; 243 if (imageMetadata.localColorTable != null) { 244 colorTable = imageMetadata.localColorTable; 245 fallbackColorTable = imageMetadata.localColorTable; 246 } else { 247 colorTable = streamMetadata.globalColorTable; 248 } 249 250 if (colorTable == null) { 251 if (fallbackColorTable == null) { 252 this.processWarningOccurred("Use default color table."); 253 254 // no color table, the spec allows to use any palette. 255 fallbackColorTable = getDefaultPalette(); 256 } 257 258 colorTable = fallbackColorTable; 259 } 260 261 // Normalize color table length to 2^1, 2^2, 2^4, or 2^8 262 int length = colorTable.length/3; 263 int bits; 264 if (length == 2) { 265 bits = 1; 266 } else if (length == 4) { 267 bits = 2; 268 } else if (length == 8 || length == 16) { 269 // Bump from 3 to 4 bits 270 bits = 4; 271 } else { 272 // Bump to 8 bits 273 bits = 8; 274 } 275 int lutLength = 1 << bits; 276 byte[] r = new byte[lutLength]; 277 byte[] g = new byte[lutLength]; 278 byte[] b = new byte[lutLength]; 279 280 // Entries from length + 1 to lutLength - 1 will be 0 281 int rgbIndex = 0; 282 for (int i = 0; i < length; i++) { 283 r[i] = colorTable[rgbIndex++]; 284 g[i] = colorTable[rgbIndex++]; 285 b[i] = colorTable[rgbIndex++]; 286 } 287 288 l.add(createIndexed(r, g, b, bits)); 289 return l.iterator(); 290 } 291 292 public ImageReadParam getDefaultReadParam() { 293 return new ImageReadParam(); 294 } 295 296 public IIOMetadata getStreamMetadata() throws IIOException { 297 readHeader(); 298 return streamMetadata; 299 } 300 301 public IIOMetadata getImageMetadata(int imageIndex) throws IIOException { 302 checkIndex(imageIndex); 303 304 int index = locateImage(imageIndex); 305 if (index != imageIndex) { 306 throw new IndexOutOfBoundsException("Bad image index!"); 307 } 308 readMetadata(); 309 return imageMetadata; 310 } 311 312 // BEGIN LZW STUFF 313 314 private void initNext32Bits() { 315 next32Bits = block[0] & 0xff; 316 next32Bits |= (block[1] & 0xff) << 8; 317 next32Bits |= (block[2] & 0xff) << 16; 318 next32Bits |= block[3] << 24; 319 nextByte = 4; 320 } 321 322 // Load a block (1-255 bytes) at a time, and maintain 323 // a 32-bit lookahead buffer that is filled from the left 324 // and extracted from the right. 325 // 326 // When the last block is found, we continue to 327 // 328 private int getCode(int codeSize, int codeMask) throws IOException { 329 if (bitPos + codeSize > 32) { 330 return eofCode; // No more data available 331 } 332 333 int code = (next32Bits >> bitPos) & codeMask; 334 bitPos += codeSize; 335 336 // Shift in a byte of new data at a time 337 while (bitPos >= 8 && !lastBlockFound) { 338 next32Bits >>>= 8; 339 bitPos -= 8; 340 341 // Check if current block is out of bytes 342 if (nextByte >= blockLength) { 343 // Get next block size 344 blockLength = stream.readUnsignedByte(); 345 if (blockLength == 0) { 346 lastBlockFound = true; 347 return code; 348 } else { 349 int left = blockLength; 350 int off = 0; 351 while (left > 0) { 352 int nbytes = stream.read(block, off, left); 353 if (nbytes == -1) { 354 throw new IIOException("Invalid block length for " + 355 "LZW encoded image data"); 356 } 357 off += nbytes; 358 left -= nbytes; 359 } 360 nextByte = 0; 361 } 362 } 363 364 next32Bits |= block[nextByte++] << 24; 365 } 366 367 return code; 368 } 369 370 public void initializeStringTable(int[] prefix, 371 byte[] suffix, 372 byte[] initial, 373 int[] length) { 374 int numEntries = 1 << initCodeSize; 375 for (int i = 0; i < numEntries; i++) { 376 prefix[i] = -1; 377 suffix[i] = (byte)i; 378 initial[i] = (byte)i; 379 length[i] = 1; 380 } 381 382 // Fill in the entire table for robustness against 383 // out-of-sequence codes. 384 for (int i = numEntries; i < 4096; i++) { 385 prefix[i] = -1; 386 length[i] = 1; 387 } 388 389 // tableIndex = numEntries + 2; 390 // codeSize = initCodeSize + 1; 391 // codeMask = (1 << codeSize) - 1; 392 } 393 394 Rectangle sourceRegion; 395 int sourceXSubsampling; 396 int sourceYSubsampling; 397 int sourceMinProgressivePass; 398 int sourceMaxProgressivePass; 399 400 Point destinationOffset; 401 Rectangle destinationRegion; 402 403 // Used only if IIOReadUpdateListeners are present 404 int updateMinY; 405 int updateYStep; 406 407 boolean decodeThisRow = true; 408 int destY = 0; 409 410 byte[] rowBuf; 411 412 private void outputRow() { 413 // Clip against ImageReadParam 414 int width = Math.min(sourceRegion.width, 415 destinationRegion.width*sourceXSubsampling); 416 int destX = destinationRegion.x; 417 418 if (sourceXSubsampling == 1) { 419 theTile.setDataElements(destX, destY, width, 1, rowBuf); 420 } else { 421 for (int x = 0; x < width; x += sourceXSubsampling, destX++) { 422 theTile.setSample(destX, destY, 0, rowBuf[x] & 0xff); 423 } 424 } 425 426 // Update IIOReadUpdateListeners, if any 427 if (updateListeners != null) { 428 int[] bands = { 0 }; 429 // updateYStep will have been initialized if 430 // updateListeners is non-null 431 processImageUpdate(theImage, 432 destX, destY, 433 width, 1, 1, updateYStep, 434 bands); 435 } 436 } 437 438 private void computeDecodeThisRow() { 439 this.decodeThisRow = 440 (destY < destinationRegion.y + destinationRegion.height) && 441 (streamY >= sourceRegion.y) && 442 (streamY < sourceRegion.y + sourceRegion.height) && 443 (((streamY - sourceRegion.y) % sourceYSubsampling) == 0); 444 } 445 446 private void outputPixels(byte[] string, int len) { 447 if (interlacePass < sourceMinProgressivePass || 448 interlacePass > sourceMaxProgressivePass) { 449 return; 450 } 451 452 for (int i = 0; i < len; i++) { 453 if (streamX >= sourceRegion.x) { 454 rowBuf[streamX - sourceRegion.x] = string[i]; 455 } 456 457 // Process end-of-row 458 ++streamX; 459 if (streamX == width) { 460 // Update IIOReadProgressListeners 461 ++rowsDone; 462 processImageProgress(100.0F*rowsDone/height); 463 if (abortRequested()) { 464 return; 465 } 466 467 if (decodeThisRow) { 468 outputRow(); 469 } 470 471 streamX = 0; 472 if (imageMetadata.interlaceFlag) { 473 streamY += interlaceIncrement[interlacePass]; 474 if (streamY >= height) { 475 // Inform IIOReadUpdateListeners of end of pass 476 if (updateListeners != null) { 477 processPassComplete(theImage); 478 } 479 480 ++interlacePass; 481 if (interlacePass > sourceMaxProgressivePass) { 482 return; 483 } 484 streamY = interlaceOffset[interlacePass]; 485 startPass(interlacePass); 486 } 487 } else { 488 ++streamY; 489 } 490 491 // Determine whether pixels from this row will 492 // be written to the destination 493 this.destY = destinationRegion.y + 494 (streamY - sourceRegion.y)/sourceYSubsampling; 495 computeDecodeThisRow(); 496 } 497 } 498 } 499 500 // END LZW STUFF 501 502 private void readHeader() throws IIOException { 503 if (gotHeader) { 504 return; 505 } 506 if (stream == null) { 507 throw new IllegalStateException("Input not set!"); 508 } 509 510 // Create an object to store the stream metadata 511 this.streamMetadata = new GIFStreamMetadata(); 512 513 try { 514 stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); 515 516 byte[] signature = new byte[6]; 517 stream.readFully(signature); 518 519 StringBuilder version = new StringBuilder(3); 520 version.append((char)signature[3]); 521 version.append((char)signature[4]); 522 version.append((char)signature[5]); 523 streamMetadata.version = version.toString(); 524 525 streamMetadata.logicalScreenWidth = stream.readUnsignedShort(); 526 streamMetadata.logicalScreenHeight = stream.readUnsignedShort(); 527 528 int packedFields = stream.readUnsignedByte(); 529 boolean globalColorTableFlag = (packedFields & 0x80) != 0; 530 streamMetadata.colorResolution = ((packedFields >> 4) & 0x7) + 1; 531 streamMetadata.sortFlag = (packedFields & 0x8) != 0; 532 int numGCTEntries = 1 << ((packedFields & 0x7) + 1); 533 534 streamMetadata.backgroundColorIndex = stream.readUnsignedByte(); 535 streamMetadata.pixelAspectRatio = stream.readUnsignedByte(); 536 537 if (globalColorTableFlag) { 538 streamMetadata.globalColorTable = new byte[3*numGCTEntries]; 539 stream.readFully(streamMetadata.globalColorTable); 540 } else { 541 streamMetadata.globalColorTable = null; 542 } 543 544 // Found position of metadata for image 0 545 imageStartPosition.add(Long.valueOf(stream.getStreamPosition())); 546 } catch (IOException e) { 547 throw new IIOException("I/O error reading header!", e); 548 } 549 550 gotHeader = true; 551 } 552 553 private boolean skipImage() throws IIOException { 554 // Stream must be at the beginning of an image descriptor 555 // upon exit 556 557 try { 558 while (true) { 559 int blockType = stream.readUnsignedByte(); 560 561 if (blockType == 0x2c) { 562 stream.skipBytes(8); 563 564 int packedFields = stream.readUnsignedByte(); 565 if ((packedFields & 0x80) != 0) { 566 // Skip color table if any 567 int bits = (packedFields & 0x7) + 1; 568 stream.skipBytes(3*(1 << bits)); 569 } 570 571 stream.skipBytes(1); 572 573 int length = 0; 574 do { 575 length = stream.readUnsignedByte(); 576 stream.skipBytes(length); 577 } while (length > 0); 578 579 return true; 580 } else if (blockType == 0x3b) { 581 return false; 582 } else if (blockType == 0x21) { 583 int label = stream.readUnsignedByte(); 584 585 int length = 0; 586 do { 587 length = stream.readUnsignedByte(); 588 stream.skipBytes(length); 589 } while (length > 0); 590 } else if (blockType == 0x0) { 591 // EOF 592 return false; 593 } else { 594 int length = 0; 595 do { 596 length = stream.readUnsignedByte(); 597 stream.skipBytes(length); 598 } while (length > 0); 599 } 600 } 601 } catch (EOFException e) { 602 return false; 603 } catch (IOException e) { 604 throw new IIOException("I/O error locating image!", e); 605 } 606 } 607 608 private int locateImage(int imageIndex) throws IIOException { 609 readHeader(); 610 611 try { 612 // Find closest known index 613 int index = Math.min(imageIndex, imageStartPosition.size() - 1); 614 615 // Seek to that position 616 Long l = imageStartPosition.get(index); 617 stream.seek(l.longValue()); 618 619 // Skip images until at desired index or last image found 620 while (index < imageIndex) { 621 if (!skipImage()) { 622 --index; 623 return index; 624 } 625 626 Long l1 = stream.getStreamPosition(); 627 imageStartPosition.add(l1); 628 ++index; 629 } 630 } catch (IOException e) { 631 throw new IIOException("Couldn't seek!", e); 632 } 633 634 if (currIndex != imageIndex) { 635 imageMetadata = null; 636 } 637 currIndex = imageIndex; 638 return imageIndex; 639 } 640 641 // Read blocks of 1-255 bytes, stop at a 0-length block 642 private byte[] concatenateBlocks() throws IOException { 643 byte[] data = new byte[0]; 644 while (true) { 645 int length = stream.readUnsignedByte(); 646 if (length == 0) { 647 break; 648 } 649 byte[] newData = new byte[data.length + length]; 650 System.arraycopy(data, 0, newData, 0, data.length); 651 stream.readFully(newData, data.length, length); 652 data = newData; 653 } 654 655 return data; 656 } 657 658 // Stream must be positioned at start of metadata for 'currIndex' 659 private void readMetadata() throws IIOException { 660 if (stream == null) { 661 throw new IllegalStateException("Input not set!"); 662 } 663 664 try { 665 // Create an object to store the image metadata 666 this.imageMetadata = new GIFImageMetadata(); 667 668 long startPosition = stream.getStreamPosition(); 669 while (true) { 670 int blockType = stream.readUnsignedByte(); 671 if (blockType == 0x2c) { // Image Descriptor 672 imageMetadata.imageLeftPosition = 673 stream.readUnsignedShort(); 674 imageMetadata.imageTopPosition = 675 stream.readUnsignedShort(); 676 imageMetadata.imageWidth = stream.readUnsignedShort(); 677 imageMetadata.imageHeight = stream.readUnsignedShort(); 678 679 int idPackedFields = stream.readUnsignedByte(); 680 boolean localColorTableFlag = 681 (idPackedFields & 0x80) != 0; 682 imageMetadata.interlaceFlag = (idPackedFields & 0x40) != 0; 683 imageMetadata.sortFlag = (idPackedFields & 0x20) != 0; 684 int numLCTEntries = 1 << ((idPackedFields & 0x7) + 1); 685 686 if (localColorTableFlag) { 687 // Read color table if any 688 imageMetadata.localColorTable = 689 new byte[3*numLCTEntries]; 690 stream.readFully(imageMetadata.localColorTable); 691 } else { 692 imageMetadata.localColorTable = null; 693 } 694 695 // Record length of this metadata block 696 this.imageMetadataLength = 697 (int)(stream.getStreamPosition() - startPosition); 698 699 // Now positioned at start of LZW-compressed pixels 700 return; 701 } else if (blockType == 0x21) { // Extension block 702 int label = stream.readUnsignedByte(); 703 704 if (label == 0xf9) { // Graphics Control Extension 705 int gceLength = stream.readUnsignedByte(); // 4 706 int gcePackedFields = stream.readUnsignedByte(); 707 imageMetadata.disposalMethod = 708 (gcePackedFields >> 2) & 0x3; 709 imageMetadata.userInputFlag = 710 (gcePackedFields & 0x2) != 0; 711 imageMetadata.transparentColorFlag = 712 (gcePackedFields & 0x1) != 0; 713 714 imageMetadata.delayTime = stream.readUnsignedShort(); 715 imageMetadata.transparentColorIndex 716 = stream.readUnsignedByte(); 717 718 int terminator = stream.readUnsignedByte(); 719 } else if (label == 0x1) { // Plain text extension 720 int length = stream.readUnsignedByte(); 721 imageMetadata.hasPlainTextExtension = true; 722 imageMetadata.textGridLeft = 723 stream.readUnsignedShort(); 724 imageMetadata.textGridTop = 725 stream.readUnsignedShort(); 726 imageMetadata.textGridWidth = 727 stream.readUnsignedShort(); 728 imageMetadata.textGridHeight = 729 stream.readUnsignedShort(); 730 imageMetadata.characterCellWidth = 731 stream.readUnsignedByte(); 732 imageMetadata.characterCellHeight = 733 stream.readUnsignedByte(); 734 imageMetadata.textForegroundColor = 735 stream.readUnsignedByte(); 736 imageMetadata.textBackgroundColor = 737 stream.readUnsignedByte(); 738 imageMetadata.text = concatenateBlocks(); 739 } else if (label == 0xfe) { // Comment extension 740 byte[] comment = concatenateBlocks(); 741 if (imageMetadata.comments == null) { 742 imageMetadata.comments = new ArrayList<>(); 743 } 744 imageMetadata.comments.add(comment); 745 } else if (label == 0xff) { // Application extension 746 int blockSize = stream.readUnsignedByte(); 747 byte[] applicationID = new byte[8]; 748 byte[] authCode = new byte[3]; 749 750 // read available data 751 byte[] blockData = new byte[blockSize]; 752 stream.readFully(blockData); 753 754 int offset = copyData(blockData, 0, applicationID); 755 offset = copyData(blockData, offset, authCode); 756 757 byte[] applicationData = concatenateBlocks(); 758 759 if (offset < blockSize) { 760 int len = blockSize - offset; 761 byte[] data = 762 new byte[len + applicationData.length]; 763 764 System.arraycopy(blockData, offset, data, 0, len); 765 System.arraycopy(applicationData, 0, data, len, 766 applicationData.length); 767 768 applicationData = data; 769 } 770 771 // Init lists if necessary 772 if (imageMetadata.applicationIDs == null) { 773 imageMetadata.applicationIDs = new ArrayList<>(); 774 imageMetadata.authenticationCodes = 775 new ArrayList<>(); 776 imageMetadata.applicationData = new ArrayList<>(); 777 } 778 imageMetadata.applicationIDs.add(applicationID); 779 imageMetadata.authenticationCodes.add(authCode); 780 imageMetadata.applicationData.add(applicationData); 781 } else { 782 // Skip over unknown extension blocks 783 int length = 0; 784 do { 785 length = stream.readUnsignedByte(); 786 stream.skipBytes(length); 787 } while (length > 0); 788 } 789 } else if (blockType == 0x3b) { // Trailer 790 throw new IndexOutOfBoundsException 791 ("Attempt to read past end of image sequence!"); 792 } else { 793 throw new IIOException("Unexpected block type " + 794 blockType + "!"); 795 } 796 } 797 } catch (IIOException iioe) { 798 throw iioe; 799 } catch (IOException ioe) { 800 throw new IIOException("I/O error reading image metadata!", ioe); 801 } 802 } 803 804 private int copyData(byte[] src, int offset, byte[] dst) { 805 int len = dst.length; 806 int rest = src.length - offset; 807 if (len > rest) { 808 len = rest; 809 } 810 System.arraycopy(src, offset, dst, 0, len); 811 return offset + len; 812 } 813 814 private void startPass(int pass) { 815 if (updateListeners == null || !imageMetadata.interlaceFlag) { 816 return; 817 } 818 819 int y = interlaceOffset[interlacePass]; 820 int yStep = interlaceIncrement[interlacePass]; 821 822 int[] vals = ReaderUtil. 823 computeUpdatedPixels(sourceRegion, 824 destinationOffset, 825 destinationRegion.x, 826 destinationRegion.y, 827 destinationRegion.x + 828 destinationRegion.width - 1, 829 destinationRegion.y + 830 destinationRegion.height - 1, 831 sourceXSubsampling, 832 sourceYSubsampling, 833 0, 834 y, 835 destinationRegion.width, 836 (destinationRegion.height + yStep - 1)/yStep, 837 1, 838 yStep); 839 840 // Initialized updateMinY and updateYStep 841 this.updateMinY = vals[1]; 842 this.updateYStep = vals[5]; 843 844 // Inform IIOReadUpdateListeners of new pass 845 int[] bands = { 0 }; 846 847 processPassStarted(theImage, 848 interlacePass, 849 sourceMinProgressivePass, 850 sourceMaxProgressivePass, 851 0, 852 updateMinY, 853 1, 854 updateYStep, 855 bands); 856 } 857 858 public BufferedImage read(int imageIndex, ImageReadParam param) 859 throws IIOException { 860 if (stream == null) { 861 throw new IllegalStateException("Input not set!"); 862 } 863 checkIndex(imageIndex); 864 865 int index = locateImage(imageIndex); 866 if (index != imageIndex) { 867 throw new IndexOutOfBoundsException("imageIndex out of bounds!"); 868 } 869 870 readMetadata(); 871 872 // A null ImageReadParam means we use the default 873 if (param == null) { 874 param = getDefaultReadParam(); 875 } 876 877 // Initialize the destination image 878 Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex); 879 this.theImage = getDestination(param, 880 imageTypes, 881 imageMetadata.imageWidth, 882 imageMetadata.imageHeight); 883 this.theTile = theImage.getWritableTile(0, 0); 884 this.width = imageMetadata.imageWidth; 885 this.height = imageMetadata.imageHeight; 886 this.streamX = 0; 887 this.streamY = 0; 888 this.rowsDone = 0; 889 this.interlacePass = 0; 890 891 // Get source region, taking subsampling offsets into account, 892 // and clipping against the true source bounds 893 894 this.sourceRegion = new Rectangle(0, 0, 0, 0); 895 this.destinationRegion = new Rectangle(0, 0, 0, 0); 896 computeRegions(param, width, height, theImage, 897 sourceRegion, destinationRegion); 898 this.destinationOffset = new Point(destinationRegion.x, 899 destinationRegion.y); 900 901 this.sourceXSubsampling = param.getSourceXSubsampling(); 902 this.sourceYSubsampling = param.getSourceYSubsampling(); 903 this.sourceMinProgressivePass = 904 Math.max(param.getSourceMinProgressivePass(), 0); 905 this.sourceMaxProgressivePass = 906 Math.min(param.getSourceMaxProgressivePass(), 3); 907 908 this.destY = destinationRegion.y + 909 (streamY - sourceRegion.y)/sourceYSubsampling; 910 computeDecodeThisRow(); 911 912 clearAbortRequest(); 913 // Inform IIOReadProgressListeners of start of image 914 processImageStarted(imageIndex); 915 if (abortRequested()) { 916 processReadAborted(); 917 return theImage; 918 } 919 startPass(0); 920 921 this.rowBuf = new byte[width]; 922 923 try { 924 // Read and decode the image data, fill in theImage 925 this.initCodeSize = stream.readUnsignedByte(); 926 // GIF allows max 8 bpp, so anything larger is bogus for the roots. 927 if (this.initCodeSize < 1 || this.initCodeSize > 8) { 928 throw new IIOException("Bad code size:" + this.initCodeSize); 929 } 930 931 // Read first data block 932 this.blockLength = stream.readUnsignedByte(); 933 int left = blockLength; 934 int off = 0; 935 while (left > 0) { 936 int nbytes = stream.read(block, off, left); 937 if (nbytes == -1) { 938 throw new IIOException("Invalid block length for " + 939 "LZW encoded image data"); 940 } 941 left -= nbytes; 942 off += nbytes; 943 } 944 945 this.bitPos = 0; 946 this.nextByte = 0; 947 this.lastBlockFound = false; 948 this.interlacePass = 0; 949 950 // Init 32-bit buffer 951 initNext32Bits(); 952 953 this.clearCode = 1 << initCodeSize; 954 this.eofCode = clearCode + 1; 955 956 int code, oldCode = 0; 957 958 int[] prefix = new int[4096]; 959 byte[] suffix = new byte[4096]; 960 byte[] initial = new byte[4096]; 961 int[] length = new int[4096]; 962 byte[] string = new byte[4096]; 963 964 initializeStringTable(prefix, suffix, initial, length); 965 int tableIndex = (1 << initCodeSize) + 2; 966 int codeSize = initCodeSize + 1; 967 int codeMask = (1 << codeSize) - 1; 968 969 do { 970 code = getCode(codeSize, codeMask); 971 972 if (code == clearCode) { 973 initializeStringTable(prefix, suffix, initial, length); 974 tableIndex = (1 << initCodeSize) + 2; 975 codeSize = initCodeSize + 1; 976 codeMask = (1 << codeSize) - 1; 977 978 code = getCode(codeSize, codeMask); 979 if (code == eofCode) { 980 // Inform IIOReadProgressListeners of end of image 981 processImageComplete(); 982 return theImage; 983 } 984 } else if (code == eofCode) { 985 // Inform IIOReadProgressListeners of end of image 986 processImageComplete(); 987 return theImage; 988 } else { 989 int newSuffixIndex; 990 if (code < tableIndex) { 991 newSuffixIndex = code; 992 } else { // code == tableIndex 993 newSuffixIndex = oldCode; 994 if (code != tableIndex) { 995 // warning - code out of sequence 996 // possibly data corruption 997 processWarningOccurred("Out-of-sequence code!"); 998 } 999 } 1000 1001 int ti = tableIndex; 1002 int oc = oldCode; 1003 1004 prefix[ti] = oc; 1005 suffix[ti] = initial[newSuffixIndex]; 1006 initial[ti] = initial[oc]; 1007 length[ti] = length[oc] + 1; 1008 1009 ++tableIndex; 1010 if ((tableIndex == (1 << codeSize)) && 1011 (tableIndex < 4096)) { 1012 ++codeSize; 1013 codeMask = (1 << codeSize) - 1; 1014 } 1015 } 1016 1017 // Reverse code 1018 int c = code; 1019 int len = length[c]; 1020 for (int i = len - 1; i >= 0; i--) { 1021 string[i] = suffix[c]; 1022 c = prefix[c]; 1023 } 1024 1025 outputPixels(string, len); 1026 oldCode = code; 1027 } while (!abortRequested()); 1028 1029 processReadAborted(); 1030 return theImage; 1031 } catch (IOException e) { 1032 throw new IIOException("I/O error reading image!", e); 1033 } 1034 } 1035 1036 /** 1037 * Remove all settings including global settings such as 1038 * {@code Locale}s and listeners, as well as stream settings. 1039 */ 1040 public void reset() { 1041 super.reset(); 1042 resetStreamSettings(); 1043 } 1044 1045 /** 1046 * Remove local settings based on parsing of a stream. 1047 */ 1048 private void resetStreamSettings() { 1049 gotHeader = false; 1050 streamMetadata = null; 1051 currIndex = -1; 1052 imageMetadata = null; 1053 imageStartPosition = new ArrayList<>(); 1054 numImages = -1; 1055 1056 // No need to reinitialize 'block' 1057 blockLength = 0; 1058 bitPos = 0; 1059 nextByte = 0; 1060 1061 next32Bits = 0; 1062 lastBlockFound = false; 1063 1064 theImage = null; 1065 theTile = null; 1066 width = -1; 1067 height = -1; 1068 streamX = -1; 1069 streamY = -1; 1070 rowsDone = 0; 1071 interlacePass = 0; 1072 1073 fallbackColorTable = null; 1074 } 1075 1076 private static byte[] defaultPalette = null; 1077 1078 private static synchronized byte[] getDefaultPalette() { 1079 if (defaultPalette == null) { 1080 BufferedImage img = new BufferedImage(1, 1, 1081 BufferedImage.TYPE_BYTE_INDEXED); 1082 IndexColorModel icm = (IndexColorModel) img.getColorModel(); 1083 1084 final int size = icm.getMapSize(); 1085 byte[] r = new byte[size]; 1086 byte[] g = new byte[size]; 1087 byte[] b = new byte[size]; 1088 icm.getReds(r); 1089 icm.getGreens(g); 1090 icm.getBlues(b); 1091 1092 defaultPalette = new byte[size * 3]; 1093 1094 for (int i = 0; i < size; i++) { 1095 defaultPalette[3 * i + 0] = r[i]; 1096 defaultPalette[3 * i + 1] = g[i]; 1097 defaultPalette[3 * i + 2] = b[i]; 1098 } 1099 } 1100 return defaultPalette; 1101 } 1102 }