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