1    /**
2     * PngEncoder takes a Java Image object and creates a byte string which can be saved as a PNG file.
3     * The Image is presumed to use the DirectColorModel.
4     *
5     * Thanks to Jay Denny at KeyPoint Software
6     *    http://www.keypoint.com/
7     * who let me develop this code on company time.
8     *
9     * You may contact me with (probably very-much-needed) improvements,
10    * comments, and bug fixes at:
11    *
12    *   david@catcode.com
13    *
14    * This library is free software; you can redistribute it and/or
15    * modify it under the terms of the GNU Lesser General Public
16    * License as published by the Free Software Foundation; either
17    * version 2.1 of the License, or (at your option) any later version.
18    * 
19    * This library is distributed in the hope that it will be useful,
20    * but WITHOUT ANY WARRANTY; without even the implied warranty of
21    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22    * Lesser General Public License for more details.
23    * 
24    * You should have received a copy of the GNU Lesser General Public
25    * License along with this library; if not, write to the Free Software
26    * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27    * A copy of the GNU LGPL may be found at
28    * http://www.gnu.org/copyleft/lesser.html,
29    *
30    * @author J. David Eisenberg
31    * @version 1.4, 31 March 2000
32    *
33    * Hacked by Ralph Hartley 8/00
34    * Instead of acuumultaing the output in an array, write it directly to
35    * a stream. A byte array big enough for a large image can be very big.
36    *
37    * In fact it has been hacked almost beyond recognition.
38    */
39   
40   import java.io.*;
41   import java.awt.*;
42   import java.awt.image.*;
43   import java.util.*;
44   import java.util.zip.*;
45   import java.io.*;
46   import javax.swing.*;
47   
48   public class PngEncoder extends Object
49   {
50     /** Constant specifying that alpha channel should be encoded. */
51     public static final boolean ENCODE_ALPHA=true;
52     /** Constant specifying that alpha channel should not be encoded. */
53     public static final boolean NO_ALPHA=false;
54     /** Constants for filters */
55     public static final int FILTER_NONE = 0;
56     public static final int FILTER_SUB = 1;
57     public static final int FILTER_UP = 2;
58     public static final int FILTER_LAST = 2;
59   
60     public static int compress = 5;
61     public static int filter = FILTER_SUB | FILTER_UP;
62     public static int chunksize;
63   
64     protected OutputStream out;
65     protected int width, height;
66     protected CRC32 crc = new CRC32();
67     protected long crcValue;
68   
69     public boolean encodeAlpha = true;
70   
71     public JLabel status = null;
72     public String message = "";
73   
74   //  public static boolean kill = false;
75   
76     /**
77      * Class constructor specifying Size of image, and stream to write to.
78      * @param int width The total width of the image.
79      * @param int height The total height of the image.
80      * @param OutPutStream out The stream to write the image to.
81      * @see java.awt.Image
82      */
83     public PngEncoder( int width,int height,OutputStream out) {
84       this.width = width;
85       this.height = height;
86       this.out = out;
87     }
88   
89     /**
90      * Write an array of bytes output stream
91      * Note: This routine has the side effect of updating
92      * the crc.
93      *
94      * @param data The data to be written .
95      */
96     protected void writeBytes( byte[] data) throws IOException {
97       out.write(data);
98       crc.update(data);
99     }
100  
101    /**
102     * Write an array of bytes into the pngBytes array, specifying number of bytes to write.
103     * Note: This routine has the side effect of updating
104     * the crc.
105     *
106     * @param data The data to be written.
107     * @param nBytes The number of bytes to be written.
108     */
109    protected void writeBytes( byte[] data, int nBytes ) throws IOException  {
110      out.write(data,0,nBytes);
111      crc.update(data,0,nBytes);
112    }
113  
114    /**
115     * Write a two-byte integer into the output.
116     *
117     * @param n The integer to be written.
118     */
119    protected void writeInt2( int n ) throws IOException {
120      byte[] temp = { (byte)((n >> 8) & 0xff),
121          	    (byte) (n & 0xff) };
122      writeBytes( temp );
123    }
124  
125    /**
126     * Write a four-byte integer into the output.
127     *
128     * @param n The integer to be written.
129     */
130    protected void writeInt4( int n ) throws IOException {
131      byte[] temp = { (byte)((n >> 24) & 0xff),
132          	    (byte) ((n >> 16) & 0xff ),
133          	    (byte) ((n >> 8) & 0xff ),
134          	    (byte) ( n & 0xff ) };
135      out.write( temp );
136      crc.update(temp);
137    }
138  
139    /**
140     * Write a four-byte integer into the output.
141     * Do not update crc.
142     *
143     * @param n The integer to be written.
144     */
145    protected void writeInt4nocrc( int n ) throws IOException {
146      byte[] temp = { (byte)((n >> 24) & 0xff),
147          	    (byte) ((n >> 16) & 0xff ),
148          	    (byte) ((n >> 8) & 0xff ),
149          	    (byte) ( n & 0xff ) };
150      out.write( temp );
151    }
152  
153    /**
154     * Write a single byte into the output.
155     *
156     * @param n The integer to be written.
157     */
158    protected void writeByte( int b ) throws IOException {
159      out.write(b);
160      crc.update(b);
161    }
162  
163    /**
164     * Write a string into the output.
165     * This uses the getBytes method, so the encoding used will
166     * be its default.
167     *
168     * @param n The integer to be written.
169     * @see java.lang.String#getBytes()
170     */
171    protected void writeString( String s ) throws IOException {
172      writeBytes( s.getBytes() );
173    }
174  
175    /**
176     * Write a PNG "IHDR" chunk to the output.
177     */
178    protected void writeHeader() throws IOException {
179      crc.reset();
180      writeInt4nocrc( 13 );
181      writeString( "IHDR");
182  //    width = image.getWidth( null );
183  //    height = image.getHeight( null );
184      writeInt4( width );
185      writeInt4( height );
186      writeByte( 8 ); // bit depth
187      writeByte( (encodeAlpha) ? 6 : 2 ); // direct model
188      writeByte( 0 ); // compression method
189      writeByte( 0 ); // filter method
190      writeByte( 0 ); // no interlace
191      crcValue = crc.getValue();
192      writeInt4nocrc( (int) crcValue );
193    }
194  
195    int bytesPerPixel = 4;
196    byte[] lastline = null;
197    byte[] raw = null;
198    byte[] line = null;
199    int[] pixels = null;
200    byte[] deflatebuff;
201    int defpos = 0;
202    Deflater scrunch;
203  
204    /**
205     * Writes the current image to the output stream in PNG format, specifying whether to encode alpha or not.
206     *
207     * @return an array of bytes, or null if there was a problem
208     */
209    public void start() throws IOException {
210  //      System.out.println("test");
211      byte[]  pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 };
212  
213      writeBytes( pngIdBytes );
214      writeHeader();
215  
216      bytesPerPixel = (encodeAlpha) ? 4 : 3;
217  
218      lastline = new byte[bytesPerPixel*width];     // previous line of image for filters.
219      line = new byte[bytesPerPixel*width+1];         // current line of image (filtered).
220      raw = new byte[bytesPerPixel*width];          // current line of image (unfiltered).
221      pixels = new int[width];     // current line of imge (packed).
222      for (int j=0;j<width*bytesPerPixel;j++) lastline[j] = (byte)0;
223        
224      scrunch = new Deflater( compress );
225  
226      if (chunksize==0) chunksize = 10000;
227  
228      deflatebuff = new byte[chunksize];
229      defpos = 0;
230  
231    }
232  
233    /**
234     * Write a slice of image data to the output stream.
235     * This may write one or more PNG "IDAT" chunks.
236     */
237    public void addSlice(BufferedImage image,
238          	       JLabel status,String message) throws IOException,CookKillException {
239      int sliceheight = image.getHeight();
240      int progress = -1;
241  
242      for (int row=0; row<sliceheight; row++) {
243  
244        if (Carto.kill==Carto.PRINTKILL) throw new CookKillException();
245  
246        if (row*100/sliceheight > progress && status!=null) {
247          status.setText(message+" "+(progress=row*100/sliceheight)+"%");
248        }
249  
250        // Get the pixels for this row.
251        pixels = (int[]) image.getRaster().getDataElements(0,row,width,1,pixels);
252          
253        line[0] = (byte) filter; 
254          
255        // convert to bytes
256        for (int j=0;j<width;j++) {
257          for (int k=0;k<3;k++)
258            raw[j*bytesPerPixel+k] = (byte)((pixels[j] >> (8*(2-k))) & 0xff);
259          if (encodeAlpha) raw[j*bytesPerPixel+3] = (byte) ((pixels[j] >> 24) & 0xff );
260        }
261  
262        // filter bytes
263        for (int j = 0;j<bytesPerPixel*width;j++) {
264          if (filter == FILTER_SUB && j>=bytesPerPixel)
265            line[j+1] = (byte)(raw[j] - raw[j-bytesPerPixel]);
266          else if (filter == FILTER_UP)
267            line[j+1] = (byte)(raw[j] - lastline[j]);
268          else line[j+1] = raw[j];
269        }
270        byte[] tmp = lastline;
271        lastline = raw;
272        raw = tmp;
273  
274        while (!scrunch.needsInput()) {
275          defpos += scrunch.deflate(deflatebuff,defpos,chunksize-defpos);
276          if (defpos==chunksize) {
277            writeDat(deflatebuff,defpos);
278            defpos = 0;
279          }
280        }
281  
282        scrunch.setInput(line);
283      }
284    }
285  
286    public void finish() throws IOException{
287      try {
288        scrunch.finish();
289        
290        while (!scrunch.finished()) {
291          defpos += scrunch.deflate(deflatebuff,defpos,chunksize-defpos);
292          if (defpos==chunksize || scrunch.finished()) {
293            writeDat(deflatebuff,defpos);
294            defpos = 0;
295          }
296        }
297        writeEnd();
298      } finally {clean();}
299    }
300  
301    public void clean() {
302      if (scrunch!=null) scrunch.end();
303      deflatebuff=null;
304      lastline=null;
305      line=null;
306      raw=null;
307      pixels=null;
308      if (out!=null) try {out.close();}catch(IOException e){}
309      out=null;
310    }
311  
312    /**
313     * Write an IDAT chunk.
314     * @param data the (compressed) data to be written.
315     * @param size The number of bytes.
316     */
317    protected void writeDat(byte[] data,int size) throws IOException {
318      crc.reset();
319      writeInt4nocrc(size);
320      writeString("IDAT");
321  
322      writeBytes(data,size);
323          
324      crcValue = crc.getValue();
325      writeInt4nocrc( (int) crcValue );
326    }
327  
328    /**
329     * Write a PNG "IEND" chunk into the pngBytes array.
330     */
331    protected void writeEnd() throws IOException {
332      writeInt4nocrc( 0 );
333      crc.reset();
334      writeString( "IEND" );
335      crcValue = crc.getValue();
336      writeInt4nocrc( (int) crcValue );
337    }
338  }
339  
340  
341  
342