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