package com.idega.graphics; import java.io.*; import java.awt.*; import java.awt.image.*; /** * 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_; short height_; int numColors_; byte pixels_[]; byte colors_[]; /** * * @uml.property name="sd_" * @uml.associationEnd multiplicity="(0 1)" */ ScreenDescriptor sd_; /** * * @uml.property name="id_" * @uml.associationEnd multiplicity="(0 1)" */ 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 { this.width_ = (short)image.getWidth(null); this.height_ = (short)image.getHeight(null); int values[] = new int[this.width_ * this.height_]; PixelGrabber grabber = new PixelGrabber(image,0,0,this.width_,this.height_,values,0,this.width_); try { if(grabber.grabPixels() != true) { throw new AWTException("Grabber returned false: " + grabber.status()); } } catch (InterruptedException e) { } byte r[][] = new byte[this.width_][this.height_]; byte g[][] = new byte[this.width_][this.height_]; byte b[][] = new byte[this.width_][this.height_]; int index = 0; for (int y = 0; y < this.height_; y++) { for (int x = 0; x < this.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 { this.width_ = (short)(r.length); this.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(this.width_,this.height_,this.numColors_); sd.Write(output); output.write(this.colors_,0,this.colors_.length); ImageDescriptor id = new ImageDescriptor(this.width_,this.height_,','); id.Write(output); byte codesize = BitUtils.BitsNeeded(this.numColors_); if (codesize == 1) { codesize++; } output.write(codesize); LZWCompressor.LZWCompress(output,codesize,this.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 { this.pixels_ = new byte[this.width_ * this.height_]; this.colors_ = new byte[256 * 3]; int colornum = 0; for (int x = 0; x < this.width_; x++) { for (int y = 0; y < this.height_; y++) { int search; for (search = 0; search < colornum; search++) { if (this.colors_[search * 3] == r[x][y] && this.colors_[search * 3 + 1] == g[x][y] && this.colors_[search * 3 + 2] == b[x][y]) { break; } } if (search > 255) { throw new AWTException("Too many colors."); } this.pixels_[y * this.width_ + x] = (byte)search; if (search == colornum) { this.colors_[search * 3] = r[x][y]; this.colors_[search * 3 + 1] = g[x][y]; this.colors_[search * 3 + 2] = b[x][y]; colornum++; } } } this.numColors_ = 1 << BitUtils.BitsNeeded(colornum); byte copy[] = new byte[this.numColors_ * 3]; System.arraycopy(this.colors_,0,copy,0,this.numColors_ * 3); this.colors_ = copy; } } class BitFile { OutputStream output_; byte buffer_[]; int index_; int bitsLeft_; public BitFile(OutputStream output) { this.output_ = output; this.buffer_ = new byte[256]; this.index_ = 0; this.bitsLeft_ = 8; } public void Flush() throws IOException { int numBytes = this.index_ + (this.bitsLeft_ == 8 ? 0 : 1); if (numBytes > 0) { this.output_.write(numBytes); this.output_.write(this.buffer_, 0, numBytes); this.buffer_[0] = 0; this.index_ = 0; this.bitsLeft_ = 8; } } public void WriteBits(int bits, int numbits) throws IOException { int bitsWritten = 0; int numBytes = 255; do { if ((this.index_ == 254 && this.bitsLeft_ == 0) || this.index_ > 254) { this.output_.write(numBytes); this.output_.write(this.buffer_, 0, numBytes); this.buffer_[0] = 0; this.index_ = 0; this.bitsLeft_ = 8; } if (numbits <= this.bitsLeft_) { this.buffer_[this.index_] |= (bits & ((1 << numbits) - 1)) << (8 - this.bitsLeft_); bitsWritten += numbits; this.bitsLeft_ -= numbits; numbits = 0; } else { this.buffer_[this.index_] |= (bits & ((1 << this.bitsLeft_) - 1)) << (8 - this.bitsLeft_); bitsWritten += this.bitsLeft_; bits >>= this.bitsLeft_; numbits -= this.bitsLeft_; this.buffer_[++this.index_] = 0; this.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_; public LZWStringTable() { this.strChr_ = new byte[MAXSTR]; this.strNxt_ = new short[MAXSTR]; this.strHsh_ = new short[HASHSIZE]; } public int AddCharString(short index, byte b) { int hshidx; if (this.numStrings_ >= MAXSTR) { return 0xFFFF; } hshidx = Hash(index, b); while (this.strHsh_[hshidx] != HASH_FREE) { hshidx = (hshidx + HASHSTEP) % HASHSIZE; } this.strHsh_[hshidx] = this.numStrings_; this.strChr_[this.numStrings_] = b; this.strNxt_[this.numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST; return this.numStrings_++; } public short FindCharString(short index, byte b) { int hshidx, nxtidx; if (index == HASH_FREE) { return b; } hshidx = Hash(index, b); while ((nxtidx = this.strHsh_[hshidx]) != HASH_FREE) { if (this.strNxt_[nxtidx] == index && this.strChr_[nxtidx] == b) { return (short)nxtidx; } hshidx = (hshidx + HASHSTEP) % HASHSIZE; } return (short)0xFFFF; } public void ClearTable(int codesize) { this.numStrings_ = 0; for (int q = 0; q < HASHSIZE; q++) { this.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 (((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, 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 { public short localScreenWidth_; public short localScreenHeight_; private byte byte_; public byte backgroundColorIndex_; public byte pixelAspectRatio_; public ScreenDescriptor(short width, short height, int numColors) { this.localScreenWidth_ = width; this.localScreenHeight_ = height; SetGlobalColorTableSize((byte)(BitUtils.BitsNeeded(numColors) - 1)); SetGlobalColorTableFlag((byte)1); SetSortFlag((byte)0); SetColorResolution((byte)7); this.backgroundColorIndex_ = 0; this.pixelAspectRatio_ = 0; } public void Write(OutputStream output) throws IOException { BitUtils.WriteWord(output, this.localScreenWidth_); BitUtils.WriteWord(output, this.localScreenHeight_); output.write(this.byte_); output.write(this.backgroundColorIndex_); output.write(this.pixelAspectRatio_); } public void SetGlobalColorTableSize(byte num) { this.byte_ |= (num & 7); } public void SetSortFlag(byte num) { this.byte_ |= (num & 1) << 3; } public void SetColorResolution(byte num) { this.byte_ |= (num & 7) << 4; } public void SetGlobalColorTableFlag(byte num) { this.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) { this.separator_ = (byte)separator; this.leftPosition_ = 0; this.topPosition_ = 0; this.width_ = width; this.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(this.separator_); BitUtils.WriteWord(output, this.leftPosition_); BitUtils.WriteWord(output, this.topPosition_); BitUtils.WriteWord(output, this.width_); BitUtils.WriteWord(output, this.height_); output.write(this.byte_); } public void SetLocalColorTableSize(byte num) { this.byte_ |= (num & 7); } public void SetReserved(byte num) { this.byte_ |= (num & 3) << 3; } public void SetSortFlag(byte num) { this.byte_ |= (num & 1) << 5; } public void SetInterlaceFlag(byte num) { this.byte_ |= (num & 1) << 6; } public void SetLocalColorTableFlag(byte num) { this.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))); } } }