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