View Javadoc

1   /*
2    The source code in this file, GIFEncoder.java,
3    belongs to the public domain.
4   */
5   
6   package net.wotonomy.ui.swing.util;
7   
8   import java.awt.AWTException;
9   import java.awt.Image;
10  import java.awt.image.PixelGrabber;
11  import java.io.IOException;
12  import java.io.OutputStream;
13  
14  /***
15   * GIFEncoder is a class which takes an image and saves it to a stream
16   * using the GIF file format. A GIFEncoder is constructed with either
17   * an AWT Image (which must be fully loaded) or a set of RGB arrays.
18   * The image can be written out with a call to <CODE>write</CODE>.<br><br>
19   *
20   * Three caveats:
21   * <UL>
22   *   <LI>GIFEncoder will convert the image to indexed color upon
23   *   construction, and this is not fast.
24   *
25   *   <LI>The image cannot have more than 256 colors, since GIF is an 8
26   *   bit format.
27   *
28   *   <LI>Since the image must be completely loaded into memory,
29   *   there may be problems with large images.
30   * </UL>
31   *
32   * This implementation is heavily based on code made available by
33   * Adam Doppelt, which was based upon gifsave.c, which was written
34   * and released by Sverre H. Huseby.  
35   *
36   * @author amd@brown.edu
37   * @author sverrehu@ifi.uio.no
38   * @author michael@mpowers.net
39   */
40  public class GIFEncoder
41  {
42      short width_, height_;
43      int numColors_;
44      byte pixels_[], colors_[];
45  
46      ScreenDescriptor sd_;
47      ImageDescriptor id_;
48  
49  /***
50   * Construct a GIFEncoder. The constructor will convert the image to
51   * an indexed color array. This may take some time.  If more than 256
52   * colors are encountered, all subsequent colors are mapped to the first
53   * color encountered.
54   * @param image The image to encode. The image must be completely loaded.
55   * @exception AWTException Will be thrown if the pixel grab fails. This
56   * can happen if Java runs out of memory. 
57   * */
58      public GIFEncoder(Image image) throws AWTException
59      {
60          width_ = (short)image.getWidth(null);
61          height_ = (short)image.getHeight(null);
62  
63          int values[] = new int[width_ * height_];
64          PixelGrabber grabber = new PixelGrabber(
65              image, 0, 0, width_, height_, values, 0, width_);
66  
67          try
68          {
69              if(grabber.grabPixels() != true)
70              throw new AWTException("Grabber returned false: " +
71                             grabber.status());
72          }
73          catch (InterruptedException e)
74          {
75          }
76  
77          byte r[][] = new byte[width_][height_];
78          byte g[][] = new byte[width_][height_];
79          byte b[][] = new byte[width_][height_];
80          int index = 0;
81          for (int y = 0; y < height_; ++y)
82          {
83              for (int x = 0; x < width_; ++x)
84              {
85                  r[x][y] = (byte)((values[index] >> 16) & 0xFF);
86                  g[x][y] = (byte)((values[index] >> 8) & 0xFF);
87                  b[x][y] = (byte)((values[index]) & 0xFF);
88                  ++index;
89              }
90          }
91          toIndexedColor(r, g, b);
92      }
93  
94  /***
95   * Construct a GIFEncoder. The constructor will convert the image to
96   * an indexed color array. This may take some time. <br><br>
97   * Each array stores intensity values for the image. In other words,
98   * r[x][y] refers to the red intensity of the pixel at column x, row y.
99   * @param r An array containing the red intensity values.
100  * @param g An array containing the green intensity values.
101  * @param b An array containing the blue intensity values.
102  *
103  * @exception AWTException Will be thrown if the image contains more than
104  * 256 colors.
105  * */
106     public GIFEncoder(byte r[][], byte g[][], byte b[][]) throws AWTException
107     {
108         width_ = (short)(r.length);
109         height_ = (short)(r[0].length);
110 
111         toIndexedColor(r, g, b);
112     }
113 
114 /***
115  * Writes the image out to a stream in the GIF file format. This will
116  * be a single GIF87a image, non-interlaced, with no background color.
117  * <B>This may take some time.</B><P>
118  *
119  * @param output The stream to output to. This should probably be a
120  * buffered stream.
121  *
122  * @exception IOException Will be thrown if a write operation fails.
123  * */
124     public void write(OutputStream output) throws IOException
125     {
126         BitUtils.writeString(output, "GIF87a");
127         ScreenDescriptor sd = new ScreenDescriptor(width_, height_,
128                                numColors_);
129         sd.write(output);
130 
131         output.write(colors_, 0, colors_.length);
132 
133         ImageDescriptor id = new ImageDescriptor(width_, height_, ',');
134         id.write(output);
135 
136         byte codesize = BitUtils.bitsNeeded(numColors_);
137         if (codesize == 1)
138         ++codesize;
139         output.write(codesize);
140 
141         LZWCompressor.LZWCompress(output, codesize, pixels_);
142         output.write(0);
143         id = new ImageDescriptor((byte)0, (byte)0, ';');
144         id.write(output);
145         output.flush();
146     }
147 
148     void toIndexedColor(byte r[][], byte g[][],
149 			byte b[][]) throws AWTException
150     {
151         pixels_ = new byte[width_ * height_];
152         colors_ = new byte[256 * 3];
153         int colornum = 0;
154         for (int x = 0; x < width_; ++x)
155         {
156             for (int y = 0; y < height_; ++y)
157             {
158                 int search;
159                 for (search = 0; search < colornum; ++search)
160                     if (colors_[search * 3]     == r[x][y] &&
161                     colors_[search * 3 + 1] == g[x][y] &&
162                     colors_[search * 3 + 2] == b[x][y])
163                     break;
164 
165                 if (search > 255)
166                     search = 0;
167                     //throw new AWTException("Too many colors.");
168 
169                 pixels_[y * width_ + x] = (byte)search;
170 
171                 if (search == colornum) {
172                     colors_[search * 3]     = r[x][y];
173                     colors_[search * 3 + 1] = g[x][y];
174                     colors_[search * 3 + 2] = b[x][y];
175                     ++colornum;
176                 }
177             }
178         }
179 
180         numColors_ = 1 << BitUtils.bitsNeeded(colornum);
181         byte copy[] = new byte[numColors_ * 3];
182         System.arraycopy(colors_, 0, copy, 0, numColors_ * 3);
183         colors_ = copy;
184     }
185 
186 }
187 
188 class BitFile
189 {
190     OutputStream output_;
191     byte buffer_[];
192     int index_, bitsLeft_;
193 
194     public BitFile(OutputStream output)
195     {
196         output_ = output;
197         buffer_ = new byte[256];
198         index_ = 0;
199         bitsLeft_ = 8;
200     }
201 
202     public void flush() throws IOException
203     {
204         int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1);
205         if (numBytes > 0)
206         {
207             output_.write(numBytes);
208             output_.write(buffer_, 0, numBytes);
209             buffer_[0] = 0;
210             index_ = 0;
211             bitsLeft_ = 8;
212         }
213     }
214 
215     public void writeBits(int bits, int numbits) throws IOException {
216     	int bitsWritten = 0;
217     	int numBytes = 255;
218         do
219         {
220             if ((index_ == 254 && bitsLeft_ == 0) || index_ > 254)
221             {
222                 output_.write(numBytes);
223                 output_.write(buffer_, 0, numBytes);
224 
225                 buffer_[0] = 0;
226                 index_ = 0;
227                 bitsLeft_ = 8;
228             }
229 
230             if (numbits <= bitsLeft_)
231             {
232                 buffer_[index_] |= (bits & ((1 << numbits) - 1)) <<
233                     (8 - bitsLeft_);
234                 bitsWritten += numbits;
235                 bitsLeft_ -= numbits;
236                 numbits = 0;
237     	    }
238             else
239             {
240                 buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) <<
241                 (8 - bitsLeft_);
242                 bitsWritten += bitsLeft_;
243                 bits >>= bitsLeft_;
244                 numbits -= bitsLeft_;
245                 buffer_[++index_] = 0;
246                 bitsLeft_ = 8;
247             }
248 
249     	}
250         while (numbits != 0);
251     }
252 }
253 
254 class LZWStringTable
255 {
256     private final static int RES_CODES = 2;
257     private final static short HASH_FREE = (short)0xFFFF;
258     private final static short NEXT_FIRST = (short)0xFFFF;
259     private final static int MAXBITS = 12;
260     private final static int MAXSTR = (1 << MAXBITS);
261     private final static short HASHSIZE = 9973;
262     private final static short HASHSTEP = 2039;
263 
264     byte strChr_[];
265     short strNxt_[];
266     short strHsh_[];
267     short numStrings_;
268 
269     public LZWStringTable()
270     {
271         strChr_ = new byte[MAXSTR];
272         strNxt_ = new short[MAXSTR];
273         strHsh_ = new short[HASHSIZE];
274     }
275 
276     public int addCharString(short index, byte b)
277     {
278         int hshidx;
279 
280         if (numStrings_ >= MAXSTR)
281             return 0xFFFF;
282 
283         hshidx = Hash(index, b);
284         while (strHsh_[hshidx] != HASH_FREE)
285             hshidx = (hshidx + HASHSTEP) % HASHSIZE;
286 
287         strHsh_[hshidx] = numStrings_;
288         strChr_[numStrings_] = b;
289         strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST;
290 
291         return numStrings_++;
292     }
293 
294     public short findCharString(short index, byte b)
295     {
296         int hshidx, nxtidx;
297 
298         if (index == HASH_FREE)
299             return b;
300 
301         hshidx = Hash(index, b);
302         while ((nxtidx = strHsh_[hshidx]) != HASH_FREE)
303         {
304             if (strNxt_[nxtidx] == index && strChr_[nxtidx] == b)
305             return (short)nxtidx;
306             hshidx = (hshidx + HASHSTEP) % HASHSIZE;
307         }
308 
309         return (short)0xFFFF;
310     }
311 
312     public void clearTable(int codesize)
313     {
314         numStrings_ = 0;
315 
316         for (int q = 0; q < HASHSIZE; q++)
317         {
318             strHsh_[q] = HASH_FREE;
319         }
320 
321         int w = (1 << codesize) + RES_CODES;
322         for (int q = 0; q < w; q++)
323         {
324             addCharString((short)0xFFFF, (byte)q);
325         }
326     }
327 
328     static public int Hash(short index, byte lastbyte)
329     {
330 	    return ((int)((short)(lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE;
331     }
332 }
333 
334 class LZWCompressor {
335 
336     public static void LZWCompress(OutputStream output, int codesize,
337 				   byte toCompress[]) throws IOException
338     {
339         byte c;
340         short index;
341         int clearcode, endofinfo, numbits, limit, errcode;
342         short prefix = (short)0xFFFF;
343 
344         BitFile bitFile = new BitFile(output);
345         LZWStringTable strings = new LZWStringTable();
346 
347         clearcode = 1 << codesize;
348         endofinfo = clearcode + 1;
349 
350         numbits = codesize + 1;
351         limit = (1 << numbits) - 1;
352         strings.clearTable(codesize);
353         bitFile.writeBits(clearcode, numbits);
354 
355         for (int loop = 0; loop < toCompress.length; ++loop)
356         {
357             c = toCompress[loop];
358             if ((index = strings.findCharString(prefix, c)) != -1)
359             {
360                 prefix = index;
361             }
362             else
363             {
364                 bitFile.writeBits(prefix, numbits);
365                 if (strings.addCharString(prefix, c) > limit) {
366                     if (++numbits > 12) {
367                     bitFile.writeBits(clearcode, numbits - 1);
368                     strings.clearTable(codesize);
369                     numbits = codesize + 1;
370                     }
371                     limit = (1 << numbits) - 1;
372                 }
373 
374                 prefix = (short)((short)c & 0xFF);
375             }
376         }
377 
378         if (prefix != -1)
379             bitFile.writeBits(prefix, numbits);
380 
381         bitFile.writeBits(endofinfo, numbits);
382         bitFile.flush();
383     }
384 }
385 
386 class ScreenDescriptor
387 {
388     public short localScreenWidth_, localScreenHeight_;
389     private byte byte_;
390     public byte backgroundColorIndex_, pixelAspectRatio_;
391 
392     public ScreenDescriptor(short width, short height, int numColors)
393     {
394         localScreenWidth_ = width;
395         localScreenHeight_ = height;
396         setGlobalColorTableSize((byte)(BitUtils.bitsNeeded(numColors) - 1));
397         setGlobalColorTableFlag((byte)1);
398         setSortFlag((byte)0);
399         setColorResolution((byte)7);
400         backgroundColorIndex_ = 0;
401         pixelAspectRatio_ = 0;
402     }
403 
404     public void write(OutputStream output) throws IOException
405     {
406         BitUtils.writeWord(output, localScreenWidth_);
407         BitUtils.writeWord(output, localScreenHeight_);
408         output.write(byte_);
409         output.write(backgroundColorIndex_);
410         output.write(pixelAspectRatio_);
411     }
412 
413     public void setGlobalColorTableSize(byte num)
414     {
415 	    byte_ |= (num & 7);
416     }
417 
418     public void setSortFlag(byte num)
419     {
420 	    byte_ |= (num & 1) << 3;
421     }
422 
423     public void setColorResolution(byte num)
424     {
425 	    byte_ |= (num & 7) << 4;
426     }
427 
428     public void setGlobalColorTableFlag(byte num)
429     {
430 	    byte_ |= (num & 1) << 7;
431     }
432 }
433 
434 class ImageDescriptor
435 {
436     public byte separator_;
437     public short leftPosition_, topPosition_, width_, height_;
438     private byte byte_;
439 
440     public ImageDescriptor(short width, short height, char separator)
441     {
442         separator_ = (byte)separator;
443         leftPosition_ = 0;
444         topPosition_ = 0;
445         width_ = width;
446         height_ = height;
447         setLocalColorTableSize((byte)0);
448         setReserved((byte)0);
449         setSortFlag((byte)0);
450         setInterlaceFlag((byte)0);
451         setLocalColorTableFlag((byte)0);
452     }
453 
454     public void write(OutputStream output) throws IOException
455     {
456         output.write(separator_);
457         BitUtils.writeWord(output, leftPosition_);
458         BitUtils.writeWord(output, topPosition_);
459         BitUtils.writeWord(output, width_);
460         BitUtils.writeWord(output, height_);
461         output.write(byte_);
462     }
463 
464     public void setLocalColorTableSize(byte num)
465     {
466 	    byte_ |= (num & 7);
467     }
468 
469     public void setReserved(byte num)
470     {
471 	    byte_ |= (num & 3) << 3;
472     }
473 
474     public void setSortFlag(byte num)
475     {
476 	    byte_ |= (num & 1) << 5;
477     }
478 
479     public void setInterlaceFlag(byte num)
480     {
481 	    byte_ |= (num & 1) << 6;
482     }
483 
484     public void setLocalColorTableFlag(byte num)
485     {
486         byte_ |= (num & 1) << 7;
487     }
488 }
489 
490 class BitUtils
491 {
492     public static byte bitsNeeded(int n)
493     {
494         byte ret = 1;
495 
496         if (n-- == 0)
497             return 0;
498 
499         while ((n >>= 1) != 0)
500             ++ret;
501 
502         return ret;
503     }
504 
505     public static void writeWord(OutputStream output,
506 				 short w) throws IOException
507     {
508         output.write(w & 0xFF);
509         output.write((w >> 8) & 0xFF);
510     }
511 
512     static void writeString(OutputStream output,
513 			    String string) throws IOException
514     {
515         for (int loop = 0; loop < string.length(); ++loop)
516             output.write((byte)(string.charAt(loop)));
517     }
518 }
519 
520