/Users/lyon/j4p/src/ip/gif/stills/WriteGIF.java

1    /** 
2     * WriteGIF is a class that takes an Image and saves it to 
3     * a GIF format file. 
4     * 
5     * Based upon gifsave.c, which was written and released by 
6     * Sverre H. Huseby. Ported to Java by Adam Doppelt of Brown 
7     * University. 
8     * Modified and integrated with the ImageProc program for 
9     * CS411X, advanced Java Programming at the University of 
10    * Bridgeport by Victor Silva 
11    * Modified again by D. Lyon 7/8/01 
12    * 
13    */ 
14   package ip.gif.stills; 
15    
16   import java.io.FileOutputStream; 
17    
18   public class WriteGIF { 
19       short width_, height_; 
20       int numColors_; 
21       byte pixels_[], colors_[]; 
22    
23       ScreenDescriptor sd_; 
24       ImageDescriptor id_; 
25    
26       public static void toFile(java.awt.Image img, String fname) { 
27           // encode the image as a GIF 
28           try { 
29               WriteGIF wg = new WriteGIF(img); 
30               wg.toOutputStream( 
31                       new java.io.BufferedOutputStream( 
32                               new FileOutputStream(fname)) 
33               ); 
34           } catch (Exception e) { 
35               System.out.println("Save GIF Exception!"); 
36           } 
37       } 
38    
39       public WriteGIF(java.awt.Image image) throws java.awt.AWTException { 
40           width_ = (short) image.getWidth(null); 
41           height_ = (short) image.getHeight(null); 
42    
43           int values[] = new int[width_ * height_]; 
44           java.awt.image.PixelGrabber grabber = new java.awt.image.PixelGrabber( 
45                   image, 0, 0, width_, height_, values, 0, width_); 
46    
47           try { 
48               if (grabber.grabPixels() != true) 
49                   throw new java.awt.AWTException("Grabber returned false: " + 
50                           grabber.status()); 
51           } catch (InterruptedException e) { 
52           } 
53           ; 
54    
55           //-------------------------------------------------------------- 
56           // TODO -> Possible Speed up - do it a row at a time, a la ACME 
57           //-------------------------------------------------------------- 
58           byte r[][] = new byte[width_][height_]; 
59           byte g[][] = new byte[width_][height_]; 
60           byte b[][] = new byte[width_][height_]; 
61           int index = 0; 
62    
63           for (int y = 0; y < height_; ++y) { 
64               for (int x = 0; x < width_; ++x) { 
65                   r[x][y] = (byte) ((values[index] >> 16) & 0xFF); 
66                   g[x][y] = (byte) ((values[index] >> 8) & 0xFF); 
67                   b[x][y] = (byte) ((values[index]) & 0xFF); 
68                   ++index; 
69               } 
70           } 
71           toIndexedColor(r, g, b); 
72       } 
73    
74    
75       public void toOutputStream(java.io.OutputStream os) 
76               throws java.io.IOException { 
77           BitUtils.writeString(os, "GIF87a"); 
78           ScreenDescriptor sd = 
79                   new ScreenDescriptor( 
80                           width_, height_, numColors_); 
81           sd.write(os); 
82           os.write(colors_, 0, colors_.length); 
83           ImageDescriptor id = 
84                   new ImageDescriptor(width_, height_, ','); 
85           id.toOutputStream(os); 
86    
87           byte codesize = BitUtils.bitsNeeded(numColors_); 
88           if (codesize == 1) ++codesize; 
89    
90           os.write(codesize); 
91    
92           LZWCompressor.compress(os, codesize, pixels_); 
93           os.write(0); 
94    
95           id = new ImageDescriptor((byte) 0, (byte) 0, ';'); 
96           id.toOutputStream(os); 
97           os.flush(); 
98       } 
99    
100      void toIndexedColor(byte r[][], byte g[][], byte b[][]) 
101              throws java.awt.AWTException { 
102          pixels_ = new byte[width_ * height_]; 
103          colors_ = new byte[256 * 3]; 
104          int colornum = 0; 
105          for (int x = 0; x < width_; ++x) { 
106              for (int y = 0; y < height_; ++y) { 
107                  int search; 
108                  for (search = 0; search < colornum; ++search) { 
109                      if (colors_[search * 3] == r[x][y] && 
110                              colors_[search * 3 + 1] == g[x][y] && 
111                              colors_[search * 3 + 2] == b[x][y]) { 
112                          break; 
113                      } 
114                  } 
115   
116                  // If there are more than 256 colors invoke 
117                  // the color quantization procedure. 
118                  //quantization(); 
119                  if (search > 255) { 
120                      throw new java.awt.AWTException("Too many colors."); 
121                  } 
122   
123                  pixels_[y * width_ + x] = (byte) search; 
124   
125                  if (search == colornum) { 
126                      colors_[search * 3] = r[x][y]; 
127                      colors_[search * 3 + 1] = g[x][y]; 
128                      colors_[search * 3 + 2] = b[x][y]; 
129                      ++colornum; 
130                  } 
131              } 
132          } 
133          numColors_ = 1 << BitUtils.bitsNeeded(colornum); 
134          byte copy[] = new byte[numColors_ * 3]; 
135          System.arraycopy(colors_, 0, copy, 0, numColors_ * 3); 
136          colors_ = copy; 
137      } 
138  } 
139   
140  class BitFile { 
141      java.io.OutputStream output_; 
142      byte buffer_[]; 
143      int index_, bitsLeft_; 
144   
145      public BitFile(java.io.OutputStream output) { 
146          output_ = output; 
147          buffer_ = new byte[256]; 
148          index_ = 0; 
149          bitsLeft_ = 8; 
150      } 
151   
152      public void flush() throws java.io.IOException { 
153          int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1); 
154          if (numBytes > 0) { 
155              output_.write(numBytes); 
156              output_.write(buffer_, 0, numBytes); 
157              buffer_[0] = 0; 
158              index_ = 0; 
159              bitsLeft_ = 8; 
160          } 
161      } 
162   
163      public void writeBits(int bits, int numbits) 
164              throws java.io.IOException { 
165          int bitsWritten = 0; 
166          int numBytes = 255; 
167          do { 
168              if ((index_ == 254 && bitsLeft_ == 0) 
169                      || index_ > 254) { 
170                  output_.write(numBytes); 
171                  output_.write(buffer_, 0, numBytes); 
172   
173                  buffer_[0] = 0; 
174                  index_ = 0; 
175                  bitsLeft_ = 8; 
176              } 
177   
178              if (numbits <= bitsLeft_) { 
179                  buffer_[index_] |= (bits & ((1 << numbits) - 1)) << 
180                          (8 - bitsLeft_); 
181                  bitsWritten += numbits; 
182                  bitsLeft_ -= numbits; 
183                  numbits = 0; 
184              } else { 
185                  buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) << 
186                          (8 - bitsLeft_); 
187                  bitsWritten += bitsLeft_; 
188                  bits >>= bitsLeft_; 
189                  numbits -= bitsLeft_; 
190                  buffer_[++index_] = 0; 
191                  bitsLeft_ = 8; 
192              } 
193          } while (numbits != 0); 
194      } 
195  } 
196   
197  class LZWStringTable { 
198      private final static int RES_CODES = 2; 
199      private final static short HASH_FREE = (short) 0xFFFF; 
200      private final static short NEXT_FIRST = (short) 0xFFFF; 
201      private final static int MAXBITS = 12; 
202      private final static int MAXSTR = (1 << MAXBITS); 
203      private final static short HASHSIZE = 9973; 
204      private final static short HASHSTEP = 2039; 
205   
206      byte strChr_[]; 
207      short strNxt_[]; 
208      short strHsh_[]; 
209      short numStrings_; 
210   
211      public LZWStringTable() { 
212          strChr_ = new byte[MAXSTR]; 
213          strNxt_ = new short[MAXSTR]; 
214          strHsh_ = new short[HASHSIZE]; 
215      } 
216   
217      public int addCharString(short index, byte b) { 
218          int hshidx; 
219   
220          if (numStrings_ >= MAXSTR) 
221              return 0xFFFF; 
222   
223          hshidx = hash(index, b); 
224          while (strHsh_[hshidx] != HASH_FREE) 
225              hshidx = (hshidx + HASHSTEP) % HASHSIZE; 
226   
227          strHsh_[hshidx] = numStrings_; 
228          strChr_[numStrings_] = b; 
229          strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST; 
230   
231          return numStrings_++; 
232      } 
233   
234      public short findCharString(short index, byte b) { 
235          int hshidx, nxtidx; 
236   
237          if (index == HASH_FREE) 
238              return b; 
239   
240          hshidx = hash(index, b); 
241          while ((nxtidx = strHsh_[hshidx]) != HASH_FREE) { 
242              if (strNxt_[nxtidx] == index 
243                      && strChr_[nxtidx] == b) 
244                  return (short) nxtidx; 
245              hshidx = (hshidx + HASHSTEP) % HASHSIZE; 
246          } 
247   
248          return (short) 0xFFFF; 
249      } 
250   
251      public void clearTable(int codesize) { 
252          numStrings_ = 0; 
253   
254          for (int q = 0; q < HASHSIZE; q++) 
255              strHsh_[q] = HASH_FREE; 
256   
257          int w = (1 << codesize) + RES_CODES; 
258          for (int q = 0; q < w; q++) 
259              addCharString((short) 0xFFFF, (byte) q); 
260      } 
261   
262      static public int hash(short index, byte lastbyte) { 
263          return ( 
264                  (int) ( 
265                  (short) (lastbyte << 8) ^ index 
266                  ) & 0xFFFF 
267                  ) % HASHSIZE; 
268      } 
269  } 
270   
271  class LZWCompressor { 
272      public static void compress( 
273              java.io.OutputStream os, int codesize, 
274              byte toCompress[]) throws java.io.IOException { 
275          byte c; 
276          short index; 
277          int clearcode, endofinfo, numbits, limit, errcode; 
278          short prefix = (short) 0xFFFF; 
279   
280          BitFile bitFile = new BitFile(os); 
281          LZWStringTable strings = new LZWStringTable(); 
282   
283          clearcode = 1 << codesize; 
284          endofinfo = clearcode + 1; 
285   
286          numbits = codesize + 1; 
287          limit = (1 << numbits) - 1; 
288   
289          strings.clearTable(codesize); 
290          bitFile.writeBits(clearcode, numbits); 
291   
292          for (int loop = 0; loop < toCompress.length; ++loop) { 
293              c = toCompress[loop]; 
294              if ((index = 
295                      strings.findCharString(prefix, c)) != -1) 
296                  prefix = index; 
297              else { 
298                  bitFile.writeBits(prefix, numbits); 
299                  if (strings.addCharString(prefix, c) > limit) { 
300                      if (++numbits > 12) { 
301                          bitFile.writeBits(clearcode, numbits - 1); 
302                          strings.clearTable(codesize); 
303                          numbits = codesize + 1; 
304                      } 
305                      limit = (1 << numbits) - 1; 
306                  } 
307   
308                  prefix = (short) ((short) c & 0xFF); 
309              } 
310          } 
311   
312          if (prefix != -1) { 
313              bitFile.writeBits(prefix, numbits); 
314          } 
315   
316          bitFile.writeBits(endofinfo, numbits); 
317          bitFile.flush(); 
318      } 
319  } 
320   
321  class ScreenDescriptor { 
322      public short localScreenWidth_, localScreenHeight_; 
323      private byte byte_; 
324      public byte backgroundColorIndex_, pixelAspectRatio_; 
325   
326      public ScreenDescriptor( 
327              short width, short height, int numColors) { 
328          localScreenWidth_ = width; 
329          localScreenHeight_ = height; 
330          SetGlobalColorTableSize( 
331                  (byte) (BitUtils.bitsNeeded(numColors) - 1)); 
332          SetGlobalColorTableFlag((byte) 1); 
333          SetSortFlag((byte) 0); 
334          SetColorResolution((byte) 7); 
335          backgroundColorIndex_ = 0; 
336          pixelAspectRatio_ = 0; 
337      } 
338   
339      public void write(java.io.OutputStream os) 
340              throws java.io.IOException { 
341          BitUtils.writeWord(os, localScreenWidth_); 
342          BitUtils.writeWord(os, localScreenHeight_); 
343          os.write(byte_); 
344          os.write(backgroundColorIndex_); 
345          os.write(pixelAspectRatio_); 
346      } 
347   
348      public void SetGlobalColorTableSize(byte num) { 
349          byte_ |= (num & 7); 
350      } 
351   
352      public void SetSortFlag(byte num) { 
353          byte_ |= (num & 1) << 3; 
354      } 
355   
356      public void SetColorResolution(byte num) { 
357          byte_ |= (num & 7) << 4; 
358      } 
359   
360      public void SetGlobalColorTableFlag(byte num) { 
361          byte_ |= (num & 1) << 7; 
362      } 
363  } 
364   
365  class ImageDescriptor { 
366      public byte separator_; 
367      public short leftPosition_, topPosition_, width_, height_; 
368      private byte byte_; 
369   
370      public ImageDescriptor( 
371              short width, short height, char separator) { 
372          separator_ = (byte) separator; 
373          leftPosition_ = 0; 
374          topPosition_ = 0; 
375          width_ = width; 
376          height_ = height; 
377          setLocalColorTableSize((byte) 0); 
378          setReserved((byte) 0); 
379          setSortFlag((byte) 0); 
380          setInterlaceFlag((byte) 0); 
381          setLocalColorTableFlag((byte) 0); 
382      } 
383   
384      public void toOutputStream(java.io.OutputStream os) 
385              throws java.io.IOException { 
386          os.write(separator_); 
387          BitUtils.writeWord(os, leftPosition_); 
388          BitUtils.writeWord(os, topPosition_); 
389          BitUtils.writeWord(os, width_); 
390          BitUtils.writeWord(os, height_); 
391          os.write(byte_); 
392      } 
393   
394      public void setLocalColorTableSize(byte num) { 
395          byte_ |= (num & 7); 
396      } 
397   
398      public void setReserved(byte num) { 
399          byte_ |= (num & 3) << 3; 
400      } 
401   
402      public void setSortFlag(byte num) { 
403          byte_ |= (num & 1) << 5; 
404      } 
405   
406      public void setInterlaceFlag(byte num) { 
407          byte_ |= (num & 1) << 6; 
408      } 
409   
410      public void setLocalColorTableFlag(byte num) { 
411          byte_ |= (num & 1) << 7; 
412      } 
413  } 
414   
415  class BitUtils { 
416      public static byte bitsNeeded(int n) { 
417          byte ret = 1; 
418   
419          if (n-- == 0) return 0; 
420   
421          while ((n >>= 1) != 0) 
422              ++ret; 
423   
424          return ret; 
425      } 
426   
427      public static void writeWord( 
428              java.io.OutputStream os, short w) 
429              throws java.io.IOException { 
430          os.write(w & 0xFF); 
431          os.write((w >> 8) & 0xFF); 
432      } 
433   
434      static void writeString( 
435              java.io.OutputStream os, String string) 
436              throws java.io.IOException { 
437          for (int i = 0; i < string.length(); ++i) 
438              os.write((byte) (string.charAt(i))); 
439      } 
440  } 
441