/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