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