1 /*
   2  * Copyright (c) 2000, 2016, 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                 if (abortRequested()) {
 460                     return;
 461                 }
 462 
 463                 if (decodeThisRow) {
 464                     outputRow();
 465                 }
 466 
 467                 streamX = 0;
 468                 if (imageMetadata.interlaceFlag) {
 469                     streamY += interlaceIncrement[interlacePass];
 470                     if (streamY >= height) {
 471                         // Inform IIOReadUpdateListeners of end of pass
 472                         if (updateListeners != null) {
 473                             processPassComplete(theImage);
 474                         }
 475 
 476                         ++interlacePass;
 477                         if (interlacePass > sourceMaxProgressivePass) {
 478                             return;
 479                         }
 480                         streamY = interlaceOffset[interlacePass];
 481                         startPass(interlacePass);
 482                     }
 483                 } else {
 484                     ++streamY;
 485                 }
 486 
 487                 // Determine whether pixels from this row will
 488                 // be written to the destination
 489                 this.destY = destinationRegion.y +
 490                     (streamY - sourceRegion.y)/sourceYSubsampling;
 491                 computeDecodeThisRow();
 492             }
 493         }
 494     }
 495 
 496     // END LZW STUFF
 497 
 498     private void readHeader() throws IIOException {
 499         if (gotHeader) {
 500             return;
 501         }
 502         if (stream == null) {
 503             throw new IllegalStateException("Input not set!");
 504         }
 505 
 506         // Create an object to store the stream metadata
 507         this.streamMetadata = new GIFStreamMetadata();
 508 
 509         try {
 510             stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
 511 
 512             byte[] signature = new byte[6];
 513             stream.readFully(signature);
 514 
 515             StringBuilder version = new StringBuilder(3);
 516             version.append((char)signature[3]);
 517             version.append((char)signature[4]);
 518             version.append((char)signature[5]);
 519             streamMetadata.version = version.toString();
 520 
 521             streamMetadata.logicalScreenWidth = stream.readUnsignedShort();
 522             streamMetadata.logicalScreenHeight = stream.readUnsignedShort();
 523 
 524             int packedFields = stream.readUnsignedByte();
 525             boolean globalColorTableFlag = (packedFields & 0x80) != 0;
 526             streamMetadata.colorResolution = ((packedFields >> 4) & 0x7) + 1;
 527             streamMetadata.sortFlag = (packedFields & 0x8) != 0;
 528             int numGCTEntries = 1 << ((packedFields & 0x7) + 1);
 529 
 530             streamMetadata.backgroundColorIndex = stream.readUnsignedByte();
 531             streamMetadata.pixelAspectRatio = stream.readUnsignedByte();
 532 
 533             if (globalColorTableFlag) {
 534                 streamMetadata.globalColorTable = new byte[3*numGCTEntries];
 535                 stream.readFully(streamMetadata.globalColorTable);
 536             } else {
 537                 streamMetadata.globalColorTable = null;
 538             }
 539 
 540             // Found position of metadata for image 0
 541             imageStartPosition.add(Long.valueOf(stream.getStreamPosition()));
 542         } catch (IOException e) {
 543             throw new IIOException("I/O error reading header!", e);
 544         }
 545 
 546         gotHeader = true;
 547     }
 548 
 549     private boolean skipImage() throws IIOException {
 550         // Stream must be at the beginning of an image descriptor
 551         // upon exit
 552 
 553         try {
 554             while (true) {
 555                 int blockType = stream.readUnsignedByte();
 556 
 557                 if (blockType == 0x2c) {
 558                     stream.skipBytes(8);
 559 
 560                     int packedFields = stream.readUnsignedByte();
 561                     if ((packedFields & 0x80) != 0) {
 562                         // Skip color table if any
 563                         int bits = (packedFields & 0x7) + 1;
 564                         stream.skipBytes(3*(1 << bits));
 565                     }
 566 
 567                     stream.skipBytes(1);
 568 
 569                     int length = 0;
 570                     do {
 571                         length = stream.readUnsignedByte();
 572                         stream.skipBytes(length);
 573                     } while (length > 0);
 574 
 575                     return true;
 576                 } else if (blockType == 0x3b) {
 577                     return false;
 578                 } else if (blockType == 0x21) {
 579                     int label = stream.readUnsignedByte();
 580 
 581                     int length = 0;
 582                     do {
 583                         length = stream.readUnsignedByte();
 584                         stream.skipBytes(length);
 585                     } while (length > 0);
 586                 } else if (blockType == 0x0) {
 587                     // EOF
 588                     return false;
 589                 } else {
 590                     int length = 0;
 591                     do {
 592                         length = stream.readUnsignedByte();
 593                         stream.skipBytes(length);
 594                     } while (length > 0);
 595                 }
 596             }
 597         } catch (EOFException e) {
 598             return false;
 599         } catch (IOException e) {
 600             throw new IIOException("I/O error locating image!", e);
 601         }
 602     }
 603 
 604     private int locateImage(int imageIndex) throws IIOException {
 605         readHeader();
 606 
 607         try {
 608             // Find closest known index
 609             int index = Math.min(imageIndex, imageStartPosition.size() - 1);
 610 
 611             // Seek to that position
 612             Long l = imageStartPosition.get(index);
 613             stream.seek(l.longValue());
 614 
 615             // Skip images until at desired index or last image found
 616             while (index < imageIndex) {
 617                 if (!skipImage()) {
 618                     --index;
 619                     return index;
 620                 }
 621 
 622                 Long l1 = stream.getStreamPosition();
 623                 imageStartPosition.add(l1);
 624                 ++index;
 625             }
 626         } catch (IOException e) {
 627             throw new IIOException("Couldn't seek!", e);
 628         }
 629 
 630         if (currIndex != imageIndex) {
 631             imageMetadata = null;
 632         }
 633         currIndex = imageIndex;
 634         return imageIndex;
 635     }
 636 
 637     // Read blocks of 1-255 bytes, stop at a 0-length block
 638     private byte[] concatenateBlocks() throws IOException {
 639         byte[] data = new byte[0];
 640         while (true) {
 641             int length = stream.readUnsignedByte();
 642             if (length == 0) {
 643                 break;
 644             }
 645             byte[] newData = new byte[data.length + length];
 646             System.arraycopy(data, 0, newData, 0, data.length);
 647             stream.readFully(newData, data.length, length);
 648             data = newData;
 649         }
 650 
 651         return data;
 652     }
 653 
 654     // Stream must be positioned at start of metadata for 'currIndex'
 655     private void readMetadata() throws IIOException {
 656         if (stream == null) {
 657             throw new IllegalStateException("Input not set!");
 658         }
 659 
 660         try {
 661             // Create an object to store the image metadata
 662             this.imageMetadata = new GIFImageMetadata();
 663 
 664             long startPosition = stream.getStreamPosition();
 665             while (true) {
 666                 int blockType = stream.readUnsignedByte();
 667                 if (blockType == 0x2c) { // Image Descriptor
 668                     imageMetadata.imageLeftPosition =
 669                         stream.readUnsignedShort();
 670                     imageMetadata.imageTopPosition =
 671                         stream.readUnsignedShort();
 672                     imageMetadata.imageWidth = stream.readUnsignedShort();
 673                     imageMetadata.imageHeight = stream.readUnsignedShort();
 674 
 675                     int idPackedFields = stream.readUnsignedByte();
 676                     boolean localColorTableFlag =
 677                         (idPackedFields & 0x80) != 0;
 678                     imageMetadata.interlaceFlag = (idPackedFields & 0x40) != 0;
 679                     imageMetadata.sortFlag = (idPackedFields & 0x20) != 0;
 680                     int numLCTEntries = 1 << ((idPackedFields & 0x7) + 1);
 681 
 682                     if (localColorTableFlag) {
 683                         // Read color table if any
 684                         imageMetadata.localColorTable =
 685                             new byte[3*numLCTEntries];
 686                         stream.readFully(imageMetadata.localColorTable);
 687                     } else {
 688                         imageMetadata.localColorTable = null;
 689                     }
 690 
 691                     // Record length of this metadata block
 692                     this.imageMetadataLength =
 693                         (int)(stream.getStreamPosition() - startPosition);
 694 
 695                     // Now positioned at start of LZW-compressed pixels
 696                     return;
 697                 } else if (blockType == 0x21) { // Extension block
 698                     int label = stream.readUnsignedByte();
 699 
 700                     if (label == 0xf9) { // Graphics Control Extension
 701                         int gceLength = stream.readUnsignedByte(); // 4
 702                         int gcePackedFields = stream.readUnsignedByte();
 703                         imageMetadata.disposalMethod =
 704                             (gcePackedFields >> 2) & 0x3;
 705                         imageMetadata.userInputFlag =
 706                             (gcePackedFields & 0x2) != 0;
 707                         imageMetadata.transparentColorFlag =
 708                             (gcePackedFields & 0x1) != 0;
 709 
 710                         imageMetadata.delayTime = stream.readUnsignedShort();
 711                         imageMetadata.transparentColorIndex
 712                             = stream.readUnsignedByte();
 713 
 714                         int terminator = stream.readUnsignedByte();
 715                     } else if (label == 0x1) { // Plain text extension
 716                         int length = stream.readUnsignedByte();
 717                         imageMetadata.hasPlainTextExtension = true;
 718                         imageMetadata.textGridLeft =
 719                             stream.readUnsignedShort();
 720                         imageMetadata.textGridTop =
 721                             stream.readUnsignedShort();
 722                         imageMetadata.textGridWidth =
 723                             stream.readUnsignedShort();
 724                         imageMetadata.textGridHeight =
 725                             stream.readUnsignedShort();
 726                         imageMetadata.characterCellWidth =
 727                             stream.readUnsignedByte();
 728                         imageMetadata.characterCellHeight =
 729                             stream.readUnsignedByte();
 730                         imageMetadata.textForegroundColor =
 731                             stream.readUnsignedByte();
 732                         imageMetadata.textBackgroundColor =
 733                             stream.readUnsignedByte();
 734                         imageMetadata.text = concatenateBlocks();
 735                     } else if (label == 0xfe) { // Comment extension
 736                         byte[] comment = concatenateBlocks();
 737                         if (imageMetadata.comments == null) {
 738                             imageMetadata.comments = new ArrayList<>();
 739                         }
 740                         imageMetadata.comments.add(comment);
 741                     } else if (label == 0xff) { // Application extension
 742                         int blockSize = stream.readUnsignedByte();
 743                         byte[] applicationID = new byte[8];
 744                         byte[] authCode = new byte[3];
 745 
 746                         // read available data
 747                         byte[] blockData = new byte[blockSize];
 748                         stream.readFully(blockData);
 749 
 750                         int offset = copyData(blockData, 0, applicationID);
 751                         offset = copyData(blockData, offset, authCode);
 752 
 753                         byte[] applicationData = concatenateBlocks();
 754 
 755                         if (offset < blockSize) {
 756                             int len = blockSize - offset;
 757                             byte[] data =
 758                                 new byte[len + applicationData.length];
 759 
 760                             System.arraycopy(blockData, offset, data, 0, len);
 761                             System.arraycopy(applicationData, 0, data, len,
 762                                              applicationData.length);
 763 
 764                             applicationData = data;
 765                         }
 766 
 767                         // Init lists if necessary
 768                         if (imageMetadata.applicationIDs == null) {
 769                             imageMetadata.applicationIDs = new ArrayList<>();
 770                             imageMetadata.authenticationCodes =
 771                                 new ArrayList<>();
 772                             imageMetadata.applicationData = new ArrayList<>();
 773                         }
 774                         imageMetadata.applicationIDs.add(applicationID);
 775                         imageMetadata.authenticationCodes.add(authCode);
 776                         imageMetadata.applicationData.add(applicationData);
 777                     } else {
 778                         // Skip over unknown extension blocks
 779                         int length = 0;
 780                         do {
 781                             length = stream.readUnsignedByte();
 782                             stream.skipBytes(length);
 783                         } while (length > 0);
 784                     }
 785                 } else if (blockType == 0x3b) { // Trailer
 786                     throw new IndexOutOfBoundsException
 787                         ("Attempt to read past end of image sequence!");
 788                 } else {
 789                     throw new IIOException("Unexpected block type " +
 790                                            blockType + "!");
 791                 }
 792             }
 793         } catch (IIOException iioe) {
 794             throw iioe;
 795         } catch (IOException ioe) {
 796             throw new IIOException("I/O error reading image metadata!", ioe);
 797         }
 798     }
 799 
 800     private int copyData(byte[] src, int offset, byte[] dst) {
 801         int len = dst.length;
 802         int rest = src.length - offset;
 803         if (len > rest) {
 804             len = rest;
 805         }
 806         System.arraycopy(src, offset, dst, 0, len);
 807         return offset + len;
 808     }
 809 
 810     private void startPass(int pass) {
 811         if (updateListeners == null || !imageMetadata.interlaceFlag) {
 812             return;
 813         }
 814 
 815         int y = interlaceOffset[interlacePass];
 816         int yStep = interlaceIncrement[interlacePass];
 817 
 818         int[] vals = ReaderUtil.
 819             computeUpdatedPixels(sourceRegion,
 820                                  destinationOffset,
 821                                  destinationRegion.x,
 822                                  destinationRegion.y,
 823                                  destinationRegion.x +
 824                                  destinationRegion.width - 1,
 825                                  destinationRegion.y +
 826                                  destinationRegion.height - 1,
 827                                  sourceXSubsampling,
 828                                  sourceYSubsampling,
 829                                  0,
 830                                  y,
 831                                  destinationRegion.width,
 832                                  (destinationRegion.height + yStep - 1)/yStep,
 833                                  1,
 834                                  yStep);
 835 
 836         // Initialized updateMinY and updateYStep
 837         this.updateMinY = vals[1];
 838         this.updateYStep = vals[5];
 839 
 840         // Inform IIOReadUpdateListeners of new pass
 841         int[] bands = { 0 };
 842 
 843         processPassStarted(theImage,
 844                            interlacePass,
 845                            sourceMinProgressivePass,
 846                            sourceMaxProgressivePass,
 847                            0,
 848                            updateMinY,
 849                            1,
 850                            updateYStep,
 851                            bands);
 852     }
 853 
 854     public BufferedImage read(int imageIndex, ImageReadParam param)
 855         throws IIOException {
 856         if (stream == null) {
 857             throw new IllegalStateException("Input not set!");
 858         }
 859         checkIndex(imageIndex);
 860 
 861         int index = locateImage(imageIndex);
 862         if (index != imageIndex) {
 863             throw new IndexOutOfBoundsException("imageIndex out of bounds!");
 864         }
 865 
 866         readMetadata();
 867 
 868         // A null ImageReadParam means we use the default
 869         if (param == null) {
 870             param = getDefaultReadParam();
 871         }
 872 
 873         // Initialize the destination image
 874         Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
 875         this.theImage = getDestination(param,
 876                                        imageTypes,
 877                                        imageMetadata.imageWidth,
 878                                        imageMetadata.imageHeight);
 879         this.theTile = theImage.getWritableTile(0, 0);
 880         this.width = imageMetadata.imageWidth;
 881         this.height = imageMetadata.imageHeight;
 882         this.streamX = 0;
 883         this.streamY = 0;
 884         this.rowsDone = 0;
 885         this.interlacePass = 0;
 886 
 887         // Get source region, taking subsampling offsets into account,
 888         // and clipping against the true source bounds
 889 
 890         this.sourceRegion = new Rectangle(0, 0, 0, 0);
 891         this.destinationRegion = new Rectangle(0, 0, 0, 0);
 892         computeRegions(param, width, height, theImage,
 893                        sourceRegion, destinationRegion);
 894         this.destinationOffset = new Point(destinationRegion.x,
 895                                            destinationRegion.y);
 896 
 897         this.sourceXSubsampling = param.getSourceXSubsampling();
 898         this.sourceYSubsampling = param.getSourceYSubsampling();
 899         this.sourceMinProgressivePass =
 900             Math.max(param.getSourceMinProgressivePass(), 0);
 901         this.sourceMaxProgressivePass =
 902             Math.min(param.getSourceMaxProgressivePass(), 3);
 903 
 904         this.destY = destinationRegion.y +
 905             (streamY - sourceRegion.y)/sourceYSubsampling;
 906         computeDecodeThisRow();
 907 
 908         clearAbortRequest();
 909         // Inform IIOReadProgressListeners of start of image
 910         processImageStarted(imageIndex);
 911         if (abortRequested()) {
 912             processReadAborted();
 913             return theImage;
 914         }
 915         startPass(0);
 916 
 917         this.rowBuf = new byte[width];
 918 
 919         try {
 920             // Read and decode the image data, fill in theImage
 921             this.initCodeSize = stream.readUnsignedByte();
 922 
 923             // Read first data block
 924             this.blockLength = stream.readUnsignedByte();
 925             int left = blockLength;
 926             int off = 0;
 927             while (left > 0) {
 928                 int nbytes = stream.read(block, off, left);
 929                 left -= nbytes;
 930                 off += nbytes;
 931             }
 932 
 933             this.bitPos = 0;
 934             this.nextByte = 0;
 935             this.lastBlockFound = false;
 936             this.interlacePass = 0;
 937 
 938             // Init 32-bit buffer
 939             initNext32Bits();
 940 
 941             this.clearCode = 1 << initCodeSize;
 942             this.eofCode = clearCode + 1;
 943 
 944             int code, oldCode = 0;
 945 
 946             int[] prefix = new int[4096];
 947             byte[] suffix = new byte[4096];
 948             byte[] initial = new byte[4096];
 949             int[] length = new int[4096];
 950             byte[] string = new byte[4096];
 951 
 952             initializeStringTable(prefix, suffix, initial, length);
 953             int tableIndex = (1 << initCodeSize) + 2;
 954             int codeSize = initCodeSize + 1;
 955             int codeMask = (1 << codeSize) - 1;
 956 
 957             do {
 958                 code = getCode(codeSize, codeMask);
 959 
 960                 if (code == clearCode) {
 961                     initializeStringTable(prefix, suffix, initial, length);
 962                     tableIndex = (1 << initCodeSize) + 2;
 963                     codeSize = initCodeSize + 1;
 964                     codeMask = (1 << codeSize) - 1;
 965 
 966                     code = getCode(codeSize, codeMask);
 967                     if (code == eofCode) {
 968                         // Inform IIOReadProgressListeners of end of image
 969                         processImageComplete();
 970                         return theImage;
 971                     }
 972                 } else if (code == eofCode) {
 973                     // Inform IIOReadProgressListeners of end of image
 974                     processImageComplete();
 975                     return theImage;
 976                 } else {
 977                     int newSuffixIndex;
 978                     if (code < tableIndex) {
 979                         newSuffixIndex = code;
 980                     } else { // code == tableIndex
 981                         newSuffixIndex = oldCode;
 982                         if (code != tableIndex) {
 983                             // warning - code out of sequence
 984                             // possibly data corruption
 985                             processWarningOccurred("Out-of-sequence code!");
 986                         }
 987                     }
 988 
 989                     int ti = tableIndex;
 990                     int oc = oldCode;
 991 
 992                     prefix[ti] = oc;
 993                     suffix[ti] = initial[newSuffixIndex];
 994                     initial[ti] = initial[oc];
 995                     length[ti] = length[oc] + 1;
 996 
 997                     ++tableIndex;
 998                     if ((tableIndex == (1 << codeSize)) &&
 999                         (tableIndex < 4096)) {
1000                         ++codeSize;
1001                         codeMask = (1 << codeSize) - 1;
1002                     }
1003                 }
1004 
1005                 // Reverse code
1006                 int c = code;
1007                 int len = length[c];
1008                 for (int i = len - 1; i >= 0; i--) {
1009                     string[i] = suffix[c];
1010                     c = prefix[c];
1011                 }
1012 
1013                 outputPixels(string, len);
1014                 oldCode = code;
1015             } while (!abortRequested());
1016 
1017             processReadAborted();
1018             return theImage;
1019         } catch (IOException e) {
1020             e.printStackTrace();
1021             throw new IIOException("I/O error reading image!", e);
1022         }
1023     }
1024 
1025     /**
1026      * Remove all settings including global settings such as
1027      * {@code Locale}s and listeners, as well as stream settings.
1028      */
1029     public void reset() {
1030         super.reset();
1031         resetStreamSettings();
1032     }
1033 
1034     /**
1035      * Remove local settings based on parsing of a stream.
1036      */
1037     private void resetStreamSettings() {
1038         gotHeader = false;
1039         streamMetadata = null;
1040         currIndex = -1;
1041         imageMetadata = null;
1042         imageStartPosition = new ArrayList<>();
1043         numImages = -1;
1044 
1045         // No need to reinitialize 'block'
1046         blockLength = 0;
1047         bitPos = 0;
1048         nextByte = 0;
1049 
1050         next32Bits = 0;
1051         lastBlockFound = false;
1052 
1053         theImage = null;
1054         theTile = null;
1055         width = -1;
1056         height = -1;
1057         streamX = -1;
1058         streamY = -1;
1059         rowsDone = 0;
1060         interlacePass = 0;
1061 
1062         fallbackColorTable = null;
1063     }
1064 
1065     private static byte[] defaultPalette = null;
1066 
1067     private static synchronized byte[] getDefaultPalette() {
1068         if (defaultPalette == null) {
1069             BufferedImage img = new BufferedImage(1, 1,
1070                     BufferedImage.TYPE_BYTE_INDEXED);
1071             IndexColorModel icm = (IndexColorModel) img.getColorModel();
1072 
1073             final int size = icm.getMapSize();
1074             byte[] r = new byte[size];
1075             byte[] g = new byte[size];
1076             byte[] b = new byte[size];
1077             icm.getReds(r);
1078             icm.getGreens(g);
1079             icm.getBlues(b);
1080 
1081             defaultPalette = new byte[size * 3];
1082 
1083             for (int i = 0; i < size; i++) {
1084                 defaultPalette[3 * i + 0] = r[i];
1085                 defaultPalette[3 * i + 1] = g[i];
1086                 defaultPalette[3 * i + 2] = b[i];
1087             }
1088         }
1089         return defaultPalette;
1090     }
1091 }