Java tutorial
package com.greenline.guahao.biz.manager.image.codes.gif; /** * It used to be against patent laws to produce unlicensed GIF images. Now the Unisys patents have all expired. -- Greg * Faron Integre Technical Publishing Co. */ import java.awt.AWTException; import java.awt.Image; import java.awt.image.PixelGrabber; import java.io.IOException; import java.io.OutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Class <CODE>GIFEncoder</CODE> takes an <CODE>Image</CODE> and saves it to a * file using the <CODE>GIF</CODE> file format (<A * HREF="http://www.dcs.ed.ac.uk/%7Emxr/gfx/">Graphics Interchange Format</A>). * A <CODE>GIFEncoder</CODE> object is constructed with either an * <CODE>Image</CODE> object (which must be fully loaded), or a set of three * 2-dimensional <CODE>byte</CODE> arrays. The image file can be written out * with a call to {@link #write(OutputStream) write()}. * <p> * Three caveats: * <UL> * <LI>Class <CODE>GIFEncoder</CODE> will convert the image to indexed color * upon construction. This will take some time, depending on the size of the * image. Also, the act of writing the image to disk will take some time.</LI> * <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 * <A HREF="http://www.csd.uwo.ca/faculty/wu/">Xialoin Wu</A>. Or check out his * <A HREF="http://www.csd.uwo.ca/faculty/wu/cq.c">C source</A>.</LI> * <LI>Since the image must be completely loaded into memory, * <CODE>GIFEncoder</CODE> may have problems with large images. Attempting to * encode an image which will not fit into memory will probably result in the * following exception: <BR> * <CODE>java.awt.AWTException: Grabber returned false: * 192</CODE></LI> * </UL> * <CODE>GIFEncoder</CODE> is based upon gifsave.c, which was written and * released by: * <p> * <DIV ALIGN="CENTER"> Sverre H. Huseby<BR> * Bjoelsengt. 17<BR> * N-0468 Oslo<BR> * Norway * <p> * Phone: +47 2 230539<BR> * <A HREF="mailto:sverrehu@ifi.uio.no">sverrehu@ifi.uio.no</A><BR> * </DIV> * * @author Adam Doppelt (dead link <A * @author Greg Faron - Integre Technical Publishing - * @version 0.90 21 Apr 1996 */ public class GifEncoder extends Object { /** * image height in pixels */ short imageWidth, imageHeight; /** * number of different colours in image */ int numberOfColors; /** * linear array of all pixels in row major order. */ byte[] allPixels = null; /** * list of all colours used in the image. */ byte[] allColors = null; protected static Log log = LogFactory.getLog(GifEncoder.class); /** * Convenience constructor for class <CODE>GIFEncoder</CODE>. The argument * will be converted to an indexed color array. <B>This may take some * time.</B> * * @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.imageWidth = (short) image.getWidth(null); this.imageHeight = (short) image.getHeight(null); int values[] = new int[this.imageWidth * this.imageHeight]; PixelGrabber grabber = new PixelGrabber(image, 0, 0, this.imageWidth, this.imageHeight, values, 0, this.imageWidth); try { if (grabber.grabPixels() != true) { log.error("GifEncoder#GifEncoder Grabber returned false: " + grabber.status()); throw new AWTException("Grabber returned false: " + grabber.status()); } } // ends try catch (InterruptedException ie) { log.error("GifEncoder#GifEncoder " + ie.getMessage(), ie); } byte[][] r = new byte[this.imageWidth][this.imageHeight]; byte[][] g = new byte[this.imageWidth][this.imageHeight]; byte[][] b = new byte[this.imageWidth][this.imageHeight]; int index = 0; for (int y = 0; y < this.imageHeight; y++) { for (int x = 0; x < this.imageWidth; x++, index++) { r[x][y] = (byte) ((values[index] >> 16) & 0xFF); g[x][y] = (byte) ((values[index] >> 8) & 0xFF); b[x][y] = (byte) ((values[index]) & 0xFF); } // ends for } // ends for this.toIndexColor(r, g, b); } // ends constructor GIFEncoder( Image ) /** * Standard constructor for class <CODE>GIFEncoder</CODE>. Each array stores * intensity values for the image. In other words, <NOBR> * <CODE>r[x][y]</CODE></NOBR> refers to the red intensity of the pixel at * column <CODE>x</CODE>, row <CODE>y</CODE>. * * @param r A 2-dimensional array containing the red intensity values. * @param g A 2-dimensional array containing the green intensity values. * @param b A 2-dimensional array containing the blue intensity values. * @exception AWTException Thrown if the image contains more than 256 * colors. */ public GifEncoder(byte[][] r, byte[][] g, byte[][] b) throws AWTException { this.imageWidth = (short) (r.length); this.imageHeight = (short) (r[0].length); this.toIndexColor(r, g, b); } // ends constructor GIFEncoder(byte[][], byte[][], byte[][] ) /** * Writes the image out to a stream in the <CODE>GIF</CODE> file format. * This will be a single GIF87a image, non-interlaced, with no background * color. <B>This may take some time.</B> * * @param output The stream to which to output. This should probably be a * buffered stream. * @exception IOException Thrown if a write operation fails. */ public void write(OutputStream output) throws IOException { BitUtils.writeString(output, "GIF87a"); ScreenDescriptor sd = new ScreenDescriptor(this.imageWidth, this.imageHeight, this.numberOfColors); sd.write(output); output.write(this.allColors, 0, this.allColors.length); ImageDescriptor id = new ImageDescriptor(this.imageWidth, this.imageHeight, ','); id.write(output); byte codesize = BitUtils.BitsNeeded(this.numberOfColors); if (codesize == 1) { codesize++; } output.write(codesize); LZWCompressor.LZWCompress(output, codesize, this.allPixels); output.write(0); id = new ImageDescriptor((byte) 0, (byte) 0, ';'); id.write(output); output.flush(); } // ends write( OutputStream ) /** * Converts rgb desrcription of image to colour number description used by * GIF. * * @param r red array of pixels * @param g green array of pixels * @param b blue array of pixels * @exception AWTException Thrown if too many different colours in image. */ void toIndexColor(byte[][] r, byte[][] g, byte[][] b) throws AWTException { this.allPixels = new byte[this.imageWidth * this.imageHeight]; this.allColors = new byte[256 * 3]; int colornum = 0; for (int x = 0; x < this.imageWidth; x++) { for (int y = 0; y < this.imageHeight; y++) { int search; for (search = 0; search < colornum; search++) { if (this.allColors[search * 3] == r[x][y] && this.allColors[search * 3 + 1] == g[x][y] && this.allColors[search * 3 + 2] == b[x][y]) { break; } // ends if } // ends for if (search > 255) throw new AWTException("Too many colors."); // row major order y=row x=col this.allPixels[y * this.imageWidth + x] = (byte) search; if (search == colornum) { this.allColors[search * 3] = r[x][y]; // [col][row] this.allColors[search * 3 + 1] = g[x][y]; this.allColors[search * 3 + 2] = b[x][y]; colornum++; } // ends if } // ends for } // ends for this.numberOfColors = 1 << BitUtils.BitsNeeded(colornum); byte copy[] = new byte[this.numberOfColors * 3]; System.arraycopy(this.allColors, 0, copy, 0, this.numberOfColors * 3); this.allColors = copy; } // ends toIndexColor(byte[][], byte[][], byte[][] ) } // ends class GIFEncoder /** * Used to write the bits composing the GIF image. */ class BitFile extends Object { /** * The outputstream where the data is written. */ OutputStream output = null; /** * buffer for bits to write. */ byte[] buffer; /** */ int indexIntoOutputStream, bitsLeft; /** * constructor * * @param output Where image will be written */ public BitFile(OutputStream output) { this.output = output; this.buffer = new byte[256]; this.indexIntoOutputStream = 0; this.bitsLeft = 8; } // ends constructor BitFile(OutputStream ) /** * Ensures image in ram gets to disk. * * @exception IOException */ public void flush() throws IOException { int numBytes = this.indexIntoOutputStream + ((this.bitsLeft == 8) ? 0 : 1); if (numBytes > 0) { this.output.write(numBytes); this.output.write(this.buffer, 0, numBytes); this.buffer[0] = 0; this.indexIntoOutputStream = 0; this.bitsLeft = 8; } // ends if } // ends flush( void ) /** * Write bits to stream. * * @param bits source of bits, low/high order? * @param numbits how many bits to write. * @exception IOException */ public void writeBits(int bits, int numbits) throws IOException { int numBytes = 255; do { if ((this.indexIntoOutputStream == 254 && this.bitsLeft == 0) || this.indexIntoOutputStream > 254) { this.output.write(numBytes); this.output.write(this.buffer, 0, numBytes); this.buffer[0] = 0; this.indexIntoOutputStream = 0; this.bitsLeft = 8; } // ends if if (numbits <= this.bitsLeft) { this.buffer[this.indexIntoOutputStream] |= (bits & ((1 << numbits) - 1)) << (8 - this.bitsLeft); this.bitsLeft -= numbits; numbits = 0; } // ends if else { this.buffer[this.indexIntoOutputStream] |= (bits & ((1 << this.bitsLeft) - 1)) << (8 - this.bitsLeft); bits >>= this.bitsLeft; numbits -= this.bitsLeft; this.buffer[++this.indexIntoOutputStream] = 0; this.bitsLeft = 8; } // ends else } while (numbits != 0); } // ends writeBits( int, int ) } // ends class BitFile /** * Used to compress the image by looking for repeating elements. */ class LZWStringTable extends Object { 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() { strChr_ = new byte[MAXSTR]; strNxt_ = new short[MAXSTR]; strHsh_ = new short[HASHSIZE]; } // ends constructor LZWStringTable( void ) 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_++; } // ends addCharString( short, byte ) public 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; } // ends while return (short) 0xFFFF; } // ends findCharString( short, byte ) 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++) this.addCharString((short) 0xFFFF, (byte) q); } // ends ClearTable(int) public static int Hash(short index, byte lastbyte) { return ((int) ((short) (lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE; } } // ends class LZWStringTable /** * Used to compress the image by looking for repeated elements. */ class LZWCompressor extends Object { 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; } // ends if limit = (1 << numbits) - 1; } // ends if prefix = (short) ((short) c & 0xFF); } // ends else } // ends for if (prefix != -1) bitFile.writeBits(prefix, numbits); bitFile.writeBits(endofinfo, numbits); bitFile.flush(); } // ends LZWCompress( OutputStream, int, byte[] ) } // ends class LWZCompressor /** */ class ScreenDescriptor extends Object { public short localScreenWidth, localScreenHeight; private byte currentByte; public byte backgroundColorIndex, pixelAspectRatio; /** * tool for dumping current screen image?? * * @param width * @param height * @param numColors */ 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; } // ends constructor ScreenDescriptor( short, short, int ) public void write(OutputStream output) throws IOException { BitUtils.writeWord(output, this.localScreenWidth); BitUtils.writeWord(output, this.localScreenHeight); output.write(this.currentByte); output.write(this.backgroundColorIndex); output.write(this.pixelAspectRatio); } // ends write( OutputStream ) public void SetGlobalColorTableSize(byte num) { this.currentByte |= (num & 7); } public void SetSortFlag(byte num) { this.currentByte |= (num & 1) << 3; } public void SetColorResolution(byte num) { this.currentByte |= (num & 7) << 4; } public void SetGlobalColorTableFlag(byte num) { this.currentByte |= (num & 1) << 7; } } // ends class ScreenDescriptor /** */ class ImageDescriptor extends Object { public byte separator_; public short leftPosition, topPosition, imageWidth, imageHeight; private byte currentByte; /** * ??? * * @param width * @param height * @param separator */ public ImageDescriptor(short width, short height, char separator) { separator_ = (byte) separator; this.leftPosition = 0; this.topPosition = 0; this.imageWidth = width; this.imageHeight = height; SetLocalColorTableSize((byte) 0); SetReserved((byte) 0); SetSortFlag((byte) 0); SetInterlaceFlag((byte) 0); SetLocalColorTableFlag((byte) 0); } // ends constructor ImageDescriptor( short, short, char ) public void write(OutputStream output) throws IOException { output.write(separator_); BitUtils.writeWord(output, this.leftPosition); BitUtils.writeWord(output, this.topPosition); BitUtils.writeWord(output, this.imageWidth); BitUtils.writeWord(output, this.imageHeight); output.write(this.currentByte); } // ends write( OutputStream ) public void SetLocalColorTableSize(byte num) { this.currentByte |= (num & 7); } public void SetReserved(byte num) { this.currentByte |= (num & 3) << 3; } public void SetSortFlag(byte num) { this.currentByte |= (num & 1) << 5; } public void SetInterlaceFlag(byte num) { this.currentByte |= (num & 1) << 6; } public void SetLocalColorTableFlag(byte num) { this.currentByte |= (num & 1) << 7; } } // ends class ImageDescriptor class BitUtils extends Object { /** * Bits needed no represent the number n??? */ public static byte BitsNeeded(int n) { byte ret = 1; if (n-- == 0) return 0; while ((n >>= 1) != 0) ret++; return ret; } // ends BitsNeeded(int) public static void writeWord(OutputStream output, short w) throws IOException { output.write(w & 0xFF); output.write((w >> 8) & 0xFF); } // ends writeWord( OutputStream, short ) static void writeString(OutputStream output, String string) throws IOException { for (int loop = 0; loop < string.length(); loop++) { output.write((byte) (string.charAt(loop))); } } // ends writeString( OutputStream, String ) } // ends class BitUtils