package com.fr.third.JAI; /* * Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * -Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * -Redistribution in binary form must reproduct the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that Software is not designed,licensed or intended for use in * the design, construction, operation or maintenance of any nuclear facility. */ import java.awt.Point; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.ImageProducer; import java.awt.image.IndexColorModel; import java.awt.image.PixelInterleavedSampleModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashMap; /** */ public class GIFImageDecoder extends ImageDecoderImpl { // The global color table. private byte[] globalColorTable = null; // Whether the last page has been encountered. private boolean maxPageFound = false; // The maximum allowable page for reading. private int maxPage; // The previous page read. private int prevPage = -1; // The previous page on which getTile() was invoked in this object. private int prevSyncedPage = -1; // Map of Integer page numbers to RenderedImages. private HashMap images = new HashMap(); /** * Read the overall stream header and return the global color map * or null. */ private static byte[] readHeader(SeekableStream input) throws IOException { byte[] globalColorTable = null; try { // Skip the version string and logical screen dimensions. input.skipBytes(10); int packedFields = input.readUnsignedByte(); boolean globalColorTableFlag = (packedFields & 0x80) != 0; int numGCTEntries = 1 << ((packedFields & 0x7) + 1); int backgroundColorIndex = input.readUnsignedByte(); // Read the aspect ratio but ignore the returned value. input.read(); if (globalColorTableFlag) { globalColorTable = new byte[3*numGCTEntries]; input.readFully(globalColorTable); } else { globalColorTable = null; } } catch (IOException e) { throw new IOException(JaiI18N.getString("GIFImageDecoder0")); } return globalColorTable; } public GIFImageDecoder(SeekableStream input, ImageDecodeParam param) { super(input, param); } public GIFImageDecoder(InputStream input, ImageDecodeParam param) { super(input, param); } public int getNumPages() throws IOException { int page = prevPage + 1; while(!maxPageFound) { try { decodeAsRenderedImage(page++); } catch(IOException e) { // Ignore } } return maxPage + 1; } public synchronized RenderedImage decodeAsRenderedImage(int page) throws IOException { // Verify that the index is in range. if (page < 0 || (maxPageFound && page > maxPage)) { throw new IOException(JaiI18N.getString("GIFImageDecoder1")); } // Attempt to get the image from the cache. Integer pageKey = new Integer(page); if(images.containsKey(pageKey)) { return (RenderedImage)images.get(pageKey); } // If the zeroth image, set the global color table. if(prevPage == -1) { try { globalColorTable = readHeader(input); } catch(IOException e) { maxPageFound = true; maxPage = -1; throw e; } } // Force previous data to be read. if(page > 0) { for(int idx = prevSyncedPage + 1; idx < page; idx++) { RenderedImage im = (RenderedImage)images.get(new Integer(idx)); im.getTile(0, 0); prevSyncedPage = idx; } } // Read as many images as possible. RenderedImage image = null; while(prevPage < page) { int index = prevPage + 1; RenderedImage ri = null; try { ri = new GIFImage(input, globalColorTable); images.put(new Integer(index), ri); if(index < page) { ri.getTile(0, 0); prevSyncedPage = index; } prevPage = index; if(index == page) { image = ri; break; } } catch(IOException e) { maxPageFound = true; maxPage = prevPage; throw e; } } return image; } } /** */ class GIFImage extends SimpleRenderedImage { // Constants used to control interlacing. private static final int[] INTERLACE_INCREMENT = { 8, 8, 4, 2, -1 }; private static final int[] INTERLACE_OFFSET = { 0, 4, 2, 1, -1 }; // The source stream. private SeekableStream input; // The interlacing flag. private boolean interlaceFlag = false; // Variables used by LZW decoding private byte[] block = new byte[255]; private int blockLength = 0; private int bitPos = 0; private int nextByte = 0; private int initCodeSize; private int clearCode; private int eofCode; // 32-bit lookahead buffer private int next32Bits = 0; // True if the end of the data blocks has been found, // and we are simply draining the 32-bit buffer private boolean lastBlockFound = false; // The current interlacing pass, starting with 0. private int interlacePass = 0; // The image's tile. private WritableRaster theTile = null; // Read blocks of 1-255 bytes, stop at a 0-length block private void skipBlocks() throws IOException { while (true) { int length = input.readUnsignedByte(); if (length == 0) { break; } input.skipBytes(length); } } /** * Create a new GIFImage. The input stream must * be positioned at the start of the image, i.e., not at the * start of the overall stream. * * @param input the stream from which to read. * @param globalColorTable the global colormap of null. * * @throws IOException. */ GIFImage(SeekableStream input, byte[] globalColorTable) throws IOException { this.input = input; byte[] localColorTable = null; boolean transparentColorFlag = false; int transparentColorIndex = 0; // Read the image header initializing the local color table, // if any, and the transparent index, if any. try { long startPosition = input.getFilePointer(); while (true) { int blockType = input.readUnsignedByte(); if (blockType == 0x2c) { // Image Descriptor // Skip image top and left position. input.skipBytes(4); width = input.readUnsignedShortLE(); height = input.readUnsignedShortLE(); int idPackedFields = input.readUnsignedByte(); boolean localColorTableFlag = (idPackedFields & 0x80) != 0; interlaceFlag = (idPackedFields & 0x40) != 0; int numLCTEntries = 1 << ((idPackedFields & 0x7) + 1); if (localColorTableFlag) { // Read color table if any localColorTable = new byte[3*numLCTEntries]; input.readFully(localColorTable); } else { localColorTable = null; } // Now positioned at start of LZW-compressed pixels break; } else if (blockType == 0x21) { // Extension block int label = input.readUnsignedByte(); if (label == 0xf9) { // Graphics Control Extension input.read(); // extension length int gcePackedFields = input.readUnsignedByte(); transparentColorFlag = (gcePackedFields & 0x1) != 0; input.skipBytes(2); // delay time transparentColorIndex = input.readUnsignedByte(); input.read(); // terminator } else if (label == 0x1) { // Plain text extension // Skip content. input.skipBytes(13); // Read but do not save content. skipBlocks(); } else if (label == 0xfe) { // Comment extension // Read but do not save content. skipBlocks(); } else if (label == 0xff) { // Application extension // Skip content. input.skipBytes(12); // Read but do not save content. skipBlocks(); } else { // Skip over unknown extension blocks int length = 0; do { length = input.readUnsignedByte(); input.skipBytes(length); } while (length > 0); } } else { throw new IOException(JaiI18N.getString("GIFImage0")+" "+ blockType + "!"); } } } catch (IOException ioe) { throw new IOException(JaiI18N.getString("GIFImage1")); } // Set the image layout from the header information. // Set the image and tile grid origin to (0, 0). minX = minY = tileGridXOffset = tileGridYOffset = 0; // Force the image to have a single tile. tileWidth = width; tileHeight = height; byte[] colorTable; if (localColorTable != null) { colorTable = localColorTable; } else { colorTable = 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 (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(transparentColorIndex, lutLength - 1); a[idx] = (byte)0; } int[] bitsPerSample = new int[1]; bitsPerSample[0] = bits; if (a == null) { colorModel = new IndexColorModel(bits, r.length, r, g, b); } else { colorModel = new IndexColorModel(bits, r.length, r, g, b, a); } sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, width, height, 1, width, new int[] {0}); } // BEGIN LZW CODE 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. 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 = input.readUnsignedByte(); if (blockLength == 0) { lastBlockFound = true; return code; } else { int left = blockLength; int off = 0; while (left > 0) { int nbytes = input.read(block, off, left); off += nbytes; left -= nbytes; } nextByte = 0; } } next32Bits |= block[nextByte++] << 24; } return code; } private 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; } } private Point outputPixels(byte[] string, int len, Point streamPos, byte[] rowBuf) { if (interlacePass < 0 || interlacePass > 3) { return streamPos; } for (int i = 0; i < len; i++) { if (streamPos.x >= minX) { rowBuf[streamPos.x - minX] = string[i]; } // Process end-of-row ++streamPos.x; if (streamPos.x == width) { theTile.setDataElements(minX, streamPos.y, width, 1, rowBuf); streamPos.x = 0; if (interlaceFlag) { streamPos.y += INTERLACE_INCREMENT[interlacePass]; if (streamPos.y >= height) { ++interlacePass; if (interlacePass > 3) { return streamPos; } streamPos.y = INTERLACE_OFFSET[interlacePass]; } } else { ++streamPos.y; } } } return streamPos; } // END LZW CODE public synchronized Raster getTile(int tileX, int tileY) { // Should be a unique tile. if (tileX != 0 || tileY != 0) { throw new IllegalArgumentException(JaiI18N.getString("GIFImage2")); } // Return the tile if it's already computed. if (theTile != null) { return theTile; } // Initialize the destination image theTile = WritableRaster.createWritableRaster(sampleModel, sampleModel.createDataBuffer(), null); // Position in stream coordinates. Point streamPos = new Point(0, 0); // Allocate a row of memory. byte[] rowBuf = new byte[width]; try { // Read and decode the image data, fill in theTile. this.initCodeSize = input.readUnsignedByte(); // Read first data block this.blockLength = input.readUnsignedByte(); int left = blockLength; int off = 0; while (left > 0) { int nbytes = input.read(block, off, left); left -= nbytes; off += nbytes; } this.bitPos = 0; this.nextByte = 0; this.lastBlockFound = false; // 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 (true) { 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) { return theTile; } } else if (code == eofCode) { return theTile; } else { int newSuffixIndex; if (code < tableIndex) { newSuffixIndex = code; } else { // code == tableIndex newSuffixIndex = oldCode; } 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, streamPos, rowBuf); oldCode = code; } } catch (IOException e) { throw new RuntimeException(JaiI18N.getString("GIFImage3")); } } }