home *** CD-ROM | disk | FTP | other *** search
/ Chip 2005 March / CMCD0305.ISO / Software / Shareware / Grafica / 3dpie / Servlet / encoder / PngEncoder.java < prev   
Text File  |  2003-02-05  |  20KB  |  622 lines

  1. package encoder;
  2.  
  3. /**
  4.  * PngEncoder takes a Java Image object and creates a byte string which can be saved as a PNG file.
  5.  * The Image is presumed to use the DirectColorModel.
  6.  *
  7.  * Thanks to Jay Denny at KeyPoint Software
  8.  *    http://www.keypoint.com/
  9.  * who let me develop this code on company time.
  10.  *
  11.  * You may contact me with (probably very-much-needed) improvements,
  12.  * comments, and bug fixes at:
  13.  *
  14.  *   david@catcode.com
  15.  *
  16.  * This library is free software; you can redistribute it and/or
  17.  * modify it under the terms of the GNU Lesser General Public
  18.  * License as published by the Free Software Foundation; either
  19.  * version 2.1 of the License, or (at your option) any later version.
  20.  *
  21.  * This library is distributed in the hope that it will be useful,
  22.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  24.  * Lesser General Public License for more details.
  25.  *
  26.  * You should have received a copy of the GNU Lesser General Public
  27.  * License along with this library; if not, write to the Free Software
  28.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  29.  * A copy of the GNU LGPL may be found at
  30.  * http://www.gnu.org/copyleft/lesser.html,
  31.  *
  32.  * @author J. David Eisenberg
  33.  * @version 1.4, 31 March 2000
  34.  */
  35.  
  36. import java.awt.*;
  37. import java.awt.image.*;
  38. import java.util.*;
  39. import java.util.zip.*;
  40. import java.io.*;
  41.  
  42. public class PngEncoder extends Object
  43. {
  44.     /** Constant specifying that alpha channel should be encoded. */
  45.     public static final boolean ENCODE_ALPHA=true;
  46.     /** Constant specifying that alpha channel should not be encoded. */
  47.     public static final boolean NO_ALPHA=false;
  48.     /** Constants for filters */
  49.     public static final int FILTER_NONE = 0;
  50.     public static final int FILTER_SUB = 1;
  51.     public static final int FILTER_UP = 2;
  52.     public static final int FILTER_LAST = 2;
  53.  
  54.     protected byte[] pngBytes;
  55.     protected byte[] priorRow;
  56.     protected byte[] leftBytes;
  57.     protected Image image;
  58.     protected int width, height;
  59.     protected int bytePos, maxPos;
  60.     protected int hdrPos, dataPos, endPos;
  61.     protected CRC32 crc = new CRC32();
  62.     protected long crcValue;
  63.     protected boolean encodeAlpha;
  64.     protected int filter;
  65.     protected int bytesPerPixel;
  66.     protected int compressionLevel;
  67.  
  68.     /**
  69.      * Class constructor
  70.      *
  71.      */
  72.     public PngEncoder()
  73.     {
  74.         this( null, false, FILTER_NONE, 0 );
  75.     }
  76.  
  77.     /**
  78.      * Class constructor specifying Image to encode, with no alpha channel encoding.
  79.      *
  80.      * @param image A Java Image object which uses the DirectColorModel
  81.      * @see java.awt.Image
  82.      */
  83.     public PngEncoder( Image image )
  84.     {
  85.         this(image, false, FILTER_NONE, 0);
  86.     }
  87.  
  88.     /**
  89.      * Class constructor specifying Image to encode, and whether to encode alpha.
  90.      *
  91.      * @param image A Java Image object which uses the DirectColorModel
  92.      * @param encodeAlpha Encode the alpha channel? false=no; true=yes
  93.      * @see java.awt.Image
  94.      */
  95.     public PngEncoder( Image image, boolean encodeAlpha )
  96.     {
  97.         this(image, encodeAlpha, FILTER_NONE, 0);
  98.     }
  99.  
  100.     /**
  101.      * Class constructor specifying Image to encode, whether to encode alpha, and filter to use.
  102.      *
  103.      * @param image A Java Image object which uses the DirectColorModel
  104.      * @param encodeAlpha Encode the alpha channel? false=no; true=yes
  105.      * @param whichFilter 0=none, 1=sub, 2=up
  106.      * @see java.awt.Image
  107.      */
  108.     public PngEncoder( Image image, boolean encodeAlpha, int whichFilter )
  109.     {
  110.         this( image, encodeAlpha, whichFilter, 0 );
  111.     }
  112.  
  113.  
  114.     /**
  115.      * Class constructor specifying Image source to encode, whether to encode alpha, filter to use, and compression level.
  116.      *
  117.      * @param image A Java Image object
  118.      * @param encodeAlpha Encode the alpha channel? false=no; true=yes
  119.      * @param whichFilter 0=none, 1=sub, 2=up
  120.      * @param compLevel 0..9
  121.      * @see java.awt.Image
  122.      */
  123.     public PngEncoder( Image image, boolean encodeAlpha, int whichFilter,
  124.        int compLevel)
  125.     {
  126.         this.image = image;
  127.         this.encodeAlpha = encodeAlpha;
  128.         setFilter( whichFilter );
  129.         if (compLevel >=0 && compLevel <=9)
  130.         {
  131.             this.compressionLevel = compLevel;
  132.         }
  133.     }
  134.  
  135.     /**
  136.      * Set the image to be encoded
  137.      *
  138.      * @param image A Java Image object which uses the DirectColorModel
  139.      * @see java.awt.Image
  140.      * @see java.awt.image.DirectColorModel
  141.      */
  142.     public void setImage( Image image )
  143.     {
  144.         this.image = image;
  145.         pngBytes = null;
  146.     }
  147.  
  148.     /**
  149.      * Creates an array of bytes that is the PNG equivalent of the current image, specifying whether to encode alpha or not.
  150.      *
  151.      * @param encodeAlpha boolean false=no alpha, true=encode alpha
  152.      * @return an array of bytes, or null if there was a problem
  153.      */
  154.     public byte[] pngEncode( boolean encodeAlpha )
  155.     {
  156.         byte[]  pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 };
  157.         int     i;
  158.  
  159.         if (image == null)
  160.         {
  161.             return null;
  162.         }
  163.         width = image.getWidth( null );
  164.         height = image.getHeight( null );
  165.         this.image = image;
  166.  
  167.         /*
  168.          * start with an array that is big enough to hold all the pixels
  169.          * (plus filter bytes), and an extra 200 bytes for header info
  170.          */
  171.         pngBytes = new byte[((width+1) * height * 3) + 200];
  172.  
  173.         /*
  174.          * keep track of largest byte written to the array
  175.          */
  176.         maxPos = 0;
  177.  
  178.         bytePos = writeBytes( pngIdBytes, 0 );
  179.         hdrPos = bytePos;
  180.         writeHeader();
  181.         dataPos = bytePos;
  182.         if (writeImageData())
  183.         {
  184.             writeEnd();
  185.             pngBytes = resizeByteArray( pngBytes, maxPos );
  186.         }
  187.         else
  188.         {
  189.             pngBytes = null;
  190.         }
  191.         return pngBytes;
  192.     }
  193.  
  194.     /**
  195.      * Creates an array of bytes that is the PNG equivalent of the current image.
  196.      * Alpha encoding is determined by its setting in the constructor.
  197.      *
  198.      * @return an array of bytes, or null if there was a problem
  199.      */
  200.     public byte[] pngEncode()
  201.     {
  202.         return pngEncode( encodeAlpha );
  203.     }
  204.  
  205.     /**
  206.      * Set the alpha encoding on or off.
  207.      *
  208.      * @param encodeAlpha  false=no, true=yes
  209.      */
  210.     public void setEncodeAlpha( boolean encodeAlpha )
  211.     {
  212.         this.encodeAlpha = encodeAlpha;
  213.     }
  214.  
  215.     /**
  216.      * Retrieve alpha encoding status.
  217.      *
  218.      * @return boolean false=no, true=yes
  219.      */
  220.     public boolean getEncodeAlpha()
  221.     {
  222.         return encodeAlpha;
  223.     }
  224.  
  225.     /**
  226.      * Set the filter to use
  227.      *
  228.      * @param whichFilter from constant list
  229.      */
  230.     public void setFilter( int whichFilter )
  231.     {
  232.         this.filter = FILTER_NONE;
  233.         if ( whichFilter <= FILTER_LAST )
  234.         {
  235.             this.filter = whichFilter;
  236.         }
  237.     }
  238.  
  239.     /**
  240.      * Retrieve filtering scheme
  241.      *
  242.      * @return int (see constant list)
  243.      */
  244.     public int getFilter()
  245.     {
  246.         return filter;
  247.     }
  248.  
  249.     /**
  250.      * Set the compression level to use
  251.      *
  252.      * @param level 0 through 9
  253.      */
  254.     public void setCompressionLevel( int level )
  255.     {
  256.         if ( level >= 0 && level <= 9)
  257.         {
  258.             this.compressionLevel = level;
  259.         }
  260.     }
  261.  
  262.     /**
  263.      * Retrieve compression level
  264.      *
  265.      * @return int in range 0-9
  266.      */
  267.     public int getCompressionLevel()
  268.     {
  269.         return compressionLevel;
  270.     }
  271.  
  272.     /**
  273.      * Increase or decrease the length of a byte array.
  274.      *
  275.      * @param array The original array.
  276.      * @param newLength The length you wish the new array to have.
  277.      * @return Array of newly desired length. If shorter than the
  278.      *         original, the trailing elements are truncated.
  279.      */
  280.     protected byte[] resizeByteArray( byte[] array, int newLength )
  281.     {
  282.         byte[]  newArray = new byte[newLength];
  283.         int     oldLength = array.length;
  284.  
  285.         System.arraycopy( array, 0, newArray, 0,
  286.             Math.min( oldLength, newLength ) );
  287.         return newArray;
  288.     }
  289.  
  290.     /**
  291.      * Write an array of bytes into the pngBytes array.
  292.      * Note: This routine has the side effect of updating
  293.      * maxPos, the largest element written in the array.
  294.      * The array is resized by 1000 bytes or the length
  295.      * of the data to be written, whichever is larger.
  296.      *
  297.      * @param data The data to be written into pngBytes.
  298.      * @param offset The starting point to write to.
  299.      * @return The next place to be written to in the pngBytes array.
  300.      */
  301.     protected int writeBytes( byte[] data, int offset )
  302.     {
  303.         maxPos = Math.max( maxPos, offset + data.length );
  304.         if (data.length + offset > pngBytes.length)
  305.         {
  306.             pngBytes = resizeByteArray( pngBytes, pngBytes.length +
  307.                 Math.max( 1000, data.length ) );
  308.         }
  309.         System.arraycopy( data, 0, pngBytes, offset, data.length );
  310.         return offset + data.length;
  311.     }
  312.  
  313.     /**
  314.      * Write an array of bytes into the pngBytes array, specifying number of bytes to write.
  315.      * Note: This routine has the side effect of updating
  316.      * maxPos, the largest element written in the array.
  317.      * The array is resized by 1000 bytes or the length
  318.      * of the data to be written, whichever is larger.
  319.      *
  320.      * @param data The data to be written into pngBytes.
  321.      * @param nBytes The number of bytes to be written.
  322.      * @param offset The starting point to write to.
  323.      * @return The next place to be written to in the pngBytes array.
  324.      */
  325.     protected int writeBytes( byte[] data, int nBytes, int offset )
  326.     {
  327.         maxPos = Math.max( maxPos, offset + nBytes );
  328.         if (nBytes + offset > pngBytes.length)
  329.         {
  330.             pngBytes = resizeByteArray( pngBytes, pngBytes.length +
  331.                 Math.max( 1000, nBytes ) );
  332.         }
  333.         System.arraycopy( data, 0, pngBytes, offset, nBytes );
  334.         return offset + nBytes;
  335.     }
  336.  
  337.     /**
  338.      * Write a two-byte integer into the pngBytes array at a given position.
  339.      *
  340.      * @param n The integer to be written into pngBytes.
  341.      * @param offset The starting point to write to.
  342.      * @return The next place to be written to in the pngBytes array.
  343.      */
  344.     protected int writeInt2( int n, int offset )
  345.     {
  346.         byte[] temp = { (byte)((n >> 8) & 0xff),
  347.             (byte) (n & 0xff) };
  348.         return writeBytes( temp, offset );
  349.     }
  350.  
  351.     /**
  352.      * Write a four-byte integer into the pngBytes array at a given position.
  353.      *
  354.      * @param n The integer to be written into pngBytes.
  355.      * @param offset The starting point to write to.
  356.      * @return The next place to be written to in the pngBytes array.
  357.      */
  358.     protected int writeInt4( int n, int offset )
  359.     {
  360.         byte[] temp = { (byte)((n >> 24) & 0xff),
  361.             (byte) ((n >> 16) & 0xff ),
  362.             (byte) ((n >> 8) & 0xff ),
  363.             (byte) ( n & 0xff ) };
  364.         return writeBytes( temp, offset );
  365.     }
  366.  
  367.     /**
  368.      * Write a single byte into the pngBytes array at a given position.
  369.      *
  370.      * @param n The integer to be written into pngBytes.
  371.      * @param offset The starting point to write to.
  372.      * @return The next place to be written to in the pngBytes array.
  373.      */
  374.     protected int writeByte( int b, int offset )
  375.     {
  376.         byte[] temp = { (byte) b };
  377.         return writeBytes( temp, offset );
  378.     }
  379.  
  380.     /**
  381.      * Write a string into the pngBytes array at a given position.
  382.      * This uses the getBytes method, so the encoding used will
  383.      * be its default.
  384.      *
  385.      * @param n The integer to be written into pngBytes.
  386.      * @param offset The starting point to write to.
  387.      * @return The next place to be written to in the pngBytes array.
  388.      * @see java.lang.String#getBytes()
  389.      */
  390.     protected int writeString( String s, int offset )
  391.     {
  392.         return writeBytes( s.getBytes(), offset );
  393.     }
  394.  
  395.     /**
  396.      * Write a PNG "IHDR" chunk into the pngBytes array.
  397.      */
  398.     protected void writeHeader()
  399.     {
  400.         int startPos;
  401.  
  402.         startPos = bytePos = writeInt4( 13, bytePos );
  403.         bytePos = writeString( "IHDR", bytePos );
  404.         width = image.getWidth( null );
  405.         height = image.getHeight( null );
  406.         bytePos = writeInt4( width, bytePos );
  407.         bytePos = writeInt4( height, bytePos );
  408.         bytePos = writeByte( 8, bytePos ); // bit depth
  409.         bytePos = writeByte( (encodeAlpha) ? 6 : 2, bytePos ); // direct model
  410.         bytePos = writeByte( 0, bytePos ); // compression method
  411.         bytePos = writeByte( 0, bytePos ); // filter method
  412.         bytePos = writeByte( 0, bytePos ); // no interlace
  413.         crc.reset();
  414.         crc.update( pngBytes, startPos, bytePos-startPos );
  415.         crcValue = crc.getValue();
  416.         bytePos = writeInt4( (int) crcValue, bytePos );
  417.     }
  418.  
  419.     /**
  420.      * Perform "sub" filtering on the given row.
  421.      * Uses temporary array leftBytes to store the original values
  422.      * of the previous pixels.  The array is 16 bytes long, which
  423.      * will easily hold two-byte samples plus two-byte alpha.
  424.      *
  425.      * @param pixels The array holding the scan lines being built
  426.      * @param startPos Starting position within pixels of bytes to be filtered.
  427.      * @param width Width of a scanline in pixels.
  428.      */
  429.     protected void filterSub( byte[] pixels, int startPos, int width )
  430.     {
  431.         int i;
  432.         int offset = bytesPerPixel;
  433.         int actualStart = startPos + offset;
  434.         int nBytes = width * bytesPerPixel;
  435.         int leftInsert = offset;
  436.         int leftExtract = 0;
  437.         byte current_byte;
  438.  
  439.         for (i=actualStart; i < startPos + nBytes; i++)
  440.         {
  441.             leftBytes[leftInsert] =  pixels[i];
  442.             pixels[i] = (byte) ((pixels[i] - leftBytes[leftExtract]) % 256);
  443.             leftInsert = (leftInsert+1) % 0x0f;
  444.             leftExtract = (leftExtract + 1) % 0x0f;
  445.         }
  446.     }
  447.  
  448.     /**
  449.      * Perform "up" filtering on the given row.
  450.      * Side effect: refills the prior row with current row
  451.      *
  452.      * @param pixels The array holding the scan lines being built
  453.      * @param startPos Starting position within pixels of bytes to be filtered.
  454.      * @param width Width of a scanline in pixels.
  455.      */
  456.     protected void filterUp( byte[] pixels, int startPos, int width )
  457.     {
  458.         int     i, nBytes;
  459.         byte    current_byte;
  460.  
  461.         nBytes = width * bytesPerPixel;
  462.  
  463.         for (i=0; i < nBytes; i++)
  464.         {
  465.             current_byte = pixels[startPos + i];
  466.             pixels[startPos + i] = (byte) ((pixels[startPos  + i] - priorRow[i]) % 256);
  467.             priorRow[i] = current_byte;
  468.         }
  469.     }
  470.  
  471.     /**
  472.      * Write the image data into the pngBytes array.
  473.      * This will write one or more PNG "IDAT" chunks. In order
  474.      * to conserve memory, this method grabs as many rows as will
  475.      * fit into 32K bytes, or the whole image; whichever is less.
  476.      *
  477.      *
  478.      * @return true if no errors; false if error grabbing pixels
  479.      */
  480.     protected boolean writeImageData()
  481.     {
  482.         int rowsLeft = height;  // number of rows remaining to write
  483.         int startRow = 0;       // starting row to process this time through
  484.         int nRows;              // how many rows to grab at a time
  485.  
  486.         byte[] scanLines;       // the scan lines to be compressed
  487.         int scanPos;            // where we are in the scan lines
  488.         int startPos;           // where this line's actual pixels start (used for filtering)
  489.  
  490.         byte[] compressedLines; // the resultant compressed lines
  491.         int nCompressed;        // how big is the compressed area?
  492.  
  493.         int depth;              // color depth ( handle only 8 or 32 )
  494.  
  495.         PixelGrabber pg;
  496.  
  497.         bytesPerPixel = (encodeAlpha) ? 4 : 3;
  498.  
  499.         Deflater scrunch = new Deflater( compressionLevel );
  500.         ByteArrayOutputStream outBytes =
  501.             new ByteArrayOutputStream(1024);
  502.  
  503.         DeflaterOutputStream compBytes =
  504.             new DeflaterOutputStream( outBytes, scrunch );
  505.         try
  506.         {
  507.             while (rowsLeft > 0)
  508.             {
  509.                 nRows = Math.min( 32767 / (width*(bytesPerPixel+1)), rowsLeft );
  510.                 // nRows = rowsLeft;
  511.  
  512.                 int[] pixels = new int[width * nRows];
  513.  
  514.                 pg = new PixelGrabber(image, 0, startRow,
  515.                     width, nRows, pixels, 0, width);
  516.                 try {
  517.                     pg.grabPixels();
  518.                 }
  519.                 catch (Exception e) {
  520.                     System.err.println("interrupted waiting for pixels!");
  521.                     return false;
  522.                 }
  523.                 if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
  524.                     System.err.println("image fetch aborted or errored");
  525.                     return false;
  526.                 }
  527.  
  528.                 /*
  529.                  * Create a data chunk. scanLines adds "nRows" for
  530.                  * the filter bytes.
  531.                  */
  532.                 scanLines = new byte[width * nRows * bytesPerPixel +  nRows];
  533.  
  534.                 if (filter == FILTER_SUB)
  535.                 {
  536.                     leftBytes = new byte[16];
  537.                 }
  538.                 if (filter == FILTER_UP)
  539.                 {
  540.                     priorRow = new byte[width*bytesPerPixel];
  541.                 }
  542.  
  543.                 scanPos = 0;
  544.                 startPos = 1;
  545.                 for (int i=0; i<width*nRows; i++)
  546.                 {
  547.                     if (i % width == 0)
  548.                     {
  549.                         scanLines[scanPos++] = (byte) filter;
  550.                         startPos = scanPos;
  551.                     }
  552.                     scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff);
  553.                     scanLines[scanPos++] = (byte) ((pixels[i] >>  8) & 0xff);
  554.                     scanLines[scanPos++] = (byte) ((pixels[i]      ) & 0xff);
  555.                     if (encodeAlpha)
  556.                     {
  557.                         scanLines[scanPos++] = (byte) ((pixels[i] >> 24) & 0xff );
  558.                     }
  559.                     if ((i % width == width-1) && (filter != FILTER_NONE))
  560.                     {
  561.                         if (filter == FILTER_SUB)
  562.                         {
  563.                             filterSub( scanLines, startPos, width );
  564.                         }
  565.                         if (filter == FILTER_UP)
  566.                         {
  567.                             filterUp( scanLines, startPos, width );
  568.                         }
  569.                     }
  570.                 }
  571.  
  572.                 /*
  573.                  * Write these lines to the output area
  574.                  */
  575.                 compBytes.write( scanLines, 0, scanPos );
  576.  
  577.  
  578.                 startRow += nRows;
  579.                 rowsLeft -= nRows;
  580.             }
  581.             compBytes.close();
  582.  
  583.             /*
  584.              * Write the compressed bytes
  585.              */
  586.             compressedLines = outBytes.toByteArray();
  587.             nCompressed = compressedLines.length;
  588.  
  589.             crc.reset();
  590.             bytePos = writeInt4( nCompressed, bytePos );
  591.             bytePos = writeString("IDAT", bytePos );
  592.             crc.update("IDAT".getBytes());
  593.             bytePos = writeBytes( compressedLines, nCompressed, bytePos );
  594.             crc.update( compressedLines, 0, nCompressed );
  595.  
  596.             crcValue = crc.getValue();
  597.             bytePos = writeInt4( (int) crcValue, bytePos );
  598.             scrunch.finish();
  599.             return true;
  600.         }
  601.         catch (IOException e)
  602.         {
  603.             System.err.println( e.toString());
  604.             return false;
  605.         }
  606.     }
  607.  
  608.     /**
  609.      * Write a PNG "IEND" chunk into the pngBytes array.
  610.      */
  611.     protected void writeEnd()
  612.     {
  613.         bytePos = writeInt4( 0, bytePos );
  614.         bytePos = writeString( "IEND", bytePos );
  615.         crc.reset();
  616.         crc.update("IEND".getBytes());
  617.         crcValue = crc.getValue();
  618.         bytePos = writeInt4( (int) crcValue, bytePos );
  619.     }
  620. }
  621.  
  622.