123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 |
- /* GIFFile.java -- GIF decoder
- Copyright (C) 2006 Free Software Foundation, Inc.
- This file is part of GNU Classpath.
- GNU Classpath is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
- GNU Classpath 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 for more details.
- You should have received a copy of the GNU General Public License
- along with GNU Classpath; see the file COPYING. If not, write to the
- Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA.
- Linking this library statically or dynamically with other modules is
- making a combined work based on this library. Thus, the terms and
- conditions of the GNU General Public License cover the whole
- combination.
- As a special exception, the copyright holders of this library give you
- permission to link this library with independent modules to produce an
- executable, regardless of the license terms of these independent
- modules, and to copy and distribute the resulting executable under
- terms of your choice, provided that you also meet, for each linked
- independent module, the terms and conditions of the license of that
- module. An independent module is a module which is not derived from
- or based on this library. If you modify this library, you may extend
- this exception to your version of the library, but you are not
- obligated to do so. If you do not wish to do so, delete this
- exception statement from your version. */
- package gnu.javax.imageio.gif;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.Vector;
- /**
- * GIFFile - reads a GIF file.
- *
- * This class only does the bare minimum work, and returns the data in raw
- * formats (described below). The class is J2ME compatible, and hopefully
- * we can keep it that way without any significant overhead.
- *
- * @author Sven de Marothy.
- */
- public class GIFFile
- {
- // "NETSCAPE2.0" - identifier
- private final static byte[] nsBlock = new byte[]
- {0x4e, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2e, 0x30 };
- /**
- * Block identifiers
- */
- private final static int EXTENSION = 0x21;
- private final static int LOCAL = 0x2C;
- private final static int TERMINATOR = 0x3B;
- /**
- * Extension block types
- */
- private final static int EXTENSION_COMMENT = 254;
- private final static int EXTENSION_GCONTROL = 249;
- private final static int EXTENSION_APPLICATION = 255;
- /**
- * Undraw commands for animation.
- */
- private final static int UNDRAW_OVERWRITE = 1;
- private final static int UNDRAW_RESTORE_BACKGROUND = 2;
- private final static int UNDRAW_RESTORE_PREVIOUS = 3;
- /**
- * Image position and dimensions (images may be partial)
- */
- private int x, y, width, height;
- /**
- * Global dimensions
- */
- private int globalWidth, globalHeight;
- /**
- * Background color index.
- */
- private byte bgIndex;
- /**
- * Number of colors
- */
- private int nColors;
- /**
- * Global palette, if any
- */
- private byte[] globalPalette;
- /**
- * Any
- */
- private boolean hasGlobalColorMap;
- /**
- * Local palette, if any (used if available)
- */
- private byte[] localPalette;
- /**
- * Interlaced GIF or not?
- */
- private boolean interlaced;
- /**
- * Has transparency?
- */
- private boolean hasTransparency;
- /**
- * Undraw mode (animations)
- */
- private int undraw;
- /**
- * Transparent index;
- */
- private int transparentIndex;
- /**
- * The uncompressed raster
- */
- private byte[] raster;
- /**
- * The compressed data (freed after uncompressing)
- */
- private byte[] compressedData;
- /**
- * Frame delay in 100ths of a second ( centiseconds, metrically )
- */
- private int duration;
- /**
- * Indices used during decompression
- */
- private int dataBlockIndex;
- /**
- * The file comment , if a comment block exists.
- */
- private String comment;
- /**
- * Fields used by getBits()
- */
- private int remainingBits = 0;
- private int currentBits = 0;
- /**
- * Netscape animation extension
- */
- private boolean isLooped = false;
- /** Number of loops, 0 = infinite */
- private int loops;
- /**
- * Additional frames if it's an animated GIF.
- */
- private Vector animationFrames;
- /**
- * Loads the file from an input stream, which is not closed.
- * @throws IOException if an I/O error occured.
- * @throws GIFException if some file parsing error occured
- */
- public GIFFile(InputStream in) throws IOException, GIFException
- {
- // Validate the signature
- if( !readSignature( in ) )
- throw new GIFException("Invalid GIF signature.");
- {
- byte[] data = new byte[7];
- if (in.read(data) != 7)
- throw new IOException("Couldn't read global descriptor.");
- globalWidth = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
- globalHeight = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
- byte flags = data[4];
- bgIndex = data[5];
- nColors = (1 << (( flags & 0x07) + 1));
- hasGlobalColorMap = ((flags & 0x80) != 0);
- }
- if( hasGlobalColorMap )
- {
- globalPalette = new byte[ nColors * 3 ];
- if( in.read( globalPalette ) != nColors * 3 )
- throw new IOException("Couldn't read color map.");
- }
- int c = in.read();
- while( c == EXTENSION )
- {
- readExtension( in );
- c = in.read();
- }
- if( c != LOCAL )
- throw new GIFException("Extension blocks not followed by a local descriptor ("+c+")");
- loadImage( in );
- c = in.read();
- if( c == TERMINATOR ) // Not an animated GIF.
- return;
- // Load animation frames. Just quit if an error occurs instead
- // of throwing an exception.
- animationFrames = new Vector();
- try
- {
- while( c != TERMINATOR )
- {
- animationFrames.add( new GIFFile( this, in, c ) );
- c = in.read();
- }
- }
- catch(IOException ioe)
- {
- }
- catch(GIFException gife)
- {
- }
- }
- /**
- * Constructor for additional animation frames.
- */
- private GIFFile(GIFFile parent, InputStream in, int c)
- throws IOException, GIFException
- {
- // Copy global properties.
- globalWidth = parent.globalWidth;
- globalHeight = parent.globalHeight;
- nColors = parent.nColors;
- globalPalette = parent.globalPalette;
- hasGlobalColorMap = parent.hasGlobalColorMap;
- interlaced = parent.interlaced;
- comment = parent.comment;
- isLooped = parent.isLooped;
- loops = parent.loops;
- while( c == EXTENSION )
- {
- readExtension( in );
- c = in.read();
- }
- if( c != LOCAL )
- throw new GIFException("Extension blocks not followed by a local descriptor ("+c+")");
- loadImage( in );
- }
- /**
- * Reads a GIF file signature from an inputstream and checks it.
- *
- * @param in - the stream (reads 6 bytes, does not close or reset).
- * @return true if the signature is a valid GIF signature.
- * @throws IOException if the signature could not be read.
- */
- public static boolean readSignature( InputStream in ) throws IOException
- {
- byte[] data = new byte[6];
- if (in.read(data) != 6)
- throw new IOException("Couldn't read signature.");
- if( data[0] != 0x47 || data[1] != 0x49 || data[2] != 0x46 ||
- data[3] != 0x38 ) // GIF8
- return false;
- if( (data[4] != 0x39 && data[4] != 0x37) || // 7 | 9
- (data[5] != 0x61 && data[5] != 0x62) ) // 'a' or 'b'
- return false;
- return true;
- }
- /**
- * Loads the image local descriptor and then loads/decodes the image raster,
- * and then performs any necessary postprocessing like deinterlacing.
- */
- private void loadImage(InputStream in)
- throws IOException, GIFException
- {
- readLocal( in );
- try
- {
- decodeRaster( in );
- }
- catch(ArrayIndexOutOfBoundsException aioobe)
- {
- throw new GIFException("Error decompressing image.");
- }
- if( interlaced ) // Clean up
- deinterlace();
- packPixels();
- }
- /**
- * Pack the pixels if it's a 2, 4 or 16 color image.
- * While GIF may support any number of colors from 2-256, we won't bother
- * trying to pack pixels not resulting in even byte boundaries.
- * (AWT doesn't support that anyway, and most apps do the same.)
- */
- private void packPixels()
- {
- if( nColors != 2 && nColors != 4 && nColors != 16 )
- return;
- int nbits = 1;
- int ppbyte = 8;
- if( nColors == 4 )
- {
- nbits = 2;
- ppbyte = 4;
- }
- else if( nColors == 16 )
- {
- nbits = 4;
- ppbyte = 2;
- }
- int rem = (width & (ppbyte - 1));
- int w = ( rem == 0 ) ? (width / ppbyte) :
- ((width + ppbyte - rem) / ppbyte);
- byte[] nr = new byte[ w * height ];
- for(int j = 0; j < height; j++)
- {
- for(int i = 0; i < width - ppbyte; i += ppbyte)
- for(int k = 0; k < ppbyte; k++)
- nr[ j * w + (i / ppbyte) ] |= (byte)((raster[ width * j + i + k ]
- << (8 - nbits * (1 + k))));
- for(int i = 0; i < rem; i++)
- nr[ j * w + w - 1 ] |= (byte)((raster[ width * j + width - rem + i ]
- << (nbits * (rem - i))));
- }
- raster = nr;
- }
- /**
- * Returns the (global) width
- */
- public int getWidth()
- {
- return width;
- }
- /**
- * Returns the image height
- */
- public int getHeight()
- {
- return height;
- }
- /**
- * Returns the # of colors.
- */
- public int getNColors()
- {
- return nColors;
- }
- /**
- * Returns whether the GIF has transparency.
- */
- public boolean hasTransparency()
- {
- return hasTransparency;
- }
- /**
- * Returns the index of the transparent color.
- */
- public int getTransparentIndex()
- {
- return transparentIndex;
- }
- /**
- * Retuns the GIF file comment, or null if none exists.
- */
- public String getComment()
- {
- return comment;
- }
- /**
- * Get duration of the frame for animations.
- */
- public int getDuration()
- {
- return duration;
- }
- /**
- * Deinterlaces the image.
- */
- private void deinterlace()
- {
- byte[] nr = new byte[ width * height ];
- int n = 0;
- for(int i = 0; i < ((height + 7) >> 3); i++)
- {
- System.arraycopy( raster, n, nr, width * i * 8, width );
- n += width;
- }
- for(int i = 0; i < ((height + 3) >> 3); i++)
- {
- System.arraycopy( raster, n, nr, width * ( 8 * i + 4 ), width );
- n += width;
- }
- for(int i = 0; i < (height >> 2); i++)
- {
- System.arraycopy( raster, n, nr, width * (4 * i + 2), width );
- n += width;
- }
- for(int i = 0; i < (height >> 1); i++)
- {
- System.arraycopy( raster, n, nr, width * (2 * i + 1), width );
- n += width;
- }
- raster = nr;
- }
- /**
- * Reads the local descriptor
- */
- private void readLocal(InputStream in) throws IOException
- {
- byte[] data = new byte[9];
- if (in.read(data) != 9)
- throw new IOException("Couldn't read local descriptor.");
- x = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
- y = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
- width = ((data[5] & 0xFF) << 8) | (data[4] & 0xFF);
- height = ((data[7] & 0xFF) << 8) | (data[6] & 0xFF);
- byte flags = data[8];
- interlaced = (( flags & 0x40 ) != 0);
- if( (flags & 0x80) != 0 )
- { // has a local color map
- int nLocalColors = (1 << (( flags & 0x07) + 1));
- if( !hasGlobalColorMap )
- nColors = nLocalColors;
- localPalette = new byte[ nLocalColors * 3 ];
- if( in.read( localPalette ) != nLocalColors * 3 )
- throw new IOException("Couldn't read color map.");
- }
- }
- /**
- * Returns the image's palette in raw format
- * (r0,g0,b0,r1,g1,b2..r(Ncolors-1),g(Ncolors-1),b(Ncolors-1))
- */
- public byte[] getRawPalette()
- {
- return hasGlobalColorMap ? globalPalette : localPalette;
- }
- /**
- * Returns the image file for animated gifs.
- */
- public GIFFile getImage( int index )
- {
- if( index == 0 )
- return this;
- if( animationFrames == null )
- throw new ArrayIndexOutOfBoundsException("Only one image in file");
- return (GIFFile)animationFrames.elementAt( index - 1 );
- }
- /**
- * Return the image's raw image data.
- * If the color depth is 1,2 or 4 bits per pixel the pixels are packed
- * and the scanlines padded up to the nearest byte if needed.
- */
- public byte[] getRawImage()
- {
- return raster;
- }
- /**
- * Return the number of images in the GIF file
- */
- public int nImages()
- {
- if( animationFrames != null )
- return 1 + animationFrames.size();
- return 1;
- }
- /**
- * Handles extension blocks.
- */
- private void readExtension(InputStream in) throws IOException, GIFException
- {
- int functionCode = in.read();
- byte[] data = readData(in);
- switch( functionCode )
- {
- case EXTENSION_COMMENT: // comment block
- comment = new String(data, "8859_1");
- break;
- case EXTENSION_GCONTROL: // Graphics control extension
- undraw = (data[0] & 0x1C) >> 2;
- // allegedly there can be bad values of this.
- if( undraw < 1 && undraw > 3 ) undraw = 1;
- hasTransparency = ((data[0] & 0x01) == 1);
- transparentIndex = (data[3] & 0xFF);
- duration = ((data[2] & 0xFF) << 8) | (data[1] & 0xFF);
- break;
- // Application extension. We only parse the Netscape animation
- // extension here. Which is the only one most use anyway.
- case EXTENSION_APPLICATION:
- boolean isNS = true;
- for(int i = 0; i < nsBlock.length; i++ )
- if( nsBlock[i] != data[i] )
- isNS = false;
- if( isNS )
- {
- isLooped = true;
- loops = ((data[12] & 0xFF) << 8) | (data[13] & 0xFF);
- }
- break;
- default:
- break;
- }
- }
- /**
- * Reads a series of data blocks and merges them into a single one.
- */
- private byte[] readData(InputStream in) throws IOException
- {
- Vector v = new Vector();
- int totalBytes = 0;
- int n = in.read();
- do
- {
- totalBytes += n;
- byte[] block = new byte[ n ];
- in.read(block);
- v.add(block);
- n = in.read();
- }
- while( n > 0 );
- n = 0;
- byte[] bigBuffer = new byte[ totalBytes ];
- for( int i = 0; i < v.size(); i++ )
- {
- byte[] block = (byte[])v.elementAt(i);
- System.arraycopy(block, 0, bigBuffer, n, block.length);
- n += block.length;
- }
- return bigBuffer;
- }
- /**
- * Loads a compressed image block and decompresses it.
- */
- private void decodeRaster(InputStream in) throws IOException
- {
- int initialCodeSize = in.read();
- compressedData = readData( in );
- dataBlockIndex = 0;
- int rasterIndex = 0; // Index into the raster
- int clearCode = (1 << initialCodeSize); // 256 usually
- int endCode = clearCode + 1; // The stop code.
- raster = new byte[ width * height ];
- int codeSize = initialCodeSize + 1;
- int code = getBits( codeSize ); // = clear
- int nextCode = endCode + 1;
- /*
- * Initialize LZW dictionary
- *
- * First index - code #
- * Second index:
- * 0 = color index
- * 1 = parent (-1 - no parent)
- * 2 = first value
- * 3 - depth
- * The latter two aren't strictly necessary but make things faster, since
- * copying the values forward is faster than going back and looking.
- */
- short[][] dictionary = new short[ 4096 ][ 4 ];
- for(short i = 0; i < nColors; i ++ )
- {
- dictionary[i][0] = i; // color index
- dictionary[i][1] = -1; // parent
- dictionary[i][2] = i; // first
- dictionary[i][3] = 1; // depth
- }
- code = getBits( codeSize ); // get second code
- raster[ rasterIndex++ ] = (byte)dictionary[code][0];
- int old = code;
- code = getBits( codeSize ); // start at the third code
- int c;
- do
- {
- if( code == clearCode )
- {
- codeSize = initialCodeSize + 1;
- nextCode = endCode + 1;
- // get and output second code
- code = getBits( codeSize );
- raster[ rasterIndex++ ] = (byte)dictionary[code][0];
- old = code;
- }
- else
- {
- dictionary[nextCode][1] = (short)old; // parent = old
- dictionary[nextCode][2] = dictionary[old][2]; // first pixel
- dictionary[nextCode][3] = (short)(dictionary[old][3] + 1); // depth
- // appended pixel = first pixel of c
- if( code < nextCode )
- {
- dictionary[nextCode][0] = dictionary[code][2];
- old = code;
- }
- else // first of old
- {
- dictionary[nextCode][0] = dictionary[old][2];
- old = nextCode;
- }
- c = old;
- // output the code c
- int depth = dictionary[c][3];
- for( int i = depth - 1; i >= 0; i-- )
- {
- raster[ rasterIndex + i ] = (byte)dictionary[c][0];
- c = dictionary[c][1]; // go to parent.
- }
- rasterIndex += depth;
- nextCode ++;
- if( codeSize < 12 && nextCode >= (1 << codeSize) )
- codeSize++;
- }
- code = getBits( codeSize );
- }
- while( code != endCode && dataBlockIndex < compressedData.length );
- compressedData = null; // throw away compressed data.
- }
- /**
- * Returns nbits number of bits (in the LSBs) from compressedData
- */
- private int getBits( int nbits )
- {
- while( nbits > remainingBits )
- {
- int c = (compressedData[ dataBlockIndex++ ] & 0xFF) << remainingBits;
- currentBits |= c;
- remainingBits += 8;
- }
- int rval = (currentBits & ((1 << nbits) - 1));
- currentBits = (currentBits >> nbits);
- remainingBits -= nbits;
- return rval;
- }
- /**
- * Generic exception used by GIFFile to report decoding errors.
- */
- public static class GIFException extends Exception
- {
- public GIFException(String message)
- {
- super(message);
- }
- }
- }
|