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