001: /*
002: * @(#)GifImageDecoder.java 1.55 06/10/24
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: /*-
028: * Reads GIF images from an InputStream and reports the
029: * image data to an InputStreamImageSource object.
030: */
031: package sun.awt.image;
032:
033: import java.util.Vector;
034: import java.util.Hashtable;
035: import java.io.InputStream;
036: import java.io.IOException;
037: import java.awt.image.*;
038:
039: /**
040: * Gif Image converter
041: *
042: * @version 1.41 01/05/01
043: * @author Arthur van Hoff
044: * @author Jim Graham
045: */
046: public class GifImageDecoder extends ImageDecoder {
047: private static final boolean verbose = false;
048:
049: private static final int IMAGESEP = 0x2c;
050: private static final int EXBLOCK = 0x21;
051: private static final int EX_GRAPHICS_CONTROL = 0xf9;
052: private static final int EX_COMMENT = 0xfe;
053: private static final int EX_APPLICATION = 0xff;
054: private static final int TERMINATOR = 0x3b;
055: private static final int TRANSPARENCYMASK = 0x01;
056: private static final int INTERLACEMASK = 0x40;
057: private static final int COLORMAPMASK = 0x80;
058:
059: int num_global_colors;
060: byte[] global_colormap;
061: int trans_pixel = -1;
062: IndexColorModel global_model;
063:
064: Hashtable props = new Hashtable();
065:
066: byte[] saved_image;
067: IndexColorModel saved_model;
068:
069: int global_width;
070: int global_height;
071: int global_bgpixel;
072:
073: GifFrame curframe;
074:
075: public GifImageDecoder(InputStreamImageSource src, InputStream is) {
076: super (src, is);
077: }
078:
079: /**
080: * An error has occurred. Throw an exception.
081: */
082: private static void error(String s1) throws ImageFormatException {
083: throw new ImageFormatException(s1);
084: }
085:
086: /**
087: * Read a number of bytes into a buffer.
088: * @return number of bytes that were not read due to EOF or error
089: */
090: private int readBytes(byte buf[], int off, int len) {
091: while (len > 0) {
092: try {
093: int n = input.read(buf, off, len);
094: if (n < 0) {
095: break;
096: }
097: off += n;
098: len -= n;
099: } catch (IOException e) {
100: break;
101: }
102: }
103: return len;
104: }
105:
106: private static final int ExtractByte(byte buf[], int off) {
107: return (buf[off] & 0xFF);
108: }
109:
110: private static final int ExtractWord(byte buf[], int off) {
111: return (buf[off] & 0xFF) | ((buf[off + 1] & 0xFF) << 8);
112: }
113:
114: /**
115: * produce an image from the stream.
116: */
117: public void produceImage() throws IOException, ImageFormatException {
118: try {
119: readHeader();
120:
121: int totalframes = 0;
122: int frameno = 0;
123: int nloops = -1;
124: int disposal_method = 0;
125: int delay = -1;
126: boolean loopsRead = false;
127: boolean isAnimation = false;
128:
129: while (!aborted) {
130: int code;
131:
132: if (!ImageProductionMonitor.allowsProduction()) {
133: abort();
134: break;
135: }
136:
137: switch (code = input.read()) {
138: case EXBLOCK:
139: switch (code = input.read()) {
140: case EX_GRAPHICS_CONTROL: {
141: byte buf[] = new byte[6];
142: if (readBytes(buf, 0, 6) != 0) {
143: return;//error("corrupt GIF file");
144: }
145: if ((buf[0] != 4) || (buf[5] != 0)) {
146: return;//error("corrupt GIF file (GCE size)");
147: }
148: // Get the index of the transparent color
149: delay = ExtractWord(buf, 2) * 10;
150: if (delay > 0 && !isAnimation) {
151: isAnimation = true;
152: ImageFetcher.startingAnimation();
153: }
154: disposal_method = (buf[1] >> 2) & 7;
155: if ((buf[1] & TRANSPARENCYMASK) != 0) {
156: trans_pixel = ExtractByte(buf, 4);
157: } else {
158: trans_pixel = -1;
159: }
160: break;
161: }
162:
163: case EX_COMMENT:
164: case EX_APPLICATION:
165: default:
166: boolean loop_tag = false;
167: String comment = "";
168: while (true) {
169: int n = input.read();
170: if (n <= 0) {
171: break;
172: }
173: byte buf[] = new byte[n];
174: if (readBytes(buf, 0, n) != 0) {
175: return;//error("corrupt GIF file");
176: }
177: if (code == EX_COMMENT) {
178: comment += new String(buf);
179: } else if (code == EX_APPLICATION) {
180: if (loop_tag) {
181: if (n == 3 && buf[0] == 1) {
182: if (loopsRead) {
183: ExtractWord(buf, 1);
184: } else {
185: nloops = ExtractWord(buf, 1);
186: loopsRead = true;
187: }
188: } else {
189: loop_tag = false;
190: }
191: }
192: if ("NETSCAPE2.0"
193: .equals(new String(buf))) {
194: loop_tag = true;
195: }
196: }
197: }
198: if (code == EX_COMMENT) {
199: props.put("comment", comment);
200: }
201: if (loop_tag && !isAnimation) {
202: isAnimation = true;
203: ImageFetcher.startingAnimation();
204: }
205: break;
206:
207: case -1:
208: return; //error("corrupt GIF file");
209: }
210: break;
211:
212: case IMAGESEP:
213: if (!isAnimation) {
214: input.mark(0); // we don't need the mark buffer
215: }
216: try {
217: if (!readImage(totalframes == 0,
218: disposal_method, delay)) {
219: return;
220: }
221: } catch (Exception e) {
222: if (verbose) {
223: e.printStackTrace();
224: }
225: return;
226: }
227: frameno++;
228: totalframes++;
229: break;
230:
231: default:
232: case -1:
233: if (verbose) {
234: if (code == -1) {
235: System.err
236: .println("Premature EOF in GIF file,"
237: + " frame " + frameno);
238: } else {
239: System.err
240: .println("corrupt GIF file (parse) ["
241: + code + "].");
242: }
243: }
244: if (frameno == 0) {
245: return;
246: }
247: // NOBREAK
248:
249: case TERMINATOR:
250: if (nloops == 0 || nloops-- >= 0) {
251: try {
252: if (curframe != null) {
253: curframe.dispose();
254: curframe = null;
255: }
256: input.reset();
257: saved_image = null;
258: saved_model = null;
259: frameno = 0;
260: break;
261: } catch (IOException e) {
262: return; // Unable to reset input buffer
263: }
264: }
265: if (verbose && frameno != 1) {
266: System.out.println("processing GIF terminator,"
267: + " frames: " + frameno + " total: "
268: + totalframes);
269: }
270: imageComplete(ImageConsumer.STATICIMAGEDONE, true);
271: return;
272: }
273: }
274: } finally {
275: close();
276: }
277: }
278:
279: /**
280: * Read Image header
281: */
282: private void readHeader() throws IOException, ImageFormatException {
283: // Create a buffer
284: byte buf[] = new byte[13];
285:
286: // Read the header
287: if (readBytes(buf, 0, 13) != 0) {
288: throw new IOException();
289: }
290:
291: // Check header
292: if ((buf[0] != 'G') || (buf[1] != 'I') || (buf[2] != 'F')) {
293: error("not a GIF file.");
294: }
295:
296: // Global width&height
297: global_width = ExtractWord(buf, 6);
298: global_height = ExtractWord(buf, 8);
299:
300: // colormap info
301: int ch = ExtractByte(buf, 10);
302: if ((ch & COLORMAPMASK) == 0) {
303: error("no global colormap in GIF file.");
304: }
305: num_global_colors = 1 << ((ch & 0x7) + 1);
306:
307: global_bgpixel = ExtractByte(buf, 11);
308:
309: if (buf[12] != 0) {
310: props.put("aspectratio", ""
311: + ((ExtractByte(buf, 12) + 15) / 64.0));
312: }
313:
314: // Read colors
315: global_colormap = new byte[num_global_colors * 3];
316: if (readBytes(global_colormap, 0, num_global_colors * 3) != 0) {
317: throw new IOException();
318: }
319:
320: input.mark(1000000); // set this mark in case this is an animated GIF
321: }
322:
323: /**
324: * The ImageConsumer hints flag for a non-interlaced GIF image.
325: */
326: private static final int normalflags = ImageConsumer.TOPDOWNLEFTRIGHT
327: | ImageConsumer.COMPLETESCANLINES
328: | ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME;
329:
330: /**
331: * The ImageConsumer hints flag for an interlaced GIF image.
332: */
333: private static final int interlaceflags = ImageConsumer.RANDOMPIXELORDER
334: | ImageConsumer.COMPLETESCANLINES
335: | ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME;
336:
337: private short prefix[] = new short[4096];
338: private byte suffix[] = new byte[4096];
339: private byte outCode[] = new byte[4097];
340:
341: //private native boolean parseImage(int x, int y, int width, int height,
342: // boolean interlace, int initCodeSize,
343: // byte block[], byte rasline[],
344: // IndexColorModel model);
345:
346: /*
347: * This is converted from the native version. The java version is
348: * much faster when we have JIT.
349: */
350: private boolean parseImage(int relx, int rely, int width,
351: int height, boolean interlace, int initCodeSize,
352: byte block[], byte rasline[], IndexColorModel model) {
353: int OUTCODELENGTH = 4097;
354:
355: int clearCode = (1 << initCodeSize);
356: int eofCode = clearCode + 1;
357: int bitMask;
358: int curCode;
359: int outCount = OUTCODELENGTH;
360:
361: /* Variables used to form reading data */
362: boolean blockEnd = false;
363: int remain = 0;
364: int byteoff = 0;
365: int accumbits = 0;
366: int accumdata = 0;
367:
368: /* Variables used to decompress the data */
369: int codeSize = initCodeSize + 1;
370: int maxCode = 1 << codeSize;
371: int codeMask = maxCode - 1;
372: int freeCode = clearCode + 2;
373: int code = 0;
374: int oldCode = 0;
375: char prevChar = 0;
376:
377: //int blockLength = 0;
378: int blockLength = 0;
379:
380: /* Variables used for writing pixels */
381: int x = width;
382: int y = 0;
383: int off = 0;
384: int passinc = interlace ? 8 : 1;
385: int passht = passinc;
386: int len;
387:
388: bitMask = model.getMapSize() - 1;
389:
390: /* Read codes until the eofCode is encountered */
391: for (;;) {
392: if (accumbits < codeSize) {
393: boolean lastByte = false;
394: /* fill the buffer if needed */
395: while (remain < 2) {
396: if (blockEnd) {
397: /* Sometimes we have one last byte to process... */
398: if (remain == 1 && accumbits + 8 >= codeSize) {
399: break;
400: }
401:
402: if (off > 0) {
403: // sendPixels(relx, rely+y, width, passht, rasline, model);
404: sendPixels(relx, rely + y, width, 1,
405: rasline, model);
406: }
407: /* quietly accept truncated GIF images */
408: return false;
409: }
410: /* move remaining bytes to the beginning of the buffer */
411: block[0] = block[byteoff];
412: byteoff = 0;
413:
414: /* fill the block */
415: len = readBytes(block, remain, blockLength + 1);
416:
417: remain += blockLength;
418: if (len > 0) {
419: remain -= (len - 1);
420: blockLength = 0;
421: } else {
422: blockLength = (block[remain] & 0xff);
423: }
424: if (blockLength == 0) {
425: blockEnd = true;
426: }
427: }
428:
429: /* 2 bytes at a time saves checking for accumbits < codeSize.
430: * We know we'll get enough and also that we can't overflow
431: * since codeSize <= 12.
432: */
433: if (!lastByte) {
434: remain -= 2;
435: accumdata += (block[byteoff++] & 0xff) << accumbits;
436: accumbits += 8;
437: accumdata += (block[byteoff++] & 0xff) << accumbits;
438: accumbits += 8;
439: } else {
440: remain--;
441: accumdata += (block[byteoff++] & 0xff) << accumbits;
442: accumbits += 8;
443: }
444: }
445:
446: /* Compute the code */
447: code = accumdata & codeMask;
448: accumdata >>= codeSize;
449: accumbits -= codeSize;
450:
451: /*
452: * Interpret the code
453: */
454: if (code == clearCode) {
455: /* Clear code sets everything back to its initial value, then
456: * reads the immediately subsequent code as uncompressed data.
457: */
458:
459: /* Note that freeCode is one less than it is supposed to be,
460: * this is because it will be incremented next time round the
461: * loop
462: */
463: freeCode = clearCode + 1;
464: codeSize = initCodeSize + 1;
465: maxCode = 1 << codeSize;
466: codeMask = maxCode - 1;
467:
468: /* Continue if we've NOT reached the end, some Gif images
469: * contain bogus codes after the last clear code.
470: */
471: if (y < height) {
472: continue;
473: }
474:
475: /* pretend we've reached the end of the data */
476: code = eofCode;
477: }
478:
479: if (code == eofCode) {
480: /* make sure we read the whole block of pixels. */
481: while (!blockEnd) {
482: if (readBytes(block, 0, blockLength + 1) != 0) {
483: /* quietly accept truncated GIF images */
484: return false;
485: }
486: blockLength = block[blockLength];
487: blockEnd = (blockLength == 0);
488: }
489: return true;
490: }
491:
492: /* It must be data: save code in CurCode */
493: curCode = code;
494: /* Whenever it gets here outCount is always equal to
495: OUTCODELENGTH, so no need to reset outCount. */
496: //outCount = OUTCODELENGTH;
497: /* If greater or equal to freeCode, not in the hash table
498: * yet; repeat the last character decoded
499: */
500: if (curCode >= freeCode) {
501: if (curCode > freeCode) {
502: /*
503: * if we get a code too far outside our range, it
504: * could case the parser to start traversing parts
505: * of our data structure that are out of range...
506: */
507: /*In native version: goto flushit;*/
508: while (!blockEnd) {
509: if (readBytes(block, 0, blockLength + 1) != 0) {
510: /* quietly accept truncated GIF images */
511: return false;
512: }
513: blockLength = block[blockLength];
514: blockEnd = (blockLength == 0);
515: }
516: return true;
517: }
518: curCode = oldCode;
519: outCode[--outCount] = (byte) prevChar;
520: }
521:
522: /* Unless this code is raw data, pursue the chain pointed
523: * to by curCode through the hash table to its end; each
524: * code in the chain puts its associated output code on
525: * the output queue.
526: */
527: while (curCode > bitMask) {
528: outCode[--outCount] = suffix[curCode];
529: if (outCount == 0) {
530: /*
531: * In theory this should never happen since our
532: * prefix and suffix arrays are monotonically
533: * decreasing and so outCode will only be filled
534: * as much as those arrays, but I don't want to
535: * take that chance and the test is probably
536: * cheap compared to the read and write operations.
537: * If we ever do overflow the array, we will just
538: * flush the rest of the data and quietly accept
539: * the GIF as truncated here.
540: */
541: //In native version: goto flushit;
542: while (!blockEnd) {
543: if (readBytes(block, 0, blockLength + 1) != 0) {
544: /* quietly accept truncated GIF images */
545: return false;
546: }
547: blockLength = block[blockLength];
548: blockEnd = (blockLength == 0);
549: }
550: return true;
551: }
552: curCode = prefix[curCode];
553: }
554:
555: /* The last code in the chain is treated as raw data. */
556: prevChar = (char) curCode;
557: outCode[--outCount] = (byte) prevChar;
558:
559: /* Now we put the data out to the Output routine. It's
560: * been stacked LIFO, so deal with it that way...
561: */
562: len = OUTCODELENGTH - outCount; /* This is why I commented out
563: the code that resets outCount. */
564: while (--len >= 0) {
565: rasline[off++] = outCode[outCount++];
566:
567: /* Update the X-coordinate, and if it overflows, update the
568: * Y-coordinate
569: */
570: if (--x == 0) {
571: int count;
572:
573: /* If a non-interlaced picture, just increment y to the next
574: * scan line. If it's interlaced, deal with the interlace as
575: * described in the GIF spec. Put the decoded scan line out
576: * to the screen if we haven't gone past the bottom of it
577: */
578: // count = sendPixels(relx, rely+y, width, passht, rasline, model);
579: count = sendPixels(relx, rely + y, width, 1,
580: rasline, model);
581:
582: if (count <= 0) {
583: /* Nobody is listening any more. */
584: return false;
585: }
586:
587: x = width;
588: off = 0;
589: /* pass inc ht ystart */
590: /* 0 8 8 0 */
591: /* 1 8 4 4 */
592: /* 2 4 2 2 */
593: /* 3 2 1 1 */
594: y += passinc;
595: while (y >= height) {
596: passinc = passht;
597: passht >>= 1;
598: y = passht;
599: if (passht == 0) {
600: //In native version: goto flushit;
601: while (!blockEnd) {
602: if (readBytes(block, 0, blockLength + 1) != 0) {
603: /* quietly accept truncated GIF images */
604: return false;
605: }
606: blockLength = block[blockLength] & 0xff;
607: blockEnd = (blockLength == 0);
608: }
609: return true;
610: }
611: }
612: }
613: }
614:
615: /* Build the hash table on-the-fly. No table is stored in the file. */
616: prefix[freeCode] = (short) oldCode;
617: suffix[freeCode] = (byte) prevChar;
618: oldCode = code;
619: /* Point to the next slot in the table. If we exceed the
620: * maxCode, increment the code size unless
621: * it's already 12. If it is, do nothing: the next code
622: * decompressed better be CLEAR
623: */
624: if (++freeCode >= maxCode) {
625: if (codeSize < 12) {
626: codeSize++;
627: maxCode <<= 1;
628: codeMask = maxCode - 1;
629: } else {
630: /* Just in case */
631: freeCode = maxCode - 1;
632: }
633: }
634: }
635: }
636:
637: private int sendPixels(int x, int y, int width, int height,
638: byte rasline[], ColorModel model) {
639: int rasbeg, rasend, x2;
640: if (y < 0) {
641: height += y;
642: y = 0;
643: }
644: if (y + height > global_height) {
645: height = global_height - y;
646: }
647: if (height <= 0) {
648: return 1;
649: }
650: // rasline[0] == pixel at coordinate (x,y)
651: // rasline[width] == pixel at coordinate (x+width, y)
652: if (x < 0) {
653: rasbeg = -x;
654: width += x; // same as (width -= rasbeg)
655: x2 = 0; // same as (x2 = x + rasbeg)
656: } else {
657: rasbeg = 0;
658: // width -= 0; // same as (width -= rasbeg)
659: x2 = x; // same as (x2 = x + rasbeg)
660: }
661: // rasline[rasbeg] == pixel at coordinate (x2,y)
662: // rasline[width] == pixel at coordinate (x+width, y)
663: // rasline[rasbeg + width] == pixel at coordinate (x2+width, y)
664: if (x2 + width > global_width) {
665: width = global_width - x2;
666: }
667: if (width <= 0) {
668: return 1;
669: }
670: rasend = rasbeg + width;
671: // rasline[rasbeg] == pixel at coordinate (x2,y)
672: // rasline[rasend] == pixel at coordinate (x2+width, y)
673: int off = y * global_width + x2;
674: boolean save = (curframe.disposal_method == GifFrame.DISPOSAL_SAVE);
675: if (trans_pixel >= 0 && !curframe.initialframe) {
676: if (saved_image != null && saved_model == model) {
677: for (int i = rasbeg; i < rasend; i++, off++) {
678: byte pixel = rasline[i];
679: if ((pixel & 0xff) == trans_pixel) {
680: rasline[i] = saved_image[off];
681: } else if (save) {
682: saved_image[off] = pixel;
683: }
684: }
685: } else {
686: // We have to do this the hard way - only transmit
687: // the non-transparent sections of the line...
688: int runstart = -1;
689: int count = 1;
690: for (int i = rasbeg; i < rasend; i++, off++) {
691: byte pixel = rasline[i];
692: if ((pixel & 0xff) == trans_pixel) {
693: if (runstart >= 0) {
694: // count = setPixels(x + runstart, y, i - runstart, height, model, rasline, runstart, 0);
695: count = setPixels(x + runstart, y, i
696: - runstart, height, model, rasline,
697: runstart, i - runstart);
698: if (count == 0) {
699: break;
700: }
701: }
702: runstart = -1;
703: } else {
704: if (runstart < 0) {
705: runstart = i;
706: }
707: if (save) {
708: saved_image[off] = pixel;
709: }
710: }
711: }
712: if (runstart >= 0) {
713: // count = setPixels(x + runstart, y, rasend - runstart, height, model, rasline, runstart, 0);
714: count = setPixels(x + runstart, y, rasend
715: - runstart, height, model, rasline,
716: runstart, rasend - runstart);
717: }
718: // Since (saved_model != model), store must be null...
719: return count;
720: }
721: } else if (save) {
722: System.arraycopy(rasline, rasbeg, saved_image, off, width);
723: }
724: // int count = setPixels(x2, y, width, height, model, rasline, rasbeg, 0);
725: int count = setPixels(x2, y, width, height, model, rasline,
726: rasbeg, width);
727: return count;
728: }
729:
730: /**
731: * Read Image data
732: */
733: private boolean readImage(boolean first, int disposal_method,
734: int delay) throws IOException {
735: if (curframe != null && !curframe.dispose()) {
736: abort();
737: return false;
738: }
739:
740: long tm = 0;
741:
742: if (verbose) {
743: tm = System.currentTimeMillis();
744: }
745:
746: // Allocate the buffer
747: byte block[] = new byte[256 + 3];
748:
749: // Read the image descriptor
750: if (readBytes(block, 0, 10) != 0) {
751: throw new IOException();
752: }
753: int x = ExtractWord(block, 0);
754: int y = ExtractWord(block, 2);
755: int width = ExtractWord(block, 4);
756: int height = ExtractWord(block, 6);
757: boolean interlace = (block[8] & INTERLACEMASK) != 0;
758:
759: IndexColorModel model = global_model;
760:
761: if ((block[8] & COLORMAPMASK) != 0) {
762: // We read one extra byte above so now when we must
763: // transfer that byte as the first colormap byte
764: // and manually read the code size when we are done
765: int num_local_colors = 1 << ((block[8] & 0x7) + 1);
766:
767: // Read local colors
768: byte[] local_colormap = new byte[num_local_colors * 3];
769: local_colormap[0] = block[9];
770: if (readBytes(local_colormap, 1, num_local_colors * 3 - 1) != 0) {
771: throw new IOException();
772: }
773:
774: // Now read the "real" code size byte which follows
775: // the local color table
776: if (readBytes(block, 9, 1) != 0) {
777: throw new IOException();
778: }
779: model = new IndexColorModel(8, num_local_colors,
780: local_colormap, 0, false, trans_pixel);
781: } else if (model == null
782: || trans_pixel != model.getTransparentPixel()) {
783: model = new IndexColorModel(8, num_global_colors,
784: global_colormap, 0, false, trans_pixel);
785: global_model = model;
786: }
787:
788: // Notify the consumers
789: if (first) {
790: if (global_width == 0)
791: global_width = width;
792: if (global_height == 0)
793: global_height = height;
794:
795: setDimensions(global_width, global_height);
796: setProperties(props);
797: setColorModel(model);
798: headerComplete();
799: }
800:
801: if (disposal_method == GifFrame.DISPOSAL_SAVE
802: && saved_image == null) {
803: saved_image = new byte[global_width * global_height];
804: }
805:
806: int hints = (interlace ? interlaceflags : normalflags);
807: setHints(hints);
808:
809: curframe = new GifFrame(this , disposal_method, delay,
810: (curframe == null), model, x, y, width, height);
811:
812: // allocate the raster data
813: byte rasline[] = new byte[width];
814:
815: if (verbose) {
816: System.out.print("Reading a " + width + " by " + height
817: + " " + (interlace ? "" : "non-")
818: + "interlaced image...");
819: }
820:
821: boolean ret = parseImage(x, y, width, height, interlace,
822: ExtractByte(block, 9), block, rasline, model);
823:
824: if (!ret) {
825: abort();
826: }
827:
828: if (verbose) {
829: System.out.println("done in "
830: + (System.currentTimeMillis() - tm) + "ms");
831: }
832:
833: return ret;
834: }
835:
836: /*
837: * Since I translated the JNI version of parseImage() into Java,
838: * we no longer need initIDs().
839: */
840: /* // if we're using JNI, we need to find the method and field IDs.
841: * private static native void initIDs();
842: * static {
843: * initIDs();
844: * }
845: */
846: }
847:
848: class GifFrame {
849: private static final boolean verbose = false;
850: private static IndexColorModel trans_model;
851:
852: static final int DISPOSAL_NONE = 0x00;
853: static final int DISPOSAL_SAVE = 0x01;
854: static final int DISPOSAL_BGCOLOR = 0x02;
855: static final int DISPOSAL_PREVIOUS = 0x03;
856:
857: GifImageDecoder decoder;
858:
859: int disposal_method;
860: int delay;
861:
862: IndexColorModel model;
863:
864: int x;
865: int y;
866: int width;
867: int height;
868:
869: boolean initialframe;
870:
871: public GifFrame(GifImageDecoder id, int dm, int dl, boolean init,
872: IndexColorModel cm, int x, int y, int w, int h) {
873: this .decoder = id;
874: this .disposal_method = dm;
875: this .delay = dl;
876: this .model = cm;
877: this .initialframe = init;
878: this .x = x;
879: this .y = y;
880: this .width = w;
881: this .height = h;
882: }
883:
884: private void setPixels(int x, int y, int w, int h, ColorModel cm,
885: byte[] pix, int off, int scan) {
886: decoder.setPixels(x, y, w, h, cm, pix, off, scan);
887: }
888:
889: public boolean dispose() {
890: if (decoder.imageComplete(ImageConsumer.SINGLEFRAMEDONE, false) == 0) {
891: return false;
892: } else {
893: if (delay > 0) {
894: try {
895: if (verbose) {
896: System.out.println("sleeping: " + delay);
897: }
898: Thread.sleep(delay);
899: } catch (InterruptedException e) {
900: return false;
901: }
902: } else {
903: Thread.yield();
904: }
905:
906: if (verbose && disposal_method != 0) {
907: System.out.println("disposal method: "
908: + disposal_method);
909: }
910:
911: int global_width = decoder.global_width;
912: int global_height = decoder.global_height;
913:
914: if (x < 0) {
915: width += x;
916: x = 0;
917: }
918: if (x + width > global_width) {
919: width = global_width - x;
920: }
921: if (width <= 0) {
922: disposal_method = DISPOSAL_NONE;
923: } else {
924: if (y < 0) {
925: height += y;
926: y = 0;
927: }
928: if (y + height > global_height) {
929: height = global_height - y;
930: }
931: if (height <= 0) {
932: disposal_method = DISPOSAL_NONE;
933: }
934: }
935:
936: switch (disposal_method) {
937: case DISPOSAL_PREVIOUS:
938: byte[] saved_image = decoder.saved_image;
939: IndexColorModel saved_model = decoder.saved_model;
940: if (saved_image != null) {
941: setPixels(x, y, width, height, saved_model,
942: saved_image, y * global_width + x,
943: global_width);
944: }
945: break;
946: case DISPOSAL_BGCOLOR:
947: byte tpix;
948: if (model.getTransparentPixel() < 0) {
949: model = trans_model;
950: if (model == null) {
951: model = new IndexColorModel(8, 1, new byte[4],
952: 0, true);
953: trans_model = model;
954: }
955: tpix = 0;
956: } else {
957: tpix = (byte) model.getTransparentPixel();
958: }
959:
960: int rassize = width * height;
961: byte[] rasfill = new byte[rassize];
962: if (tpix != 0) {
963: for (int i = 0; i < rassize; i++) {
964: rasfill[i] = tpix;
965: }
966: }
967: // setPixels(x, y, width, height, model, rasfill, 0, 0);
968: setPixels(x, y, width, height, model, rasfill, 0, width);
969: break;
970: case DISPOSAL_SAVE:
971: decoder.saved_model = model;
972: break;
973: }
974: }
975: return true;
976: }
977: }
|