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 }