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