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