Source for gnu.javax.imageio.png.PNGChunk

   1: /* PNGChunk.java -- Generic PNG chunk
   2:    Copyright (C) 2006 Free Software Foundation
   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.png;
  39: 
  40: import java.io.InputStream;
  41: import java.io.OutputStream;
  42: import java.io.IOException;
  43: import java.io.UnsupportedEncodingException;
  44: 
  45: /**
  46:  * Class to load and validate a generic PNG chunk.
  47:  */
  48: public class PNGChunk
  49: {
  50: 
  51:   /**
  52:    * CRC table and initialization code.
  53:    */
  54:   private static long[] crcTable;
  55: 
  56:   static 
  57:    {
  58:      long c;
  59:      crcTable = new long[256];
  60:    
  61:      for(int i = 0; i < 256; i++)
  62:        {
  63:      c = i;
  64:      for(int j = 0; j < 8; j++) 
  65:        if( (c & 1) == 1 )
  66:          c = 0xEDB88320L ^ (c >> 1);
  67:        else
  68:          c = c >> 1;
  69:      crcTable[i] = c;
  70:        }
  71:    }
  72: 
  73:   /**
  74:    * (recognized) PNG chunk types.
  75:    */
  76:   public static final int TYPE_HEADER = 0x49484452; // 'IHDR'
  77:   public static final int TYPE_PALETTE = 0x504c5445;// 'PLTE'
  78:   public static final int TYPE_DATA = 0x49444154;   // 'IDAT'
  79:   public static final int TYPE_TIME = 0x74494d45;   // 'tIME'
  80:   public static final int TYPE_END = 0x49454e44;    // 'IEND'
  81:   public static final int TYPE_PHYS = 0x70485973;   // 'pHYS'
  82:   public static final int TYPE_GAMMA = 0x67414d41;  // 'gAMA'
  83:   public static final int TYPE_PROFILE = 0x69434350;  // 'iCCP'
  84: 
  85:   /**
  86:    * The chunk type - Represented in the file as 4 ASCII bytes,
  87:    */
  88:   private int type;
  89: 
  90:   /**
  91:    * The chunk data
  92:    */
  93:   protected byte[] data;
  94:   
  95:   /**
  96:    * The chunk's crc
  97:    */
  98:   private int crc;
  99: 
 100:   /**
 101:    * Constructor for reading a generic chunk.
 102:    */
 103:   protected PNGChunk( int type, byte[] data, int crc )
 104:   {
 105:     this.type = type;
 106:     this.data = data;
 107:     this.crc = crc;
 108:   }
 109: 
 110:   /**
 111:    * Constructor for creating new chunks. 
 112:    * (only used by subclasses - creating a generic chunk is rather useless)
 113:    */
 114:   protected PNGChunk( int type )
 115:   {
 116:     this.type = type;
 117:   }
 118: 
 119:   /**
 120:    * Loads a chunk from an InputStream. Does not perform validation,
 121:    * but will throw an IOException if the read fails.
 122:    * @param in - th einputstream to read from
 123:    * @param strict - if true, a PNGException is thrown on all invalid chunks,
 124:    * if false, only critical chunks will throw PNGExceptions.
 125:    */
 126:   public static PNGChunk readChunk(InputStream in, boolean strict) 
 127:     throws IOException, PNGException
 128:   {
 129:     byte data[] = new byte[4];
 130:     if( in.read( data ) != 4 )
 131:       throw new IOException("Could not read chunk length.");
 132:     int length = ((data[0] & 0xFF) << 24) | ((data[1] & 0xFF) << 16 ) | 
 133:       ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
 134: 
 135:     if( in.read( data ) != 4 )
 136:       throw new IOException("Could not read chunk type.");
 137:     int type = ((data[0] & 0xFF) << 24) | ((data[1] & 0xFF) << 16 ) | 
 138:       ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
 139: 
 140:     byte[] chkdata = new byte[ length ];
 141:     if( in.read( chkdata ) != length )
 142:       throw new IOException("Could not read chunk data.");
 143:     
 144:     if( in.read( data ) != 4 )
 145:       throw new IOException("Could not read chunk CRC.");
 146:     
 147:     int crc = ((data[0] & 0xFF) << 24) | ( (data[1] & 0xFF) << 16 ) | 
 148:       ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
 149: 
 150:     if( strict )
 151:       return getChunk( type, chkdata, crc );
 152:     else
 153:       {
 154:     try
 155:       {
 156:         return getChunk( type, chkdata, crc );
 157:       }
 158:     catch(PNGException pnge)
 159:       {
 160:         if( isEssentialChunk( type ) )
 161:           throw pnge;
 162:         return null;
 163:       }
 164:       }
 165:   }
 166: 
 167:   /**
 168:    * Returns a specialied object for a chunk, if we have one.
 169:    */
 170:   private static PNGChunk getChunk( int type, byte[] data, int crc )
 171:     throws PNGException
 172:   {
 173:     switch( type )
 174:       {
 175:       case TYPE_HEADER:
 176:     return new PNGHeader( type, data, crc );
 177:       case TYPE_DATA:
 178:     return new PNGData( type, data, crc );
 179:       case TYPE_PALETTE:
 180:     return new PNGPalette( type, data, crc );
 181:       case TYPE_TIME:
 182:     return new PNGTime( type, data, crc );
 183:       case TYPE_PHYS:
 184:     return new PNGPhys( type, data, crc );
 185:       case TYPE_GAMMA:
 186:     return new PNGGamma( type, data, crc );
 187:       case TYPE_PROFILE:
 188:     return new PNGICCProfile( type, data, crc );
 189:       default:
 190:     return new PNGChunk( type, data, crc );
 191:       }
 192:   }
 193: 
 194:   /**
 195:    * Returns whether the chunk is essential or not
 196:    */
 197:   private static boolean isEssentialChunk( int type )
 198:   {
 199:     switch( type )
 200:       {
 201:       case TYPE_HEADER:
 202:       case TYPE_DATA:
 203:       case TYPE_PALETTE:
 204:       case TYPE_END:
 205:     return true;
 206:       default:
 207:     return false;
 208:       }
 209:   }
 210: 
 211:   /**
 212:    * Validates the chunk
 213:    */
 214:   public boolean isValidChunk()
 215:   {
 216:     return (crc == calcCRC());
 217:   }
 218: 
 219:   /**
 220:    * Returns the chunk type.
 221:    */
 222:   public int getType()
 223:   {
 224:     return type;
 225:   }
 226: 
 227:   /**
 228:    * Writes a PNG chunk to an output stream, 
 229:    * performing the CRC calculation as well.
 230:    */
 231:   public void writeChunk(OutputStream out) throws IOException
 232:   {
 233:     out.write( getInt(data.length) );
 234:     out.write( getInt(type) );
 235:     out.write( data );
 236:     out.write( getInt(calcCRC()) );
 237:   }
 238: 
 239:   /**
 240:    * Return whether the chunk contains any data.
 241:    */
 242:   public boolean isEmpty()
 243:   {
 244:     return ( data.length == 0 );
 245:   }
 246: 
 247:   /**
 248:    * Convenience method. Cast an int to four bytes (big endian).
 249:    * (Now why doesn't java have a simple way of doing this?)
 250:    */
 251:   public static byte[] getInt(int intValue)
 252:   {
 253:     long i = (intValue & 0xFFFFFFFFL);
 254:     byte[] b = new byte[4];
 255:     b[0] = (byte)((i & 0xFF000000L) >> 24);
 256:     b[1] = (byte)((i & 0x00FF0000L) >> 16);
 257:     b[2] = (byte)((i & 0x0000FF00L) >> 8);
 258:     b[3] = (byte)(i & 0x000000FFL);
 259:     return b;
 260:   }
 261: 
 262:   /**
 263:    * Calculates this chunk's CRC value.
 264:    */
 265:   private int calcCRC()
 266:   {
 267:     long c = 0xFFFFFFFFL;
 268:     byte[] t = getInt( type );
 269:     for(int i = 0; i < 4; i++)
 270:       c = crcTable[ (int)((c ^ t[i]) & 0xFF) ] ^ (c >> 8);
 271:       
 272:     for(int i = 0; i < data.length; i++)
 273:       c = crcTable[ (int)((c ^ data[i]) & 0xFF) ] ^ (c >> 8);
 274: 
 275:     return (int)(c ^ 0xFFFFFFFFL);
 276:   }
 277: 
 278:   public String toString()
 279:   {
 280:     return "PNG Chunk. Type: " + new String( getInt(type) ) + " , CRC: " + 
 281:       crc + " , calculated CRC: "+calcCRC();
 282:   }
 283:    
 284: }