/* * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ 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.BufferedInputStream; import java.io.DataInputStream; import java.io.EOFException; import java.io.InputStream; import java.io.IOException; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; 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; public class GIFImageReader extends ImageReader { // The current ImageInputStream source. ImageInputStream stream = null; // Per-stream settings // True if the file header including stream metadata has been read. boolean gotHeader = false; // Global metadata, read once per input setting. GIFStreamMetadata streamMetadata = null; // The current image index int currIndex = -1; // Metadata for image at 'currIndex', or null. GIFImageMetadata imageMetadata = null; // A List of Longs indicating the stream positions of the // start of the metadata for each image. Entries are added // as needed. List imageStartPosition = new ArrayList(); // Length of metadata for image at 'currIndex', valid only if // imageMetadata != null. int imageMetadataLength; // The number of images in the stream, if known, otherwise -1. int numImages = -1; // Variables used by the LZW decoding process byte[] block = new byte[255]; int blockLength = 0; int bitPos = 0; int nextByte = 0; int initCodeSize; int clearCode; int eofCode; // 32-bit lookahead buffer int next32Bits = 0; // Try if the end of the data blocks has been found, // and we are simply draining the 32-bit buffer boolean lastBlockFound = false; // The image to be written. BufferedImage theImage = null; // The image's tile. WritableRaster theTile = null; // The image dimensions (from the stream). int width = -1, height = -1; // The pixel currently being decoded (in the stream's coordinates). int streamX = -1, streamY = -1; // The number of rows decoded int rowsDone = 0; // The current interlace pass, starting with 0. int interlacePass = 0; // End per-stream settings // Constants used to control interlacing. static final int[] interlaceIncrement = { 8, 8, 4, 2, -1 }; static final int[] interlaceOffset = { 0, 4, 2, 1, -1 }; public GIFImageReader(ImageReaderSpi originatingProvider) { super(originatingProvider); } // Take input from an ImageInputStream 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; } // Clear all values based on the previous stream contents 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, // and bump minIndex if required. 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; } 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; } else { colorTable = streamMetadata.globalColorTable; } // Normalize color table length to 2^1, 2^2, 2^4, or 2^8 int length = colorTable.length/3; int bits; if (length == 2) { bits = 1; } else if (length == 4) { bits = 2; } else if (length == 8 || length == 16) { // Bump from 3 to 4 bits bits = 4; } else { // Bump to 8 bits bits = 8; } int lutLength = 1 << bits; byte[] r = new byte[lutLength]; byte[] g = new byte[lutLength]; byte[] b = new byte[lutLength]; // Entries from length + 1 to lutLength - 1 will be 0 int rgbIndex = 0; for (int i = 0; i < length; i++) { r[i] = colorTable[rgbIndex++]; g[i] = colorTable[rgbIndex++]; b[i] = colorTable[rgbIndex++]; } byte[] a = null; if (imageMetadata.transparentColorFlag) { a = new byte[lutLength]; Arrays.fill(a, (byte)255); // Some files erroneously have a transparent color index // of 255 even though there are fewer than 256 colors. int idx = Math.min(imageMetadata.transparentColorIndex, lutLength - 1); a[idx] = (byte)0; } int[] bitsPerSample = new int[1]; bitsPerSample[0] = bits; l.add(ImageTypeSpecifier.createIndexed(r, g, b, a, bits, DataBuffer.TYPE_BYTE)); 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; // No more data available } int code = (next32Bits >> bitPos) & codeMask; bitPos += codeSize; // Shift in a byte of new data at a time while (bitPos >= 8 && !lastBlockFound) { next32Bits >>>= 8; bitPos -= 8; // Check if current block is out of bytes if (nextByte >= blockLength) { // Get next block size 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 // out-of-sequence codes. 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; // Used only if IIOReadUpdateListeners are present int updateMinY; int updateYStep; boolean decodeThisRow = true; int destY = 0; byte[] rowBuf; private void outputRow() { // Clip against ImageReadParam 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); } } // Update IIOReadUpdateListeners, if any if (updateListeners != null) { int[] bands = { 0 }; // updateYStep will have been initialized if // updateListeners is non-null 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]; } // Process end-of-row ++streamX; if (streamX == width) { // Update IIOReadProgressListeners ++rowsDone; processImageProgress(100.0F*rowsDone/height); if (decodeThisRow) { outputRow(); } streamX = 0; if (imageMetadata.interlaceFlag) { streamY += interlaceIncrement[interlacePass]; if (streamY >= height) { // Inform IIOReadUpdateListeners of end of pass if (updateListeners != null) { processPassComplete(theImage); } ++interlacePass; if (interlacePass > sourceMaxProgressivePass) { return; } streamY = interlaceOffset[interlacePass]; startPass(interlacePass); } } else { ++streamY; } // Determine whether pixels from this row will // be written to the destination 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!"); } // Create an object to store the stream metadata 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; } // Found position of metadata for image 0 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) { // Skip color table if any 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) { // EOF 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 { // Find closest known index int index = Math.min(imageIndex, imageStartPosition.size() - 1); // Seek to that position Long l = (Long)imageStartPosition.get(index); stream.seek(l.longValue()); // Skip images until at desired index or last image found 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; } // Read blocks of 1-255 bytes, stop at a 0-length block 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; } // Stream must be positioned at start of metadata for 'currIndex' private void readMetadata() throws IIOException { if (stream == null) { throw new IllegalStateException("Input not set!"); } try { // Create an object to store the image metadata this.imageMetadata = new GIFImageMetadata(); long startPosition = stream.getStreamPosition(); while (true) { int blockType = stream.readUnsignedByte(); if (blockType == 0x2c) { // Image Descriptor 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) { // Read color table if any imageMetadata.localColorTable = new byte[3*numLCTEntries]; stream.readFully(imageMetadata.localColorTable); } else { imageMetadata.localColorTable = null; } // Record length of this metadata block this.imageMetadataLength = (int)(stream.getStreamPosition() - startPosition); // Now positioned at start of LZW-compressed pixels return; } else if (blockType == 0x21) { // Extension block int label = stream.readUnsignedByte(); if (label == 0xf9) { // Graphics Control Extension int gceLength = stream.readUnsignedByte(); // 4 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) { // Plain text extension 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) { // Comment extension byte[] comment = concatenateBlocks(); if (imageMetadata.comments == null) { imageMetadata.comments = new ArrayList(); } imageMetadata.comments.add(comment); } else if (label == 0xff) { // Application extension int blockSize = stream.readUnsignedByte(); byte[] applicationID = new byte[8]; byte[] authCode = new byte[3]; // read available data 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; } // Init lists if necessary 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 { // Skip over unknown extension blocks int length = 0; do { length = stream.readUnsignedByte(); stream.skipBytes(length); } while (length > 0); } } else if (blockType == 0x3b) { // Trailer 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) { return; } int y = 0; int yStep = 1; if (imageMetadata.interlaceFlag) { y = interlaceOffset[interlacePass]; 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); // Initialized updateMinY and updateYStep this.updateMinY = vals[1]; this.updateYStep = vals[5]; // Inform IIOReadUpdateListeners of new pass 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(); // A null ImageReadParam means we use the default if (param == null) { param = getDefaultReadParam(); } // Initialize the destination image 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(); // Inform IIOReadProgressListeners of start of image processImageStarted(imageIndex); startPass(0); this.rowBuf = new byte[width]; try { // Read and decode the image data, fill in theImage this.initCodeSize = stream.readUnsignedByte(); // Read first data block 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; // Init 32-bit buffer 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) { // Inform IIOReadProgressListeners of end of image processImageComplete(); return theImage; } } else if (code == eofCode) { // Inform IIOReadProgressListeners of end of image processImageComplete(); return theImage; } else { int newSuffixIndex; if (code < tableIndex) { newSuffixIndex = code; } else { // code == tableIndex newSuffixIndex = oldCode; if (code != tableIndex) { // warning - code out of sequence // possibly data corruption 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; } } // Reverse code 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) { e.printStackTrace(); throw new IIOException("I/O error reading image!", e); } } /** * Remove all settings including global settings such as * Locales and listeners, as well as stream settings. */ public void reset() { super.reset(); resetStreamSettings(); } /** * Remove local settings based on parsing of a stream. */ private void resetStreamSettings() { gotHeader = false; streamMetadata = null; currIndex = -1; imageMetadata = null; imageStartPosition = new ArrayList(); numImages = -1; // No need to reinitialize 'block' 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; } }