Back to index

wims  3.65+svn20090927
PngEncoder.java
Go to the documentation of this file.
00001 package rene.util;
00002 
00036 import java.awt.Image;
00037 import java.awt.image.ImageObserver;
00038 import java.awt.image.PixelGrabber;
00039 import java.io.ByteArrayOutputStream;
00040 import java.io.IOException;
00041 import java.util.zip.CRC32;
00042 import java.util.zip.Deflater;
00043 import java.util.zip.DeflaterOutputStream;
00044 
00045 public class PngEncoder extends Object
00046 {
00048     public static final boolean ENCODE_ALPHA=true;
00050     public static final boolean NO_ALPHA=false;
00052     public static final int FILTER_NONE = 0;
00053     public static final int FILTER_SUB = 1;
00054     public static final int FILTER_UP = 2;
00055     public static final int FILTER_LAST = 2;
00056 
00057     protected byte[] pngBytes;
00058     protected byte[] priorRow;
00059     protected byte[] leftBytes;
00060     protected Image image;
00061     protected int width, height;
00062     protected int bytePos, maxPos;
00063     protected int hdrPos, dataPos, endPos;
00064     protected CRC32 crc = new CRC32();
00065     protected long crcValue;
00066     protected boolean encodeAlpha;
00067     protected int filter;
00068     protected int bytesPerPixel;
00069     protected int compressionLevel;
00070     protected double DPI=300;
00071 
00076     public PngEncoder()
00077     {
00078         this( null, false, FILTER_NONE, 0 );
00079     }
00080 
00087     public PngEncoder( Image image )
00088     {
00089         this(image, false, FILTER_NONE, 0);
00090     }
00091 
00099     public PngEncoder( Image image, boolean encodeAlpha )
00100     {
00101         this(image, encodeAlpha, FILTER_NONE, 0);
00102     }
00103 
00112     public PngEncoder( Image image, boolean encodeAlpha, int whichFilter )
00113     {
00114         this( image, encodeAlpha, whichFilter, 0 );
00115     }
00116 
00117 
00127     public PngEncoder( Image image, boolean encodeAlpha, int whichFilter,
00128        int compLevel)
00129     {
00130         this.image = image;
00131         this.encodeAlpha = encodeAlpha;
00132         setFilter( whichFilter );
00133         if (compLevel >=0 && compLevel <=9)
00134         {
00135             this.compressionLevel = compLevel;
00136         }
00137     }
00138 
00146     public void setImage( Image image )
00147     {
00148         this.image = image;
00149         pngBytes = null;
00150     }
00151     
00152     public void setDPI (double dpi)
00153     {  DPI=dpi;
00154     }
00155 
00162     public byte[] pngEncode( boolean encodeAlpha )
00163     {
00164         byte[]  pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 };
00165         int     i;
00166 
00167         if (image == null)
00168         {
00169             return null;
00170         }
00171         width = image.getWidth( null );
00172         height = image.getHeight( null );
00173         
00174         /*
00175          * start with an array that is big enough to hold all the pixels
00176          * (plus filter bytes), and an extra 200 bytes for header info
00177          */
00178         pngBytes = new byte[((width+1) * height * 3) + 200];
00179 
00180         /*
00181          * keep track of largest byte written to the array
00182          */
00183         maxPos = 0;
00184 
00185         bytePos = writeBytes( pngIdBytes, 0 );
00186         hdrPos = bytePos;
00187         writeHeader();
00188               writePhys();
00189               dataPos = bytePos;
00190         if (writeImageData())
00191         {
00192                      writeEnd();
00193             pngBytes = resizeByteArray( pngBytes, maxPos );
00194         }
00195         else
00196         {
00197             pngBytes = null;
00198         }
00199         return pngBytes;
00200     }
00201 
00208     public byte[] pngEncode()
00209     {
00210         return pngEncode( encodeAlpha );
00211     }
00212 
00218     public void setEncodeAlpha( boolean encodeAlpha )
00219     {
00220         this.encodeAlpha = encodeAlpha;
00221     }
00222 
00228     public boolean getEncodeAlpha()
00229     {
00230         return encodeAlpha;
00231     }
00232 
00238     public void setFilter( int whichFilter )
00239     {
00240         this.filter = FILTER_NONE;
00241         if ( whichFilter <= FILTER_LAST )
00242         {
00243             this.filter = whichFilter;
00244         }
00245     }
00246 
00252     public int getFilter()
00253     {
00254         return filter;
00255     }
00256     
00262     public void setCompressionLevel( int level )
00263     {
00264         if ( level >= 0 && level <= 9)
00265         {
00266             this.compressionLevel = level;
00267         }
00268     }
00269 
00275     public int getCompressionLevel()
00276     {
00277         return compressionLevel;
00278     }
00279 
00288     protected byte[] resizeByteArray( byte[] array, int newLength )
00289     {
00290         byte[]  newArray = new byte[newLength];
00291         int     oldLength = array.length;
00292 
00293         System.arraycopy( array, 0, newArray, 0,
00294             Math.min( oldLength, newLength ) );
00295         return newArray;
00296     }
00297 
00309     protected int writeBytes( byte[] data, int offset )
00310     {
00311         maxPos = Math.max( maxPos, offset + data.length );
00312         if (data.length + offset > pngBytes.length)
00313         {
00314             pngBytes = resizeByteArray( pngBytes, pngBytes.length +
00315                 Math.max( 1000, data.length ) );
00316         }
00317         System.arraycopy( data, 0, pngBytes, offset, data.length );
00318         return offset + data.length;
00319     }
00320 
00333     protected int writeBytes( byte[] data, int nBytes, int offset )
00334     {
00335         maxPos = Math.max( maxPos, offset + nBytes );
00336         if (nBytes + offset > pngBytes.length)
00337         {
00338             pngBytes = resizeByteArray( pngBytes, pngBytes.length +
00339                 Math.max( 1000, nBytes ) );
00340         }
00341         System.arraycopy( data, 0, pngBytes, offset, nBytes );
00342         return offset + nBytes;
00343     }
00344 
00352     protected int writeInt2( int n, int offset )
00353     {
00354         byte[] temp = { (byte)((n >> 8) & 0xff),
00355             (byte) (n & 0xff) };
00356         return writeBytes( temp, offset );
00357     }
00358 
00366     protected int writeInt4( int n, int offset )
00367     {
00368         byte[] temp = { (byte)((n >> 24) & 0xff),
00369             (byte) ((n >> 16) & 0xff ),
00370             (byte) ((n >> 8) & 0xff ),
00371             (byte) ( n & 0xff ) };
00372         return writeBytes( temp, offset );
00373     }
00374 
00382     protected int writeByte( int b, int offset )
00383     {
00384         byte[] temp = { (byte) b };
00385         return writeBytes( temp, offset );
00386     }
00387 
00398     protected int writeString( String s, int offset )
00399     {
00400         return writeBytes( s.getBytes(), offset );
00401     }
00402 
00406     protected void writeHeader()
00407     {
00408         int startPos;
00409 
00410         startPos = bytePos = writeInt4( 13, bytePos );
00411         bytePos = writeString( "IHDR", bytePos );
00412         width = image.getWidth( null );
00413         height = image.getHeight( null );
00414         bytePos = writeInt4( width, bytePos );
00415         bytePos = writeInt4( height, bytePos );
00416         bytePos = writeByte( 8, bytePos ); // bit depth
00417         bytePos = writeByte( (encodeAlpha) ? 6 : 2, bytePos ); // direct model
00418         bytePos = writeByte( 0, bytePos ); // compression method
00419         bytePos = writeByte( 0, bytePos ); // filter method
00420         bytePos = writeByte( 0, bytePos ); // no interlace
00421         crc.reset();
00422         crc.update( pngBytes, startPos, bytePos-startPos );
00423         crcValue = crc.getValue();
00424         bytePos = writeInt4( (int) crcValue, bytePos );
00425     }
00426 
00430        protected void writePhys()
00431        {
00432               int startPos;
00433 
00434               startPos = bytePos = writeInt4( 9, bytePos );
00435               bytePos = writeString( "pHYs", bytePos );
00436               int dots=(int)(DPI*39.37);
00437               bytePos = writeInt4( dots, bytePos );
00438               bytePos = writeInt4( dots, bytePos );
00439               bytePos = writeByte( 1, bytePos ); // bit depth
00440               crc.reset();
00441               crc.update( pngBytes, startPos, bytePos-startPos );
00442               crcValue = crc.getValue();
00443               bytePos = writeInt4( (int) crcValue, bytePos );
00444        }
00445 
00456     protected void filterSub( byte[] pixels, int startPos, int width )
00457     {
00458         int i;
00459         int offset = bytesPerPixel;
00460         int actualStart = startPos + offset;
00461         int nBytes = width * bytesPerPixel;
00462         int leftInsert = offset;
00463         int leftExtract = 0;
00464         byte current_byte;
00465 
00466         for (i=actualStart; i < startPos + nBytes; i++)
00467         {
00468             leftBytes[leftInsert] =  pixels[i];
00469             pixels[i] = (byte) ((pixels[i] - leftBytes[leftExtract]) % 256);
00470             leftInsert = (leftInsert+1) % 0x0f;
00471             leftExtract = (leftExtract + 1) % 0x0f;
00472         }
00473     }
00474 
00483     protected void filterUp( byte[] pixels, int startPos, int width )
00484     {
00485         int     i, nBytes;
00486         byte    current_byte;
00487 
00488         nBytes = width * bytesPerPixel;
00489 
00490         for (i=0; i < nBytes; i++)
00491         {
00492             current_byte = pixels[startPos + i];
00493             pixels[startPos + i] = (byte) ((pixels[startPos  + i] - priorRow[i]) % 256);
00494             priorRow[i] = current_byte;
00495         }
00496     }
00497 
00507     protected boolean writeImageData()
00508     {
00509         int rowsLeft = height;  // number of rows remaining to write
00510         int startRow = 0;       // starting row to process this time through
00511         int nRows;              // how many rows to grab at a time
00512 
00513         byte[] scanLines;       // the scan lines to be compressed
00514         int scanPos;            // where we are in the scan lines
00515         int startPos;           // where this line's actual pixels start (used for filtering)
00516 
00517         byte[] compressedLines; // the resultant compressed lines
00518         int nCompressed;        // how big is the compressed area?
00519 
00520         int depth;              // color depth ( handle only 8 or 32 )
00521 
00522         PixelGrabber pg;
00523 
00524         bytesPerPixel = (encodeAlpha) ? 4 : 3;
00525 
00526         Deflater scrunch = new Deflater( compressionLevel );
00527         ByteArrayOutputStream outBytes = 
00528             new ByteArrayOutputStream(1024);
00529             
00530         DeflaterOutputStream compBytes =
00531             new DeflaterOutputStream( outBytes, scrunch );
00532         try
00533         {
00534               nRows = (64*32768-1) / (width*(bytesPerPixel+1));
00535             
00536               int[] pixels = new int[width * nRows];
00537 
00538             while (rowsLeft > 0)
00539             {
00540               if (nRows>=rowsLeft) nRows=rowsLeft;
00541               
00542                  pg = new PixelGrabber(image, 0, startRow,
00543                     width, nRows, pixels, 0, width);
00544                 try {
00545                     pg.grabPixels();
00546                 }
00547                 catch (Exception e) {
00548                     System.err.println("interrupted waiting for pixels!");
00549                     return false;
00550                 }
00551                 if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
00552                     System.err.println("image fetch aborted or errored");
00553                     return false;
00554                 }
00555 
00556                 /*
00557                  * Create a data chunk. scanLines adds "nRows" for
00558                  * the filter bytes. 
00559                  */
00560                 scanLines = new byte[width * nRows * bytesPerPixel +  nRows];
00561 
00562                 if (filter == FILTER_SUB)
00563                 {
00564                     leftBytes = new byte[16];
00565                 }
00566                 if (filter == FILTER_UP)
00567                 {
00568                     priorRow = new byte[width*bytesPerPixel];
00569                 }
00570 
00571                 scanPos = 0;
00572                 startPos = 1;
00573                 for (int i=0; i<width*nRows; i++)
00574                 {
00575                     if (i % width == 0)
00576                     {
00577                         scanLines[scanPos++] = (byte) filter; 
00578                         startPos = scanPos;
00579                     }
00580                     scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff);
00581                     scanLines[scanPos++] = (byte) ((pixels[i] >>  8) & 0xff);
00582                     scanLines[scanPos++] = (byte) ((pixels[i]      ) & 0xff);
00583                     if (encodeAlpha)
00584                     {
00585                         scanLines[scanPos++] = (byte) ((pixels[i] >> 24) & 0xff );
00586                     }
00587                     if ((i % width == width-1) && (filter != FILTER_NONE))
00588                     {
00589                         if (filter == FILTER_SUB)
00590                         {
00591                             filterSub( scanLines, startPos, width );
00592                         }
00593                         if (filter == FILTER_UP)
00594                         {
00595                             filterUp( scanLines, startPos, width );
00596                         }
00597                     }
00598                 }
00599 
00600                 /*
00601                  * Write these lines to the output area
00602                  */
00603                 compBytes.write( scanLines, 0, scanPos );
00604 
00605 
00606                 startRow += nRows;
00607                 rowsLeft -= nRows;
00608             }
00609             compBytes.close();
00610 
00611             /*
00612              * Write the compressed bytes
00613              */
00614             compressedLines = outBytes.toByteArray();
00615             nCompressed = compressedLines.length;
00616 
00617             crc.reset();
00618             bytePos = writeInt4( nCompressed, bytePos );
00619             bytePos = writeString("IDAT", bytePos );
00620             crc.update("IDAT".getBytes());
00621             bytePos = writeBytes( compressedLines, nCompressed, bytePos );
00622             crc.update( compressedLines, 0, nCompressed );
00623 
00624             crcValue = crc.getValue();
00625             bytePos = writeInt4( (int) crcValue, bytePos );
00626             scrunch.finish();
00627             return true;
00628         }
00629         catch (IOException e)
00630         {
00631             System.err.println( e.toString());
00632             return false;
00633         }
00634     }
00635 
00639     protected void writeEnd()
00640     {
00641         bytePos = writeInt4( 0, bytePos );
00642         bytePos = writeString( "IEND", bytePos );
00643         crc.reset();
00644         crc.update("IEND".getBytes());
00645         crcValue = crc.getValue();
00646         bytePos = writeInt4( (int) crcValue, bytePos );
00647     }
00648 }
00649