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