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