Source for gnu.javax.imageio.gif.GIFFile

   1: /* GIFFile.java -- GIF decoder
   2:    Copyright (C) 2006  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package gnu.javax.imageio.gif;
  39: 
  40: import java.io.IOException;
  41: import java.io.InputStream;
  42: import java.util.Vector;
  43: 
  44: /**
  45:  * GIFFile - reads a GIF file.
  46:  *
  47:  * This class only does the bare minimum work, and returns the data in raw
  48:  * formats (described below). The class is J2ME compatible, and hopefully
  49:  * we can keep it that way without any significant overhead.
  50:  *
  51:  * @author Sven de Marothy.
  52:  */
  53: public class GIFFile
  54: {
  55:   // "NETSCAPE2.0" - identifier
  56:   private final static byte[] nsBlock = new byte[]
  57:   {0x4e, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2e, 0x30 };
  58: 
  59:   /**
  60:    * Block identifiers
  61:    */
  62:   private final static int EXTENSION = 0x21;
  63:   private final static int LOCAL = 0x2C;
  64:   private final static int TERMINATOR = 0x3B;
  65: 
  66:   /**
  67:    * Extension block types
  68:    */
  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:   /**
  74:    * Undraw commands for animation.
  75:    */
  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:   /**
  81:    * Image position and dimensions (images may be partial)
  82:    */
  83:   private int x, y, width, height;
  84: 
  85:   /**
  86:    * Global dimensions
  87:    */
  88:   private int globalWidth, globalHeight;
  89: 
  90:   /**
  91:    * Background color index.
  92:    */
  93:   private byte bgIndex;
  94: 
  95:   /**
  96:    * Number of colors
  97:    */
  98:   private int nColors;
  99: 
 100:   /**
 101:    * Global palette, if any
 102:    */
 103:   private byte[] globalPalette;
 104: 
 105:   /**
 106:    * Any
 107:    */
 108:   private boolean hasGlobalColorMap;
 109: 
 110:   /**
 111:    * Local palette, if any (used if available)
 112:    */
 113:   private byte[] localPalette;
 114: 
 115:   /**
 116:    * Interlaced GIF or not?
 117:    */
 118:   private boolean interlaced;
 119: 
 120:   /**
 121:    * Has transparency?
 122:    */
 123:   private boolean hasTransparency;
 124: 
 125:   /**
 126:    * Undraw mode (animations)
 127:    */
 128:   private int undraw;
 129: 
 130:   /**
 131:    * Transparent index;
 132:    */
 133:   private int transparentIndex;
 134: 
 135:   /**
 136:    * The uncompressed raster
 137:    */
 138:   private byte[] raster;
 139: 
 140:   /**
 141:    * The compressed data (freed after uncompressing)
 142:    */
 143:   private byte[] compressedData;
 144: 
 145:   /**
 146:    * Frame delay in 100ths of a second ( centiseconds, metrically )
 147:    */
 148:   private int duration;
 149: 
 150:   /**
 151:    * Indices used during decompression
 152:    */
 153:   private int dataBlockIndex;
 154: 
 155:   /**
 156:    * The file comment , if a comment block exists.
 157:    */
 158:   private String comment;
 159: 
 160:   /**
 161:    * Fields used by getBits()
 162:    */
 163:   private int remainingBits = 0;
 164:   private int currentBits = 0;
 165: 
 166:   /**
 167:    * Netscape animation extension
 168:    */
 169:   private boolean isLooped = false;
 170: 
 171:   /** Number of loops, 0 = infinite */
 172:   private int loops;
 173: 
 174:   /**
 175:    * Additional frames if it's an animated GIF.
 176:    */
 177:   private Vector animationFrames;
 178: 
 179:   /**
 180:    * Loads the file from an input stream, which is not closed.
 181:    * @throws IOException if an I/O error occured.
 182:    * @throws GIFException if some file parsing error occured
 183:    */
 184:   public GIFFile(InputStream in) throws IOException, GIFException
 185:   {
 186:     // Validate the signature
 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 ) // Not an animated GIF.
 224:       return;
 225: 
 226:     // Load animation frames. Just quit if an error occurs instead
 227:     // of throwing an exception.
 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:   /**
 246:    * Constructor for additional animation frames.
 247:    */
 248:   private GIFFile(GIFFile parent, InputStream in, int c)
 249:     throws IOException, GIFException
 250:   {
 251:     // Copy global properties.
 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:   /**
 275:    * Reads a GIF file signature from an inputstream and checks it.
 276:    *
 277:    * @param in - the stream (reads 6 bytes, does not close or reset).
 278:    * @return true if the signature is a valid GIF signature.
 279:    * @throws IOException if the signature could not be read.
 280:    */
 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 ) // GIF8
 289:       return false;
 290: 
 291:     if( (data[4] != 0x39 && data[4] != 0x37) || // 7 | 9
 292:         (data[5] != 0x61 && data[5] != 0x62) ) // 'a' or 'b'
 293:       return false;
 294:     return true;
 295:   }
 296: 
 297: 
 298:   /**
 299:    * Loads the image local descriptor and then loads/decodes the image raster,
 300:    * and then performs any necessary postprocessing like deinterlacing.
 301:    */
 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 )  // Clean up
 317:       deinterlace();
 318:     packPixels();
 319:   }
 320: 
 321:   /**
 322:    * Pack the pixels if it's a 2, 4 or 16 color image.
 323:    * While GIF may support any number of colors from 2-256, we won't bother
 324:    * trying to pack pixels not resulting in even byte boundaries.
 325:    * (AWT doesn't support that anyway, and most apps do the same.)
 326:    */
 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:   /**
 363:    * Returns the (global) width
 364:    */
 365:   public int getWidth()
 366:   {
 367:     return width;
 368:   }
 369: 
 370:   /**
 371:    * Returns the image height
 372:    */
 373:   public int getHeight()
 374:   {
 375:     return height;
 376:   }
 377: 
 378:   /**
 379:    * Returns the # of colors.
 380:    */
 381:   public int getNColors()
 382:   {
 383:     return nColors;
 384:   }
 385: 
 386:   /**
 387:    * Returns whether the GIF has transparency.
 388:    */
 389:   public boolean hasTransparency()
 390:   {
 391:     return hasTransparency;
 392:   }
 393: 
 394:   /**
 395:    * Returns the index of the transparent color.
 396:    */
 397:   public int getTransparentIndex()
 398:   {
 399:     return transparentIndex;
 400:   }
 401: 
 402:   /**
 403:    * Retuns the GIF file comment, or null if none exists.
 404:    */
 405:   public String getComment()
 406:   {
 407:     return comment;
 408:   }
 409: 
 410:   /**
 411:    * Get duration of the frame for animations.
 412:    */
 413:   public int getDuration()
 414:   {
 415:     return duration;
 416:   }
 417: 
 418:   /**
 419:    * Deinterlaces the image.
 420:    */
 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:   /**
 449:    * Reads the local descriptor
 450:    */
 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:       { // has a local color map
 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:   /**
 474:    * Returns the image's palette in raw format
 475:    * (r0,g0,b0,r1,g1,b2..r(Ncolors-1),g(Ncolors-1),b(Ncolors-1))
 476:    */
 477:   public byte[] getRawPalette()
 478:   {
 479:     return hasGlobalColorMap ? globalPalette : localPalette;
 480:   }
 481: 
 482:   /**
 483:    * Returns the image file for animated gifs.
 484:    */
 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:   /**
 495:    * Return the image's raw image data.
 496:    * If the color depth is 1,2 or 4 bits per pixel the pixels are packed
 497:    * and the scanlines padded up to the nearest byte if needed.
 498:    */
 499:   public byte[] getRawImage()
 500:   {
 501:     return raster;
 502:   }
 503: 
 504:   /**
 505:    * Return the number of images in the GIF file
 506:    */
 507:   public int nImages()
 508:   {
 509:     if( animationFrames != null )
 510:       return 1 + animationFrames.size();
 511:     return 1;
 512:   }
 513: 
 514:   /**
 515:    * Handles extension blocks.
 516:    */
 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: // comment block
 524:         comment = new String(data, "8859_1");
 525:         break;
 526: 
 527:       case EXTENSION_GCONTROL: // Graphics control extension
 528:         undraw = (data[0] & 0x1C) >> 2;
 529:         // allegedly there can be bad values of this.
 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:         // Application extension. We only parse the Netscape animation
 537:         // extension here. Which is the only one most use anyway.
 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:   /**
 556:    * Reads a series of data blocks and merges them into a single one.
 557:    */
 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:   /**
 586:    * Loads a compressed image block and decompresses it.
 587:    */
 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; // Index into the raster
 595:     int clearCode = (1 << initialCodeSize); // 256 usually
 596:     int endCode = clearCode + 1; // The stop code.
 597: 
 598:     raster = new byte[ width * height ];
 599: 
 600:     int codeSize = initialCodeSize + 1;
 601:     int code = getBits( codeSize ); // = clear
 602:     int nextCode = endCode + 1;
 603: 
 604:     /*
 605:      * Initialize LZW dictionary
 606:      *
 607:      * First index - code #
 608:      * Second index:
 609:      * 0 = color index
 610:      * 1 = parent (-1 - no parent)
 611:      * 2 = first value
 612:      * 3 - depth
 613:      * The latter two aren't strictly necessary but make things faster, since
 614:      * copying the values forward is faster than going back and looking.
 615:      */
 616:     short[][] dictionary = new short[ 4096 ][ 4 ];
 617: 
 618:     for(short i = 0; i < nColors; i ++ )
 619:       {
 620:         dictionary[i][0] = i;  // color index
 621:         dictionary[i][1] = -1; // parent
 622:         dictionary[i][2] = i;  // first
 623:         dictionary[i][3] = 1;  // depth
 624:       }
 625: 
 626:     code = getBits( codeSize ); // get second code
 627:     raster[ rasterIndex++ ] = (byte)dictionary[code][0];
 628:     int old = code;
 629:     code = getBits( codeSize ); // start at the third code
 630:     int c;
 631: 
 632:     do
 633:       {
 634:         if( code == clearCode )
 635:           {
 636:             codeSize = initialCodeSize + 1;
 637:             nextCode = endCode + 1;
 638:             // get and output second code
 639:             code = getBits( codeSize );
 640:             raster[ rasterIndex++ ] = (byte)dictionary[code][0];
 641:             old = code;
 642:           }
 643:         else
 644:           {
 645:             dictionary[nextCode][1] = (short)old; // parent = old
 646:             dictionary[nextCode][2] = dictionary[old][2]; // first pixel
 647:             dictionary[nextCode][3] = (short)(dictionary[old][3] + 1); // depth
 648: 
 649:             // appended pixel  = first pixel of c
 650:             if( code < nextCode )
 651:               {
 652:                 dictionary[nextCode][0] = dictionary[code][2];
 653:                 old = code;
 654:               }
 655:             else // first of old
 656:               {
 657:                 dictionary[nextCode][0] = dictionary[old][2];
 658:                 old = nextCode;
 659:               }
 660: 
 661:             c = old;
 662:             // output the code c
 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]; // go to parent.
 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; // throw away compressed data.
 680:   }
 681: 
 682:   /**
 683:    * Returns nbits number of bits (in the LSBs) from compressedData
 684:    */
 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:   /**
 700:    * Generic exception used by GIFFile to report decoding errors.
 701:    */
 702:   public static class GIFException extends Exception
 703:   {
 704:     public GIFException(String message)
 705:     {
 706:       super(message);
 707:     }
 708:   }
 709: }