/** * Copyright (c) 2003-2009, Xith3D Project Group all rights reserved. * * Portions based on the Java3D interface, Copyright by Sun Microsystems. * Many thanks to the developers of Java3D and Sun Microsystems for their * innovation and design. * * 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. * * Redistributions in binary form must reproduce 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 the 'Xith3D Project Group' nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.xith3d.utility.image; import java.awt.AWTException; import java.awt.Image; import java.awt.image.PixelGrabber; import java.io.IOException; import java.io.OutputStream; /** * GIFEncoder is a class which takes an image and saves it to a stream * using the GIF file format (<A * HREF="http://www.dcs.ed.ac.uk/%7Emxr/gfx/">Graphics Interchange * Format</A>). A GIFEncoder * is constructed with either an AWT Image (which must be fully * loaded) or a set of RGB arrays. The image can be written out with a * call to <CODE>Write</CODE>.<P> * * Three caveats: * <UL> * <LI>GIFEncoder will convert the image to indexed color upon * construction. This will take some time, depending on the size of * the image. Also, actually writing the image out (Write) will take * time.<P> * * <LI>The image cannot have more than 256 colors, since GIF is an 8 * bit format. For a 24 bit to 8 bit quantization algorithm, see * Graphics Gems II III.2 by Xialoin Wu. Or check out his <A * HREF="http://www.csd.uwo.ca/faculty/wu/cq.c">C source</A>.<P> * * <LI>Since the image must be completely loaded into memory, * GIFEncoder may have problems with large images. Attempting to * encode an image which will not fit into memory will probably * result in the following exception:<P> * <CODE>java.awt.AWTException: Grabber returned false: 192</CODE><P> * </UL><P> * * GIFEncoder is based upon gifsave.c, which was written and released * by:<P> * <CENTER> * Sverre H. Huseby<BR> * Bjoelsengt. 17<BR> * N-0468 Oslo<BR> * Norway<P> * * Phone: +47 2 230539<BR> * sverrehu@ifi.uio.no<P> * </CENTER> * * creation time 0.90 4/21/96 * @author <A HREF="http://www.cs.brown.edu/people/amd/">Adam Doppelt</A> * * @version 0.90 21 Apr 1996 */ public class GIFEncoder { private short width_; private short height_; private int numColors_; private byte[] pixels_; private byte[] colors_; /** * Construct a GIFEncoder. The constructor will convert the image to * an indexed color array. <B>This may take some time.</B><P> * * @param image The image to encode. The image <B>must</B> be * completely loaded. * @exception AWTException Will be thrown if the pixel grab fails. This * can happen if Java runs out of memory. It may also indicate that the image * contains more than 256 colors. * */ public GIFEncoder( Image image ) throws AWTException { width_ = (short)image.getWidth( null ); height_ = (short)image.getHeight( null ); int[] values = new int[ width_ * height_ ]; PixelGrabber grabber = new PixelGrabber( image, 0, 0, width_, height_, values, 0, width_ ); try { if ( grabber.grabPixels() != true ) { throw new AWTException( "Grabber returned false: " + grabber.status() ); } } catch ( InterruptedException e ) { ; } byte[][] r = new byte[ width_ ][ height_ ]; byte[][] g = new byte[ width_ ][ height_ ]; byte[][] b = new byte[ width_ ][ height_ ]; int index = 0; for ( int y = 0; y < height_; ++y ) for ( int x = 0; x < width_; ++x ) { r[ x ][ y ] = (byte)( ( values[ index ] >> 16 ) & 0xFF ); g[ x ][ y ] = (byte)( ( values[ index ] >> 8 ) & 0xFF ); b[ x ][ y ] = (byte)( ( values[ index ] ) & 0xFF ); ++index; } ToIndexedColor( r, g, b ); } /** * Construct a GIFEncoder. The constructor will convert the image to * an indexed color array. <B>This may take some time.</B><P> * * Each array stores intensity values for the image. In other words, * r[x][y] refers to the red intensity of the pixel at column x, row * y.<P> * * @param r An array containing the red intensity values. * @param g An array containing the green intensity values. * @param b An array containing the blue intensity values. * * @exception AWTException Will be thrown if the image contains more than * 256 colors. * */ public GIFEncoder( byte[][] r, byte[][] g, byte[][] b ) throws AWTException { width_ = (short)( r.length ); height_ = (short)( r[ 0 ].length ); ToIndexedColor( r, g, b ); } /** * Writes the image out to a stream in the GIF file format. This will * be a single GIF87a image, non-interlaced, with no background color. * <B>This may take some time.</B><P> * * @param output The stream to output to. This should probably be a * buffered stream. * * @exception IOException Will be thrown if a write operation fails. * */ public void Write( OutputStream output ) throws IOException { BitUtils.WriteString( output, "GIF87a" ); ScreenDescriptor sd = new ScreenDescriptor( width_, height_, numColors_ ); sd.Write( output ); output.write( colors_, 0, colors_.length ); ImageDescriptor id = new ImageDescriptor( width_, height_, ',' ); id.Write( output ); byte codesize = BitUtils.BitsNeeded( numColors_ ); if ( codesize == 1 ) { ++codesize; } output.write( codesize ); LZWCompressor.LZWCompress( output, codesize, pixels_ ); output.write( 0 ); id = new ImageDescriptor( (byte)0, (byte)0, ';' ); id.Write( output ); output.flush(); } void ToIndexedColor( byte[][] r, byte[][] g, byte[][] b ) throws AWTException { pixels_ = new byte[ width_ * height_ ]; colors_ = new byte[ 256 * 3 ]; int colornum = 0; for ( int x = 0; x < width_; ++x ) { for ( int y = 0; y < height_; ++y ) { int search; for ( search = 0; search < colornum; ++search ) if ( ( colors_[ search * 3 ] == r[ x ][ y ] ) && ( colors_[ ( search * 3 ) + 1 ] == g[ x ][ y ] ) && ( colors_[ ( search * 3 ) + 2 ] == b[ x ][ y ] ) ) { break; } if ( search > 255 ) { throw new AWTException( "Too many colors." ); } pixels_[ ( y * width_ ) + x ] = (byte)search; if ( search == colornum ) { colors_[ search * 3 ] = r[ x ][ y ]; colors_[ ( search * 3 ) + 1 ] = g[ x ][ y ]; colors_[ ( search * 3 ) + 2 ] = b[ x ][ y ]; ++colornum; } } } numColors_ = 1 << BitUtils.BitsNeeded( colornum ); byte[] copy = new byte[ numColors_ * 3 ]; System.arraycopy( colors_, 0, copy, 0, numColors_ * 3 ); colors_ = copy; } } class BitFile { private OutputStream output_; private byte[] buffer_; private int index_; private int bitsLeft_; public BitFile( OutputStream output ) { output_ = output; buffer_ = new byte[ 256 ]; index_ = 0; bitsLeft_ = 8; } public void Flush() throws IOException { int numBytes = index_ + ( ( bitsLeft_ == 8 ) ? 0 : 1 ); if ( numBytes > 0 ) { output_.write( numBytes ); output_.write( buffer_, 0, numBytes ); buffer_[ 0 ] = 0; index_ = 0; bitsLeft_ = 8; } } public void WriteBits( int bits, int numbits ) throws IOException { int bitsWritten = 0; int numBytes = 255; do { if ( ( ( index_ == 254 ) && ( bitsLeft_ == 0 ) ) || ( index_ > 254 ) ) { output_.write( numBytes ); output_.write( buffer_, 0, numBytes ); buffer_[ 0 ] = 0; index_ = 0; bitsLeft_ = 8; } if ( numbits <= bitsLeft_ ) { buffer_[ index_ ] |= ( ( bits & ( ( 1 << numbits ) - 1 ) ) << ( 8 - bitsLeft_ ) ); bitsWritten += numbits; bitsLeft_ -= numbits; numbits = 0; } else { buffer_[ index_ ] |= ( ( bits & ( ( 1 << bitsLeft_ ) - 1 ) ) << ( 8 - bitsLeft_ ) ); bitsWritten += bitsLeft_; bits >>= bitsLeft_; numbits -= bitsLeft_; buffer_[ ++index_ ] = 0; bitsLeft_ = 8; } } while ( numbits != 0 ); } } class LZWStringTable { private final static int RES_CODES = 2; private final static short HASH_FREE = (short)0xFFFF; private final static short NEXT_FIRST = (short)0xFFFF; private final static int MAXBITS = 12; private final static int MAXSTR = ( 1 << MAXBITS ); private final static short HASHSIZE = 9973; private final static short HASHSTEP = 2039; private byte[] strChr_; private short[] strNxt_; private short[] strHsh_; private short numStrings_; public LZWStringTable() { strChr_ = new byte[ MAXSTR ]; strNxt_ = new short[ MAXSTR ]; strHsh_ = new short[ HASHSIZE ]; } public int AddCharString( short index, byte b ) { int hshidx; if ( numStrings_ >= MAXSTR ) { return 0xFFFF; } hshidx = Hash( index, b ); while ( strHsh_[ hshidx ] != HASH_FREE ) hshidx = ( hshidx + HASHSTEP ) % HASHSIZE; strHsh_[ hshidx ] = numStrings_; strChr_[ numStrings_ ] = b; strNxt_[ numStrings_ ] = ( index != HASH_FREE ) ? index : NEXT_FIRST; return numStrings_++; } public short FindCharString( short index, byte b ) { int hshidx; int nxtidx; if ( index == HASH_FREE ) { return b; } hshidx = Hash( index, b ); while ( ( nxtidx = strHsh_[ hshidx ] ) != HASH_FREE ) { if ( ( strNxt_[ nxtidx ] == index ) && ( strChr_[ nxtidx ] == b ) ) { return (short)nxtidx; } hshidx = ( hshidx + HASHSTEP ) % HASHSIZE; } return (short)0xFFFF; } public void ClearTable( int codesize ) { numStrings_ = 0; for ( int q = 0; q < HASHSIZE; q++ ) { strHsh_[ q ] = HASH_FREE; } int w = ( 1 << codesize ) + RES_CODES; for ( int q = 0; q < w; q++ ) AddCharString( (short)0xFFFF, (byte)q ); } static public int Hash( short index, byte lastbyte ) { return ( (int)( (short)( lastbyte << 8 ) ^ index ) & 0xFFFF ) % HASHSIZE; } } class LZWCompressor { public static void LZWCompress( OutputStream output, int codesize, byte[] toCompress ) throws IOException { byte c; short index; int clearcode; int endofinfo; int numbits; int limit; short prefix = (short)0xFFFF; BitFile bitFile = new BitFile( output ); LZWStringTable strings = new LZWStringTable(); clearcode = 1 << codesize; endofinfo = clearcode + 1; numbits = codesize + 1; limit = ( 1 << numbits ) - 1; strings.ClearTable( codesize ); bitFile.WriteBits( clearcode, numbits ); for ( int loop = 0; loop < toCompress.length; ++loop ) { c = toCompress[ loop ]; if ( ( index = strings.FindCharString( prefix, c ) ) != -1 ) { prefix = index; } else { bitFile.WriteBits( prefix, numbits ); if ( strings.AddCharString( prefix, c ) > limit ) { if ( ++numbits > 12 ) { bitFile.WriteBits( clearcode, numbits - 1 ); strings.ClearTable( codesize ); numbits = codesize + 1; } limit = ( 1 << numbits ) - 1; } prefix = (short)( (short)c & 0xFF ); } } if ( prefix != -1 ) { bitFile.WriteBits( prefix, numbits ); } bitFile.WriteBits( endofinfo, numbits ); bitFile.Flush(); } } class ScreenDescriptor { public short localScreenWidth_; public short localScreenHeight_; private byte byte_; public byte backgroundColorIndex_; public byte pixelAspectRatio_; public ScreenDescriptor( short width, short height, int numColors ) { localScreenWidth_ = width; localScreenHeight_ = height; SetGlobalColorTableSize( (byte)( BitUtils.BitsNeeded( numColors ) - 1 ) ); SetGlobalColorTableFlag( (byte)1 ); SetSortFlag( (byte)0 ); SetColorResolution( (byte)7 ); backgroundColorIndex_ = 0; pixelAspectRatio_ = 0; } public void Write( OutputStream output ) throws IOException { BitUtils.WriteWord( output, localScreenWidth_ ); BitUtils.WriteWord( output, localScreenHeight_ ); output.write( byte_ ); output.write( backgroundColorIndex_ ); output.write( pixelAspectRatio_ ); } public void SetGlobalColorTableSize( byte num ) { byte_ |= ( num & 7 ); } public void SetSortFlag( byte num ) { byte_ |= ( ( num & 1 ) << 3 ); } public void SetColorResolution( byte num ) { byte_ |= ( ( num & 7 ) << 4 ); } public void SetGlobalColorTableFlag( byte num ) { byte_ |= ( ( num & 1 ) << 7 ); } } class ImageDescriptor { public byte separator_; public short leftPosition_; public short topPosition_; public short width_; public short height_; private byte byte_; public ImageDescriptor( short width, short height, char separator ) { separator_ = (byte)separator; leftPosition_ = 0; topPosition_ = 0; width_ = width; height_ = height; SetLocalColorTableSize( (byte)0 ); SetReserved( (byte)0 ); SetSortFlag( (byte)0 ); SetInterlaceFlag( (byte)0 ); SetLocalColorTableFlag( (byte)0 ); } public void Write( OutputStream output ) throws IOException { output.write( separator_ ); BitUtils.WriteWord( output, leftPosition_ ); BitUtils.WriteWord( output, topPosition_ ); BitUtils.WriteWord( output, width_ ); BitUtils.WriteWord( output, height_ ); output.write( byte_ ); } public void SetLocalColorTableSize( byte num ) { byte_ |= ( num & 7 ); } public void SetReserved( byte num ) { byte_ |= ( ( num & 3 ) << 3 ); } public void SetSortFlag( byte num ) { byte_ |= ( ( num & 1 ) << 5 ); } public void SetInterlaceFlag( byte num ) { byte_ |= ( ( num & 1 ) << 6 ); } public void SetLocalColorTableFlag( byte num ) { byte_ |= ( ( num & 1 ) << 7 ); } } class BitUtils { public static byte BitsNeeded( int n ) { byte ret = 1; if ( n-- == 0 ) { return 0; } while ( ( n >>= 1 ) != 0 ) ++ret; return ret; } public static void WriteWord( OutputStream output, short w ) throws IOException { output.write( w & 0xFF ); output.write( ( w >> 8 ) & 0xFF ); } static void WriteString( OutputStream output, String string ) throws IOException { for ( int loop = 0; loop < string.length(); ++loop ) output.write( (byte)( string.charAt( loop ) ) ); } }