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

1    package ip.gif.stills; 
2     
3    import j2d.animation.GifUtils; 
4     
5    import java.awt.*; 
6    import java.awt.image.BufferedImage; 
7    import java.awt.image.DataBufferInt; 
8    import java.io.BufferedInputStream; 
9    import java.io.FileInputStream; 
10   import java.io.IOException; 
11   import java.net.URL; 
12   import java.util.ArrayList; 
13    
14   import j2d.animation.GifUtils; 
15    
16   /** 
17    * Class GifDecoder - Decodes a GIF file into one or more frames. 
18    * <br><pre> 
19    * Example: 
20    *    GifDecoder d = new GifDecoder(); 
21    *    d.read("sample.gif"); 
22    *    int n = d.getFrameCount(); 
23    *    for (int i = 0; i < n; i++) { 
24    *       BufferedImage frame = d.getFrame(i);  // frame i 
25    *       int t = d.getDelay(i);  // display duration of frame in milliseconds 
26    *       // do something with frame 
27    *    } 
28    * </pre> 
29    * No copyright asserted on the source code of this class.  May be used for 
30    * any purpose, however, refer to the Unisys LZW patent for any additional 
31    * restrictions.  Please forward any corrections to kweiner@fmsware.com. 
32    * 
33    * @author Kevin Weiner, FM Software; LZW decoder adapted from John Cristy's ImageMagick. 
34    * @version 1.01 July 2001 
35    * 
36    */ 
37    
38   public class GifDecoder { 
39    
40       /** 
41        * File read status: No errors. 
42        */ 
43       public static final int STATUS_OK = 0; 
44    
45       /** 
46        * File read status: Error decoding file (may be partially decoded) 
47        */ 
48       public static final int STATUS_FORMAT_ERROR = 1; 
49    
50       /** 
51        * File read status: Unable to open source. 
52        */ 
53       public static final int STATUS_OPEN_ERROR = 2; 
54    
55       private BufferedInputStream in; 
56       private int status; 
57    
58       private int width;            // full image width 
59       private int height;           // full image height 
60       private boolean gctFlag;      // global color table used 
61       private int gctSize;          // size of global color table 
62       private int loopCount = 1;    // iterations; 0 = repeat forever 
63    
64       private int[] gct;            // global color table 
65       private int[] lct;            // local color table 
66       private int[] act;            // active color table 
67    
68       private int bgIndex;          // background color index 
69       private int bgColor;          // background color 
70       private int lastBgColor;      // previous bg color 
71       private int pixelAspect; 
72       // pixel aspect ratio      (yes it is used!) 
73    
74       private boolean lctFlag;      // local color table flag 
75       private boolean interlace;    // interlace flag 
76       private int lctSize;          // local color table size 
77    
78       private int ix, iy, iw, ih;   // current image rectangle 
79       private Rectangle lastRect;   // last image rect 
80       private BufferedImage currentBufImg;  // current frame 
81       private BufferedImage prevBufImg;  // previous frame 
82    
83       private byte[] currentDataBlock = new byte[256];  // current data block 
84       private int blockSize = 0;    // block size 
85    
86       // last graphic control extension info 
87       private int dispose = 0;   // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev 
88       private int lastDispose = 0; 
89       private boolean transparency = false;   // use transparent color 
90       private int delay = 0;        // delay in milliseconds 
91       private int transIndex;       // transparent color index 
92    
93       private static final int MaxStackSize = 4096;   // max decoder pixel stack size 
94    
95       // LZW decoder working arrays 
96       private short[] prefix; 
97       private byte[] suffix; 
98       private byte[] pixelStack; 
99       private byte[] pixels; 
100   
101      private ArrayList frames;     // frames read from current file 
102      private int frameCount; 
103   
104      public static class GifFrame { 
105          public GifFrame(BufferedImage bufferedImage, int delay) { 
106              this.bufferedImage = bufferedImage; 
107              this.delay = delay; 
108          } 
109   
110          public BufferedImage bufferedImage; 
111          public int delay; 
112      } 
113   
114   
115      /** 
116       * Gets display duration for specified frame. 
117       * 
118       * @param n int index of frame 
119       * @return delay in milliseconds 
120       */ 
121      public int getDelay(int n) { 
122          // 
123          delay = -1; 
124          if ((n >= 0) && (n < frameCount)) 
125              delay = ((GifFrame) frames.get(n)).delay; 
126          return delay; 
127      } 
128   
129   
130      /** 
131       * Gets the image contents of frame n. 
132       * 
133       * @return BufferedImage representation of frame, or null if n is invalid. 
134       */ 
135      public BufferedImage getFrame(int n) { 
136          BufferedImage bi = null; 
137          if ((n >= 0) && (n < frameCount)) 
138              bi = ((GifFrame) frames.get(n)).bufferedImage; 
139          return bi; 
140      } 
141   
142   
143      /** 
144       * Gets the number of frames read from file. 
145       * @return frame count 
146       */ 
147      public int getFrameCount() { 
148          return frameCount; 
149      } 
150   
151   
152      /** 
153       * Gets the first (or only) image read. 
154       * 
155       * @return BufferedImage containing first frame, or null if none. 
156       */ 
157      public BufferedImage getCurrentBufImg() { 
158          return getFrame(0); 
159      } 
160   
161   
162      /** 
163       * Gets the "Netscape" iteration count, if any. 
164       * A count of 0 means repeat indefinitiely. 
165       * 
166       * @return iteration count if one was specified, else 1. 
167       */ 
168      public int getLoopCount() { 
169          return loopCount; 
170      } 
171   
172      /** 
173       * 
174       * @param is  An inputStream with gif animation 
175       * @return    status code (0=no error) 
176       */ 
177      public int read(BufferedInputStream is) { 
178          init(); 
179          if (is != null) { 
180              in = is; 
181              readHeader(); 
182              if (!err()) { 
183                  readContents(); 
184                  if (frameCount < 0) 
185                      status = STATUS_FORMAT_ERROR; 
186              } 
187          } else { 
188              status = STATUS_OPEN_ERROR; 
189          } 
190          try { 
191              is.close(); 
192          } catch (IOException e) { 
193          } 
194          return status; 
195      } 
196   
197   
198      /** 
199       * Reads GIF file from specified source (file or URL string) 
200       * 
201       * @param name String containing source 
202       * @return read status code (0 = no errors) 
203       */ 
204      public int read(String name) { 
205          status = STATUS_OK; 
206          try { 
207              name = name.trim(); 
208              if (name.indexOf("://") > 0) { 
209                  URL url = new URL(name); 
210                  in = new BufferedInputStream(url.openStream()); 
211              } else { 
212                  in = new BufferedInputStream(new FileInputStream(name)); 
213              } 
214              status = read(in); 
215          } catch (IOException e) { 
216              status = STATUS_OPEN_ERROR; 
217          } 
218   
219          return status; 
220      } 
221   
222   
223      /** 
224       * Decodes LZW image data into pixel array. 
225       * Adapted from John Cristy's ImageMagick. 
226       */ 
227      protected void decodeImageData() { 
228          int NullCode = -1; 
229          int npix = iw * ih; 
230          int available, clear, code_mask, code_size, end_of_information, in_code, old_code, 
231                  bits, code, count, i, datum, data_size, first, top, bi, pi; 
232   
233          if ((pixels == null) || (pixels.length < npix)) 
234              pixels = new byte[npix];    // allocate new pixel array 
235   
236          if (prefix == null) 
237              prefix = new short[MaxStackSize]; 
238          if (suffix == null) 
239              suffix = new byte[MaxStackSize]; 
240          if (pixelStack == null) 
241              pixelStack = new byte[MaxStackSize + 1]; 
242   
243   
244          //  Initialize GIF data stream decoder. 
245   
246          data_size = read(); 
247          clear = 1 << data_size; 
248          end_of_information = clear + 1; 
249          available = clear + 2; 
250          old_code = NullCode; 
251          code_size = data_size + 1; 
252          code_mask = (1 << code_size) - 1; 
253          for (code = 0; code < clear; code++) { 
254              prefix[code] = 0; 
255              suffix[code] = (byte) code; 
256          } 
257   
258          //  Decode GIF pixel stream. 
259   
260          datum = bits = count = first = top = pi = bi = 0; 
261   
262          for (i = 0; i < npix;) { 
263              if (top == 0) { 
264                  if (bits < code_size) { 
265                      //  Load bytes until there are enough bits for a code. 
266                      if (count == 0) { 
267                          // Read a new data block. 
268                          count = readBlock(); 
269                          if (count <= 0) 
270                              break; 
271                          bi = 0; 
272                      } 
273                      datum += (((int) currentDataBlock[bi]) & 0xff) << bits; 
274                      bits += 8; 
275                      bi++; 
276                      count--; 
277                      continue; 
278                  } 
279   
280                  //  Get the next code. 
281   
282                  code = datum & code_mask; 
283                  datum >>= code_size; 
284                  bits -= code_size; 
285   
286                  //  Interpret the code 
287   
288                  if ((code > available) || (code == end_of_information)) 
289                      break; 
290                  if (code == clear) { 
291                      //  Reset decoder. 
292                      code_size = data_size + 1; 
293                      code_mask = (1 << code_size) - 1; 
294                      available = clear + 2; 
295                      old_code = NullCode; 
296                      continue; 
297                  } 
298                  if (old_code == NullCode) { 
299                      pixelStack[top++] = suffix[code]; 
300                      old_code = code; 
301                      first = code; 
302                      continue; 
303                  } 
304                  in_code = code; 
305                  if (code == available) { 
306                      pixelStack[top++] = (byte) first; 
307                      code = old_code; 
308                  } 
309                  while (code > clear) { 
310                      pixelStack[top++] = suffix[code]; 
311                      code = prefix[code]; 
312                  } 
313                  first = ((int) suffix[code]) & 0xff; 
314   
315                  //  Add a new string to the string table, 
316   
317                  if (available >= MaxStackSize) 
318                      break; 
319                  pixelStack[top++] = (byte) first; 
320                  prefix[available] = (short) old_code; 
321                  suffix[available] = (byte) first; 
322                  available++; 
323                  if (((available & code_mask) == 0) && (available < MaxStackSize)) { 
324                      code_size++; 
325                      code_mask += available; 
326                  } 
327                  old_code = in_code; 
328              } 
329   
330              //  Pop a pixel off the pixel stack. 
331   
332              top--; 
333              pixels[pi++] = pixelStack[top]; 
334              i++; 
335          } 
336   
337          for (i = pi; i < npix; i++) 
338              pixels[i] = 0;  // clear missing pixels 
339   
340      } 
341   
342   
343      /** 
344       * Returns true if an error was encountered during reading/decoding 
345       */ 
346      protected boolean err() { 
347          return status != STATUS_OK; 
348      } 
349   
350   
351      /** 
352       * Initializes or re-initializes reader 
353       */ 
354      protected void init() { 
355          status = STATUS_OK; 
356          frameCount = 0; 
357          frames = new ArrayList(); 
358          gct = null; 
359          lct = null; 
360      } 
361   
362   
363      /** 
364       * Reads a single byte from the input stream. 
365       */ 
366      protected int read() { 
367          int curByte = 0; 
368          try { 
369              curByte = in.read(); 
370          } catch (IOException e) { 
371              status = STATUS_FORMAT_ERROR; 
372          } 
373          return curByte; 
374      } 
375   
376   
377      /** 
378       * Reads next variable length block from input. 
379       * 
380       * @return number of bytes stored in "buffer" 
381       */ 
382      protected int readBlock() { 
383          blockSize = read(); 
384          int n = 0; 
385          if (blockSize > 0) { 
386              try { 
387                  int count = 0; 
388                  while (n < blockSize) { 
389                      count = in.read(currentDataBlock, n, blockSize - n); 
390                      if (count == -1) 
391                          break; 
392                      n += count; 
393                  } 
394              } catch (IOException e) { 
395              } 
396   
397              if (n < blockSize) 
398                  status = STATUS_FORMAT_ERROR; 
399          } 
400          return n; 
401      } 
402   
403   
404      /** 
405       * Reads color table as 256 RGB integer values 
406       * 
407       * @param ncolors int number of colors to read 
408       * @return int array containing 256 colors (packed ARGB with full alpha) 
409       */ 
410      protected int[] readColorTable(int ncolors) { 
411          int nbytes = 3 * ncolors; 
412          int[] tab = null; 
413          byte[] c = new byte[nbytes]; 
414          int n = 0; 
415          try { 
416              n = in.read(c); 
417          } catch (IOException e) { 
418          } 
419          if (n < nbytes) 
420              status = STATUS_FORMAT_ERROR; 
421          else { 
422              tab = new int[256];  // max size to avoid bounds checks 
423              int i = 0; 
424              int j = 0; 
425              while (i < ncolors) { 
426                  int r = ((int) c[j++]) & 0xff; 
427                  int g = ((int) c[j++]) & 0xff; 
428                  int b = ((int) c[j++]) & 0xff; 
429                  tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b; 
430              } 
431          } 
432          return tab; 
433      } 
434   
435   
436      /** 
437       * Main file parser.  Reads GIF content blocks. 
438       */ 
439      protected void readContents() { 
440          // read GIF file content blocks 
441          boolean done = false; 
442          while (!(done || err())) { 
443              int code = read(); 
444              switch (code) { 
445   
446                  case 0x2C:    // image separator 
447                      readImage(); 
448                      break; 
449   
450                  case 0x21:    // extension 
451                      code = read(); 
452                      switch (code) { 
453   
454                          case 0xf9:    // graphics control extension 
455                              readGraphicControlExt(); 
456                              break; 
457   
458                          case 0xff:    // application extension 
459                              readBlock(); 
460                              String app = ""; 
461                              for (int i = 0; i < 11; i++) 
462                                  app += (char) currentDataBlock[i]; 
463                              if (app.equals("NETSCAPE2.0")) 
464                                  readNetscapeExt(); 
465                              else 
466                                  skip();        // don't care 
467                              break; 
468   
469                          default:    // uninteresting extension 
470                              skip(); 
471                      } 
472                      break; 
473   
474                  case 0x3b:        // terminator 
475                      done = true; 
476                      break; 
477   
478                  default: 
479                      status = STATUS_FORMAT_ERROR; 
480              } 
481          } 
482      } 
483   
484   
485      /** 
486       * Reads Graphics Control Extension values 
487       */ 
488      protected void readGraphicControlExt() { 
489          read();    // block size 
490          int packed = read();   // packed fields 
491          dispose = (packed & 0x1c) >> 2;   // disposal method 
492          if (dispose == 0) 
493              dispose = 1;   // elect to keep old image if discretionary 
494          transparency = (packed & 1) != 0; 
495          delay = readShort() * 10;   // delay in milliseconds 
496          transIndex = read();        // transparent color index 
497          read();                     // block terminator 
498      } 
499   
500   
501      /** 
502       * Reads GIF file header information. 
503       */ 
504      protected void readHeader() { 
505          String id = ""; 
506          for (int i = 0; i < 6; i++) 
507              id += (char) read(); 
508          if (!id.startsWith("GIF")) { 
509              status = STATUS_FORMAT_ERROR; 
510              return; 
511          } 
512   
513          readLSD(); 
514          if (gctFlag && !err()) { 
515              gct = readColorTable(gctSize); 
516              bgColor = gct[bgIndex]; 
517          } 
518      } 
519   
520   
521      /** 
522       * Reads next frame image 
523       */ 
524      protected void readImage() { 
525          ix = readShort();    // (sub)image position & size 
526          iy = readShort(); 
527          iw = readShort(); 
528          ih = readShort(); 
529   
530          int packed = read(); 
531          lctFlag = (packed & 0x80) != 0;     // 1 - local color table flag 
532          interlace = (packed & 0x40) != 0;   // 2 - interlace flag 
533          // 3 - sort flag 
534          // 4-5 - reserved 
535          lctSize = 2 << (packed & 7);        // 6-8 - local color table size 
536   
537          if (lctFlag) { 
538              lct = readColorTable(lctSize);   // read table 
539              act = lct;    // make local table active 
540          } else { 
541              act = gct;    // make global table active 
542              if (bgIndex == transIndex) 
543                  bgColor = 0; 
544          } 
545          int save = 0; 
546          if (transparency) { 
547              save = act[transIndex]; 
548              act[transIndex] = 0;    // set transparent color if specified 
549          } 
550   
551          if (act == null) 
552              status = STATUS_FORMAT_ERROR;   // no color table defined 
553   
554          if (err()) return; 
555   
556          decodeImageData();   // decode pixel data 
557          skip(); 
558   
559          if (err()) return; 
560   
561          frameCount++; 
562   
563          // create new image to receive frame data 
564          currentBufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); 
565   
566          setPixels();   // transfer pixel data to image 
567   
568          frames.add(new GifFrame(currentBufImg, delay));   // add image to frame list 
569   
570          if (transparency) 
571              act[transIndex] = save; 
572          resetFrame(); 
573   
574      } 
575   
576   
577      /** 
578       * Reads Logical Screen Descriptor 
579       */ 
580      protected void readLSD() { 
581   
582          // logical screen size 
583          width = readShort(); 
584          height = readShort(); 
585   
586          // packed fields 
587          int packed = read(); 
588          gctFlag = (packed & 0x80) != 0;      // 1   : global color table flag 
589          // 2-4 : color resolution 
590          // 5   : gct sort flag 
591          gctSize = 2 << (packed & 7);      // 6-8 : gct size 
592   
593          bgIndex = read();        // background color index 
594          pixelAspect = read();    // pixel aspect ratio 
595      } 
596   
597   
598      /** 
599       * Reads Netscape extenstion to obtain iteration count 
600       */ 
601      protected void readNetscapeExt() { 
602          do { 
603              readBlock(); 
604              if (currentDataBlock[0] == 1) { 
605                  // loop count sub-block 
606                  int b1 = ((int) currentDataBlock[1]) & 0xff; 
607                  int b2 = ((int) currentDataBlock[2]) & 0xff; 
608                  loopCount = (b2 << 8) | b1; 
609              } 
610          } while ((blockSize > 0) && !err()); 
611      } 
612   
613   
614      /** 
615       * Reads next 16-bit value, LSB first 
616       */ 
617      protected int readShort() { 
618          // read 16-bit value, LSB first 
619          return read() | (read() << 8); 
620      } 
621   
622   
623      /** 
624       * Resets frame state for reading next image. 
625       */ 
626      protected void resetFrame() { 
627          lastDispose = dispose; 
628          lastRect = new Rectangle(ix, iy, iw, ih); 
629          prevBufImg = currentBufImg; 
630          lastBgColor = bgColor; 
631          // int dispose = 0; 
632          //boolean transparency = false; 
633          //int delay = 0; 
634          lct = null; 
635      } 
636   
637   
638      /** 
639       * Creates new frame image from current data (and previous 
640       * frames as specified by their disposition codes). 
641       */ 
642      protected void setPixels() { 
643          // expose destination image's pixels as int array 
644          int[] dest = ((DataBufferInt) 
645                  currentBufImg.getRaster().getDataBuffer()).getData(); 
646   
647          // fill in starting image contents based on last image's dispose code 
648          if (lastDispose > 0) { 
649              if (lastDispose == 3) { 
650                  // use image before last 
651                  int n = frameCount - 2; 
652                  if (n > 0) 
653                      prevBufImg = getFrame(n - 1); 
654                  else 
655                      prevBufImg = null; 
656              } 
657   
658              if (prevBufImg != null) { 
659                  int[] prev = ((DataBufferInt) prevBufImg.getRaster().getDataBuffer()).getData(); 
660                  System.arraycopy(prev, 0, dest, 0, width * height);  // copy pixels 
661   
662                  if (lastDispose == 2) { 
663                      // fill last image rect area with background color 
664                      Graphics2D g = currentBufImg.createGraphics(); 
665                      Color c = null; 
666                      if (transparency) 
667                          c = new Color(0, 0, 0, 0);       // assume background is transparent 
668                      else 
669                          c = new Color(lastBgColor);   // use given background color 
670                      g.setColor(c); 
671                      g.setComposite(AlphaComposite.Src);      // replace area 
672                      g.fill(lastRect); 
673                      g.dispose(); 
674                  } 
675              } 
676          } 
677   
678          // copy each source line to the appropriate place in the destination 
679          int pass = 1; 
680          int inc = 8; 
681          int iline = 0; 
682          for (int i = 0; i < ih; i++) { 
683              int line = i; 
684              if (interlace) { 
685                  if (iline >= ih) { 
686                      pass++; 
687                      switch (pass) { 
688                          case 2: 
689                              iline = 4; 
690                              break; 
691                          case 3: 
692                              iline = 2; 
693                              inc = 4; 
694                              break; 
695                          case 4: 
696                              iline = 1; 
697                              inc = 2; 
698                      } 
699                  } 
700                  line = iline; 
701                  iline += inc; 
702              } 
703              line += iy; 
704              if (line < height) { 
705                  int k = line * width; 
706                  int dx = k + ix;          // start of line in dest 
707                  int dlim = dx + iw;       // end of dest line 
708                  if ((k + width) < dlim) 
709                      dlim = k + width;      // past dest edge 
710                  int sx = i * iw;          // start of line in source 
711                  while (dx < dlim) { 
712                      // map color and insert in destination 
713                      int index = ((int) pixels[sx++]) & 0xff; 
714                      int c = act[index]; 
715                      if (c != 0) { 
716                          dest[dx] = c; 
717                      } 
718                      dx++; 
719                  } 
720              } 
721          } 
722      } 
723   
724   
725      /** 
726       * Skips variable length blocks up to and including 
727       * next zero length block. 
728       */ 
729      protected void skip() { 
730          do { 
731              readBlock(); 
732          } while ((blockSize > 0) && !err()); 
733      } 
734   
735      public static void main(String args[]) { 
736          GifUtils.getGifs(); 
737   
738      } 
739  } 
740