/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ /* * @(#)GIFEncoder.java 0.90 4/21/96 Adam Doppelt */ // package com.gurge.amd; package org.opensourcephysics.media.gif; 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> * @version 0.90 21 Apr 1996 * @author <A HREF="http://www.cs.brown.edu/people/amd/">Adam Doppelt</A> */ public class GIFEncoder { short width_, height_; int numColors_; byte pixels_[], colors_[]; ScreenDescriptor sd_; ImageDescriptor id_; /** * 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()); //$NON-NLS-1$ } } catch(InterruptedException ex) { ex.printStackTrace(); } 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"); //$NON-NLS-1$ 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."); //$NON-NLS-1$ } 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 { OutputStream output_; byte buffer_[]; int index_, bitsLeft_; BitFile(OutputStream output) { output_ = output; buffer_ = new byte[256]; index_ = 0; bitsLeft_ = 8; } 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; } } void WriteBits(int bits, int numbits) throws IOException { 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_); bitsLeft_ -= numbits; numbits = 0; } else { buffer_[index_] |= (bits&((1<<bitsLeft_)-1))<<(8-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; byte strChr_[]; short strNxt_[]; short strHsh_[]; short numStrings_; LZWStringTable() { strChr_ = new byte[MAXSTR]; strNxt_ = new short[MAXSTR]; strHsh_ = new short[HASHSIZE]; } 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_++; } short FindCharString(short index, byte b) { int hshidx, 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; } 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 int Hash(short index, byte lastbyte) { return(((short) (lastbyte<<8)^index)&0xFFFF)%HASHSIZE; } } class LZWCompressor { static void LZWCompress(OutputStream output, int codesize, byte toCompress[]) throws IOException { byte c; short index; int clearcode, endofinfo, numbits, 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) (c&0xFF); } } if(prefix!=-1) { bitFile.WriteBits(prefix, numbits); } bitFile.WriteBits(endofinfo, numbits); bitFile.Flush(); } } class ScreenDescriptor { short localScreenWidth_, localScreenHeight_; private byte byte_; byte backgroundColorIndex_, pixelAspectRatio_; 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; } void Write(OutputStream output) throws IOException { BitUtils.WriteWord(output, localScreenWidth_); BitUtils.WriteWord(output, localScreenHeight_); output.write(byte_); output.write(backgroundColorIndex_); output.write(pixelAspectRatio_); } void SetGlobalColorTableSize(byte num) { byte_ |= (num&7); } void SetSortFlag(byte num) { byte_ |= (num&1)<<3; } void SetColorResolution(byte num) { byte_ |= (num&7)<<4; } void SetGlobalColorTableFlag(byte num) { byte_ |= (num&1)<<7; } } class ImageDescriptor { byte separator_; short leftPosition_, topPosition_, width_, height_; private byte byte_; 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); } 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_); } void SetLocalColorTableSize(byte num) { byte_ |= (num&7); } void SetReserved(byte num) { byte_ |= (num&3)<<3; } void SetSortFlag(byte num) { byte_ |= (num&1)<<5; } void SetInterlaceFlag(byte num) { byte_ |= (num&1)<<6; } void SetLocalColorTableFlag(byte num) { byte_ |= (num&1)<<7; } } class BitUtils { static byte BitsNeeded(int n) { byte ret = 1; if(n--==0) { return 0; } while((n >>= 1)!=0) { ++ret; } return ret; } 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))); } } } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * This software 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 this; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */