4 import java.awt.image.BufferedImage;
5 import java.awt.image.DataBuffer;
6 import java.awt.image.IndexColorModel;
7 import java.awt.image.ImageObserver;
8 import java.awt.image.PixelGrabber;
9 import java.awt.image.WritableRaster;
10 import java.io.ByteArrayOutputStream;
11 import java.io.IOException;
12 import java.util.zip.CRC32;
13 import java.util.zip.Deflater;
14 import java.util.zip.DeflaterOutputStream;
17 * PngEncoderB takes a Java BufferedImage object and creates a byte string which can be saved as a PNG file.
18 * The encoder will accept BufferedImages with eight-bit samples
19 * or 4-byte ARGB samples.
21 * <p>There is also code to handle 4-byte samples returned as
22 * one int per pixel, but that has not been tested.</p>
24 * <p>Thanks to Jay Denny at KeyPoint Software
25 * <code>http://www.keypoint.com/</code>
26 * who let me develop this code on company time.</p>
28 * <p>You may contact me with (probably very-much-needed) improvements,
29 * comments, and bug fixes at:</p>
31 * <p><code>david@catcode.com</code></p>
33 * <p>This library is free software; you can redistribute it and/or
34 * modify it under the terms of the GNU Lesser General Public
35 * License as published by the Free Software Foundation; either
36 * version 2.1 of the License, or (at your option) any later version.<p>
38 * <p>This library is distributed in the hope that it will be useful,
39 * but WITHOUT ANY WARRANTY; without even the implied warranty of
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
41 * Lesser General Public License for more details.</p>
43 * <p>You should have received a copy of the GNU Lesser General Public
44 * License along with this library; if not, write to the Free Software
45 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
46 * A copy of the GNU LGPL may be found at
47 * <code>http://www.gnu.org/copyleft/lesser.html</code></p>
49 * @author J. David Eisenberg
50 * @version 1.5, 19 Oct 2003
54 * 19-Sep-2003 : Fix for platforms using EBCDIC (contributed by Paulo Soares);
55 * 19-Oct-2003 : Change private fields to protected fields so that
56 * PngEncoderB can inherit them (JDE)
57 * Fixed bug with calculation of nRows
58 * Added modifications for unsigned short images
59 * (contributed by Christian at xpogen.com)
62 public class PngEncoderB extends PngEncoder
66 private static final byte PLTE[] = { 80, 76, 84, 69 };
68 protected BufferedImage image;
69 protected WritableRaster wRaster;
78 this( null, false, FILTER_NONE, 0 );
82 * Class constructor specifying BufferedImage to encode, with no alpha channel encoding.
84 * @param image A Java BufferedImage object
86 public PngEncoderB( BufferedImage image )
88 this(image, false, FILTER_NONE, 0);
92 * Class constructor specifying BufferedImage to encode, and whether to encode alpha.
94 * @param image A Java BufferedImage object
95 * @param encodeAlpha Encode the alpha channel? false=no; true=yes
97 public PngEncoderB( BufferedImage image, boolean encodeAlpha )
99 this( image, encodeAlpha, FILTER_NONE, 0 );
103 * Class constructor specifying BufferedImage to encode, whether to encode alpha, and filter to use.
105 * @param image A Java BufferedImage object
106 * @param encodeAlpha Encode the alpha channel? false=no; true=yes
107 * @param whichFilter 0=none, 1=sub, 2=up
109 public PngEncoderB( BufferedImage image, boolean encodeAlpha,
112 this( image, encodeAlpha, whichFilter, 0 );
116 * Class constructor specifying BufferedImage source to encode, whether to encode alpha, filter to use, and compression level
118 * @param image A Java BufferedImage object
119 * @param encodeAlpha Encode the alpha channel? false=no; true=yes
120 * @param whichFilter 0=none, 1=sub, 2=up
121 * @param compLevel 0..9
123 public PngEncoderB( BufferedImage image, boolean encodeAlpha,
124 int whichFilter, int compLevel )
127 this.encodeAlpha = encodeAlpha;
128 setFilter( whichFilter );
129 if (compLevel >=0 && compLevel <=9)
131 this.compressionLevel = compLevel;
136 * Set the BufferedImage to be encoded
138 * @param BufferedImage A Java BufferedImage object
140 public void setImage( BufferedImage image )
147 * Creates an array of bytes that is the PNG equivalent of the current image, specifying whether to encode alpha or not.
149 * @param encodeAlpha boolean false=no alpha, true=encode alpha
150 * @return an array of bytes, or null if there was a problem
152 public byte[] pngEncode( boolean encodeAlpha )
154 byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 };
159 System.err.println("pngEncode: image is null; returning null");
162 width = image.getWidth( null );
163 height = image.getHeight( null );
166 if (!establishStorageInfo())
168 System.err.println("pngEncode: cannot establish storage info");
173 * start with an array that is big enough to hold all the pixels
174 * (plus filter bytes), and an extra 200 bytes for header info
176 pngBytes = new byte[((width+1) * height * 3) + 200];
179 * keep track of largest byte written to the array
183 bytePos = writeBytes( pngIdBytes, 0 );
186 // dataPos = bytePos;
187 if (writeImageData())
190 pngBytes = resizeByteArray( pngBytes, maxPos );
194 System.err.println("pngEncode: writeImageData failed => null");
201 * Creates an array of bytes that is the PNG equivalent of the current image.
202 * Alpha encoding is determined by its setting in the constructor.
204 * @return an array of bytes, or null if there was a problem
206 public byte[] pngEncode()
208 return pngEncode( encodeAlpha );
213 * Get and set variables that determine how picture is stored.
215 * Retrieves the writable raster of the buffered image,
216 * as well its transfer type.
218 * Sets number of output bytes per pixel, and, if only
219 * eight-bit bytes, turns off alpha encoding.
220 * @return true if 1-byte or 4-byte data, false otherwise
222 protected boolean establishStorageInfo()
226 wRaster = image.getRaster();
227 dataBytes = wRaster.getNumDataElements();
228 tType = wRaster.getTransferType();
230 if (((tType == DataBuffer.TYPE_BYTE ) && (dataBytes == 4)) ||
231 ((tType == DataBuffer.TYPE_INT ) && (dataBytes == 1)) ||
232 // on Win 2k/ME, tType == 1, dataBytes == 1
233 ((tType == DataBuffer.TYPE_USHORT) && (dataBytes == 1)) )
235 bytesPerPixel = (encodeAlpha) ? 4 : 3;
237 else if ((tType == DataBuffer.TYPE_BYTE) && (dataBytes == 1))
240 encodeAlpha = false; // one-byte samples
244 System.err.println("PNG encoder cannot establish storage info:");
245 System.err.println(" TransferType == " + tType );
246 System.err.println(" NumDataElements == " + dataBytes);
253 * Write a PNG "IHDR" chunk into the pngBytes array.
255 protected void writeHeader()
259 startPos = bytePos = writeInt4( 13, bytePos );
260 bytePos = writeBytes( IHDR, bytePos );
261 width = image.getWidth( null );
262 height = image.getHeight( null );
263 bytePos = writeInt4( width, bytePos );
264 bytePos = writeInt4( height, bytePos );
265 bytePos = writeByte( 8, bytePos ); // bit depth
266 if (bytesPerPixel != 1)
268 bytePos = writeByte( (encodeAlpha) ? 6 : 2, bytePos ); // direct model
272 bytePos = writeByte( 3, bytePos ); // indexed
274 bytePos = writeByte( 0, bytePos ); // compression method
275 bytePos = writeByte( 0, bytePos ); // filter method
276 bytePos = writeByte( 0, bytePos ); // no interlace
278 crc.update( pngBytes, startPos, bytePos-startPos );
279 crcValue = crc.getValue();
280 bytePos = writeInt4( (int) crcValue, bytePos );
283 protected void writePalette( IndexColorModel icm )
285 byte[] redPal = new byte[256];
286 byte[] greenPal = new byte[256];
287 byte[] bluePal = new byte[256];
288 byte[] allPal = new byte[768];
291 icm.getReds( redPal );
292 icm.getGreens( greenPal );
293 icm.getBlues( bluePal );
294 for (i=0; i<256; i++)
296 allPal[i*3 ] = redPal[i];
297 allPal[i*3+1] = greenPal[i];
298 allPal[i*3+2] = bluePal[i];
300 bytePos = writeInt4( 768, bytePos );
301 bytePos = writeBytes( PLTE, bytePos );
304 bytePos = writeBytes( allPal, bytePos );
305 crc.update( allPal );
306 crcValue = crc.getValue();
307 bytePos = writeInt4( (int) crcValue, bytePos );
311 * Write the image data into the pngBytes array.
312 * This will write one or more PNG "IDAT" chunks. In order
313 * to conserve memory, this method grabs as many rows as will
314 * fit into 32K bytes, or the whole image; whichever is less.
317 * @return true if no errors; false if error grabbing pixels
319 protected boolean writeImageData()
321 int rowsLeft = height; // number of rows remaining to write
322 int startRow = 0; // starting row to process this time through
323 int nRows; // how many rows to grab at a time
325 byte[] scanLines; // the scan lines to be compressed
326 int scanPos; // where we are in the scan lines
327 int startPos; // where this line's actual pixels start (used for filtering)
328 int readPos; // position from which source pixels are read
330 byte[] compressedLines; // the resultant compressed lines
331 int nCompressed; // how big is the compressed area?
333 byte[] pixels; // storage area for byte-sized pixels
334 int[] iPixels; // storage area for int-sized pixels
335 short[] sPixels; // for Win 2000/ME ushort pixels
336 final int type = image.getType();
339 // TYPE_INT_ARGB_PRE = 3
341 // TYPE_3BYTE_BGR = 5
342 // TYPE_4BYTE_ABGR = 6
343 // TYPE_4BYTE_ABGR_PRE = 7
344 // TYPE_BYTE_GRAY = 10
345 // TYPE_BYTE_BINARY = 12
346 // TYPE_BYTE_INDEXED = 13
347 // TYPE_USHORT_GRAY = 11
348 // TYPE_USHORT_565_RGB = 8
349 // TYPE_USHORT_555_RGB = 9
352 Deflater scrunch = new Deflater( compressionLevel );
353 ByteArrayOutputStream outBytes =
354 new ByteArrayOutputStream(1024);
356 DeflaterOutputStream compBytes =
357 new DeflaterOutputStream( outBytes, scrunch );
359 if (bytesPerPixel == 1)
361 writePalette( (IndexColorModel) image.getColorModel() );
368 nRows = Math.min( 32767 / (width*(bytesPerPixel+1)), rowsLeft );
369 nRows = Math.max( nRows, 1 );
372 * Create a data chunk. scanLines adds "nRows" for
375 scanLines = new byte[width * nRows * bytesPerPixel + nRows];
377 if (filter == FILTER_SUB)
379 leftBytes = new byte[16];
381 if (filter == FILTER_UP)
383 priorRow = new byte[width*bytesPerPixel];
387 wRaster.getDataElements( 0, startRow, width, nRows, null );
392 if (tType == DataBuffer.TYPE_BYTE)
394 pixels = (byte[]) data;
396 else if (tType == DataBuffer.TYPE_INT)
398 iPixels = (int[]) data;
400 else if (tType == DataBuffer.TYPE_USHORT)
402 sPixels = (short[]) data;
408 for (int i=0; i<width*nRows; i++)
412 scanLines[scanPos++] = (byte) filter;
416 if (bytesPerPixel == 1) // assume TYPE_BYTE, indexed
418 scanLines[scanPos++] = pixels[readPos++];
420 else if (tType == DataBuffer.TYPE_BYTE)
422 scanLines[scanPos++] = pixels[readPos++];
423 scanLines[scanPos++] = pixels[readPos++];
424 scanLines[scanPos++] = pixels[readPos++];
427 scanLines[scanPos++] = pixels[readPos++];
434 else if (tType == DataBuffer.TYPE_USHORT)
436 short pxl = sPixels[readPos++];
437 if (type == BufferedImage.TYPE_USHORT_565_RGB) {
438 scanLines[scanPos++] = (byte) ((pxl >> 8) & 0xf8);
439 scanLines[scanPos++] = (byte) ((pxl >> 2) & 0xfc);
440 } else { // assume USHORT_555_RGB
441 scanLines[scanPos++] = (byte) ((pxl >> 7) & 0xf8);
442 scanLines[scanPos++] = (byte) ((pxl >> 2) & 0xf8);
444 scanLines[scanPos++] = (byte) ((pxl << 3) & 0xf8);
446 else // assume tType INT and type RGB or ARGB
448 int pxl = iPixels[readPos++];
449 scanLines[scanPos++] = (byte) ((pxl >> 16) & 0xff);
450 scanLines[scanPos++] = (byte) ((pxl >> 8) & 0xff);
451 scanLines[scanPos++] = (byte) ((pxl ) & 0xff);
453 scanLines[scanPos++] = (byte) ((pxl >> 24) & 0xff);
457 if ((i % width == width-1) && (filter != FILTER_NONE))
459 if (filter == FILTER_SUB)
461 filterSub( scanLines, startPos, width );
463 if (filter == FILTER_UP)
465 filterUp( scanLines, startPos, width );
471 * Write these lines to the output area
473 compBytes.write( scanLines, 0, scanPos );
481 * Write the compressed bytes
483 compressedLines = outBytes.toByteArray();
484 nCompressed = compressedLines.length;
487 bytePos = writeInt4( nCompressed, bytePos );
488 bytePos = writeBytes( IDAT, bytePos );
490 bytePos = writeBytes( compressedLines, nCompressed, bytePos );
491 crc.update( compressedLines, 0, nCompressed );
493 crcValue = crc.getValue();
494 bytePos = writeInt4( (int) crcValue, bytePos );
498 catch (IOException e)
500 System.err.println( e.toString());