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 
GIFImageReader(ImageReaderSpi originatingProvider)126     public GIFImageReader(ImageReaderSpi originatingProvider) {
127         super(originatingProvider);
128     }
129 
130     // Take input from an ImageInputStream
setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata)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 
getNumImages(boolean allowSearch)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.
checkIndex(int imageIndex)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 
getWidth(int imageIndex)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 
getHeight(int imageIndex)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
createIndexed(byte[] r, byte[] g, byte[] b, int bits)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 
getImageTypes(int imageIndex)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 
getDefaultReadParam()292     public ImageReadParam getDefaultReadParam() {
293         return new ImageReadParam();
294     }
295 
getStreamMetadata()296     public IIOMetadata getStreamMetadata() throws IIOException {
297         readHeader();
298         return streamMetadata;
299     }
300 
getImageMetadata(int imageIndex)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 
initNext32Bits()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     //
getCode(int codeSize, int codeMask)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 
initializeStringTable(int[] prefix, byte[] suffix, byte[] initial, int[] length)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 
outputRow()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 
computeDecodeThisRow()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 
outputPixels(byte[] string, int len)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 
readHeader()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 
skipImage()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 
locateImage(int imageIndex)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
concatenateBlocks()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'
readMetadata()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 
copyData(byte[] src, int offset, byte[] dst)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 
startPass(int pass)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 
read(int imageIndex, ImageReadParam param)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      */
reset()1029     public void reset() {
1030         super.reset();
1031         resetStreamSettings();
1032     }
1033 
1034     /**
1035      * Remove local settings based on parsing of a stream.
1036      */
resetStreamSettings()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 
getDefaultPalette()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 }
1092