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