1 /* 2 * Copyright (c) 2000, 2008, 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 off += nbytes; 354 left -= nbytes; 355 } 356 nextByte = 0; 357 } 358 } 359 360 next32Bits |= block[nextByte++] << 24; 361 } 362 363 return code; 364 } 365 366 public void initializeStringTable(int[] prefix, 367 byte[] suffix, 368 byte[] initial, 369 int[] length) { 370 int numEntries = 1 << initCodeSize; 371 for (int i = 0; i < numEntries; i++) { 372 prefix[i] = -1; 373 suffix[i] = (byte)i; 374 initial[i] = (byte)i; 375 length[i] = 1; 376 } 377 378 // Fill in the entire table for robustness against 379 // out-of-sequence codes. 380 for (int i = numEntries; i < 4096; i++) { 381 prefix[i] = -1; 382 length[i] = 1; 383 } 384 385 // tableIndex = numEntries + 2; 386 // codeSize = initCodeSize + 1; 387 // codeMask = (1 << codeSize) - 1; 388 } 389 390 Rectangle sourceRegion; 391 int sourceXSubsampling; 392 int sourceYSubsampling; 393 int sourceMinProgressivePass; 394 int sourceMaxProgressivePass; 395 396 Point destinationOffset; 397 Rectangle destinationRegion; 398 399 // Used only if IIOReadUpdateListeners are present 400 int updateMinY; 401 int updateYStep; 402 403 boolean decodeThisRow = true; 404 int destY = 0; 405 406 byte[] rowBuf; 407 408 private void outputRow() { 409 // Clip against ImageReadParam 410 int width = Math.min(sourceRegion.width, 411 destinationRegion.width*sourceXSubsampling); 412 int destX = destinationRegion.x; 413 414 if (sourceXSubsampling == 1) { 415 theTile.setDataElements(destX, destY, width, 1, rowBuf); 416 } else { 417 for (int x = 0; x < width; x += sourceXSubsampling, destX++) { 418 theTile.setSample(destX, destY, 0, rowBuf[x] & 0xff); 419 } 420 } 421 422 // Update IIOReadUpdateListeners, if any 423 if (updateListeners != null) { 424 int[] bands = { 0 }; 425 // updateYStep will have been initialized if 426 // updateListeners is non-null 427 processImageUpdate(theImage, 428 destX, destY, 429 width, 1, 1, updateYStep, 430 bands); 431 } 432 } 433 434 private void computeDecodeThisRow() { 435 this.decodeThisRow = 436 (destY < destinationRegion.y + destinationRegion.height) && 437 (streamY >= sourceRegion.y) && 438 (streamY < sourceRegion.y + sourceRegion.height) && 439 (((streamY - sourceRegion.y) % sourceYSubsampling) == 0); 440 } 441 442 private void outputPixels(byte[] string, int len) { 443 if (interlacePass < sourceMinProgressivePass || 444 interlacePass > sourceMaxProgressivePass) { 445 return; 446 } 447 448 for (int i = 0; i < len; i++) { 449 if (streamX >= sourceRegion.x) { 450 rowBuf[streamX - sourceRegion.x] = string[i]; 451 } 452 453 // Process end-of-row 454 ++streamX; 455 if (streamX == width) { 456 // Update IIOReadProgressListeners 457 ++rowsDone; 458 processImageProgress(100.0F*rowsDone/height); 459 460 if (decodeThisRow) { 461 outputRow(); 462 } 463 464 streamX = 0; 465 if (imageMetadata.interlaceFlag) { 466 streamY += interlaceIncrement[interlacePass]; 467 if (streamY >= height) { 468 // Inform IIOReadUpdateListeners of end of pass 469 if (updateListeners != null) { 470 processPassComplete(theImage); 471 } 472 473 ++interlacePass; 474 if (interlacePass > sourceMaxProgressivePass) { 475 return; 476 } 477 streamY = interlaceOffset[interlacePass]; 478 startPass(interlacePass); 479 } 480 } else { 481 ++streamY; 482 } 483 484 // Determine whether pixels from this row will 485 // be written to the destination 486 this.destY = destinationRegion.y + 487 (streamY - sourceRegion.y)/sourceYSubsampling; 488 computeDecodeThisRow(); 489 } 490 } 491 } 492 493 // END LZW STUFF 494 495 private void readHeader() throws IIOException { 496 if (gotHeader) { 497 return; 498 } 499 if (stream == null) { 500 throw new IllegalStateException("Input not set!"); 501 } 502 503 // Create an object to store the stream metadata 504 this.streamMetadata = new GIFStreamMetadata(); 505 506 try { 507 stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); 508 509 byte[] signature = new byte[6]; 510 stream.readFully(signature); 511 512 StringBuffer version = new StringBuffer(3); 513 version.append((char)signature[3]); 514 version.append((char)signature[4]); 515 version.append((char)signature[5]); 516 streamMetadata.version = version.toString(); 517 518 streamMetadata.logicalScreenWidth = stream.readUnsignedShort(); 519 streamMetadata.logicalScreenHeight = stream.readUnsignedShort(); 520 521 int packedFields = stream.readUnsignedByte(); 522 boolean globalColorTableFlag = (packedFields & 0x80) != 0; 523 streamMetadata.colorResolution = ((packedFields >> 4) & 0x7) + 1; 524 streamMetadata.sortFlag = (packedFields & 0x8) != 0; 525 int numGCTEntries = 1 << ((packedFields & 0x7) + 1); 526 527 streamMetadata.backgroundColorIndex = stream.readUnsignedByte(); 528 streamMetadata.pixelAspectRatio = stream.readUnsignedByte(); 529 530 if (globalColorTableFlag) { 531 streamMetadata.globalColorTable = new byte[3*numGCTEntries]; 532 stream.readFully(streamMetadata.globalColorTable); 533 } else { 534 streamMetadata.globalColorTable = null; 535 } 536 537 // Found position of metadata for image 0 538 imageStartPosition.add(Long.valueOf(stream.getStreamPosition())); 539 } catch (IOException e) { 540 throw new IIOException("I/O error reading header!", e); 541 } 542 543 gotHeader = true; 544 } 545 546 private boolean skipImage() throws IIOException { 547 // Stream must be at the beginning of an image descriptor 548 // upon exit 549 550 try { 551 while (true) { 552 int blockType = stream.readUnsignedByte(); 553 554 if (blockType == 0x2c) { 555 stream.skipBytes(8); 556 557 int packedFields = stream.readUnsignedByte(); 558 if ((packedFields & 0x80) != 0) { 559 // Skip color table if any 560 int bits = (packedFields & 0x7) + 1; 561 stream.skipBytes(3*(1 << bits)); 562 } 563 564 stream.skipBytes(1); 565 566 int length = 0; 567 do { 568 length = stream.readUnsignedByte(); 569 stream.skipBytes(length); 570 } while (length > 0); 571 572 return true; 573 } else if (blockType == 0x3b) { 574 return false; 575 } else if (blockType == 0x21) { 576 int label = stream.readUnsignedByte(); 577 578 int length = 0; 579 do { 580 length = stream.readUnsignedByte(); 581 stream.skipBytes(length); 582 } while (length > 0); 583 } else if (blockType == 0x0) { 584 // EOF 585 return false; 586 } else { 587 int length = 0; 588 do { 589 length = stream.readUnsignedByte(); 590 stream.skipBytes(length); 591 } while (length > 0); 592 } 593 } 594 } catch (EOFException e) { 595 return false; 596 } catch (IOException e) { 597 throw new IIOException("I/O error locating image!", e); 598 } 599 } 600 601 private int locateImage(int imageIndex) throws IIOException { 602 readHeader(); 603 604 try { 605 // Find closest known index 606 int index = Math.min(imageIndex, imageStartPosition.size() - 1); 607 608 // Seek to that position 609 Long l = imageStartPosition.get(index); 610 stream.seek(l.longValue()); 611 612 // Skip images until at desired index or last image found 613 while (index < imageIndex) { 614 if (!skipImage()) { 615 --index; 616 return index; 617 } 618 619 Long l1 = new Long(stream.getStreamPosition()); 620 imageStartPosition.add(l1); 621 ++index; 622 } 623 } catch (IOException e) { 624 throw new IIOException("Couldn't seek!", e); 625 } 626 627 if (currIndex != imageIndex) { 628 imageMetadata = null; 629 } 630 currIndex = imageIndex; 631 return imageIndex; 632 } 633 634 // Read blocks of 1-255 bytes, stop at a 0-length block 635 private byte[] concatenateBlocks() throws IOException { 636 byte[] data = new byte[0]; 637 while (true) { 638 int length = stream.readUnsignedByte(); 639 if (length == 0) { 640 break; 641 } 642 byte[] newData = new byte[data.length + length]; 643 System.arraycopy(data, 0, newData, 0, data.length); 644 stream.readFully(newData, data.length, length); 645 data = newData; 646 } 647 648 return data; 649 } 650 651 // Stream must be positioned at start of metadata for 'currIndex' 652 private void readMetadata() throws IIOException { 653 if (stream == null) { 654 throw new IllegalStateException("Input not set!"); 655 } 656 657 try { 658 // Create an object to store the image metadata 659 this.imageMetadata = new GIFImageMetadata(); 660 661 long startPosition = stream.getStreamPosition(); 662 while (true) { 663 int blockType = stream.readUnsignedByte(); 664 if (blockType == 0x2c) { // Image Descriptor 665 imageMetadata.imageLeftPosition = 666 stream.readUnsignedShort(); 667 imageMetadata.imageTopPosition = 668 stream.readUnsignedShort(); 669 imageMetadata.imageWidth = stream.readUnsignedShort(); 670 imageMetadata.imageHeight = stream.readUnsignedShort(); 671 672 int idPackedFields = stream.readUnsignedByte(); 673 boolean localColorTableFlag = 674 (idPackedFields & 0x80) != 0; 675 imageMetadata.interlaceFlag = (idPackedFields & 0x40) != 0; 676 imageMetadata.sortFlag = (idPackedFields & 0x20) != 0; 677 int numLCTEntries = 1 << ((idPackedFields & 0x7) + 1); 678 679 if (localColorTableFlag) { 680 // Read color table if any 681 imageMetadata.localColorTable = 682 new byte[3*numLCTEntries]; 683 stream.readFully(imageMetadata.localColorTable); 684 } else { 685 imageMetadata.localColorTable = null; 686 } 687 688 // Record length of this metadata block 689 this.imageMetadataLength = 690 (int)(stream.getStreamPosition() - startPosition); 691 692 // Now positioned at start of LZW-compressed pixels 693 return; 694 } else if (blockType == 0x21) { // Extension block 695 int label = stream.readUnsignedByte(); 696 697 if (label == 0xf9) { // Graphics Control Extension 698 int gceLength = stream.readUnsignedByte(); // 4 699 int gcePackedFields = stream.readUnsignedByte(); 700 imageMetadata.disposalMethod = 701 (gcePackedFields >> 2) & 0x3; 702 imageMetadata.userInputFlag = 703 (gcePackedFields & 0x2) != 0; 704 imageMetadata.transparentColorFlag = 705 (gcePackedFields & 0x1) != 0; 706 707 imageMetadata.delayTime = stream.readUnsignedShort(); 708 imageMetadata.transparentColorIndex 709 = stream.readUnsignedByte(); 710 711 int terminator = stream.readUnsignedByte(); 712 } else if (label == 0x1) { // Plain text extension 713 int length = stream.readUnsignedByte(); 714 imageMetadata.hasPlainTextExtension = true; 715 imageMetadata.textGridLeft = 716 stream.readUnsignedShort(); 717 imageMetadata.textGridTop = 718 stream.readUnsignedShort(); 719 imageMetadata.textGridWidth = 720 stream.readUnsignedShort(); 721 imageMetadata.textGridHeight = 722 stream.readUnsignedShort(); 723 imageMetadata.characterCellWidth = 724 stream.readUnsignedByte(); 725 imageMetadata.characterCellHeight = 726 stream.readUnsignedByte(); 727 imageMetadata.textForegroundColor = 728 stream.readUnsignedByte(); 729 imageMetadata.textBackgroundColor = 730 stream.readUnsignedByte(); 731 imageMetadata.text = concatenateBlocks(); 732 } else if (label == 0xfe) { // Comment extension 733 byte[] comment = concatenateBlocks(); 734 if (imageMetadata.comments == null) { 735 imageMetadata.comments = new ArrayList<>(); 736 } 737 imageMetadata.comments.add(comment); 738 } else if (label == 0xff) { // Application extension 739 int blockSize = stream.readUnsignedByte(); 740 byte[] applicationID = new byte[8]; 741 byte[] authCode = new byte[3]; 742 743 // read available data 744 byte[] blockData = new byte[blockSize]; 745 stream.readFully(blockData); 746 747 int offset = copyData(blockData, 0, applicationID); 748 offset = copyData(blockData, offset, authCode); 749 750 byte[] applicationData = concatenateBlocks(); 751 752 if (offset < blockSize) { 753 int len = blockSize - offset; 754 byte[] data = 755 new byte[len + applicationData.length]; 756 757 System.arraycopy(blockData, offset, data, 0, len); 758 System.arraycopy(applicationData, 0, data, len, 759 applicationData.length); 760 761 applicationData = data; 762 } 763 764 // Init lists if necessary 765 if (imageMetadata.applicationIDs == null) { 766 imageMetadata.applicationIDs = new ArrayList<>(); 767 imageMetadata.authenticationCodes = 768 new ArrayList<>(); 769 imageMetadata.applicationData = new ArrayList<>(); 770 } 771 imageMetadata.applicationIDs.add(applicationID); 772 imageMetadata.authenticationCodes.add(authCode); 773 imageMetadata.applicationData.add(applicationData); 774 } else { 775 // Skip over unknown extension blocks 776 int length = 0; 777 do { 778 length = stream.readUnsignedByte(); 779 stream.skipBytes(length); 780 } while (length > 0); 781 } 782 } else if (blockType == 0x3b) { // Trailer 783 throw new IndexOutOfBoundsException 784 ("Attempt to read past end of image sequence!"); 785 } else { 786 throw new IIOException("Unexpected block type " + 787 blockType + "!"); 788 } 789 } 790 } catch (IIOException iioe) { 791 throw iioe; 792 } catch (IOException ioe) { 793 throw new IIOException("I/O error reading image metadata!", ioe); 794 } 795 } 796 797 private int copyData(byte[] src, int offset, byte[] dst) { 798 int len = dst.length; 799 int rest = src.length - offset; 800 if (len > rest) { 801 len = rest; 802 } 803 System.arraycopy(src, offset, dst, 0, len); 804 return offset + len; 805 } 806 807 private void startPass(int pass) { 808 if (updateListeners == null || !imageMetadata.interlaceFlag) { 809 return; 810 } 811 812 int y = interlaceOffset[interlacePass]; 813 int yStep = interlaceIncrement[interlacePass]; 814 815 int[] vals = ReaderUtil. 816 computeUpdatedPixels(sourceRegion, 817 destinationOffset, 818 destinationRegion.x, 819 destinationRegion.y, 820 destinationRegion.x + 821 destinationRegion.width - 1, 822 destinationRegion.y + 823 destinationRegion.height - 1, 824 sourceXSubsampling, 825 sourceYSubsampling, 826 0, 827 y, 828 destinationRegion.width, 829 (destinationRegion.height + yStep - 1)/yStep, 830 1, 831 yStep); 832 833 // Initialized updateMinY and updateYStep 834 this.updateMinY = vals[1]; 835 this.updateYStep = vals[5]; 836 837 // Inform IIOReadUpdateListeners of new pass 838 int[] bands = { 0 }; 839 840 processPassStarted(theImage, 841 interlacePass, 842 sourceMinProgressivePass, 843 sourceMaxProgressivePass, 844 0, 845 updateMinY, 846 1, 847 updateYStep, 848 bands); 849 } 850 851 public BufferedImage read(int imageIndex, ImageReadParam param) 852 throws IIOException { 853 if (stream == null) { 854 throw new IllegalStateException("Input not set!"); 855 } 856 checkIndex(imageIndex); 857 858 int index = locateImage(imageIndex); 859 if (index != imageIndex) { 860 throw new IndexOutOfBoundsException("imageIndex out of bounds!"); 861 } 862 863 clearAbortRequest(); 864 readMetadata(); 865 866 // A null ImageReadParam means we use the default 867 if (param == null) { 868 param = getDefaultReadParam(); 869 } 870 871 // Initialize the destination image 872 Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex); 873 this.theImage = getDestination(param, 874 imageTypes, 875 imageMetadata.imageWidth, 876 imageMetadata.imageHeight); 877 this.theTile = theImage.getWritableTile(0, 0); 878 this.width = imageMetadata.imageWidth; 879 this.height = imageMetadata.imageHeight; 880 this.streamX = 0; 881 this.streamY = 0; 882 this.rowsDone = 0; 883 this.interlacePass = 0; 884 885 // Get source region, taking subsampling offsets into account, 886 // and clipping against the true source bounds 887 888 this.sourceRegion = new Rectangle(0, 0, 0, 0); 889 this.destinationRegion = new Rectangle(0, 0, 0, 0); 890 computeRegions(param, width, height, theImage, 891 sourceRegion, destinationRegion); 892 this.destinationOffset = new Point(destinationRegion.x, 893 destinationRegion.y); 894 895 this.sourceXSubsampling = param.getSourceXSubsampling(); 896 this.sourceYSubsampling = param.getSourceYSubsampling(); 897 this.sourceMinProgressivePass = 898 Math.max(param.getSourceMinProgressivePass(), 0); 899 this.sourceMaxProgressivePass = 900 Math.min(param.getSourceMaxProgressivePass(), 3); 901 902 this.destY = destinationRegion.y + 903 (streamY - sourceRegion.y)/sourceYSubsampling; 904 computeDecodeThisRow(); 905 906 // Inform IIOReadProgressListeners of start of image 907 processImageStarted(imageIndex); 908 startPass(0); 909 910 this.rowBuf = new byte[width]; 911 912 try { 913 // Read and decode the image data, fill in theImage 914 this.initCodeSize = stream.readUnsignedByte(); 915 916 // Read first data block 917 this.blockLength = stream.readUnsignedByte(); 918 int left = blockLength; 919 int off = 0; 920 while (left > 0) { 921 int nbytes = stream.read(block, off, left); 922 left -= nbytes; 923 off += nbytes; 924 } 925 926 this.bitPos = 0; 927 this.nextByte = 0; 928 this.lastBlockFound = false; 929 this.interlacePass = 0; 930 931 // Init 32-bit buffer 932 initNext32Bits(); 933 934 this.clearCode = 1 << initCodeSize; 935 this.eofCode = clearCode + 1; 936 937 int code, oldCode = 0; 938 939 int[] prefix = new int[4096]; 940 byte[] suffix = new byte[4096]; 941 byte[] initial = new byte[4096]; 942 int[] length = new int[4096]; 943 byte[] string = new byte[4096]; 944 945 initializeStringTable(prefix, suffix, initial, length); 946 int tableIndex = (1 << initCodeSize) + 2; 947 int codeSize = initCodeSize + 1; 948 int codeMask = (1 << codeSize) - 1; 949 950 while (!abortRequested()) { 951 code = getCode(codeSize, codeMask); 952 953 if (code == clearCode) { 954 initializeStringTable(prefix, suffix, initial, length); 955 tableIndex = (1 << initCodeSize) + 2; 956 codeSize = initCodeSize + 1; 957 codeMask = (1 << codeSize) - 1; 958 959 code = getCode(codeSize, codeMask); 960 if (code == eofCode) { 961 // Inform IIOReadProgressListeners of end of image 962 processImageComplete(); 963 return theImage; 964 } 965 } else if (code == eofCode) { 966 // Inform IIOReadProgressListeners of end of image 967 processImageComplete(); 968 return theImage; 969 } else { 970 int newSuffixIndex; 971 if (code < tableIndex) { 972 newSuffixIndex = code; 973 } else { // code == tableIndex 974 newSuffixIndex = oldCode; 975 if (code != tableIndex) { 976 // warning - code out of sequence 977 // possibly data corruption 978 processWarningOccurred("Out-of-sequence code!"); 979 } 980 } 981 982 int ti = tableIndex; 983 int oc = oldCode; 984 985 prefix[ti] = oc; 986 suffix[ti] = initial[newSuffixIndex]; 987 initial[ti] = initial[oc]; 988 length[ti] = length[oc] + 1; 989 990 ++tableIndex; 991 if ((tableIndex == (1 << codeSize)) && 992 (tableIndex < 4096)) { 993 ++codeSize; 994 codeMask = (1 << codeSize) - 1; 995 } 996 } 997 998 // Reverse code 999 int c = code; 1000 int len = length[c]; 1001 for (int i = len - 1; i >= 0; i--) { 1002 string[i] = suffix[c]; 1003 c = prefix[c]; 1004 } 1005 1006 outputPixels(string, len); 1007 oldCode = code; 1008 } 1009 1010 processReadAborted(); 1011 return theImage; 1012 } catch (IOException e) { 1013 e.printStackTrace(); 1014 throw new IIOException("I/O error reading image!", e); 1015 } 1016 } 1017 1018 /** 1019 * Remove all settings including global settings such as 1020 * <code>Locale</code>s and listeners, as well as stream settings. 1021 */ 1022 public void reset() { 1023 super.reset(); 1024 resetStreamSettings(); 1025 } 1026 1027 /** 1028 * Remove local settings based on parsing of a stream. 1029 */ 1030 private void resetStreamSettings() { 1031 gotHeader = false; 1032 streamMetadata = null; 1033 currIndex = -1; 1034 imageMetadata = null; 1035 imageStartPosition = new ArrayList<>(); 1036 numImages = -1; 1037 1038 // No need to reinitialize 'block' 1039 blockLength = 0; 1040 bitPos = 0; 1041 nextByte = 0; 1042 1043 next32Bits = 0; 1044 lastBlockFound = false; 1045 1046 theImage = null; 1047 theTile = null; 1048 width = -1; 1049 height = -1; 1050 streamX = -1; 1051 streamY = -1; 1052 rowsDone = 0; 1053 interlacePass = 0; 1054 1055 fallbackColorTable = null; 1056 } 1057 1058 private static byte[] defaultPalette = null; 1059 1060 private static synchronized byte[] getDefaultPalette() { 1061 if (defaultPalette == null) { 1062 BufferedImage img = new BufferedImage(1, 1, 1063 BufferedImage.TYPE_BYTE_INDEXED); 1064 IndexColorModel icm = (IndexColorModel) img.getColorModel(); 1065 1066 final int size = icm.getMapSize(); 1067 byte[] r = new byte[size]; 1068 byte[] g = new byte[size]; 1069 byte[] b = new byte[size]; 1070 icm.getReds(r); 1071 icm.getGreens(g); 1072 icm.getBlues(b); 1073 1074 defaultPalette = new byte[size * 3]; 1075 1076 for (int i = 0; i < size; i++) { 1077 defaultPalette[3 * i + 0] = r[i]; 1078 defaultPalette[3 * i + 1] = g[i]; 1079 defaultPalette[3 * i + 2] = b[i]; 1080 } 1081 } 1082 return defaultPalette; 1083 } 1084 }