| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
 | 
 | 
package com.sun.imageio.plugins.gif;  | 
 | 
 | 
 | 
import java.awt.Point;  | 
 | 
import java.awt.Rectangle;  | 
 | 
import java.awt.image.BufferedImage;  | 
 | 
import java.awt.image.DataBuffer;  | 
 | 
import java.awt.image.WritableRaster;  | 
 | 
import java.io.EOFException;  | 
 | 
import java.io.IOException;  | 
 | 
import java.nio.ByteOrder;  | 
 | 
import java.util.ArrayList;  | 
 | 
import java.util.Iterator;  | 
 | 
import java.util.List;  | 
 | 
import javax.imageio.IIOException;  | 
 | 
import javax.imageio.ImageReader;  | 
 | 
import javax.imageio.ImageReadParam;  | 
 | 
import javax.imageio.ImageTypeSpecifier;  | 
 | 
import javax.imageio.metadata.IIOMetadata;  | 
 | 
import javax.imageio.spi.ImageReaderSpi;  | 
 | 
import javax.imageio.stream.ImageInputStream;  | 
 | 
import com.sun.imageio.plugins.common.ReaderUtil;  | 
 | 
import java.awt.image.ColorModel;  | 
 | 
import java.awt.image.IndexColorModel;  | 
 | 
import java.awt.image.MultiPixelPackedSampleModel;  | 
 | 
import java.awt.image.PixelInterleavedSampleModel;  | 
 | 
import java.awt.image.SampleModel;  | 
 | 
 | 
 | 
public class GIFImageReader extends ImageReader { | 
 | 
 | 
 | 
      | 
 | 
    ImageInputStream stream = null;  | 
 | 
 | 
 | 
    // Per-stream settings  | 
 | 
 | 
 | 
      | 
 | 
    boolean gotHeader = false;  | 
 | 
 | 
 | 
      | 
 | 
    GIFStreamMetadata streamMetadata = null;  | 
 | 
 | 
 | 
      | 
 | 
    int currIndex = -1;  | 
 | 
 | 
 | 
      | 
 | 
    GIFImageMetadata imageMetadata = null;  | 
 | 
 | 
 | 
    // A List of Longs indicating the stream positions of the  | 
 | 
    // start of the metadata for each image.  Entries are added  | 
 | 
      | 
 | 
    List imageStartPosition = new ArrayList();  | 
 | 
 | 
 | 
    // Length of metadata for image at 'currIndex', valid only if  | 
 | 
      | 
 | 
    int imageMetadataLength;  | 
 | 
 | 
 | 
      | 
 | 
    int numImages = -1;  | 
 | 
 | 
 | 
      | 
 | 
    byte[] block = new byte[255];  | 
 | 
    int blockLength = 0;  | 
 | 
    int bitPos = 0;  | 
 | 
    int nextByte = 0;  | 
 | 
    int initCodeSize;  | 
 | 
    int clearCode;  | 
 | 
    int eofCode;  | 
 | 
 | 
 | 
      | 
 | 
    int next32Bits = 0;  | 
 | 
 | 
 | 
    // Try if the end of the data blocks has been found,  | 
 | 
      | 
 | 
    boolean lastBlockFound = false;  | 
 | 
 | 
 | 
      | 
 | 
    BufferedImage theImage = null;  | 
 | 
 | 
 | 
      | 
 | 
    WritableRaster theTile = null;  | 
 | 
 | 
 | 
      | 
 | 
    int width = -1, height = -1;  | 
 | 
 | 
 | 
      | 
 | 
    int streamX = -1, streamY = -1;  | 
 | 
 | 
 | 
      | 
 | 
    int rowsDone = 0;  | 
 | 
 | 
 | 
      | 
 | 
    int interlacePass = 0;  | 
 | 
 | 
 | 
    private byte[] fallbackColorTable = null;  | 
 | 
 | 
 | 
    // End per-stream settings  | 
 | 
 | 
 | 
      | 
 | 
    static final int[] interlaceIncrement = { 8, 8, 4, 2, -1 }; | 
 | 
    static final int[] interlaceOffset = { 0, 4, 2, 1, -1 }; | 
 | 
 | 
 | 
    public GIFImageReader(ImageReaderSpi originatingProvider) { | 
 | 
        super(originatingProvider);  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
    public void setInput(Object input,  | 
 | 
                         boolean seekForwardOnly,  | 
 | 
                         boolean ignoreMetadata) { | 
 | 
        super.setInput(input, seekForwardOnly, ignoreMetadata);  | 
 | 
        if (input != null) { | 
 | 
            if (!(input instanceof ImageInputStream)) { | 
 | 
                throw new IllegalArgumentException  | 
 | 
                    ("input not an ImageInputStream!"); | 
 | 
            }  | 
 | 
            this.stream = (ImageInputStream)input;  | 
 | 
        } else { | 
 | 
            this.stream = null;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        resetStreamSettings();  | 
 | 
    }  | 
 | 
 | 
 | 
    public int getNumImages(boolean allowSearch) throws IIOException { | 
 | 
        if (stream == null) { | 
 | 
            throw new IllegalStateException("Input not set!"); | 
 | 
        }  | 
 | 
        if (seekForwardOnly && allowSearch) { | 
 | 
            throw new IllegalStateException  | 
 | 
                ("seekForwardOnly and allowSearch can't both be true!"); | 
 | 
        }  | 
 | 
 | 
 | 
        if (numImages > 0) { | 
 | 
            return numImages;  | 
 | 
        }  | 
 | 
        if (allowSearch) { | 
 | 
            this.numImages = locateImage(Integer.MAX_VALUE) + 1;  | 
 | 
        }  | 
 | 
        return numImages;  | 
 | 
    }  | 
 | 
 | 
 | 
    // Throw an IndexOutOfBoundsException if index < minIndex,  | 
 | 
      | 
 | 
    private void checkIndex(int imageIndex) { | 
 | 
        if (imageIndex < minIndex) { | 
 | 
            throw new IndexOutOfBoundsException("imageIndex < minIndex!"); | 
 | 
        }  | 
 | 
        if (seekForwardOnly) { | 
 | 
            minIndex = imageIndex;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    public int getWidth(int imageIndex) throws IIOException { | 
 | 
        checkIndex(imageIndex);  | 
 | 
 | 
 | 
        int index = locateImage(imageIndex);  | 
 | 
        if (index != imageIndex) { | 
 | 
            throw new IndexOutOfBoundsException();  | 
 | 
        }  | 
 | 
        readMetadata();  | 
 | 
        return imageMetadata.imageWidth;  | 
 | 
    }  | 
 | 
 | 
 | 
    public int getHeight(int imageIndex) throws IIOException { | 
 | 
        checkIndex(imageIndex);  | 
 | 
 | 
 | 
        int index = locateImage(imageIndex);  | 
 | 
        if (index != imageIndex) { | 
 | 
            throw new IndexOutOfBoundsException();  | 
 | 
        }  | 
 | 
        readMetadata();  | 
 | 
        return imageMetadata.imageHeight;  | 
 | 
    }  | 
 | 
 | 
 | 
    // We don't check all parameters as ImageTypeSpecifier.createIndexed do  | 
 | 
      | 
 | 
    private ImageTypeSpecifier createIndexed(byte[] r, byte[] g, byte[] b,  | 
 | 
                                             int bits) { | 
 | 
        ColorModel colorModel;  | 
 | 
        if (imageMetadata.transparentColorFlag) { | 
 | 
            // Some files erroneously have a transparent color index  | 
 | 
              | 
 | 
            int idx = Math.min(imageMetadata.transparentColorIndex,  | 
 | 
                    r.length - 1);  | 
 | 
            colorModel = new IndexColorModel(bits, r.length, r, g, b, idx);  | 
 | 
        } else { | 
 | 
            colorModel = new IndexColorModel(bits, r.length, r, g, b);  | 
 | 
        }  | 
 | 
 | 
 | 
        SampleModel sampleModel;  | 
 | 
        if (bits == 8) { | 
 | 
            int[] bandOffsets = {0}; | 
 | 
            sampleModel =  | 
 | 
                    new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE,  | 
 | 
                    1, 1, 1, 1,  | 
 | 
                    bandOffsets);  | 
 | 
        } else { | 
 | 
            sampleModel =  | 
 | 
                    new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,  | 
 | 
                    1, 1, bits);  | 
 | 
        }  | 
 | 
        return new ImageTypeSpecifier(colorModel, sampleModel);  | 
 | 
    }  | 
 | 
 | 
 | 
    public Iterator getImageTypes(int imageIndex) throws IIOException { | 
 | 
        checkIndex(imageIndex);  | 
 | 
 | 
 | 
        int index = locateImage(imageIndex);  | 
 | 
        if (index != imageIndex) { | 
 | 
            throw new IndexOutOfBoundsException();  | 
 | 
        }  | 
 | 
        readMetadata();  | 
 | 
 | 
 | 
        List l = new ArrayList(1);  | 
 | 
 | 
 | 
        byte[] colorTable;  | 
 | 
        if (imageMetadata.localColorTable != null) { | 
 | 
            colorTable = imageMetadata.localColorTable;  | 
 | 
            fallbackColorTable = imageMetadata.localColorTable;  | 
 | 
        } else { | 
 | 
            colorTable = streamMetadata.globalColorTable;  | 
 | 
        }  | 
 | 
 | 
 | 
        if (colorTable == null) { | 
 | 
            if (fallbackColorTable == null) { | 
 | 
                this.processWarningOccurred("Use default color table."); | 
 | 
 | 
 | 
                  | 
 | 
                fallbackColorTable = getDefaultPalette();  | 
 | 
            }  | 
 | 
 | 
 | 
            colorTable = fallbackColorTable;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        int length = colorTable.length/3;  | 
 | 
        int bits;  | 
 | 
        if (length == 2) { | 
 | 
            bits = 1;  | 
 | 
        } else if (length == 4) { | 
 | 
            bits = 2;  | 
 | 
        } else if (length == 8 || length == 16) { | 
 | 
              | 
 | 
            bits = 4;  | 
 | 
        } else { | 
 | 
              | 
 | 
            bits = 8;  | 
 | 
        }  | 
 | 
        int lutLength = 1 << bits;  | 
 | 
        byte[] r = new byte[lutLength];  | 
 | 
        byte[] g = new byte[lutLength];  | 
 | 
        byte[] b = new byte[lutLength];  | 
 | 
 | 
 | 
          | 
 | 
        int rgbIndex = 0;  | 
 | 
        for (int i = 0; i < length; i++) { | 
 | 
            r[i] = colorTable[rgbIndex++];  | 
 | 
            g[i] = colorTable[rgbIndex++];  | 
 | 
            b[i] = colorTable[rgbIndex++];  | 
 | 
        }  | 
 | 
 | 
 | 
        l.add(createIndexed(r, g, b, bits));  | 
 | 
        return l.iterator();  | 
 | 
    }  | 
 | 
 | 
 | 
    public ImageReadParam getDefaultReadParam() { | 
 | 
        return new ImageReadParam();  | 
 | 
    }  | 
 | 
 | 
 | 
    public IIOMetadata getStreamMetadata() throws IIOException { | 
 | 
        readHeader();  | 
 | 
        return streamMetadata;  | 
 | 
    }  | 
 | 
 | 
 | 
    public IIOMetadata getImageMetadata(int imageIndex) throws IIOException { | 
 | 
        checkIndex(imageIndex);  | 
 | 
 | 
 | 
        int index = locateImage(imageIndex);  | 
 | 
        if (index != imageIndex) { | 
 | 
            throw new IndexOutOfBoundsException("Bad image index!"); | 
 | 
        }  | 
 | 
        readMetadata();  | 
 | 
        return imageMetadata;  | 
 | 
    }  | 
 | 
 | 
 | 
    // BEGIN LZW STUFF  | 
 | 
 | 
 | 
    private void initNext32Bits() { | 
 | 
        next32Bits = block[0] & 0xff;  | 
 | 
        next32Bits |= (block[1] & 0xff) << 8;  | 
 | 
        next32Bits |= (block[2] & 0xff) << 16;  | 
 | 
        next32Bits |= block[3] << 24;  | 
 | 
        nextByte = 4;  | 
 | 
    }  | 
 | 
 | 
 | 
    // Load a block (1-255 bytes) at a time, and maintain  | 
 | 
    // a 32-bit lookahead buffer that is filled from the left  | 
 | 
    // and extracted from the right.  | 
 | 
    //  | 
 | 
    // When the last block is found, we continue to  | 
 | 
      | 
 | 
    private int getCode(int codeSize, int codeMask) throws IOException { | 
 | 
        if (bitPos + codeSize > 32) { | 
 | 
            return eofCode;   | 
 | 
        }  | 
 | 
 | 
 | 
        int code = (next32Bits >> bitPos) & codeMask;  | 
 | 
        bitPos += codeSize;  | 
 | 
 | 
 | 
          | 
 | 
        while (bitPos >= 8 && !lastBlockFound) { | 
 | 
            next32Bits >>>= 8;  | 
 | 
            bitPos -= 8;  | 
 | 
 | 
 | 
              | 
 | 
            if (nextByte >= blockLength) { | 
 | 
                  | 
 | 
                blockLength = stream.readUnsignedByte();  | 
 | 
                if (blockLength == 0) { | 
 | 
                    lastBlockFound = true;  | 
 | 
                    return code;  | 
 | 
                } else { | 
 | 
                    int left = blockLength;  | 
 | 
                    int off = 0;  | 
 | 
                    while (left > 0) { | 
 | 
                        int nbytes = stream.read(block, off, left);  | 
 | 
                        off += nbytes;  | 
 | 
                        left -= nbytes;  | 
 | 
                    }  | 
 | 
                    nextByte = 0;  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            next32Bits |= block[nextByte++] << 24;  | 
 | 
        }  | 
 | 
 | 
 | 
        return code;  | 
 | 
    }  | 
 | 
 | 
 | 
    public void initializeStringTable(int[] prefix,  | 
 | 
                                      byte[] suffix,  | 
 | 
                                      byte[] initial,  | 
 | 
                                      int[] length) { | 
 | 
        int numEntries = 1 << initCodeSize;  | 
 | 
        for (int i = 0; i < numEntries; i++) { | 
 | 
            prefix[i] = -1;  | 
 | 
            suffix[i] = (byte)i;  | 
 | 
            initial[i] = (byte)i;  | 
 | 
            length[i] = 1;  | 
 | 
        }  | 
 | 
 | 
 | 
        // Fill in the entire table for robustness against  | 
 | 
          | 
 | 
        for (int i = numEntries; i < 4096; i++) { | 
 | 
            prefix[i] = -1;  | 
 | 
            length[i] = 1;  | 
 | 
        }  | 
 | 
 | 
 | 
        // tableIndex = numEntries + 2;  | 
 | 
        // codeSize = initCodeSize + 1;  | 
 | 
        // codeMask = (1 << codeSize) - 1;  | 
 | 
    }  | 
 | 
 | 
 | 
    Rectangle sourceRegion;  | 
 | 
    int sourceXSubsampling;  | 
 | 
    int sourceYSubsampling;  | 
 | 
    int sourceMinProgressivePass;  | 
 | 
    int sourceMaxProgressivePass;  | 
 | 
 | 
 | 
    Point destinationOffset;  | 
 | 
    Rectangle destinationRegion;  | 
 | 
 | 
 | 
      | 
 | 
    int updateMinY;  | 
 | 
    int updateYStep;  | 
 | 
 | 
 | 
    boolean decodeThisRow = true;  | 
 | 
    int destY = 0;  | 
 | 
 | 
 | 
    byte[] rowBuf;  | 
 | 
 | 
 | 
    private void outputRow() { | 
 | 
          | 
 | 
        int width = Math.min(sourceRegion.width,  | 
 | 
                             destinationRegion.width*sourceXSubsampling);  | 
 | 
        int destX = destinationRegion.x;  | 
 | 
 | 
 | 
        if (sourceXSubsampling == 1) { | 
 | 
            theTile.setDataElements(destX, destY, width, 1, rowBuf);  | 
 | 
        } else { | 
 | 
            for (int x = 0; x < width; x += sourceXSubsampling, destX++) { | 
 | 
                theTile.setSample(destX, destY, 0, rowBuf[x] & 0xff);  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (updateListeners != null) { | 
 | 
            int[] bands = { 0 }; | 
 | 
            // updateYStep will have been initialized if  | 
 | 
              | 
 | 
            processImageUpdate(theImage,  | 
 | 
                               destX, destY,  | 
 | 
                               width, 1, 1, updateYStep,  | 
 | 
                               bands);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private void computeDecodeThisRow() { | 
 | 
        this.decodeThisRow =  | 
 | 
            (destY < destinationRegion.y + destinationRegion.height) &&  | 
 | 
            (streamY >= sourceRegion.y) &&  | 
 | 
            (streamY < sourceRegion.y + sourceRegion.height) &&  | 
 | 
            (((streamY - sourceRegion.y) % sourceYSubsampling) == 0);  | 
 | 
    }  | 
 | 
 | 
 | 
    private void outputPixels(byte[] string, int len) { | 
 | 
        if (interlacePass < sourceMinProgressivePass ||  | 
 | 
            interlacePass > sourceMaxProgressivePass) { | 
 | 
            return;  | 
 | 
        }  | 
 | 
 | 
 | 
        for (int i = 0; i < len; i++) { | 
 | 
            if (streamX >= sourceRegion.x) { | 
 | 
                rowBuf[streamX - sourceRegion.x] = string[i];  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            ++streamX;  | 
 | 
            if (streamX == width) { | 
 | 
                  | 
 | 
                ++rowsDone;  | 
 | 
                processImageProgress(100.0F*rowsDone/height);  | 
 | 
 | 
 | 
                if (decodeThisRow) { | 
 | 
                    outputRow();  | 
 | 
                }  | 
 | 
 | 
 | 
                streamX = 0;  | 
 | 
                if (imageMetadata.interlaceFlag) { | 
 | 
                    streamY += interlaceIncrement[interlacePass];  | 
 | 
                    if (streamY >= height) { | 
 | 
                          | 
 | 
                        if (updateListeners != null) { | 
 | 
                            processPassComplete(theImage);  | 
 | 
                        }  | 
 | 
 | 
 | 
                        ++interlacePass;  | 
 | 
                        if (interlacePass > sourceMaxProgressivePass) { | 
 | 
                            return;  | 
 | 
                        }  | 
 | 
                        streamY = interlaceOffset[interlacePass];  | 
 | 
                        startPass(interlacePass);  | 
 | 
                    }  | 
 | 
                } else { | 
 | 
                    ++streamY;  | 
 | 
                }  | 
 | 
 | 
 | 
                // Determine whether pixels from this row will  | 
 | 
                  | 
 | 
                this.destY = destinationRegion.y +  | 
 | 
                    (streamY - sourceRegion.y)/sourceYSubsampling;  | 
 | 
                computeDecodeThisRow();  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    // END LZW STUFF  | 
 | 
 | 
 | 
    private void readHeader() throws IIOException { | 
 | 
        if (gotHeader) { | 
 | 
            return;  | 
 | 
        }  | 
 | 
        if (stream == null) { | 
 | 
            throw new IllegalStateException("Input not set!"); | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        this.streamMetadata = new GIFStreamMetadata();  | 
 | 
 | 
 | 
        try { | 
 | 
            stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);  | 
 | 
 | 
 | 
            byte[] signature = new byte[6];  | 
 | 
            stream.readFully(signature);  | 
 | 
 | 
 | 
            StringBuffer version = new StringBuffer(3);  | 
 | 
            version.append((char)signature[3]);  | 
 | 
            version.append((char)signature[4]);  | 
 | 
            version.append((char)signature[5]);  | 
 | 
            streamMetadata.version = version.toString();  | 
 | 
 | 
 | 
            streamMetadata.logicalScreenWidth = stream.readUnsignedShort();  | 
 | 
            streamMetadata.logicalScreenHeight = stream.readUnsignedShort();  | 
 | 
 | 
 | 
            int packedFields = stream.readUnsignedByte();  | 
 | 
            boolean globalColorTableFlag = (packedFields & 0x80) != 0;  | 
 | 
            streamMetadata.colorResolution = ((packedFields >> 4) & 0x7) + 1;  | 
 | 
            streamMetadata.sortFlag = (packedFields & 0x8) != 0;  | 
 | 
            int numGCTEntries = 1 << ((packedFields & 0x7) + 1);  | 
 | 
 | 
 | 
            streamMetadata.backgroundColorIndex = stream.readUnsignedByte();  | 
 | 
            streamMetadata.pixelAspectRatio = stream.readUnsignedByte();  | 
 | 
 | 
 | 
            if (globalColorTableFlag) { | 
 | 
                streamMetadata.globalColorTable = new byte[3*numGCTEntries];  | 
 | 
                stream.readFully(streamMetadata.globalColorTable);  | 
 | 
            } else { | 
 | 
                streamMetadata.globalColorTable = null;  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            imageStartPosition.add(Long.valueOf(stream.getStreamPosition()));  | 
 | 
        } catch (IOException e) { | 
 | 
            throw new IIOException("I/O error reading header!", e); | 
 | 
        }  | 
 | 
 | 
 | 
        gotHeader = true;  | 
 | 
    }  | 
 | 
 | 
 | 
    private boolean skipImage() throws IIOException { | 
 | 
        // Stream must be at the beginning of an image descriptor  | 
 | 
        // upon exit  | 
 | 
 | 
 | 
        try { | 
 | 
            while (true) { | 
 | 
                int blockType = stream.readUnsignedByte();  | 
 | 
 | 
 | 
                if (blockType == 0x2c) { | 
 | 
                    stream.skipBytes(8);  | 
 | 
 | 
 | 
                    int packedFields = stream.readUnsignedByte();  | 
 | 
                    if ((packedFields & 0x80) != 0) { | 
 | 
                          | 
 | 
                        int bits = (packedFields & 0x7) + 1;  | 
 | 
                        stream.skipBytes(3*(1 << bits));  | 
 | 
                    }  | 
 | 
 | 
 | 
                    stream.skipBytes(1);  | 
 | 
 | 
 | 
                    int length = 0;  | 
 | 
                    do { | 
 | 
                        length = stream.readUnsignedByte();  | 
 | 
                        stream.skipBytes(length);  | 
 | 
                    } while (length > 0);  | 
 | 
 | 
 | 
                    return true;  | 
 | 
                } else if (blockType == 0x3b) { | 
 | 
                    return false;  | 
 | 
                } else if (blockType == 0x21) { | 
 | 
                    int label = stream.readUnsignedByte();  | 
 | 
 | 
 | 
                    int length = 0;  | 
 | 
                    do { | 
 | 
                        length = stream.readUnsignedByte();  | 
 | 
                        stream.skipBytes(length);  | 
 | 
                    } while (length > 0);  | 
 | 
                } else if (blockType == 0x0) { | 
 | 
                      | 
 | 
                    return false;  | 
 | 
                } else { | 
 | 
                    int length = 0;  | 
 | 
                    do { | 
 | 
                        length = stream.readUnsignedByte();  | 
 | 
                        stream.skipBytes(length);  | 
 | 
                    } while (length > 0);  | 
 | 
                }  | 
 | 
            }  | 
 | 
        } catch (EOFException e) { | 
 | 
            return false;  | 
 | 
        } catch (IOException e) { | 
 | 
            throw new IIOException("I/O error locating image!", e); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private int locateImage(int imageIndex) throws IIOException { | 
 | 
        readHeader();  | 
 | 
 | 
 | 
        try { | 
 | 
              | 
 | 
            int index = Math.min(imageIndex, imageStartPosition.size() - 1);  | 
 | 
 | 
 | 
              | 
 | 
            Long l = (Long)imageStartPosition.get(index);  | 
 | 
            stream.seek(l.longValue());  | 
 | 
 | 
 | 
              | 
 | 
            while (index < imageIndex) { | 
 | 
                if (!skipImage()) { | 
 | 
                    --index;  | 
 | 
                    return index;  | 
 | 
                }  | 
 | 
 | 
 | 
                Long l1 = new Long(stream.getStreamPosition());  | 
 | 
                imageStartPosition.add(l1);  | 
 | 
                ++index;  | 
 | 
            }  | 
 | 
        } catch (IOException e) { | 
 | 
            throw new IIOException("Couldn't seek!", e); | 
 | 
        }  | 
 | 
 | 
 | 
        if (currIndex != imageIndex) { | 
 | 
            imageMetadata = null;  | 
 | 
        }  | 
 | 
        currIndex = imageIndex;  | 
 | 
        return imageIndex;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
    private byte[] concatenateBlocks() throws IOException { | 
 | 
        byte[] data = new byte[0];  | 
 | 
        while (true) { | 
 | 
            int length = stream.readUnsignedByte();  | 
 | 
            if (length == 0) { | 
 | 
                break;  | 
 | 
            }  | 
 | 
            byte[] newData = new byte[data.length + length];  | 
 | 
            System.arraycopy(data, 0, newData, 0, data.length);  | 
 | 
            stream.readFully(newData, data.length, length);  | 
 | 
            data = newData;  | 
 | 
        }  | 
 | 
 | 
 | 
        return data;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
    private void readMetadata() throws IIOException { | 
 | 
        if (stream == null) { | 
 | 
            throw new IllegalStateException("Input not set!"); | 
 | 
        }  | 
 | 
 | 
 | 
        try { | 
 | 
              | 
 | 
            this.imageMetadata = new GIFImageMetadata();  | 
 | 
 | 
 | 
            long startPosition = stream.getStreamPosition();  | 
 | 
            while (true) { | 
 | 
                int blockType = stream.readUnsignedByte();  | 
 | 
                if (blockType == 0x2c) {  | 
 | 
                    imageMetadata.imageLeftPosition =  | 
 | 
                        stream.readUnsignedShort();  | 
 | 
                    imageMetadata.imageTopPosition =  | 
 | 
                        stream.readUnsignedShort();  | 
 | 
                    imageMetadata.imageWidth = stream.readUnsignedShort();  | 
 | 
                    imageMetadata.imageHeight = stream.readUnsignedShort();  | 
 | 
 | 
 | 
                    int idPackedFields = stream.readUnsignedByte();  | 
 | 
                    boolean localColorTableFlag =  | 
 | 
                        (idPackedFields & 0x80) != 0;  | 
 | 
                    imageMetadata.interlaceFlag = (idPackedFields & 0x40) != 0;  | 
 | 
                    imageMetadata.sortFlag = (idPackedFields & 0x20) != 0;  | 
 | 
                    int numLCTEntries = 1 << ((idPackedFields & 0x7) + 1);  | 
 | 
 | 
 | 
                    if (localColorTableFlag) { | 
 | 
                          | 
 | 
                        imageMetadata.localColorTable =  | 
 | 
                            new byte[3*numLCTEntries];  | 
 | 
                        stream.readFully(imageMetadata.localColorTable);  | 
 | 
                    } else { | 
 | 
                        imageMetadata.localColorTable = null;  | 
 | 
                    }  | 
 | 
 | 
 | 
                      | 
 | 
                    this.imageMetadataLength =  | 
 | 
                        (int)(stream.getStreamPosition() - startPosition);  | 
 | 
 | 
 | 
                      | 
 | 
                    return;  | 
 | 
                } else if (blockType == 0x21) {  | 
 | 
                    int label = stream.readUnsignedByte();  | 
 | 
 | 
 | 
                    if (label == 0xf9) { // Graphics Control Extension | 
 | 
                        int gceLength = stream.readUnsignedByte();   | 
 | 
                        int gcePackedFields = stream.readUnsignedByte();  | 
 | 
                        imageMetadata.disposalMethod =  | 
 | 
                            (gcePackedFields >> 2) & 0x3;  | 
 | 
                        imageMetadata.userInputFlag =  | 
 | 
                            (gcePackedFields & 0x2) != 0;  | 
 | 
                        imageMetadata.transparentColorFlag =  | 
 | 
                            (gcePackedFields & 0x1) != 0;  | 
 | 
 | 
 | 
                        imageMetadata.delayTime = stream.readUnsignedShort();  | 
 | 
                        imageMetadata.transparentColorIndex  | 
 | 
                            = stream.readUnsignedByte();  | 
 | 
 | 
 | 
                        int terminator = stream.readUnsignedByte();  | 
 | 
                    } else if (label == 0x1) {  | 
 | 
                        int length = stream.readUnsignedByte();  | 
 | 
                        imageMetadata.hasPlainTextExtension = true;  | 
 | 
                        imageMetadata.textGridLeft =  | 
 | 
                            stream.readUnsignedShort();  | 
 | 
                        imageMetadata.textGridTop =  | 
 | 
                            stream.readUnsignedShort();  | 
 | 
                        imageMetadata.textGridWidth =  | 
 | 
                            stream.readUnsignedShort();  | 
 | 
                        imageMetadata.textGridHeight =  | 
 | 
                            stream.readUnsignedShort();  | 
 | 
                        imageMetadata.characterCellWidth =  | 
 | 
                            stream.readUnsignedByte();  | 
 | 
                        imageMetadata.characterCellHeight =  | 
 | 
                            stream.readUnsignedByte();  | 
 | 
                        imageMetadata.textForegroundColor =  | 
 | 
                            stream.readUnsignedByte();  | 
 | 
                        imageMetadata.textBackgroundColor =  | 
 | 
                            stream.readUnsignedByte();  | 
 | 
                        imageMetadata.text = concatenateBlocks();  | 
 | 
                    } else if (label == 0xfe) {  | 
 | 
                        byte[] comment = concatenateBlocks();  | 
 | 
                        if (imageMetadata.comments == null) { | 
 | 
                            imageMetadata.comments = new ArrayList();  | 
 | 
                        }  | 
 | 
                        imageMetadata.comments.add(comment);  | 
 | 
                    } else if (label == 0xff) {  | 
 | 
                        int blockSize = stream.readUnsignedByte();  | 
 | 
                        byte[] applicationID = new byte[8];  | 
 | 
                        byte[] authCode = new byte[3];  | 
 | 
 | 
 | 
                          | 
 | 
                        byte[] blockData = new byte[blockSize];  | 
 | 
                        stream.readFully(blockData);  | 
 | 
 | 
 | 
                        int offset = copyData(blockData, 0, applicationID);  | 
 | 
                        offset = copyData(blockData, offset, authCode);  | 
 | 
 | 
 | 
                        byte[] applicationData = concatenateBlocks();  | 
 | 
 | 
 | 
                        if (offset < blockSize) { | 
 | 
                            int len = blockSize - offset;  | 
 | 
                            byte[] data =  | 
 | 
                                new byte[len + applicationData.length];  | 
 | 
 | 
 | 
                            System.arraycopy(blockData, offset, data, 0, len);  | 
 | 
                            System.arraycopy(applicationData, 0, data, len,  | 
 | 
                                             applicationData.length);  | 
 | 
 | 
 | 
                            applicationData = data;  | 
 | 
                        }  | 
 | 
 | 
 | 
                          | 
 | 
                        if (imageMetadata.applicationIDs == null) { | 
 | 
                            imageMetadata.applicationIDs = new ArrayList();  | 
 | 
                            imageMetadata.authenticationCodes =  | 
 | 
                                new ArrayList();  | 
 | 
                            imageMetadata.applicationData = new ArrayList();  | 
 | 
                        }  | 
 | 
                        imageMetadata.applicationIDs.add(applicationID);  | 
 | 
                        imageMetadata.authenticationCodes.add(authCode);  | 
 | 
                        imageMetadata.applicationData.add(applicationData);  | 
 | 
                    } else { | 
 | 
                          | 
 | 
                        int length = 0;  | 
 | 
                        do { | 
 | 
                            length = stream.readUnsignedByte();  | 
 | 
                            stream.skipBytes(length);  | 
 | 
                        } while (length > 0);  | 
 | 
                    }  | 
 | 
                } else if (blockType == 0x3b) {  | 
 | 
                    throw new IndexOutOfBoundsException  | 
 | 
                        ("Attempt to read past end of image sequence!"); | 
 | 
                } else { | 
 | 
                    throw new IIOException("Unexpected block type " + | 
 | 
                                           blockType + "!");  | 
 | 
                }  | 
 | 
            }  | 
 | 
        } catch (IIOException iioe) { | 
 | 
            throw iioe;  | 
 | 
        } catch (IOException ioe) { | 
 | 
            throw new IIOException("I/O error reading image metadata!", ioe); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private int copyData(byte[] src, int offset, byte[] dst) { | 
 | 
        int len = dst.length;  | 
 | 
        int rest = src.length - offset;  | 
 | 
        if (len > rest) { | 
 | 
            len = rest;  | 
 | 
        }  | 
 | 
        System.arraycopy(src, offset, dst, 0, len);  | 
 | 
        return offset + len;  | 
 | 
    }  | 
 | 
 | 
 | 
    private void startPass(int pass) { | 
 | 
        if (updateListeners == null || !imageMetadata.interlaceFlag) { | 
 | 
            return;  | 
 | 
        }  | 
 | 
 | 
 | 
        int y = interlaceOffset[interlacePass];  | 
 | 
        int yStep = interlaceIncrement[interlacePass];  | 
 | 
 | 
 | 
        int[] vals = ReaderUtil.  | 
 | 
            computeUpdatedPixels(sourceRegion,  | 
 | 
                                 destinationOffset,  | 
 | 
                                 destinationRegion.x,  | 
 | 
                                 destinationRegion.y,  | 
 | 
                                 destinationRegion.x +  | 
 | 
                                 destinationRegion.width - 1,  | 
 | 
                                 destinationRegion.y +  | 
 | 
                                 destinationRegion.height - 1,  | 
 | 
                                 sourceXSubsampling,  | 
 | 
                                 sourceYSubsampling,  | 
 | 
                                 0,  | 
 | 
                                 y,  | 
 | 
                                 destinationRegion.width,  | 
 | 
                                 (destinationRegion.height + yStep - 1)/yStep,  | 
 | 
                                 1,  | 
 | 
                                 yStep);  | 
 | 
 | 
 | 
          | 
 | 
        this.updateMinY = vals[1];  | 
 | 
        this.updateYStep = vals[5];  | 
 | 
 | 
 | 
          | 
 | 
        int[] bands = { 0 }; | 
 | 
 | 
 | 
        processPassStarted(theImage,  | 
 | 
                           interlacePass,  | 
 | 
                           sourceMinProgressivePass,  | 
 | 
                           sourceMaxProgressivePass,  | 
 | 
                           0,  | 
 | 
                           updateMinY,  | 
 | 
                           1,  | 
 | 
                           updateYStep,  | 
 | 
                           bands);  | 
 | 
    }  | 
 | 
 | 
 | 
    public BufferedImage read(int imageIndex, ImageReadParam param)  | 
 | 
        throws IIOException { | 
 | 
        if (stream == null) { | 
 | 
            throw new IllegalStateException("Input not set!"); | 
 | 
        }  | 
 | 
        checkIndex(imageIndex);  | 
 | 
 | 
 | 
        int index = locateImage(imageIndex);  | 
 | 
        if (index != imageIndex) { | 
 | 
            throw new IndexOutOfBoundsException("imageIndex out of bounds!"); | 
 | 
        }  | 
 | 
 | 
 | 
        clearAbortRequest();  | 
 | 
        readMetadata();  | 
 | 
 | 
 | 
          | 
 | 
        if (param == null) { | 
 | 
            param = getDefaultReadParam();  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        Iterator imageTypes = getImageTypes(imageIndex);  | 
 | 
        this.theImage = getDestination(param,  | 
 | 
                                       imageTypes,  | 
 | 
                                       imageMetadata.imageWidth,  | 
 | 
                                       imageMetadata.imageHeight);  | 
 | 
        this.theTile = theImage.getWritableTile(0, 0);  | 
 | 
        this.width = imageMetadata.imageWidth;  | 
 | 
        this.height = imageMetadata.imageHeight;  | 
 | 
        this.streamX = 0;  | 
 | 
        this.streamY = 0;  | 
 | 
        this.rowsDone = 0;  | 
 | 
        this.interlacePass = 0;  | 
 | 
 | 
 | 
        // Get source region, taking subsampling offsets into account,  | 
 | 
        // and clipping against the true source bounds  | 
 | 
 | 
 | 
        this.sourceRegion = new Rectangle(0, 0, 0, 0);  | 
 | 
        this.destinationRegion = new Rectangle(0, 0, 0, 0);  | 
 | 
        computeRegions(param, width, height, theImage,  | 
 | 
                       sourceRegion, destinationRegion);  | 
 | 
        this.destinationOffset = new Point(destinationRegion.x,  | 
 | 
                                           destinationRegion.y);  | 
 | 
 | 
 | 
        this.sourceXSubsampling = param.getSourceXSubsampling();  | 
 | 
        this.sourceYSubsampling = param.getSourceYSubsampling();  | 
 | 
        this.sourceMinProgressivePass =  | 
 | 
            Math.max(param.getSourceMinProgressivePass(), 0);  | 
 | 
        this.sourceMaxProgressivePass =  | 
 | 
            Math.min(param.getSourceMaxProgressivePass(), 3);  | 
 | 
 | 
 | 
        this.destY = destinationRegion.y +  | 
 | 
            (streamY - sourceRegion.y)/sourceYSubsampling;  | 
 | 
        computeDecodeThisRow();  | 
 | 
 | 
 | 
          | 
 | 
        processImageStarted(imageIndex);  | 
 | 
        startPass(0);  | 
 | 
 | 
 | 
        this.rowBuf = new byte[width];  | 
 | 
 | 
 | 
        try { | 
 | 
              | 
 | 
            this.initCodeSize = stream.readUnsignedByte();  | 
 | 
              | 
 | 
            if (this.initCodeSize < 1 || this.initCodeSize > 8) { | 
 | 
                throw new IIOException("Bad code size:" + this.initCodeSize); | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            this.blockLength = stream.readUnsignedByte();  | 
 | 
            int left = blockLength;  | 
 | 
            int off = 0;  | 
 | 
            while (left > 0) { | 
 | 
                int nbytes = stream.read(block, off, left);  | 
 | 
                left -= nbytes;  | 
 | 
                off += nbytes;  | 
 | 
            }  | 
 | 
 | 
 | 
            this.bitPos = 0;  | 
 | 
            this.nextByte = 0;  | 
 | 
            this.lastBlockFound = false;  | 
 | 
            this.interlacePass = 0;  | 
 | 
 | 
 | 
              | 
 | 
            initNext32Bits();  | 
 | 
 | 
 | 
            this.clearCode = 1 << initCodeSize;  | 
 | 
            this.eofCode = clearCode + 1;  | 
 | 
 | 
 | 
            int code, oldCode = 0;  | 
 | 
 | 
 | 
            int[] prefix = new int[4096];  | 
 | 
            byte[] suffix = new byte[4096];  | 
 | 
            byte[] initial = new byte[4096];  | 
 | 
            int[] length = new int[4096];  | 
 | 
            byte[] string = new byte[4096];  | 
 | 
 | 
 | 
            initializeStringTable(prefix, suffix, initial, length);  | 
 | 
            int tableIndex = (1 << initCodeSize) + 2;  | 
 | 
            int codeSize = initCodeSize + 1;  | 
 | 
            int codeMask = (1 << codeSize) - 1;  | 
 | 
 | 
 | 
            while (!abortRequested()) { | 
 | 
                code = getCode(codeSize, codeMask);  | 
 | 
 | 
 | 
                if (code == clearCode) { | 
 | 
                    initializeStringTable(prefix, suffix, initial, length);  | 
 | 
                    tableIndex = (1 << initCodeSize) + 2;  | 
 | 
                    codeSize = initCodeSize + 1;  | 
 | 
                    codeMask = (1 << codeSize) - 1;  | 
 | 
 | 
 | 
                    code = getCode(codeSize, codeMask);  | 
 | 
                    if (code == eofCode) { | 
 | 
                          | 
 | 
                        processImageComplete();  | 
 | 
                        return theImage;  | 
 | 
                    }  | 
 | 
                } else if (code == eofCode) { | 
 | 
                      | 
 | 
                    processImageComplete();  | 
 | 
                    return theImage;  | 
 | 
                } else { | 
 | 
                    int newSuffixIndex;  | 
 | 
                    if (code < tableIndex) { | 
 | 
                        newSuffixIndex = code;  | 
 | 
                    } else {  | 
 | 
                        newSuffixIndex = oldCode;  | 
 | 
                        if (code != tableIndex) { | 
 | 
                            // warning - code out of sequence  | 
 | 
                              | 
 | 
                            processWarningOccurred("Out-of-sequence code!"); | 
 | 
                        }  | 
 | 
                    }  | 
 | 
 | 
 | 
                    int ti = tableIndex;  | 
 | 
                    int oc = oldCode;  | 
 | 
 | 
 | 
                    prefix[ti] = oc;  | 
 | 
                    suffix[ti] = initial[newSuffixIndex];  | 
 | 
                    initial[ti] = initial[oc];  | 
 | 
                    length[ti] = length[oc] + 1;  | 
 | 
 | 
 | 
                    ++tableIndex;  | 
 | 
                    if ((tableIndex == (1 << codeSize)) &&  | 
 | 
                        (tableIndex < 4096)) { | 
 | 
                        ++codeSize;  | 
 | 
                        codeMask = (1 << codeSize) - 1;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
 | 
 | 
                  | 
 | 
                int c = code;  | 
 | 
                int len = length[c];  | 
 | 
                for (int i = len - 1; i >= 0; i--) { | 
 | 
                    string[i] = suffix[c];  | 
 | 
                    c = prefix[c];  | 
 | 
                }  | 
 | 
 | 
 | 
                outputPixels(string, len);  | 
 | 
                oldCode = code;  | 
 | 
            }  | 
 | 
 | 
 | 
            processReadAborted();  | 
 | 
            return theImage;  | 
 | 
        } catch (IOException e) { | 
 | 
            throw new IIOException("I/O error reading image!", e); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    public void reset() { | 
 | 
        super.reset();  | 
 | 
        resetStreamSettings();  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private void resetStreamSettings() { | 
 | 
        gotHeader = false;  | 
 | 
        streamMetadata = null;  | 
 | 
        currIndex = -1;  | 
 | 
        imageMetadata = null;  | 
 | 
        imageStartPosition = new ArrayList();  | 
 | 
        numImages = -1;  | 
 | 
 | 
 | 
          | 
 | 
        blockLength = 0;  | 
 | 
        bitPos = 0;  | 
 | 
        nextByte = 0;  | 
 | 
 | 
 | 
        next32Bits = 0;  | 
 | 
        lastBlockFound = false;  | 
 | 
 | 
 | 
        theImage = null;  | 
 | 
        theTile = null;  | 
 | 
        width = -1;  | 
 | 
        height = -1;  | 
 | 
        streamX = -1;  | 
 | 
        streamY = -1;  | 
 | 
        rowsDone = 0;  | 
 | 
        interlacePass = 0;  | 
 | 
 | 
 | 
        fallbackColorTable = null;  | 
 | 
    }  | 
 | 
 | 
 | 
    private static byte[] defaultPalette = null;  | 
 | 
 | 
 | 
    private static synchronized byte[] getDefaultPalette() { | 
 | 
        if (defaultPalette == null) { | 
 | 
            BufferedImage img = new BufferedImage(1, 1,  | 
 | 
                    BufferedImage.TYPE_BYTE_INDEXED);  | 
 | 
            IndexColorModel icm = (IndexColorModel) img.getColorModel();  | 
 | 
 | 
 | 
            final int size = icm.getMapSize();  | 
 | 
            byte[] r = new byte[size];  | 
 | 
            byte[] g = new byte[size];  | 
 | 
            byte[] b = new byte[size];  | 
 | 
            icm.getReds(r);  | 
 | 
            icm.getGreens(g);  | 
 | 
            icm.getBlues(b);  | 
 | 
 | 
 | 
            defaultPalette = new byte[size * 3];  | 
 | 
 | 
 | 
            for (int i = 0; i < size; i++) { | 
 | 
                defaultPalette[3 * i + 0] = r[i];  | 
 | 
                defaultPalette[3 * i + 1] = g[i];  | 
 | 
                defaultPalette[3 * i + 2] = b[i];  | 
 | 
            }  | 
 | 
        }  | 
 | 
        return defaultPalette;  | 
 | 
    }  | 
 | 
}  |