Back to index

moin  1.9.0~rc2
PngEncoderIndexed.java
Go to the documentation of this file.
00001 package com.keypoint;
00002 
00003 import java.awt.Image;
00004 import java.awt.image.ImageObserver;
00005 import java.awt.image.PixelGrabber;
00006 import java.io.ByteArrayOutputStream;
00007 import java.io.IOException;
00008 import java.util.zip.CRC32;
00009 import java.util.zip.Deflater;
00010 import java.util.zip.DeflaterOutputStream;
00011 import java.util.*;
00012 import java.io.*;
00013 
00055 public class PngEncoderIndexed extends Object {
00056 
00058     protected static final byte IHDR[] = {73, 72, 68, 82};
00059     
00061     protected static final byte PLTE[] = {80, 76, 84, 69};
00062 
00064     protected static final byte tRNS[] = {116, 82, 78, 83};
00065 
00067     protected static final byte IDAT[] = {73, 68, 65, 84};
00068     
00070     protected static final byte IEND[] = {73, 69, 78, 68};
00071 
00072        protected static final int DEFAULT_COMPRESSION = 5;
00073 
00075     protected byte[] pngBytes;
00076 
00078     protected Image image;
00079 
00081     protected int width, height;
00082 
00084     protected int bytePos, maxPos;
00085 
00087     protected CRC32 crc = new CRC32();
00088 
00090     protected long crcValue;
00091 
00093     protected int compressionLevel;
00094 
00098     public PngEncoderIndexed() {
00099         this(null, DEFAULT_COMPRESSION);
00100     }
00101 
00108     public PngEncoderIndexed(Image image) {
00109         this(image, DEFAULT_COMPRESSION);
00110     }
00111 
00120     public PngEncoderIndexed(Image image, int compLevel) {
00121         this.image = image;
00122         if (compLevel >= 0 && compLevel <= 9) {
00123             this.compressionLevel = compLevel;
00124         }
00125     }
00126 
00134     public void setImage(Image image) {
00135         this.image = image;
00136         pngBytes = null;
00137     }
00138 
00144     public byte[] pngEncode() {
00145         byte[]  pngIdBytes = {-119, 80, 78, 71, 13, 10, 26, 10};
00146 
00147         if (image == null) {
00148             return null;
00149         }
00150         width = image.getWidth(null);
00151         height = image.getHeight(null);
00152 
00153         /*
00154          * start with an array that is big enough to hold all the pixels
00155          * (plus filter bytes), and an extra 200 bytes for header info
00156          */
00157         pngBytes = new byte[((width + 1) * height * 3) + 200];
00158 
00159         /*
00160          * keep track of largest byte written to the array
00161          */
00162         maxPos = 0;
00163 
00164         bytePos = writeBytes(pngIdBytes, 0);
00165         //hdrPos = bytePos;
00166         writeHeader();
00167         //dataPos = bytePos;
00168         if (writeImageData()) {
00169             writeEnd();
00170             pngBytes = resizeByteArray(pngBytes, maxPos);
00171         }
00172         else {
00173             pngBytes = null;
00174         }
00175         return pngBytes;
00176     }
00177 
00183     public void setCompressionLevel(int level) {
00184         if (level >= 0 && level <= 9) {
00185             this.compressionLevel = level;
00186         }
00187     }
00188 
00194     public int getCompressionLevel() {
00195         return compressionLevel;
00196     }
00197 
00206     protected byte[] resizeByteArray(byte[] array, int newLength) {
00207         byte[]  newArray = new byte[newLength];
00208         int     oldLength = array.length;
00209 
00210         System.arraycopy(array, 0, newArray, 0, Math.min(oldLength, newLength));
00211         return newArray;
00212     }
00213 
00225     protected int writeBytes(byte[] data, int offset) {
00226         maxPos = Math.max(maxPos, offset + data.length);
00227         if (data.length + offset > pngBytes.length) {
00228             pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, data.length));
00229         }
00230         System.arraycopy(data, 0, pngBytes, offset, data.length);
00231         return offset + data.length;
00232     }
00233 
00246     protected int writeBytes(byte[] data, int nBytes, int offset) {
00247         maxPos = Math.max(maxPos, offset + nBytes);
00248         if (nBytes + offset > pngBytes.length) {
00249             pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, nBytes));
00250         }
00251         System.arraycopy(data, 0, pngBytes, offset, nBytes);
00252         return offset + nBytes;
00253     }
00254 
00262     protected int writeInt2(int n, int offset) {
00263         byte[] temp = {(byte) ((n >> 8) & 0xff), (byte) (n & 0xff)};
00264         return writeBytes(temp, offset);
00265     }
00266 
00274     protected int writeInt4(int n, int offset) {
00275         byte[] temp = {(byte) ((n >> 24) & 0xff),
00276                        (byte) ((n >> 16) & 0xff),
00277                        (byte) ((n >> 8) & 0xff),
00278                        (byte) (n & 0xff)};
00279         return writeBytes(temp, offset);
00280     }
00281 
00289     protected int writeByte(int b, int offset) {
00290         byte[] temp = {(byte) b};
00291         return writeBytes(temp, offset);
00292     }
00293 
00297     protected void writeHeader() {
00298         int startPos;
00299 
00300         startPos = bytePos = writeInt4(13, bytePos);
00301         bytePos = writeBytes(IHDR, bytePos);
00302         width = image.getWidth(null);
00303         height = image.getHeight(null);
00304         bytePos = writeInt4(width, bytePos);
00305         bytePos = writeInt4(height, bytePos);
00306         bytePos = writeByte(8, bytePos); // bit depth
00307         bytePos = writeByte(3, bytePos); // palette
00308         bytePos = writeByte(0, bytePos); // compression method
00309         bytePos = writeByte(0, bytePos); // filter method
00310         bytePos = writeByte(0, bytePos); // no interlace
00311         crc.reset();
00312         crc.update(pngBytes, startPos, bytePos - startPos);
00313         crcValue = crc.getValue();
00314         bytePos = writeInt4((int) crcValue, bytePos);
00315     }
00316 
00326     protected boolean writeImageData() {
00327         int rowsLeft;  // number of rows remaining to write
00328         int startRow;       // starting row to process this time through
00329         int nRows;              // how many rows to grab at a time
00330 
00331         byte[] scanLines;       // the scan lines to be compressed
00332         int scanPos;            // where we are in the scan lines
00333         int startPos;           // where this line's actual pixels start (used for filtering)
00334 
00335         byte[] compressedLines; // the resultant compressed lines
00336         int nCompressed;        // how big is the compressed area?
00337 
00338         //int depth;              // color depth ( handle only 8 or 32 )
00339 
00340               // palette without transparency (TWikiDrawPlugin does not need it)
00341               Acme.IntHashtable palette = new Acme.IntHashtable();
00342               PngEncoderHashitem item;
00343               int paletteIndex = 0, transIndex = -1, transRGBA = 0;
00344 
00345         PixelGrabber pg;
00346 
00347         Deflater scrunch = new Deflater(compressionLevel);
00348         ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024);
00349 
00350         DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes, scrunch);
00351         try {
00352                      /*
00353                       * create palette for image
00354                       */
00355                      rowsLeft = height;
00356                      startRow = 0;
00357             while (rowsLeft > 0) {
00358                 nRows = Math.min(64000 / (width * 4), rowsLeft);
00359                 nRows = Math.max(nRows, 1);
00360 
00361                 int[] pixels = new int[width * nRows];
00362 
00363                 pg = new PixelGrabber(image, 0, startRow, width, nRows,
00364                                                                  pixels, 0, width);
00365                 try {
00366                     pg.grabPixels();
00367                 }
00368                 catch (Exception e) {
00369                     System.err.println("interrupted waiting for pixels!");
00370                     return false;
00371                 }
00372                 if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
00373                     System.err.println("image fetch aborted or errored");
00374                     return false;
00375                 }
00376 
00377                 scanPos = 0;
00378                 startPos = 1;
00379                 for (int i = 0; i < width * nRows; i++) {
00380                                    int rgba = pixels[i];
00381                                    item = (PngEncoderHashitem) palette.get( rgba );
00382                                    if (item == null) {
00383                                           if (paletteIndex >= 256)
00384                                                  throw new IOException("too many colors for a PNG");
00385                                           item = new PngEncoderHashitem(rgba, 1, paletteIndex, false);
00386                                           palette.put( rgba, item );
00387                                           ++paletteIndex;
00388                                    } else
00389                                           ++item.count;
00390                 }
00391 
00392                 startRow += nRows;
00393                 rowsLeft -= nRows;
00394             }
00395 
00396                      /*
00397                       * write PLTE chunk
00398                       */
00399             crc.reset();
00400             bytePos = writeInt4(paletteIndex * 3, bytePos); // length
00401             bytePos = writeBytes(PLTE, bytePos);            // magic
00402             crc.update(PLTE);
00403                      // write palette data
00404                      byte[] pltedata = new byte[paletteIndex * 3];
00405                      for (Enumeration e = palette.elements(); e.hasMoreElements();) {
00406                             item = (PngEncoderHashitem) e.nextElement();
00407                             pltedata[item.index*3]=(byte) ((item.rgba>>16)&0xff);
00408                             pltedata[item.index*3+1]=(byte) ((item.rgba>> 8)&0xff);
00409                             pltedata[item.index*3+2]=(byte) ( item.rgba     &0xff);
00410                      }
00411                      bytePos = writeBytes(pltedata, bytePos);
00412                      crc.update(pltedata);
00413                      // add crc
00414             crcValue = crc.getValue();
00415             bytePos = writeInt4((int) crcValue, bytePos);
00416                      
00417                      /*
00418                       * create scanline with palette indices
00419                       */
00420                      rowsLeft = height;
00421                      startRow = 0;
00422             while (rowsLeft > 0) {
00423                 nRows = Math.min(64000 / (width * 4), rowsLeft);
00424                 nRows = Math.max(nRows, 1);
00425 
00426                 int[] pixels = new int[width * nRows];
00427 
00428                 pg = new PixelGrabber(image, 0, startRow,
00429                     width, nRows, pixels, 0, width);
00430                 try {
00431                     pg.grabPixels();
00432                 }
00433                 catch (Exception e) {
00434                     System.err.println("interrupted waiting for pixels!");
00435                     return false;
00436                 }
00437                 if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
00438                     System.err.println("image fetch aborted or errored");
00439                     return false;
00440                 }
00441 
00442                 /*
00443                  * Create a data chunk. scanLines adds "nRows" for
00444                  * the filter bytes.
00445                  */
00446                 scanLines = new byte[width * nRows + nRows];
00447                             
00448                 scanPos = 0;
00449                 startPos = 1;
00450                 for (int i = 0; i < width * nRows; i++) {
00451                     if (i % width == 0) {
00452                         scanLines[scanPos++] = (byte) 0; /* no filter */
00453                         startPos = scanPos;
00454                     }
00455                                    item = (PngEncoderHashitem) palette.get( pixels[i] );
00456                                    if (item == null)
00457                                           throw new IOException( "color not found" );
00458                                    scanLines[scanPos++] = (byte) item.index;
00459                 }
00460                             
00461                 /*
00462                  * Write these lines to the output area
00463                  */
00464                 compBytes.write(scanLines, 0, scanPos);
00465                             
00466                 startRow += nRows;
00467                 rowsLeft -= nRows;
00468             }
00469             compBytes.close();
00470                      
00471             /*
00472              * Write the compressed bytes
00473              */
00474             compressedLines = outBytes.toByteArray();
00475             nCompressed = compressedLines.length;
00476 
00477             crc.reset();
00478             bytePos = writeInt4(nCompressed, bytePos);
00479             bytePos = writeBytes(IDAT, bytePos);
00480             crc.update(IDAT);
00481             bytePos = writeBytes(compressedLines, nCompressed, bytePos);
00482             crc.update(compressedLines, 0, nCompressed);
00483 
00484             crcValue = crc.getValue();
00485             bytePos = writeInt4((int) crcValue, bytePos);
00486             scrunch.finish();
00487             return true;
00488         }
00489         catch (IOException e) {
00490             System.err.println(e.toString());
00491             return false;
00492         }
00493     }
00494 
00498     protected void writeEnd() {
00499         bytePos = writeInt4(0, bytePos);
00500         bytePos = writeBytes(IEND, bytePos);
00501         crc.reset();
00502         crc.update(IEND);
00503         crcValue = crc.getValue();
00504         bytePos = writeInt4((int) crcValue, bytePos);
00505     }
00506 
00507 }
00508 
00509 class PngEncoderHashitem {
00510        
00511     public int rgba;
00512     public int count;
00513     public int index;
00514     public boolean isTrans;
00515        
00516     public PngEncoderHashitem(int rgba, int count, int index, boolean isTrans) {
00517               this.rgba = rgba;
00518               this.count = count;
00519               this.index = index;
00520               this.isTrans = isTrans;
00521        }
00522 }