001: package com.javelin.swinglets;
002:
003: // It wasn't originally in a package but I put it in one
004: // for my own purposes. Feel free to change this. -- DWC
005:
006: /*
007: * @(#)GIFEncoder.java 0.90 4/21/96 Adam Doppelt
008: */
009: import java.io.*;
010: import java.awt.*;
011: import java.awt.image.*;
012:
013: /**
014: * GIFEncoder is a class which takes an image and saves it to a stream
015: * using the GIF file format (<A
016: * HREF="http://www.dcs.ed.ac.uk/%7Emxr/gfx/">Graphics Interchange
017: * Format</A>). A GIFEncoder
018: * is constructed with either an AWT Image (which must be fully
019: * loaded) or a set of RGB arrays. The image can be written out with a
020: * call to <CODE>write</CODE>.<P>
021: *
022: * Three caveats:
023: * <UL>
024: * <LI>GIFEncoder will convert the image to indexed color upon
025: * construction. This will take some time, depending on the size of
026: * the image. Also, actually writing the image out (Write) will take
027: * time.<P>
028: *
029: * <LI>The image cannot have more than 256 colors, since GIF is an 8
030: * bit format. For a 24 bit to 8 bit quantization algorithm, see
031: * Graphics Gems II III.2 by Xialoin Wu. Or check out his <A
032: * HREF="http://www.csd.uwo.ca/faculty/wu/cq.c">C source</A>.<P>
033: *
034: * <LI>Since the image must be completely loaded into memory,
035: * GIFEncoder may have problems with large images. Attempting to
036: * encode an image which will not fit into memory will probably
037: * result in the following exception:<P>
038: * <CODE>java.awt.AWTException: Grabber returned false: 192</CODE><P>
039: * </UL><P>
040: *
041: * GIFEncoder is based upon gifsave.c, which was written and released
042: * by:<P>
043: * <CENTER>
044: * Sverre H. Huseby<BR>
045: * Bjoelsengt. 17<BR>
046: * N-0468 Oslo<BR>
047: * Norway<P>
048: *
049: * Phone: +47 2 230539<BR>
050: * sverrehu@ifi.uio.no<P>
051: * </CENTER>
052: * @version DWC 0.91 1997-09-28 derived from version 0.90 21 Apr 1996
053: * @author
054: * <A HREF="http://www.cs.brown.edu/people/amd/">Adam Doppelt</A>
055: * @author
056: * <A HREF="http://www.alumni.caltech.edu/~croft/">David W. Croft</A>
057: */
058: public class GIFEncoder {
059: short width_, height_;
060: int numColors_;
061: byte pixels_[], colors_[];
062:
063: ScreenDescriptor sd_;
064: ImageDescriptor id_;
065:
066: /**
067: * Construct a GIFEncoder. The constructor will convert the image to
068: * an indexed color array. <B>This may take some time.</B><P>
069: *
070: * @param image The image to encode. The image <B>must</B> be
071: * completely loaded.
072: * @exception AWTException Will be thrown if the pixel grab fails. This
073: * can happen if Java runs out of memory. It may also indicate that the image
074: * contains more than 256 colors.
075: * */
076: public GIFEncoder(Image image) throws AWTException {
077: width_ = (short) image.getWidth(null);
078: height_ = (short) image.getHeight(null);
079:
080: int values[] = new int[width_ * height_];
081: PixelGrabber grabber = new PixelGrabber(image, 0, 0, width_,
082: height_, values, 0, width_);
083:
084: try {
085: if (grabber.grabPixels() != true)
086: throw new AWTException("Grabber returned false: "
087: + grabber.status());
088: } catch (InterruptedException e) {
089: ;
090: }
091:
092: byte r[][] = new byte[width_][height_];
093: byte g[][] = new byte[width_][height_];
094: byte b[][] = new byte[width_][height_];
095: toRGB(width_, height_, values, r, g, b);
096: toIndexedColor(r, g, b);
097: }
098:
099: /**
100: * Construct a GIFEncoder. The constructor will convert the image to
101: * an indexed color array. <B>This may take some time.</B><P>
102: *
103: * Each array stores intensity values for the image. In other words,
104: * r[x][y] refers to the red intensity of the pixel at column x, row
105: * y.<P>
106: *
107: * @param r An array containing the red intensity values.
108: * @param g An array containing the green intensity values.
109: * @param b An array containing the blue intensity values.
110: *
111: * @exception AWTException Will be thrown if the image contains more than
112: * 256 colors.
113: * */
114: public GIFEncoder(byte r[][], byte g[][], byte b[][])
115: throws AWTException {
116: width_ = (short) (r.length);
117: height_ = (short) (r[0].length);
118:
119: toIndexedColor(r, g, b);
120: }
121:
122: /*********************************************************************
123: * @param values
124: * An array of 24-bit color values. The length of values[]
125: * must be width * height. There must be 256 colors or less.
126: * @author
127: * <A HREF="http://www.alumni.caltech.edu/~croft/">David W. Croft</A>
128: *********************************************************************/
129: public GIFEncoder(short width, short height, int[] values)
130: throws AWTException {
131: //////////////////////////////////////////////////////////////////////
132: this .width_ = width;
133: this .height_ = height;
134: byte[][] r = new byte[width][height];
135: byte[][] g = new byte[width][height];
136: byte[][] b = new byte[width][height];
137: toRGB(width, height, values, r, g, b);
138: toIndexedColor(r, g, b);
139: }
140:
141: /**
142: * Writes the image out to a stream in the GIF file format. This will
143: * be a single GIF87a image, non-interlaced, with no background color.
144: * <B>This may take some time.</B><P>
145: *
146: * @param output The stream to output to. This should probably be a
147: * buffered stream.
148: *
149: * @exception IOException Will be thrown if a write operation fails.
150: * */
151: public synchronized void write(OutputStream output)
152: throws IOException {
153: BitUtils.WriteString(output, "GIF87a");
154:
155: ScreenDescriptor sd = new ScreenDescriptor(width_, height_,
156: numColors_);
157: sd.Write(output);
158:
159: output.write(colors_, 0, colors_.length);
160:
161: ImageDescriptor id = new ImageDescriptor(width_, height_, ',');
162: id.Write(output);
163:
164: byte codesize = BitUtils.BitsNeeded(numColors_);
165: if (codesize == 1)
166: ++codesize;
167: output.write(codesize);
168:
169: LZWCompressor.LZWCompress(output, codesize, pixels_);
170: output.write(0);
171:
172: id = new ImageDescriptor((byte) 0, (byte) 0, ';');
173: id.Write(output);
174: output.flush();
175: }
176:
177: void toIndexedColor(byte r[][], byte g[][], byte b[][])
178: throws AWTException {
179: pixels_ = new byte[width_ * height_];
180: colors_ = new byte[256 * 3];
181: int colornum = 0;
182: for (int x = 0; x < width_; ++x) {
183: for (int y = 0; y < height_; ++y) {
184: int search;
185: for (search = 0; search < colornum; ++search)
186: if (colors_[search * 3] == r[x][y]
187: && colors_[search * 3 + 1] == g[x][y]
188: && colors_[search * 3 + 2] == b[x][y])
189: break;
190:
191: if (search > 255)
192: throw new AWTException("Too many colors.");
193:
194: pixels_[y * width_ + x] = (byte) search;
195:
196: if (search == colornum) {
197: colors_[search * 3] = r[x][y];
198: colors_[search * 3 + 1] = g[x][y];
199: colors_[search * 3 + 2] = b[x][y];
200: ++colornum;
201: }
202: }
203: }
204: numColors_ = 1 << BitUtils.BitsNeeded(colornum);
205: byte copy[] = new byte[numColors_ * 3];
206: System.arraycopy(colors_, 0, copy, 0, numColors_ * 3);
207: colors_ = copy;
208: }
209:
210: /*********************************************************************
211: * Loads the values of three 8-bit arrays (red, green, blue) of
212: * arrays ([ width ] [ height ]) with the masked values from a
213: * 24-bit color array of length width * height.
214: *
215: * @param values
216: * An array of 24-bit color values. The length of values[]
217: * must be width * height. There must be 256 colors or less.
218: * @author
219: * <A HREF="http://www.alumni.caltech.edu/~croft/">David W. Croft</A>
220: *********************************************************************/
221: public static void toRGB(short width, short height, int[] values,
222: byte[][] r, byte[][] g, byte[][] b) throws AWTException {
223: //////////////////////////////////////////////////////////////////////
224: if (values.length != width * height) {
225: throw new AWTException("values.length != width * height");
226: }
227: if ((r.length != width) || (g.length != width)
228: || (b.length != width)) {
229: throw new AWTException("r, g, or b array length != width");
230: }
231: if ((r[0].length != height) || (g[0].length != height)
232: || (b[0].length != height)) {
233: throw new AWTException("r, g, or b array length != height");
234: }
235: int index = 0;
236: for (int y = 0; y < height; y++) {
237: for (int x = 0; x < width; x++) {
238: int noalpha = values[index] & 0xFFFFFF;
239: r[x][y] = (byte) (noalpha >>> 16);
240: g[x][y] = (byte) (noalpha >>> 8);
241: b[x][y] = (byte) (noalpha);
242: index++;
243: }
244: }
245: }
246:
247: }
248:
249: class BitFile {
250: OutputStream output_;
251: byte buffer_[];
252: int index_, bitsLeft_;
253:
254: public BitFile(OutputStream output) {
255: output_ = output;
256: buffer_ = new byte[256];
257: index_ = 0;
258: bitsLeft_ = 8;
259: }
260:
261: public void Flush() throws IOException {
262: int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1);
263: if (numBytes > 0) {
264: output_.write(numBytes);
265: output_.write(buffer_, 0, numBytes);
266: buffer_[0] = 0;
267: index_ = 0;
268: bitsLeft_ = 8;
269: }
270: }
271:
272: public void WriteBits(int bits, int numbits) throws IOException {
273: int bitsWritten = 0;
274: int numBytes = 255;
275: do {
276: if ((index_ == 254 && bitsLeft_ == 0) || index_ > 254) {
277: output_.write(numBytes);
278: output_.write(buffer_, 0, numBytes);
279:
280: buffer_[0] = 0;
281: index_ = 0;
282: bitsLeft_ = 8;
283: }
284:
285: if (numbits <= bitsLeft_) {
286: buffer_[index_] |= (bits & ((1 << numbits) - 1)) << (8 - bitsLeft_);
287: bitsWritten += numbits;
288: bitsLeft_ -= numbits;
289: numbits = 0;
290: } else {
291: buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) << (8 - bitsLeft_);
292: bitsWritten += bitsLeft_;
293: bits >>= bitsLeft_;
294: numbits -= bitsLeft_;
295: buffer_[++index_] = 0;
296: bitsLeft_ = 8;
297: }
298: } while (numbits != 0);
299: }
300: }
301:
302: class LZWStringTable {
303: private final static int RES_CODES = 2;
304: private final static short HASH_FREE = (short) 0xFFFF;
305: private final static short NEXT_FIRST = (short) 0xFFFF;
306: private final static int MAXBITS = 12;
307: private final static int MAXSTR = (1 << MAXBITS);
308: private final static short HASHSIZE = 9973;
309: private final static short HASHSTEP = 2039;
310:
311: byte strChr_[];
312: short strNxt_[];
313: short strHsh_[];
314: short numStrings_;
315:
316: public LZWStringTable() {
317: strChr_ = new byte[MAXSTR];
318: strNxt_ = new short[MAXSTR];
319: strHsh_ = new short[HASHSIZE];
320: }
321:
322: public int AddCharString(short index, byte b) {
323: int hshidx;
324:
325: if (numStrings_ >= MAXSTR)
326: return 0xFFFF;
327:
328: hshidx = Hash(index, b);
329: while (strHsh_[hshidx] != HASH_FREE)
330: hshidx = (hshidx + HASHSTEP) % HASHSIZE;
331:
332: strHsh_[hshidx] = numStrings_;
333: strChr_[numStrings_] = b;
334: strNxt_[numStrings_] = (index != HASH_FREE) ? index
335: : NEXT_FIRST;
336:
337: return numStrings_++;
338: }
339:
340: public short FindCharString(short index, byte b) {
341: int hshidx, nxtidx;
342:
343: if (index == HASH_FREE)
344: return b;
345:
346: hshidx = Hash(index, b);
347: while ((nxtidx = strHsh_[hshidx]) != HASH_FREE) {
348: if (strNxt_[nxtidx] == index && strChr_[nxtidx] == b)
349: return (short) nxtidx;
350: hshidx = (hshidx + HASHSTEP) % HASHSIZE;
351: }
352:
353: return (short) 0xFFFF;
354: }
355:
356: public void ClearTable(int codesize) {
357: numStrings_ = 0;
358:
359: for (int q = 0; q < HASHSIZE; q++) {
360: strHsh_[q] = HASH_FREE;
361: }
362:
363: int w = (1 << codesize) + RES_CODES;
364: for (int q = 0; q < w; q++)
365: AddCharString((short) 0xFFFF, (byte) q);
366: }
367:
368: static public int Hash(short index, byte lastbyte) {
369: return ((int) ((short) (lastbyte << 8) ^ index) & 0xFFFF)
370: % HASHSIZE;
371: }
372: }
373:
374: class LZWCompressor {
375:
376: public static void LZWCompress(OutputStream output, int codesize,
377: byte toCompress[]) throws IOException {
378: byte c;
379: short index;
380: int clearcode, endofinfo, numbits, limit, errcode;
381: short prefix = (short) 0xFFFF;
382:
383: BitFile bitFile = new BitFile(output);
384: LZWStringTable strings = new LZWStringTable();
385:
386: clearcode = 1 << codesize;
387: endofinfo = clearcode + 1;
388:
389: numbits = codesize + 1;
390: limit = (1 << numbits) - 1;
391:
392: strings.ClearTable(codesize);
393: bitFile.WriteBits(clearcode, numbits);
394:
395: for (int loop = 0; loop < toCompress.length; ++loop) {
396: c = toCompress[loop];
397: if ((index = strings.FindCharString(prefix, c)) != -1)
398: prefix = index;
399: else {
400: bitFile.WriteBits(prefix, numbits);
401: if (strings.AddCharString(prefix, c) > limit) {
402: if (++numbits > 12) {
403: bitFile.WriteBits(clearcode, numbits - 1);
404: strings.ClearTable(codesize);
405: numbits = codesize + 1;
406: }
407: limit = (1 << numbits) - 1;
408: }
409:
410: prefix = (short) ((short) c & 0xFF);
411: }
412: }
413:
414: if (prefix != -1)
415: bitFile.WriteBits(prefix, numbits);
416:
417: bitFile.WriteBits(endofinfo, numbits);
418: bitFile.Flush();
419: }
420: }
421:
422: class ScreenDescriptor {
423: public short localScreenWidth_, localScreenHeight_;
424: private byte byte_;
425: public byte backgroundColorIndex_, pixelAspectRatio_;
426:
427: public ScreenDescriptor(short width, short height, int numColors) {
428: localScreenWidth_ = width;
429: localScreenHeight_ = height;
430: SetGlobalColorTableSize((byte) (BitUtils.BitsNeeded(numColors) - 1));
431: SetGlobalColorTableFlag((byte) 1);
432: SetSortFlag((byte) 0);
433: SetColorResolution((byte) 7);
434: backgroundColorIndex_ = 0;
435: pixelAspectRatio_ = 0;
436: }
437:
438: public void Write(OutputStream output) throws IOException {
439: BitUtils.WriteWord(output, localScreenWidth_);
440: BitUtils.WriteWord(output, localScreenHeight_);
441: output.write(byte_);
442: output.write(backgroundColorIndex_);
443: output.write(pixelAspectRatio_);
444: }
445:
446: public void SetGlobalColorTableSize(byte num) {
447: byte_ |= (num & 7);
448: }
449:
450: public void SetSortFlag(byte num) {
451: byte_ |= (num & 1) << 3;
452: }
453:
454: public void SetColorResolution(byte num) {
455: byte_ |= (num & 7) << 4;
456: }
457:
458: public void SetGlobalColorTableFlag(byte num) {
459: byte_ |= (num & 1) << 7;
460: }
461: }
462:
463: class ImageDescriptor {
464: public byte separator_;
465: public short leftPosition_, topPosition_, width_, height_;
466: private byte byte_;
467:
468: public ImageDescriptor(short width, short height, char separator) {
469: separator_ = (byte) separator;
470: leftPosition_ = 0;
471: topPosition_ = 0;
472: width_ = width;
473: height_ = height;
474: SetLocalColorTableSize((byte) 0);
475: SetReserved((byte) 0);
476: SetSortFlag((byte) 0);
477: SetInterlaceFlag((byte) 0);
478: SetLocalColorTableFlag((byte) 0);
479: }
480:
481: public void Write(OutputStream output) throws IOException {
482: output.write(separator_);
483: BitUtils.WriteWord(output, leftPosition_);
484: BitUtils.WriteWord(output, topPosition_);
485: BitUtils.WriteWord(output, width_);
486: BitUtils.WriteWord(output, height_);
487: output.write(byte_);
488: }
489:
490: public void SetLocalColorTableSize(byte num) {
491: byte_ |= (num & 7);
492: }
493:
494: public void SetReserved(byte num) {
495: byte_ |= (num & 3) << 3;
496: }
497:
498: public void SetSortFlag(byte num) {
499: byte_ |= (num & 1) << 5;
500: }
501:
502: public void SetInterlaceFlag(byte num) {
503: byte_ |= (num & 1) << 6;
504: }
505:
506: public void SetLocalColorTableFlag(byte num) {
507: byte_ |= (num & 1) << 7;
508: }
509: }
510:
511: class BitUtils {
512: public static byte BitsNeeded(int n) {
513: byte ret = 1;
514:
515: if (n-- == 0)
516: return 0;
517:
518: while ((n >>= 1) != 0)
519: ++ret;
520:
521: return ret;
522: }
523:
524: public static void WriteWord(OutputStream output, short w)
525: throws IOException {
526: output.write(w & 0xFF);
527: output.write((w >> 8) & 0xFF);
528: }
529:
530: static void WriteString(OutputStream output, String string)
531: throws IOException {
532: for (int loop = 0; loop < string.length(); ++loop)
533: output.write((byte) (string.charAt(loop)));
534: }
535: }
|