1:
37:
38: package ;
39:
40: import ;
41: import ;
42: import ;
43:
44:
53: public class GIFFile
54: {
55:
56: private final static byte[] nsBlock = new byte[]
57: {0x4e, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2e, 0x30 };
58:
59:
62: private final static int EXTENSION = 0x21;
63: private final static int LOCAL = 0x2C;
64: private final static int TERMINATOR = 0x3B;
65:
66:
69: private final static int EXTENSION_COMMENT = 254;
70: private final static int EXTENSION_GCONTROL = 249;
71: private final static int EXTENSION_APPLICATION = 255;
72:
73:
76: private final static int UNDRAW_OVERWRITE = 1;
77: private final static int UNDRAW_RESTORE_BACKGROUND = 2;
78: private final static int UNDRAW_RESTORE_PREVIOUS = 3;
79:
80:
83: private int x, y, width, height;
84:
85:
88: private int globalWidth, globalHeight;
89:
90:
93: private byte bgIndex;
94:
95:
98: private int nColors;
99:
100:
103: private byte[] globalPalette;
104:
105:
108: private boolean hasGlobalColorMap;
109:
110:
113: private byte[] localPalette;
114:
115:
118: private boolean interlaced;
119:
120:
123: private boolean hasTransparency;
124:
125:
128: private int undraw;
129:
130:
133: private int transparentIndex;
134:
135:
138: private byte[] raster;
139:
140:
143: private byte[] compressedData;
144:
145:
148: private int duration;
149:
150:
153: private int dataBlockIndex;
154:
155:
158: private String comment;
159:
160:
163: private int remainingBits = 0;
164: private int currentBits = 0;
165:
166:
169: private boolean isLooped = false;
170:
171:
172: private int loops;
173:
174:
177: private Vector animationFrames;
178:
179:
184: public GIFFile(InputStream in) throws IOException, GIFException
185: {
186:
187: if( !readSignature( in ) )
188: throw new GIFException("Invalid GIF signature.");
189:
190: {
191: byte[] data = new byte[7];
192: if (in.read(data) != 7)
193: throw new IOException("Couldn't read global descriptor.");
194:
195: globalWidth = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
196: globalHeight = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
197: byte flags = data[4];
198: bgIndex = data[5];
199: nColors = (1 << (( flags & 0x07) + 1));
200: hasGlobalColorMap = ((flags & 0x80) != 0);
201: }
202:
203: if( hasGlobalColorMap )
204: {
205: globalPalette = new byte[ nColors * 3 ];
206: if( in.read( globalPalette ) != nColors * 3 )
207: throw new IOException("Couldn't read color map.");
208: }
209:
210: int c = in.read();
211: while( c == EXTENSION )
212: {
213: readExtension( in );
214: c = in.read();
215: }
216:
217: if( c != LOCAL )
218: throw new GIFException("Extension blocks not followed by a local descriptor ("+c+")");
219:
220: loadImage( in );
221: c = in.read();
222:
223: if( c == TERMINATOR )
224: return;
225:
226:
227:
228: animationFrames = new Vector();
229: try
230: {
231: while( c != TERMINATOR )
232: {
233: animationFrames.add( new GIFFile( this, in, c ) );
234: c = in.read();
235: }
236: }
237: catch(IOException ioe)
238: {
239: }
240: catch(GIFException gife)
241: {
242: }
243: }
244:
245:
248: private GIFFile(GIFFile parent, InputStream in, int c)
249: throws IOException, GIFException
250: {
251:
252: globalWidth = parent.globalWidth;
253: globalHeight = parent.globalHeight;
254: nColors = parent.nColors;
255: globalPalette = parent.globalPalette;
256: hasGlobalColorMap = parent.hasGlobalColorMap;
257: interlaced = parent.interlaced;
258: comment = parent.comment;
259: isLooped = parent.isLooped;
260: loops = parent.loops;
261:
262: while( c == EXTENSION )
263: {
264: readExtension( in );
265: c = in.read();
266: }
267:
268: if( c != LOCAL )
269: throw new GIFException("Extension blocks not followed by a local descriptor ("+c+")");
270:
271: loadImage( in );
272: }
273:
274:
281: public static boolean readSignature( InputStream in ) throws IOException
282: {
283: byte[] data = new byte[6];
284: if (in.read(data) != 6)
285: throw new IOException("Couldn't read signature.");
286:
287: if( data[0] != 0x47 || data[1] != 0x49 || data[2] != 0x46 ||
288: data[3] != 0x38 )
289: return false;
290:
291: if( (data[4] != 0x39 && data[4] != 0x37) ||
292: (data[5] != 0x61 && data[5] != 0x62) )
293: return false;
294: return true;
295: }
296:
297:
298:
302: private void loadImage(InputStream in)
303: throws IOException, GIFException
304: {
305: readLocal( in );
306:
307: try
308: {
309: decodeRaster( in );
310: }
311: catch(ArrayIndexOutOfBoundsException aioobe)
312: {
313: throw new GIFException("Error decompressing image.");
314: }
315:
316: if( interlaced )
317: deinterlace();
318: packPixels();
319: }
320:
321:
327: private void packPixels()
328: {
329: if( nColors != 2 && nColors != 4 && nColors != 16 )
330: return;
331:
332: int nbits = 1;
333: int ppbyte = 8;
334: if( nColors == 4 )
335: {
336: nbits = 2;
337: ppbyte = 4;
338: }
339: else if( nColors == 16 )
340: {
341: nbits = 4;
342: ppbyte = 2;
343: }
344:
345: int rem = (width & (ppbyte - 1));
346: int w = ( rem == 0 ) ? (width / ppbyte) :
347: ((width + ppbyte - rem) / ppbyte);
348: byte[] nr = new byte[ w * height ];
349: for(int j = 0; j < height; j++)
350: {
351: for(int i = 0; i < width - ppbyte; i += ppbyte)
352: for(int k = 0; k < ppbyte; k++)
353: nr[ j * w + (i / ppbyte) ] |= (byte)((raster[ width * j + i + k ]
354: << (8 - nbits * (1 + k))));
355: for(int i = 0; i < rem; i++)
356: nr[ j * w + w - 1 ] |= (byte)((raster[ width * j + width - rem + i ]
357: << (nbits * (rem - i))));
358: }
359: raster = nr;
360: }
361:
362:
365: public int getWidth()
366: {
367: return width;
368: }
369:
370:
373: public int getHeight()
374: {
375: return height;
376: }
377:
378:
381: public int getNColors()
382: {
383: return nColors;
384: }
385:
386:
389: public boolean hasTransparency()
390: {
391: return hasTransparency;
392: }
393:
394:
397: public int getTransparentIndex()
398: {
399: return transparentIndex;
400: }
401:
402:
405: public String getComment()
406: {
407: return comment;
408: }
409:
410:
413: public int getDuration()
414: {
415: return duration;
416: }
417:
418:
421: private void deinterlace()
422: {
423: byte[] nr = new byte[ width * height ];
424: int n = 0;
425: for(int i = 0; i < ((height + 7) >> 3); i++)
426: {
427: System.arraycopy( raster, n, nr, width * i * 8, width );
428: n += width;
429: }
430: for(int i = 0; i < ((height + 3) >> 3); i++)
431: {
432: System.arraycopy( raster, n, nr, width * ( 8 * i + 4 ), width );
433: n += width;
434: }
435: for(int i = 0; i < (height >> 2); i++)
436: {
437: System.arraycopy( raster, n, nr, width * (4 * i + 2), width );
438: n += width;
439: }
440: for(int i = 0; i < (height >> 1); i++)
441: {
442: System.arraycopy( raster, n, nr, width * (2 * i + 1), width );
443: n += width;
444: }
445: raster = nr;
446: }
447:
448:
451: private void readLocal(InputStream in) throws IOException
452: {
453: byte[] data = new byte[9];
454: if (in.read(data) != 9)
455: throw new IOException("Couldn't read local descriptor.");
456: x = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
457: y = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
458: width = ((data[5] & 0xFF) << 8) | (data[4] & 0xFF);
459: height = ((data[7] & 0xFF) << 8) | (data[6] & 0xFF);
460: byte flags = data[8];
461: interlaced = (( flags & 0x40 ) != 0);
462: if( (flags & 0x80) != 0 )
463: {
464: int nLocalColors = (1 << (( flags & 0x07) + 1));
465: if( !hasGlobalColorMap )
466: nColors = nLocalColors;
467: localPalette = new byte[ nLocalColors * 3 ];
468: if( in.read( localPalette ) != nLocalColors * 3 )
469: throw new IOException("Couldn't read color map.");
470: }
471: }
472:
473:
477: public byte[] getRawPalette()
478: {
479: return hasGlobalColorMap ? globalPalette : localPalette;
480: }
481:
482:
485: public GIFFile getImage( int index )
486: {
487: if( index == 0 )
488: return this;
489: if( animationFrames == null )
490: throw new ArrayIndexOutOfBoundsException("Only one image in file");
491: return (GIFFile)animationFrames.elementAt( index - 1 );
492: }
493:
494:
499: public byte[] getRawImage()
500: {
501: return raster;
502: }
503:
504:
507: public int nImages()
508: {
509: if( animationFrames != null )
510: return 1 + animationFrames.size();
511: return 1;
512: }
513:
514:
517: private void readExtension(InputStream in) throws IOException, GIFException
518: {
519: int functionCode = in.read();
520: byte[] data = readData(in);
521: switch( functionCode )
522: {
523: case EXTENSION_COMMENT:
524: comment = new String(data, "8859_1");
525: break;
526:
527: case EXTENSION_GCONTROL:
528: undraw = (data[0] & 0x1C) >> 2;
529:
530: if( undraw < 1 && undraw > 3 ) undraw = 1;
531: hasTransparency = ((data[0] & 0x01) == 1);
532: transparentIndex = (data[3] & 0xFF);
533: duration = ((data[2] & 0xFF) << 8) | (data[1] & 0xFF);
534: break;
535:
536:
537:
538: case EXTENSION_APPLICATION:
539: boolean isNS = true;
540: for(int i = 0; i < nsBlock.length; i++ )
541: if( nsBlock[i] != data[i] )
542: isNS = false;
543: if( isNS )
544: {
545: isLooped = true;
546: loops = ((data[12] & 0xFF) << 8) | (data[13] & 0xFF);
547: }
548: break;
549:
550: default:
551: break;
552: }
553: }
554:
555:
558: private byte[] readData(InputStream in) throws IOException
559: {
560: Vector v = new Vector();
561: int totalBytes = 0;
562:
563: int n = in.read();
564: do
565: {
566: totalBytes += n;
567: byte[] block = new byte[ n ];
568: in.read(block);
569: v.add(block);
570: n = in.read();
571: }
572: while( n > 0 );
573:
574: n = 0;
575: byte[] bigBuffer = new byte[ totalBytes ];
576: for( int i = 0; i < v.size(); i++ )
577: {
578: byte[] block = (byte[])v.elementAt(i);
579: System.arraycopy(block, 0, bigBuffer, n, block.length);
580: n += block.length;
581: }
582: return bigBuffer;
583: }
584:
585:
588: private void decodeRaster(InputStream in) throws IOException
589: {
590: int initialCodeSize = in.read();
591: compressedData = readData( in );
592: dataBlockIndex = 0;
593:
594: int rasterIndex = 0;
595: int clearCode = (1 << initialCodeSize);
596: int endCode = clearCode + 1;
597:
598: raster = new byte[ width * height ];
599:
600: int codeSize = initialCodeSize + 1;
601: int code = getBits( codeSize );
602: int nextCode = endCode + 1;
603:
604:
616: short[][] dictionary = new short[ 4096 ][ 4 ];
617:
618: for(short i = 0; i < nColors; i ++ )
619: {
620: dictionary[i][0] = i;
621: dictionary[i][1] = -1;
622: dictionary[i][2] = i;
623: dictionary[i][3] = 1;
624: }
625:
626: code = getBits( codeSize );
627: raster[ rasterIndex++ ] = (byte)dictionary[code][0];
628: int old = code;
629: code = getBits( codeSize );
630: int c;
631:
632: do
633: {
634: if( code == clearCode )
635: {
636: codeSize = initialCodeSize + 1;
637: nextCode = endCode + 1;
638:
639: code = getBits( codeSize );
640: raster[ rasterIndex++ ] = (byte)dictionary[code][0];
641: old = code;
642: }
643: else
644: {
645: dictionary[nextCode][1] = (short)old;
646: dictionary[nextCode][2] = dictionary[old][2];
647: dictionary[nextCode][3] = (short)(dictionary[old][3] + 1);
648:
649:
650: if( code < nextCode )
651: {
652: dictionary[nextCode][0] = dictionary[code][2];
653: old = code;
654: }
655: else
656: {
657: dictionary[nextCode][0] = dictionary[old][2];
658: old = nextCode;
659: }
660:
661: c = old;
662:
663: int depth = dictionary[c][3];
664: for( int i = depth - 1; i >= 0; i-- )
665: {
666: raster[ rasterIndex + i ] = (byte)dictionary[c][0];
667: c = dictionary[c][1];
668: }
669: rasterIndex += depth;
670: nextCode ++;
671:
672: if( codeSize < 12 && nextCode >= (1 << codeSize) )
673: codeSize++;
674: }
675: code = getBits( codeSize );
676: }
677: while( code != endCode && dataBlockIndex < compressedData.length );
678:
679: compressedData = null;
680: }
681:
682:
685: private int getBits( int nbits )
686: {
687: while( nbits > remainingBits )
688: {
689: int c = (compressedData[ dataBlockIndex++ ] & 0xFF) << remainingBits;
690: currentBits |= c;
691: remainingBits += 8;
692: }
693: int rval = (currentBits & ((1 << nbits) - 1));
694: currentBits = (currentBits >> nbits);
695: remainingBits -= nbits;
696: return rval;
697: }
698:
699:
702: public static class GIFException extends Exception
703: {
704: public GIFException(String message)
705: {
706: super(message);
707: }
708: }
709: }