Source for gnu.javax.imageio.png.PNGFile

   1: /* PNGFile.java -- High-level representation of a PNG file.
   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.util.Vector;
  44: import java.awt.image.BufferedImage;
  45: import java.awt.image.WritableRaster;
  46: import java.awt.image.ColorModel;
  47: import java.awt.color.ColorSpace;
  48: 
  49: public class PNGFile
  50: {
  51:   /**
  52:    * The PNG file signature.
  53:    */
  54:   private static final byte[] signature = new byte[]
  55:   { (byte)137, 80, 78, 71, 13, 10, 26, 10 };
  56: 
  57:   /**
  58:    * The end chunk in raw form, no need for anything fancy here, it's just
  59:    * 0 bytes of length, the "IEND" tag and its CRC.
  60:    */
  61:   private static final byte[] endChunk = new byte[]
  62:   { 0, 0, 0, 0, (byte)0x49, (byte)0x45, (byte)0x4E, (byte)0x44,
  63:     (byte)0xAE, (byte)0x42, (byte)0x60, (byte)0x82 };
  64: 
  65:   /**
  66:    * The loaded data.
  67:    */
  68:   private Vector chunks;
  69: 
  70:   /**
  71:    * The Header chunk
  72:    */
  73:   private PNGHeader header;
  74: 
  75:   /**
  76:    * Whether this file has a palette chunk or not.
  77:    */
  78:   private boolean hasPalette;
  79: 
  80:   /**
  81:    * Image width and height.
  82:    */
  83:   private int width, height;
  84: 
  85:   /**
  86:    * The decoder, if any.
  87:    */
  88:   private PNGDecoder decoder;
  89: 
  90:   /**
  91:    * The encoder, if any. (Either this or the above must exist).
  92:    */
  93:   private PNGEncoder encoder;
  94: 
  95:   /**
  96:    * The source of this PNG (if encoding)
  97:    */
  98:   private BufferedImage sourceImage;
  99: 
 100:   /**
 101:    * Creates a PNGFile object from an InputStream.
 102:    */
 103:   public PNGFile(InputStream in) throws IOException, PNGException
 104:   {
 105:     PNGChunk chunk;
 106:     byte[] fileHdr = new byte[8];
 107:     chunks = new Vector();
 108:     hasPalette = false;
 109: 
 110:     if( in.read( fileHdr ) != 8 )
 111:       throw new IOException("Could not read file header.");
 112:     if( !validateHeader( fileHdr ) )
 113:       throw new PNGException("Invalid file header. Not a PNG file.");
 114: 
 115:     chunk = PNGChunk.readChunk( in, false );
 116:     if( !(chunk instanceof PNGHeader) )
 117:       throw new PNGException("First chunk not a header chunk.");
 118:     header = (PNGHeader)chunk;
 119:     if( !header.isValidChunk() )
 120:       throw new PNGException("First chunk not a valid header.");
 121:     System.out.println(header);
 122: 
 123:     decoder = new PNGDecoder( header );
 124:     // Read chunks.
 125:     do
 126:       {
 127:         chunk = PNGChunk.readChunk( in, false );
 128:         /*
 129:          * We could exit here or output some kind of warning.
 130:          * But in the meantime, we'll just silently drop invalid chunks.
 131:          */
 132:         if( chunk.isValidChunk() )
 133:           {
 134:             if( chunk instanceof PNGData )
 135:               decoder.addData( (PNGData)chunk );
 136:             else // Silently ignore multiple headers, and use only the first.
 137:               if( chunk.getType() != PNGChunk.TYPE_END )
 138:                 {
 139:                   chunks.add( chunk );
 140:                   hasPalette |= ( chunk instanceof PNGPalette );
 141:                 }
 142:           }
 143:         else
 144:           System.out.println("WARNING: Invalid chunk!");
 145:       }
 146:     while( chunk.getType() != PNGChunk.TYPE_END );
 147: 
 148:     if( header.isIndexed() && !hasPalette )
 149:       throw new PNGException("File is indexed color and has no palette.");
 150: 
 151:     width = header.getWidth();
 152:     height = header.getHeight();
 153:   }
 154: 
 155:   /**
 156:    * Creates a PNG file from an existing BufferedImage.
 157:    */
 158:   public PNGFile(BufferedImage bi) throws PNGException
 159:   {
 160:     sourceImage = bi;
 161:     width = bi.getWidth();
 162:     height = bi.getHeight();
 163:     chunks = new Vector();
 164:     encoder = new PNGEncoder( bi );
 165:     header = encoder.getHeader();
 166:     if( header.isIndexed() )
 167:       chunks.add( encoder.getPalette() );
 168: 
 169:     // Do the compression and put the data chunks in the list.
 170:     chunks.addAll( encoder.encodeImage() );
 171:   }
 172: 
 173:   /**
 174:    * Writes a PNG file to an OutputStream
 175:    */
 176:   public void writePNG(OutputStream out) throws IOException
 177:   {
 178:     out.write( signature ); // write the signature.
 179:     header.writeChunk( out );
 180:     for( int i = 0; i < chunks.size(); i++ )
 181:       {
 182:         PNGChunk chunk = ((PNGChunk)chunks.elementAt(i));
 183:         chunk.writeChunk( out );
 184:       }
 185:     out.write( endChunk );
 186:   }
 187: 
 188:   /**
 189:    * Check 8 bytes to see if it's a valid PNG header.
 190:    */
 191:   private boolean validateHeader( byte[] hdr )
 192:   {
 193:     if( hdr.length != 8 )
 194:       return false;
 195:     for( int i = 0; i < 8; i++ )
 196:       if( signature[i] != hdr[i] )
 197:         return false;
 198:     return true;
 199:   }
 200: 
 201:   /**
 202:    * Return a loaded image as a bufferedimage.
 203:    */
 204:   public BufferedImage getBufferedImage()
 205:   {
 206:     if( decoder == null )
 207:       return sourceImage;
 208: 
 209:     WritableRaster r = decoder.getRaster( header );
 210:     ColorModel cm;
 211:     if( header.isIndexed() )
 212:       {
 213:         PNGPalette pngp = getPalette();
 214:         cm = pngp.getPalette( getColorSpace() );
 215:       }
 216:     else
 217:       cm = decoder.getColorModel( getColorSpace(),
 218:                                   header.getColorType(),
 219:                                   header.getDepth() );
 220: 
 221:     return new BufferedImage(cm, r, false, null);
 222:   }
 223: 
 224:   /**
 225:    * Find the palette chunk and return it
 226:    */
 227:   private PNGPalette getPalette()
 228:   {
 229:     for(int i = 0; i < chunks.size(); i++ )
 230:       if( chunks.elementAt(i) instanceof PNGPalette )
 231:         return ((PNGPalette)chunks.elementAt(i));
 232:     return null;
 233:   }
 234: 
 235:   /**
 236:    * Return the Color space to use, first preference is ICC profile, then
 237:    * a gamma chunk, or returns null for the default sRGB.
 238:    */
 239:   private ColorSpace getColorSpace()
 240:   {
 241:     PNGICCProfile icc = null;
 242:     PNGGamma gamma = null;
 243:     for(int i = 0; i < chunks.size(); i++ )
 244:       {
 245:         if( chunks.elementAt(i) instanceof PNGICCProfile )
 246:           icc = ((PNGICCProfile)chunks.elementAt(i));
 247:         else if(chunks.elementAt(i) instanceof PNGGamma )
 248:           gamma = ((PNGGamma)chunks.elementAt(i));
 249:       }
 250: 
 251:     if( icc != null )
 252:       return icc.getColorSpace();
 253: //     if( gamma != null && !header.isGrayscale())
 254: //       return gamma.getColorSpace( header.isGrayscale() );
 255:     return null;
 256:   }
 257: }