/* * 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];Lines 1–500 of 1028. Load more
/* * 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];