Back to index

moin  1.9.0~rc2
PngEncoder.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 
00053 public class PngEncoder extends Object {
00054 
00056     public static final boolean ENCODE_ALPHA = true;
00057 
00059     public static final boolean NO_ALPHA = false;
00060 
00062     public static final int FILTER_NONE = 0;
00063 
00065     public static final int FILTER_SUB = 1;
00066 
00068     public static final int FILTER_UP = 2;
00069 
00071     public static final int FILTER_LAST = 2;
00072     
00074     protected static final byte IHDR[] = {73, 72, 68, 82};
00075     
00077     protected static final byte IDAT[] = {73, 68, 65, 84};
00078     
00080     protected static final byte IEND[] = {73, 69, 78, 68};
00081 
00083     protected byte[] pngBytes;
00084 
00086     protected byte[] priorRow;
00087 
00089     protected byte[] leftBytes;
00090 
00092     protected Image image;
00093 
00095     protected int width, height;
00096 
00098     protected int bytePos, maxPos;
00099 
00101     protected CRC32 crc = new CRC32();
00102 
00104     protected long crcValue;
00105 
00107     protected boolean encodeAlpha;
00108 
00110     protected int filter;
00111 
00113     protected int bytesPerPixel;
00114 
00116     protected int compressionLevel;
00117 
00121     public PngEncoder() {
00122         this(null, false, FILTER_NONE, 0);
00123     }
00124 
00131     public PngEncoder(Image image) {
00132         this(image, false, FILTER_NONE, 0);
00133     }
00134 
00142     public PngEncoder(Image image, boolean encodeAlpha) {
00143         this(image, encodeAlpha, FILTER_NONE, 0);
00144     }
00145 
00154     public PngEncoder(Image image, boolean encodeAlpha, int whichFilter) {
00155         this(image, encodeAlpha, whichFilter, 0);
00156     }
00157 
00158 
00169     public PngEncoder(Image image, boolean encodeAlpha, int whichFilter, int compLevel) {
00170         this.image = image;
00171         this.encodeAlpha = encodeAlpha;
00172         setFilter(whichFilter);
00173         if (compLevel >= 0 && compLevel <= 9) {
00174             this.compressionLevel = compLevel;
00175         }
00176     }
00177 
00185     public void setImage(Image image) {
00186         this.image = image;
00187         pngBytes = null;
00188     }
00189 
00197     public byte[] pngEncode(boolean encodeAlpha) {
00198         byte[]  pngIdBytes = {-119, 80, 78, 71, 13, 10, 26, 10};
00199 
00200         if (image == null) {
00201             return null;
00202         }
00203         width = image.getWidth(null);
00204         height = image.getHeight(null);
00205 
00206         /*
00207          * start with an array that is big enough to hold all the pixels
00208          * (plus filter bytes), and an extra 200 bytes for header info
00209          */
00210         pngBytes = new byte[((width + 1) * height * 3) + 200];
00211 
00212         /*
00213          * keep track of largest byte written to the array
00214          */
00215         maxPos = 0;
00216 
00217         bytePos = writeBytes(pngIdBytes, 0);
00218         //hdrPos = bytePos;
00219         writeHeader();
00220         //dataPos = bytePos;
00221         if (writeImageData()) {
00222             writeEnd();
00223             pngBytes = resizeByteArray(pngBytes, maxPos);
00224         }
00225         else {
00226             pngBytes = null;
00227         }
00228         return pngBytes;
00229     }
00230 
00237     public byte[] pngEncode() {
00238         return pngEncode(encodeAlpha);
00239     }
00240 
00246     public void setEncodeAlpha(boolean encodeAlpha) {
00247         this.encodeAlpha = encodeAlpha;
00248     }
00249 
00255     public boolean getEncodeAlpha() {
00256         return encodeAlpha;
00257     }
00258 
00264     public void setFilter(int whichFilter) {
00265         this.filter = FILTER_NONE;
00266         if (whichFilter <= FILTER_LAST) {
00267             this.filter = whichFilter;
00268         }
00269     }
00270 
00276     public int getFilter() {
00277         return filter;
00278     }
00279 
00285     public void setCompressionLevel(int level) {
00286         if (level >= 0 && level <= 9) {
00287             this.compressionLevel = level;
00288         }
00289     }
00290 
00296     public int getCompressionLevel() {
00297         return compressionLevel;
00298     }
00299 
00308     protected byte[] resizeByteArray(byte[] array, int newLength) {
00309         byte[]  newArray = new byte[newLength];
00310         int     oldLength = array.length;
00311 
00312         System.arraycopy(array, 0, newArray, 0, Math.min(oldLength, newLength));
00313         return newArray;
00314     }
00315 
00327     protected int writeBytes(byte[] data, int offset) {
00328         maxPos = Math.max(maxPos, offset + data.length);
00329         if (data.length + offset > pngBytes.length) {
00330             pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, data.length));
00331         }
00332         System.arraycopy(data, 0, pngBytes, offset, data.length);
00333         return offset + data.length;
00334     }
00335 
00348     protected int writeBytes(byte[] data, int nBytes, int offset) {
00349         maxPos = Math.max(maxPos, offset + nBytes);
00350         if (nBytes + offset > pngBytes.length) {
00351             pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, nBytes));
00352         }
00353         System.arraycopy(data, 0, pngBytes, offset, nBytes);
00354         return offset + nBytes;
00355     }
00356 
00364     protected int writeInt2(int n, int offset) {
00365         byte[] temp = {(byte) ((n >> 8) & 0xff), (byte) (n & 0xff)};
00366         return writeBytes(temp, offset);
00367     }
00368 
00376     protected int writeInt4(int n, int offset) {
00377         byte[] temp = {(byte) ((n >> 24) & 0xff),
00378                        (byte) ((n >> 16) & 0xff),
00379                        (byte) ((n >> 8) & 0xff),
00380                        (byte) (n & 0xff)};
00381         return writeBytes(temp, offset);
00382     }
00383 
00391     protected int writeByte(int b, int offset) {
00392         byte[] temp = {(byte) b};
00393         return writeBytes(temp, offset);
00394     }
00395 
00399     protected void writeHeader() {
00400         int startPos;
00401 
00402         startPos = bytePos = writeInt4(13, bytePos);
00403         bytePos = writeBytes(IHDR, bytePos);
00404         width = image.getWidth(null);
00405         height = image.getHeight(null);
00406         bytePos = writeInt4(width, bytePos);
00407         bytePos = writeInt4(height, bytePos);
00408         bytePos = writeByte(8, bytePos); // bit depth
00409         bytePos = writeByte((encodeAlpha) ? 6 : 2, bytePos); // direct model
00410         bytePos = writeByte(0, bytePos); // compression method
00411         bytePos = writeByte(0, bytePos); // filter method
00412         bytePos = writeByte(0, bytePos); // no interlace
00413         crc.reset();
00414         crc.update(pngBytes, startPos, bytePos - startPos);
00415         crcValue = crc.getValue();
00416         bytePos = writeInt4((int) crcValue, bytePos);
00417     }
00418 
00429     protected void filterSub(byte[] pixels, int startPos, int width) {
00430         int i;
00431         int offset = bytesPerPixel;
00432         int actualStart = startPos + offset;
00433         int nBytes = width * bytesPerPixel;
00434         int leftInsert = offset;
00435         int leftExtract = 0;
00436 
00437         for (i = actualStart; i < startPos + nBytes; i++) {
00438             leftBytes[leftInsert] =  pixels[i];
00439             pixels[i] = (byte) ((pixels[i] - leftBytes[leftExtract]) % 256);
00440             leftInsert = (leftInsert + 1) % 0x0f;
00441             leftExtract = (leftExtract + 1) % 0x0f;
00442         }
00443     }
00444 
00453     protected void filterUp(byte[] pixels, int startPos, int width) {
00454         int     i, nBytes;
00455         byte    currentByte;
00456 
00457         nBytes = width * bytesPerPixel;
00458 
00459         for (i = 0; i < nBytes; i++) {
00460             currentByte = pixels[startPos + i];
00461             pixels[startPos + i] = (byte) ((pixels[startPos  + i] - priorRow[i]) % 256);
00462             priorRow[i] = currentByte;
00463         }
00464     }
00465 
00475     protected boolean writeImageData() {
00476         int rowsLeft = height;  // number of rows remaining to write
00477         int startRow = 0;       // starting row to process this time through
00478         int nRows;              // how many rows to grab at a time
00479 
00480         byte[] scanLines;       // the scan lines to be compressed
00481         int scanPos;            // where we are in the scan lines
00482         int startPos;           // where this line's actual pixels start (used for filtering)
00483 
00484         byte[] compressedLines; // the resultant compressed lines
00485         int nCompressed;        // how big is the compressed area?
00486 
00487         //int depth;              // color depth ( handle only 8 or 32 )
00488 
00489         PixelGrabber pg;
00490 
00491         bytesPerPixel = (encodeAlpha) ? 4 : 3;
00492 
00493         Deflater scrunch = new Deflater(compressionLevel);
00494         ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024);
00495 
00496         DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes, scrunch);
00497         try {
00498             while (rowsLeft > 0) {
00499                 nRows = Math.min(32767 / (width * (bytesPerPixel + 1)), rowsLeft);
00500                 nRows = Math.max( nRows, 1 );
00501 
00502                 int[] pixels = new int[width * nRows];
00503 
00504                 pg = new PixelGrabber(image, 0, startRow,
00505                     width, nRows, pixels, 0, width);
00506                 try {
00507                     pg.grabPixels();
00508                 }
00509                 catch (Exception e) {
00510                     System.err.println("interrupted waiting for pixels!");
00511                     return false;
00512                 }
00513                 if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
00514                     System.err.println("image fetch aborted or errored");
00515                     return false;
00516                 }
00517 
00518                 /*
00519                  * Create a data chunk. scanLines adds "nRows" for
00520                  * the filter bytes.
00521                  */
00522                 scanLines = new byte[width * nRows * bytesPerPixel +  nRows];
00523 
00524                 if (filter == FILTER_SUB) {
00525                     leftBytes = new byte[16];
00526                 }
00527                 if (filter == FILTER_UP) {
00528                     priorRow = new byte[width * bytesPerPixel];
00529                 }
00530 
00531                 scanPos = 0;
00532                 startPos = 1;
00533                 for (int i = 0; i < width * nRows; i++) {
00534                     if (i % width == 0) {
00535                         scanLines[scanPos++] = (byte) filter;
00536                         startPos = scanPos;
00537                     }
00538                     scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff);
00539                     scanLines[scanPos++] = (byte) ((pixels[i] >>  8) & 0xff);
00540                     scanLines[scanPos++] = (byte) ((pixels[i]) & 0xff);
00541                     if (encodeAlpha) {
00542                         scanLines[scanPos++] = (byte) ((pixels[i] >> 24) & 0xff);
00543                     }
00544                     if ((i % width == width - 1) && (filter != FILTER_NONE)) {
00545                         if (filter == FILTER_SUB) {
00546                             filterSub(scanLines, startPos, width);
00547                         }
00548                         if (filter == FILTER_UP) {
00549                             filterUp(scanLines, startPos, width);
00550                         }
00551                     }
00552                 }
00553 
00554                 /*
00555                  * Write these lines to the output area
00556                  */
00557                 compBytes.write(scanLines, 0, scanPos);
00558 
00559                 startRow += nRows;
00560                 rowsLeft -= nRows;
00561             }
00562             compBytes.close();
00563 
00564             /*
00565              * Write the compressed bytes
00566              */
00567             compressedLines = outBytes.toByteArray();
00568             nCompressed = compressedLines.length;
00569 
00570             crc.reset();
00571             bytePos = writeInt4(nCompressed, bytePos);
00572             bytePos = writeBytes(IDAT, bytePos);
00573             crc.update(IDAT);
00574             bytePos = writeBytes(compressedLines, nCompressed, bytePos);
00575             crc.update(compressedLines, 0, nCompressed);
00576 
00577             crcValue = crc.getValue();
00578             bytePos = writeInt4((int) crcValue, bytePos);
00579             scrunch.finish();
00580             return true;
00581         }
00582         catch (IOException e) {
00583             System.err.println(e.toString());
00584             return false;
00585         }
00586     }
00587 
00591     protected void writeEnd() {
00592         bytePos = writeInt4(0, bytePos);
00593         bytePos = writeBytes(IEND, bytePos);
00594         crc.reset();
00595         crc.update(IEND);
00596         crcValue = crc.getValue();
00597         bytePos = writeInt4((int) crcValue, bytePos);
00598     }
00599 
00600 }