Java/2D Graphics GUI/PNG File
Содержание
Draw an Image and save to png
<source lang="java"> import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.GradientPaint; import java.awt.Graphics2D; import java.awt.geom.Ellipse2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; public class WriteImageType {
static public void main(String args[]) throws Exception { try { int width = 200, height = 200; // TYPE_INT_ARGB specifies the image format: 8-bit RGBA packed // into integer pixels BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D ig2 = bi.createGraphics();
Font font = new Font("TimesRoman", Font.BOLD, 20); ig2.setFont(font); String message = "www.jexp.ru!"; FontMetrics fontMetrics = ig2.getFontMetrics(); int stringWidth = fontMetrics.stringWidth(message); int stringHeight = fontMetrics.getAscent(); ig2.setPaint(Color.black); ig2.drawString(message, (width - stringWidth) / 2, height / 2 + stringHeight / 4); ImageIO.write(bi, "PNG", new File("c:\\yourImageName.PNG")); ImageIO.write(bi, "JPEG", new File("c:\\yourImageName.JPG")); ImageIO.write(bi, "gif", new File("c:\\yourImageName.GIF")); ImageIO.write(bi, "BMP", new File("c:\\yourImageName.BMP")); } catch (IOException ie) { ie.printStackTrace(); } }
}
</source>
Encodes a java.awt.Image into PNG format
<source lang="java"> /*
* This file is part of the Echo Web Application Framework (hereinafter "Echo"). * Copyright (C) 2002-2009 NextApp, Inc. * * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. */
import java.awt.Graphics; import java.awt.Image; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; import java.awt.image.PixelGrabber; import java.awt.image.Raster; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import java.util.zip.CheckedOutputStream; import java.util.zip.Checksum; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import javax.swing.ImageIcon; /**
* Encodes a java.awt.Image into PNG format. * For more information on the PNG specification, see the W3C PNG page at * . */
public class PngEncoder {
/** * Utility class for converting*Image
s toBufferedImage
s. */ private static class ImageToBufferedImage { /** * Converts anImage
to aBufferedImage
. * If the image is already aBufferedImage
, the original is returned. * * @param image the image to convert * @return the image as aBufferedImage
*/ static BufferedImage toBufferedImage(Image image) { if (image instanceof BufferedImage) { // Return image unchanged if it is already a BufferedImage. return (BufferedImage) image; } // Ensure image is loaded. image = new ImageIcon(image).getImage(); int type = hasAlpha(image) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), type); Graphics g = bufferedImage.createGraphics(); g.drawImage(image, 0, 0, null); g.dispose(); return bufferedImage; } /** * Determines if an image has an alpha channel. * * @param image theImage
* @return true if the image has an alpha channel */ static boolean hasAlpha(Image image) { PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false); try { pg.grabPixels(); } catch (InterruptedException ex) { } return pg.getColorModel().hasAlpha(); } } /**SubFilter
singleton. */ public static final Filter SUB_FILTER = new SubFilter(); /**UpFilter
singleton. */ public static final Filter UP_FILTER = new UpFilter(); /**AverageFilter
singleton. */ public static final Filter AVERAGE_FILTER = new AverageFilter(); /**PaethFilter
singleton. */ public static final Filter PAETH_FILTER = new PaethFilter(); /** PNG signature bytes. */ private static final byte[] SIGNATURE = { (byte)0x89, (byte)0x50, (byte)0x4e, (byte)0x47, (byte)0x0d, (byte)0x0a, (byte)0x1a, (byte)0x0a }; /** Image header (IHDR) chunk header. */ private static final byte[] IHDR = { (byte) "I", (byte) "H", (byte) "D", (byte) "R" }; /** Palate (PLTE) chunk header. */ private static final byte[] PLTE = { (byte) "P", (byte) "L", (byte) "T", (byte) "E" }; /** Image Data (IDAT) chunk header. */ private static final byte[] IDAT = { (byte) "I", (byte) "D", (byte) "A", (byte) "T" }; /** End-of-file (IEND) chunk header. */ private static final byte[] IEND = { (byte) "I", (byte) "E", (byte) "N", (byte) "D" }; /** Sub filter type constant. */ private static final int SUB_FILTER_TYPE = 1; /** Up filter type constant. */ private static final int UP_FILTER_TYPE = 2; /** Average filter type constant. */ private static final int AVERAGE_FILTER_TYPE = 3; /** Paeth filter type constant. */ private static final int PAETH_FILTER_TYPE = 4; /** Image bit depth. */ private static final byte BIT_DEPTH = (byte) 8; /** Indexed color type rendered value. */ private static final byte COLOR_TYPE_INDEXED = (byte) 3; /** RGB color type rendered value. */ private static final byte COLOR_TYPE_RGB = (byte) 2; /** RGBA color type rendered value. */ private static final byte COLOR_TYPE_RGBA = (byte) 6; /** Integer-to-integer map used for RGBA/ARGB conversion. */ private static final int[] INT_TRANSLATOR_CHANNEL_MAP = new int[]{2, 1, 0, 3}; /** * Writes an 32-bit integer value to the output stream. * * @param out the stream * @param i the value */ private static void writeInt(OutputStream out, int i) throws IOException { out.write(new byte[]{(byte) (i >> 24), (byte) ((i >> 16) & 0xff), (byte) ((i >> 8) & 0xff), (byte) (i & 0xff)}); } /** * An interface for PNG filters. Filters are used to modify the method in * which pixels of the image are stored in ways that will achieve better * compression. */ public interface Filter { /** * Filters the data in a given row of the image. * * @param currentRow a byte array containing the data of the row of the * image to be filtered * @param previousRow a byte array containing the data of the previous * row of the image to be filtered * @param filterOutput a byte array into which the filtered data will * be placed */ public void filter(byte[] filterOutput, byte[] currentRow, byte[] previousRow, int outputBpp); /** * Returns the PNG type code for the filter. */ public int getType(); } /** * An implementation of a "Sub" filter. */ private static class SubFilter implements Filter { /** * @see nextapp.echo.webcontainer.util.PngEncoder.Filter#filter(byte[], byte[], byte[], int) */ public void filter(byte[] filterOutput, byte[] currentRow, byte[] previousRow, int outputBpp) { for (int index = 0; index < filterOutput.length; ++index) { if (index < outputBpp) { filterOutput[index] = currentRow[index]; } else { filterOutput[index] = (byte) (currentRow[index] - currentRow[index - outputBpp]); } } } /** * @see nextapp.echo.webcontainer.util.PngEncoder.Filter#getType() */ public int getType() { return SUB_FILTER_TYPE; } } /** * An implementation of an "Up" filter. */ private static class UpFilter implements Filter { /** * @see nextapp.echo.webcontainer.util.PngEncoder.Filter#filter(byte[], byte[], byte[], int) */ public void filter(byte[] filterOutput, byte[] currentRow, byte[] previousRow, int outputBpp) { for (int index = 0; index < currentRow.length; ++index) { filterOutput[index] = (byte) (currentRow[index] - previousRow[index]); } } /** * @see nextapp.echo.webcontainer.util.PngEncoder.Filter#getType() */ public int getType() { return UP_FILTER_TYPE; } } /** * An implementation of an "Average" filter. */ private static class AverageFilter implements Filter { /** * @see nextapp.echo.webcontainer.util.PngEncoder.Filter#filter(byte[], byte[], byte[], int) */ public void filter(byte[] filterOutput, byte[] currentRow, byte[] previousRow, int outputBpp) { int w, n; for (int index = 0; index < filterOutput.length; ++index) { n = (previousRow[index] + 0x100) & 0xff; if (index < outputBpp) { w = 0; } else { w = (currentRow[index - outputBpp] + 0x100) & 0xff; } filterOutput[index] = (byte) (currentRow[index] - (byte) ((w + n) / 2)); } } /** * @see nextapp.echo.webcontainer.util.PngEncoder.Filter#getType() */ public int getType() { return AVERAGE_FILTER_TYPE; } } /** * An implementation of a "Paeth" filter. */ private static class PaethFilter implements Filter { /** * @see nextapp.echo.webcontainer.util.PngEncoder.Filter#filter(byte[], byte[], byte[], int) */ public void filter(byte[] filterOutput, byte[] currentRow, byte[] previousRow, int outputBpp) { byte pv; int n, w, nw, p, pn, pw, pnw; for (int index = 0; index < filterOutput.length; ++index) { n = (previousRow[index] + 0x100) & 0xff; if (index < outputBpp) { w = 0; nw = 0; } else { w = (currentRow[index - outputBpp] + 0x100) & 0xff; nw = (previousRow[index - outputBpp] + 0x100) & 0xff; } p = w + n - nw; pw = Math.abs(p - w); pn = Math.abs(p - n); pnw = Math.abs(p - w); if (pw <= pn && pw <= pnw) { pv = (byte) w; } else if (pn <= pnw) { pv = (byte) n; } else { pv = (byte) nw; } filterOutput[index] = (byte) (currentRow[index] - pv); } } /** * @see nextapp.echo.webcontainer.util.PngEncoder.Filter#getType() */ public int getType() { return PAETH_FILTER_TYPE; } } /** * An interface for translators, which translate pixel data from a * writable raster into an R/G/B/A ordering required by the PNG * specification. Pixel data in the raster might be available * in three bytes per pixel, four bytes per pixel, or as integers. */ interface Translator { /** * Translates a row of the image into a byte array ordered * properly for a PNG image. * * @param outputPixelQueue the byte array in which to store the * translated pixels * @param row the row index of the image to translate */ public void translate(byte[] outputPixelQueue, int row); } /** * Translates byte-based rasters. */ private class ByteTranslator implements Translator { int rowWidth = width * outputBpp; // size of image data in a row in bytes. byte[] inputPixelQueue = new byte[rowWidth + outputBpp]; int column; int channel; /** * @see nextapp.echo.webcontainer.util.PngEncoder.Translator#translate(byte[], int) */ public void translate(byte[] outputPixelQueue, int row) { raster.getDataElements(0, row, width, 1, inputPixelQueue); for (column = 0; column < width; ++column) { for (channel = 0; channel < outputBpp; ++channel) { outputPixelQueue[column * outputBpp + channel] = inputPixelQueue[column * inputBpp + channel]; } } } } /** * Translates integer-based rasters. */ private class IntTranslator implements Translator { int[] inputPixelQueue = new int[width]; int column; int channel; /** * @see nextapp.echo.webcontainer.util.PngEncoder.Translator#translate(byte[], int) */ public void translate(byte[] outputPixelQueue, int row) { image.getRGB(0, row, width, 1, inputPixelQueue, 0, width); // Line below (commented out) replaces line above, almost halving time to encode, but doesn"t work with certain pixel // arrangements. Need to find method of determining pixel order (BGR vs RGB, ARGB, etc) // // raster.getDataElements(0, row, width, 1, inputPixelQueue); for (column = 0; column < width; ++column) { for (channel = 0; channel < outputBpp; ++channel) { outputPixelQueue[column * outputBpp + channel] = (byte) (inputPixelQueue[column] >> (INT_TRANSLATOR_CHANNEL_MAP[channel] * 8)); } } } } /** The image being encoded. */ private BufferedImage image; /** The PNG encoding filter to be used. */ private Filter filter; /** The the deflater compression level. */ private int compressionLevel; /** The pixel width of the image. */ private int width; /** The pixel height of the image. */ private int height; /** The imageRaster
transfer type. */ private int transferType; /** The imageRaster
data. */ private Raster raster; /** The source image bits-per-pixel. */ private int inputBpp; /** The encoded image bits-per-pixel. */ private int outputBpp; /** TheTranslator
being used for encoding. */ private Translator translator; /** * Creates a PNG encoder for an image. * * @param image the image to be encoded * @param encodeAlpha true if the image"s alpha channel should be encoded * @param filter The filter to be applied to the image data, one of the * following values:
-
*
- SUB_FILTER *
- UP_FILTER *
- AVERAGE_FILTER *
- PAETH_FILTER *
* If a null value is specified, no filtering will be performed. * @param compressionLevel the deflater compression level that will be used * for compressing the image data: Valid values range from 0 to 9. * Higher values result in smaller files and therefore decrease * network traffic, but require more CPU time to encode. The normal * compromise value is 3. */ public PngEncoder(Image image, boolean encodeAlpha, Filter filter, int compressionLevel) { super(); this.image = ImageToBufferedImage.toBufferedImage(image); this.filter = filter; this.rupressionLevel = compressionLevel; width = this.image.getWidth(null); height = this.image.getHeight(null); raster = this.image.getRaster(); transferType = raster.getTransferType(); // Establish storage information int dataBytes = raster.getNumDataElements(); if (transferType == DataBuffer.TYPE_BYTE && dataBytes == 4) { outputBpp = encodeAlpha ? 4 : 3; inputBpp = 4; translator = new ByteTranslator(); } else if (transferType == DataBuffer.TYPE_BYTE && dataBytes == 3) { outputBpp = 3; inputBpp = 3; encodeAlpha = false; translator = new ByteTranslator(); } else if (transferType == DataBuffer.TYPE_INT && dataBytes == 1) { outputBpp = encodeAlpha ? 4 : 3; inputBpp = 4; translator = new IntTranslator(); } else if (transferType == DataBuffer.TYPE_BYTE && dataBytes == 1) { throw new UnsupportedOperationException("Encoding indexed-color images not yet supported."); } else { throw new IllegalArgumentException( "Cannot determine appropriate bits-per-pixel for provided image."); } } /** * Encodes the image. * * @param out an OutputStream to which the encoded image will be * written * @throws IOException if a problem is encountered writing the output */ public synchronized void encode(OutputStream out) throws IOException { Checksum csum = new CRC32(); out = new CheckedOutputStream(out, csum); out.write(SIGNATURE); writeIhdrChunk(out, csum); if (outputBpp == 1) { writePlteChunk(out, csum); } writeIdatChunks(out, csum); writeIendChunk(out, csum); } /** * Writes the IDAT (Image data) chunks to the output stream. * * @param out the OutputStream to write the chunk to * @param csum the Checksum that is updated as data is written * to the passed-in OutputStream * @throws IOException if a problem is encountered writing the output */ private void writeIdatChunks(OutputStream out, Checksum csum) throws IOException { int rowWidth = width * outputBpp; // size of image data in a row in bytes. int row = 0; Deflater deflater = new Deflater(compressionLevel); ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); DeflaterOutputStream defOut = new DeflaterOutputStream(byteOut, deflater); byte[] filteredPixelQueue = new byte[rowWidth]; // Output Pixel Queues byte[][] outputPixelQueue = new byte[2][rowWidth]; Arrays.fill(outputPixelQueue[1], (byte) 0); int outputPixelQueueRow = 0; int outputPixelQueuePrevRow = 1; while (row < height) { if (filter == null) { defOut.write(0); translator.translate(outputPixelQueue[outputPixelQueueRow], row); defOut.write(outputPixelQueue[outputPixelQueueRow], 0, rowWidth); } else { defOut.write(filter.getType()); translator.translate(outputPixelQueue[outputPixelQueueRow], row); filter.filter(filteredPixelQueue, outputPixelQueue[outputPixelQueueRow], outputPixelQueue[outputPixelQueuePrevRow], outputBpp); defOut.write(filteredPixelQueue, 0, rowWidth); } ++row; outputPixelQueueRow = row & 1; outputPixelQueuePrevRow = outputPixelQueueRow ^ 1; } defOut.finish(); byteOut.close(); writeInt(out, byteOut.size()); csum.reset(); out.write(IDAT); byteOut.writeTo(out); writeInt(out, (int) csum.getValue()); } /** * Writes the IEND (End-of-file) chunk to the output stream. * * @param out the OutputStream to write the chunk to * @param csum the Checksum that is updated as data is written * to the passed-in OutputStream * @throws IOException if a problem is encountered writing the output */ private void writeIendChunk(OutputStream out, Checksum csum) throws IOException { writeInt(out, 0); csum.reset(); out.write(IEND); writeInt(out, (int) csum.getValue()); } /** * writes the IHDR (Image Header) chunk to the output stream * * @param out the OutputStream to write the chunk to * @param csum the Checksum that is updated as data is written * to the passed-in OutputStream * @throws IOException if a problem is encountered writing the output */ private void writeIhdrChunk(OutputStream out, Checksum csum) throws IOException { writeInt(out, 13); // Chunk Size csum.reset(); out.write(IHDR); writeInt(out, width); writeInt(out, height); out.write(BIT_DEPTH); switch (outputBpp) { case 1: out.write(COLOR_TYPE_INDEXED); break; case 3: out.write(COLOR_TYPE_RGB); break; case 4: out.write(COLOR_TYPE_RGBA); break; default: throw new IllegalStateException("Invalid bytes per pixel"); } out.write(0); // Compression Method out.write(0); // Filter Method out.write(0); // Interlace writeInt(out, (int) csum.getValue()); } /** * Writes the PLTE (Palate) chunk to the output stream. * * @param out the OutputStream to write the chunk to * @param csum the Checksum that is updated as data is written * to the passed-in OutputStream * @throws IOException if a problem is encountered writing the output */ private void writePlteChunk(OutputStream out, Checksum csum) throws IOException { IndexColorModel icm = (IndexColorModel) image.getColorModel(); writeInt(out, 768); // Chunk Size csum.reset(); out.write(PLTE); byte[] reds = new byte[256]; icm.getReds(reds); byte[] greens = new byte[256]; icm.getGreens(greens); byte[] blues = new byte[256]; icm.getBlues(blues); for (int index = 0; index < 256; ++index) { out.write(reds[index]); out.write(greens[index]); out.write(blues[index]); } writeInt(out, (int) csum.getValue()); }
}
</source>
PNG Decoder
<source lang="java"> /*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): Alexandre Iline. * * The Original Software is the Jemmy library. * The Initial Developer of the Original Software is Alexandre Iline. * All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. * * * * $Id$ $Revision$ $Date$ * */
import java.awt.AWTException; import java.awt.Color; import java.awt.ruponent; import java.awt.Rectangle; import java.awt.Robot; import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.CRC32; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; /**
* Allows to load PNG graphical file. * @author Alexandre Iline */
public class PNGDecoder extends Object {
InputStream in; /** * Constructs a PNGDecoder object. * @param in input stream to read PNG image from. */ public PNGDecoder(InputStream in) { this.in = in; } byte read() throws IOException { byte b = (byte)in.read(); return(b); } int readInt() throws IOException { byte b[] = read(4); return(((b[0]&0xff)<<24) + ((b[1]&0xff)<<16) + ((b[2]&0xff)<<8) + ((b[3]&0xff))); } byte[] read(int count) throws IOException { byte[] result = new byte[count]; for(int i = 0; i < count; i++) { result[i] = read(); } return(result); } boolean compare(byte[] b1, byte[] b2) { if(b1.length != b2.length) { return(false); } for(int i = 0; i < b1.length; i++) { if(b1[i] != b2[i]) { return(false); } } return(true); } void checkEquality(byte[] b1, byte[] b2) { if(!compare(b1, b2)) { throw(new RuntimeException("Format error")); } } /** * Decodes image from an input stream passed into constructor. * @return a BufferedImage object * @throws IOException */ public BufferedImage decode() throws IOException { byte[] id = read(12); checkEquality(id, new byte[] {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13}); byte[] ihdr = read(4); checkEquality(ihdr, "IHDR".getBytes()); int width = readInt(); int height = readInt(); BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); byte[] head = read(5); int mode; if(compare(head, new byte[]{1, 0, 0, 0, 0})) { mode = PNGEncoder.BW_MODE; } else if(compare(head, new byte[]{8, 0, 0, 0, 0})) { mode = PNGEncoder.GREYSCALE_MODE; } else if(compare(head, new byte[]{8, 2, 0, 0, 0})) { mode = PNGEncoder.COLOR_MODE; } else { throw(new RuntimeException("Format error")); } readInt();//!!crc int size = readInt(); byte[] idat = read(4); checkEquality(idat, "IDAT".getBytes()); byte[] data = read(size);
Inflater inflater = new Inflater(); inflater.setInput(data, 0, size); int color; try { switch (mode) { case PNGEncoder.BW_MODE: { int bytes = (int)(width / 8); if((width % 8) != 0) { bytes++; } byte colorset; byte[] row = new byte[bytes]; for (int y = 0; y < height; y++) { inflater.inflate(new byte[1]); inflater.inflate(row); for (int x = 0; x < bytes; x++) { colorset = row[x]; for (int sh = 0; sh < 8; sh++) { if(x * 8 + sh >= width) { break; } if((colorset & 0x80) == 0x80) { result.setRGB(x * 8 + sh, y, Color.white.getRGB()); } else { result.setRGB(x * 8 + sh, y, Color.black.getRGB()); } colorset <<= 1; } } } } break; case PNGEncoder.GREYSCALE_MODE: { byte[] row = new byte[width]; for (int y = 0; y < height; y++) { inflater.inflate(new byte[1]); inflater.inflate(row); for (int x = 0; x < width; x++) { color = row[x]; result.setRGB(x, y, (color << 16) + (color << 8) + color); } } } break; case PNGEncoder.COLOR_MODE: { byte[] row = new byte[width * 3]; for (int y = 0; y < height; y++) { inflater.inflate(new byte[1]); inflater.inflate(row); for (int x = 0; x < width; x++) { result.setRGB(x, y, ((row[x * 3 + 0]&0xff) << 16) + ((row[x * 3 + 1]&0xff) << 8) + ((row[x * 3 + 2]&0xff))); } } } } } catch(DataFormatException e) { throw(new RuntimeException("ZIP error"+e)); } readInt();//!!crc readInt();//0 byte[] iend = read(4); checkEquality(iend, "IEND".getBytes()); readInt();//!!crc in.close(); return(result); } /** * Decodes image from file. * @param fileName a file to read image from * @return a BufferedImage instance. */ public static BufferedImage decode(String fileName) { try { return(new PNGDecoder(new FileInputStream(fileName)).decode()); } catch(IOException e) { throw(new RuntimeException("IOException during image reading"+ e)); } }
} class PNGEncoder extends Object {
/** black and white image mode. */ public static final byte BW_MODE = 0; /** grey scale image mode. */ public static final byte GREYSCALE_MODE = 1; /** full color image mode. */ public static final byte COLOR_MODE = 2; OutputStream out; CRC32 crc; byte mode; /** public constructor of PNGEncoder class with greyscale mode by default. * @param out output stream for PNG image format to write into */ public PNGEncoder(OutputStream out) { this(out, GREYSCALE_MODE); } /** public constructor of PNGEncoder class. * @param out output stream for PNG image format to write into * @param mode BW_MODE, GREYSCALE_MODE or COLOR_MODE */ public PNGEncoder(OutputStream out, byte mode) { crc=new CRC32(); this.out = out; if (mode<0 || mode>2) throw new IllegalArgumentException("Unknown color mode"); this.mode = mode; } void write(int i) throws IOException { byte b[]={(byte)((i>>24)&0xff),(byte)((i>>16)&0xff),(byte)((i>>8)&0xff),(byte)(i&0xff)}; write(b); } void write(byte b[]) throws IOException { out.write(b); crc.update(b); } /** main encoding method (stays blocked till encoding is finished). * @param image BufferedImage to encode * @throws IOException IOException */ public void encode(BufferedImage image) throws IOException { int width = image.getWidth(null); int height = image.getHeight(null); final byte id[] = {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13}; write(id); crc.reset(); write("IHDR".getBytes()); write(width); write(height); byte head[]=null; switch (mode) { case BW_MODE: head=new byte[]{1, 0, 0, 0, 0}; break; case GREYSCALE_MODE: head=new byte[]{8, 0, 0, 0, 0}; break; case COLOR_MODE: head=new byte[]{8, 2, 0, 0, 0}; break; } write(head); write((int) crc.getValue()); ByteArrayOutputStream compressed = new ByteArrayOutputStream(65536); BufferedOutputStream bos = new BufferedOutputStream( new DeflaterOutputStream(compressed, new Deflater(9))); int pixel; int color; int colorset; switch (mode) { case BW_MODE: int rest=width%8; int bytes=width/8; for (int y=0;y<height;y++) { bos.write(0); for (int x=0;x<bytes;x++) { colorset=0; for (int sh=0; sh<8; sh++) { pixel=image.getRGB(x*8+sh,y); color=((pixel >> 16) & 0xff); color+=((pixel >> 8) & 0xff); color+=(pixel & 0xff); colorset<<=1; if (color>=3*128) colorset|=1; } bos.write((byte)colorset); } if (rest>0) { colorset=0; for (int sh=0; sh<width%8; sh++) { pixel=image.getRGB(bytes*8+sh,y); color=((pixel >> 16) & 0xff); color+=((pixel >> 8) & 0xff); color+=(pixel & 0xff); colorset<<=1; if (color>=3*128) colorset|=1; } colorset<<=8-rest; bos.write((byte)colorset); } } break; case GREYSCALE_MODE: for (int y=0;y<height;y++) { bos.write(0); for (int x=0;x<width;x++) { pixel=image.getRGB(x,y); color=((pixel >> 16) & 0xff); color+=((pixel >> 8) & 0xff); color+=(pixel & 0xff); bos.write((byte)(color/3)); } } break; case COLOR_MODE: for (int y=0;y<height;y++) { bos.write(0); for (int x=0;x<width;x++) { pixel=image.getRGB(x,y); bos.write((byte)((pixel >> 16) & 0xff)); bos.write((byte)((pixel >> 8) & 0xff)); bos.write((byte)(pixel & 0xff)); } } break; } bos.close(); write(compressed.size()); crc.reset(); write("IDAT".getBytes()); write(compressed.toByteArray()); write((int) crc.getValue()); write(0); crc.reset(); write("IEND".getBytes()); write((int) crc.getValue()); out.close(); } /** Static method performing screen capture into PNG image format file with given fileName. * @param rect Rectangle of screen to be captured * @param fileName file name for screen capture PNG image file */ public static void captureScreen(Rectangle rect, String fileName) { captureScreen(rect, fileName, GREYSCALE_MODE); } /** Static method performing screen capture into PNG image format file with given fileName. * @param rect Rectangle of screen to be captured * @param mode image color mode * @param fileName file name for screen capture PNG image file */ public static void captureScreen(Rectangle rect, String fileName, byte mode) { try { BufferedImage capture=new Robot().createScreenCapture(rect); BufferedOutputStream file=new BufferedOutputStream(new FileOutputStream(fileName)); PNGEncoder encoder=new PNGEncoder(file, mode); encoder.encode(capture); } catch (AWTException awte) { awte.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } } /** Static method performing one component screen capture into PNG image format file with given fileName. * @param comp Component to be captured * @param fileName String image target filename */ public static void captureScreen(Component comp, String fileName) { captureScreen(comp, fileName, GREYSCALE_MODE); } /** Static method performing one component screen capture into PNG image format file with given fileName. * @param comp Component to be captured * @param fileName String image target filename * @param mode image color mode */ public static void captureScreen(Component comp, String fileName, byte mode) {
captureScreen(new Rectangle(comp.getLocationOnScreen(),
comp.getSize()), fileName, mode); } /** Static method performing whole screen capture into PNG image format file with given fileName. * @param fileName String image target filename */ public static void captureScreen(String fileName) { captureScreen(fileName, GREYSCALE_MODE); } /** Static method performing whole screen capture into PNG image format file with given fileName. * @param fileName String image target filename * @param mode image color mode */ public static void captureScreen(String fileName, byte mode) {
captureScreen(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()), fileName, mode);
}
}
</source>
PNG Encoder
<source lang="java"> /*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): Alexandre Iline. * * The Original Software is the Jemmy library. * The Initial Developer of the Original Software is Alexandre Iline. * All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. * * * * $Id$ $Revision$ $Date$ * */
import java.awt.AWTException; import java.awt.ruponent; import java.awt.Rectangle; import java.awt.Robot; import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; /** This class allows to encode BufferedImage into B/W, greyscale or true color PNG
* image format with maximum compression.
* It also provides complete functionality for capturing full screen, part of * screen or single component, encoding and saving captured image info PNG file. * @author Adam Sotona * @version 1.0 */
public class PNGEncoder extends Object {
/** black and white image mode. */ public static final byte BW_MODE = 0; /** grey scale image mode. */ public static final byte GREYSCALE_MODE = 1; /** full color image mode. */ public static final byte COLOR_MODE = 2; OutputStream out; CRC32 crc; byte mode; /** public constructor of PNGEncoder class with greyscale mode by default. * @param out output stream for PNG image format to write into */ public PNGEncoder(OutputStream out) { this(out, GREYSCALE_MODE); } /** public constructor of PNGEncoder class. * @param out output stream for PNG image format to write into * @param mode BW_MODE, GREYSCALE_MODE or COLOR_MODE */ public PNGEncoder(OutputStream out, byte mode) { crc=new CRC32(); this.out = out; if (mode<0 || mode>2) throw new IllegalArgumentException("Unknown color mode"); this.mode = mode; } void write(int i) throws IOException { byte b[]={(byte)((i>>24)&0xff),(byte)((i>>16)&0xff),(byte)((i>>8)&0xff),(byte)(i&0xff)}; write(b); } void write(byte b[]) throws IOException { out.write(b); crc.update(b); } /** main encoding method (stays blocked till encoding is finished). * @param image BufferedImage to encode * @throws IOException IOException */ public void encode(BufferedImage image) throws IOException { int width = image.getWidth(null); int height = image.getHeight(null); final byte id[] = {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13}; write(id); crc.reset(); write("IHDR".getBytes()); write(width); write(height); byte head[]=null; switch (mode) { case BW_MODE: head=new byte[]{1, 0, 0, 0, 0}; break; case GREYSCALE_MODE: head=new byte[]{8, 0, 0, 0, 0}; break; case COLOR_MODE: head=new byte[]{8, 2, 0, 0, 0}; break; } write(head); write((int) crc.getValue()); ByteArrayOutputStream compressed = new ByteArrayOutputStream(65536); BufferedOutputStream bos = new BufferedOutputStream( new DeflaterOutputStream(compressed, new Deflater(9))); int pixel; int color; int colorset; switch (mode) { case BW_MODE: int rest=width%8; int bytes=width/8; for (int y=0;y<height;y++) { bos.write(0); for (int x=0;x<bytes;x++) { colorset=0; for (int sh=0; sh<8; sh++) { pixel=image.getRGB(x*8+sh,y); color=((pixel >> 16) & 0xff); color+=((pixel >> 8) & 0xff); color+=(pixel & 0xff); colorset<<=1; if (color>=3*128) colorset|=1; } bos.write((byte)colorset); } if (rest>0) { colorset=0; for (int sh=0; sh<width%8; sh++) { pixel=image.getRGB(bytes*8+sh,y); color=((pixel >> 16) & 0xff); color+=((pixel >> 8) & 0xff); color+=(pixel & 0xff); colorset<<=1; if (color>=3*128) colorset|=1; } colorset<<=8-rest; bos.write((byte)colorset); } } break; case GREYSCALE_MODE: for (int y=0;y<height;y++) { bos.write(0); for (int x=0;x<width;x++) { pixel=image.getRGB(x,y); color=((pixel >> 16) & 0xff); color+=((pixel >> 8) & 0xff); color+=(pixel & 0xff); bos.write((byte)(color/3)); } } break; case COLOR_MODE: for (int y=0;y<height;y++) { bos.write(0); for (int x=0;x<width;x++) { pixel=image.getRGB(x,y); bos.write((byte)((pixel >> 16) & 0xff)); bos.write((byte)((pixel >> 8) & 0xff)); bos.write((byte)(pixel & 0xff)); } } break; } bos.close(); write(compressed.size()); crc.reset(); write("IDAT".getBytes()); write(compressed.toByteArray()); write((int) crc.getValue()); write(0); crc.reset(); write("IEND".getBytes()); write((int) crc.getValue()); out.close(); } /** Static method performing screen capture into PNG image format file with given fileName. * @param rect Rectangle of screen to be captured * @param fileName file name for screen capture PNG image file */ public static void captureScreen(Rectangle rect, String fileName) { captureScreen(rect, fileName, GREYSCALE_MODE); } /** Static method performing screen capture into PNG image format file with given fileName. * @param rect Rectangle of screen to be captured * @param mode image color mode * @param fileName file name for screen capture PNG image file */ public static void captureScreen(Rectangle rect, String fileName, byte mode) { try { BufferedImage capture=new Robot().createScreenCapture(rect); BufferedOutputStream file=new BufferedOutputStream(new FileOutputStream(fileName)); PNGEncoder encoder=new PNGEncoder(file, mode); encoder.encode(capture); } catch (AWTException awte) { awte.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } } /** Static method performing one component screen capture into PNG image format file with given fileName. * @param comp Component to be captured * @param fileName String image target filename */ public static void captureScreen(Component comp, String fileName) { captureScreen(comp, fileName, GREYSCALE_MODE); } /** Static method performing one component screen capture into PNG image format file with given fileName. * @param comp Component to be captured * @param fileName String image target filename * @param mode image color mode */ public static void captureScreen(Component comp, String fileName, byte mode) { captureScreen(new Rectangle(comp.getLocationOnScreen(), comp.getSize()), fileName, mode); } /** Static method performing whole screen capture into PNG image format file with given fileName. * @param fileName String image target filename */ public static void captureScreen(String fileName) { captureScreen(fileName, GREYSCALE_MODE); } /** Static method performing whole screen capture into PNG image format file with given fileName. * @param fileName String image target filename * @param mode image color mode */ public static void captureScreen(String fileName, byte mode) { captureScreen(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()), fileName, mode); }
}
</source>
PngEncoder takes a Java Image object and creates a byte string which can be saved as a PNG file
<source lang="java">
import java.awt.Image; import java.awt.image.ImageObserver; import java.awt.image.PixelGrabber; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; /**
* PngEncoder takes a Java Image object and creates a byte string which can be * saved as a PNG file. The Image is presumed to use the DirectColorModel. **
Thanks to Jay Denny at KeyPoint Software * http://www.keypoint.ru/ * who let me develop this code on company time.
**
You may contact me with (probably very-much-needed) improvements, * comments, and bug fixes at:
**
david@catcode.ru
**
This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version.
**
This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details.
**
You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA. A copy of the GNU LGPL may be found at
* http://www.gnu.org/copyleft/lesser.html
* * @author J. David Eisenberg * @version 1.5, 19 Oct 2003 * * CHANGES: * -------- * 19-Nov-2002 : CODING STYLE CHANGES ONLY (by David Gilbert for Object * Refinery Limited); * 19-Sep-2003 : Fix for platforms using EBCDIC (contributed by Paulo Soares); * 19-Oct-2003 : Change private fields to protected fields so that * PngEncoderB can inherit them (JDE) * Fixed bug with calculation of nRows * 15-Aug-2008 : Added scrunch.end() in writeImageData() method - see * JFreeChart bug report 2037930 (David Gilbert); */
public class PngEncoder {
/** Constant specifying that alpha channel should be encoded. */ public static final boolean ENCODE_ALPHA = true; /** Constant specifying that alpha channel should not be encoded. */ public static final boolean NO_ALPHA = false; /** Constants for filter (NONE). */ public static final int FILTER_NONE = 0; /** Constants for filter (SUB). */ public static final int FILTER_SUB = 1; /** Constants for filter (UP). */ public static final int FILTER_UP = 2; /** Constants for filter (LAST). */ public static final int FILTER_LAST = 2; /** IHDR tag. */ protected static final byte[] IHDR = {73, 72, 68, 82}; /** IDAT tag. */ protected static final byte[] IDAT = {73, 68, 65, 84}; /** IEND tag. */ protected static final byte[] IEND = {73, 69, 78, 68}; /** PHYS tag. */ protected static final byte[] PHYS = {(byte)"p", (byte)"H", (byte)"Y", (byte)"s"}; /** The png bytes. */ protected byte[] pngBytes; /** The prior row. */ protected byte[] priorRow; /** The left bytes. */ protected byte[] leftBytes; /** The image. */ protected Image image; /** The width. */ protected int width; /** The height. */ protected int height; /** The byte position. */ protected int bytePos; /** The maximum position. */ protected int maxPos; /** CRC. */ protected CRC32 crc = new CRC32(); /** The CRC value. */ protected long crcValue; /** Encode alpha? */ protected boolean encodeAlpha; /** The filter type. */ protected int filter; /** The bytes-per-pixel. */ protected int bytesPerPixel; /** The physical pixel dimension : number of pixels per inch on the X axis. */ private int xDpi = 0; /** The physical pixel dimension : number of pixels per inch on the Y axis. */ private int yDpi = 0; /** Used for conversion of DPI to Pixels per Meter. */ static private float INCH_IN_METER_UNIT = 0.0254f; /** * The compression level (1 = best speed, 9 = best compression, * 0 = no compression). */ protected int compressionLevel; /** * Class constructor. */ public PngEncoder() { this(null, false, FILTER_NONE, 0); } /** * Class constructor specifying Image to encode, with no alpha channel * encoding. * * @param image A Java Image object which uses the DirectColorModel * @see java.awt.Image */ public PngEncoder(Image image) { this(image, false, FILTER_NONE, 0); } /** * Class constructor specifying Image to encode, and whether to encode * alpha. * * @param image A Java Image object which uses the DirectColorModel * @param encodeAlpha Encode the alpha channel? false=no; true=yes * @see java.awt.Image */ public PngEncoder(Image image, boolean encodeAlpha) { this(image, encodeAlpha, FILTER_NONE, 0); } /** * Class constructor specifying Image to encode, whether to encode alpha, * and filter to use. * * @param image A Java Image object which uses the DirectColorModel * @param encodeAlpha Encode the alpha channel? false=no; true=yes * @param whichFilter 0=none, 1=sub, 2=up * @see java.awt.Image */ public PngEncoder(Image image, boolean encodeAlpha, int whichFilter) { this(image, encodeAlpha, whichFilter, 0); }
/** * Class constructor specifying Image source to encode, whether to encode * alpha, filter to use, and compression level. * * @param image A Java Image object * @param encodeAlpha Encode the alpha channel? false=no; true=yes * @param whichFilter 0=none, 1=sub, 2=up * @param compLevel 0..9 (1 = best speed, 9 = best compression, 0 = no * compression) * @see java.awt.Image */ public PngEncoder(Image image, boolean encodeAlpha, int whichFilter, int compLevel) { this.image = image; this.encodeAlpha = encodeAlpha; setFilter(whichFilter); if (compLevel >= 0 && compLevel <= 9) { this.rupressionLevel = compLevel; } } /** * Set the image to be encoded. * * @param image A Java Image object which uses the DirectColorModel * @see java.awt.Image * @see java.awt.image.DirectColorModel */ public void setImage(Image image) { this.image = image; this.pngBytes = null; } /** * Returns the image to be encoded. * * @return The image. */ public Image getImage() { return this.image; } /** * Creates an array of bytes that is the PNG equivalent of the current * image, specifying whether to encode alpha or not. * * @param encodeAlpha boolean false=no alpha, true=encode alpha * @return an array of bytes, or null if there was a problem */ public byte[] pngEncode(boolean encodeAlpha) { byte[] pngIdBytes = {-119, 80, 78, 71, 13, 10, 26, 10}; if (this.image == null) { return null; } this.width = this.image.getWidth(null); this.height = this.image.getHeight(null); /* * start with an array that is big enough to hold all the pixels * (plus filter bytes), and an extra 200 bytes for header info */ this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200]; /* * keep track of largest byte written to the array */ this.maxPos = 0; this.bytePos = writeBytes(pngIdBytes, 0); //hdrPos = bytePos; writeHeader(); writeResolution(); //dataPos = bytePos; if (writeImageData()) { writeEnd(); this.pngBytes = resizeByteArray(this.pngBytes, this.maxPos); } else { this.pngBytes = null; } return this.pngBytes; } /** * Creates an array of bytes that is the PNG equivalent of the current * image. Alpha encoding is determined by its setting in the constructor. * * @return an array of bytes, or null if there was a problem */ public byte[] pngEncode() { return pngEncode(this.encodeAlpha); } /** * Set the alpha encoding on or off. * * @param encodeAlpha false=no, true=yes */ public void setEncodeAlpha(boolean encodeAlpha) { this.encodeAlpha = encodeAlpha; } /** * Retrieve alpha encoding status. * * @return boolean false=no, true=yes */ public boolean getEncodeAlpha() { return this.encodeAlpha; } /** * Set the filter to use. * * @param whichFilter from constant list */ public void setFilter(int whichFilter) { this.filter = FILTER_NONE; if (whichFilter <= FILTER_LAST) { this.filter = whichFilter; } } /** * Retrieve filtering scheme. * * @return int (see constant list) */ public int getFilter() { return this.filter; } /** * Set the compression level to use. * * @param level the compression level (1 = best speed, 9 = best compression, * 0 = no compression) */ public void setCompressionLevel(int level) { if (level >= 0 && level <= 9) { this.rupressionLevel = level; } } /** * Retrieve compression level. * * @return int (1 = best speed, 9 = best compression, 0 = no compression) */ public int getCompressionLevel() { return this.rupressionLevel; } /** * Increase or decrease the length of a byte array. * * @param array The original array. * @param newLength The length you wish the new array to have. * @return Array of newly desired length. If shorter than the * original, the trailing elements are truncated. */ protected byte[] resizeByteArray(byte[] array, int newLength) { byte[] newArray = new byte[newLength]; int oldLength = array.length; System.arraycopy(array, 0, newArray, 0, Math.min(oldLength, newLength)); return newArray; } /** * Write an array of bytes into the pngBytes array. * Note: This routine has the side effect of updating * maxPos, the largest element written in the array. * The array is resized by 1000 bytes or the length * of the data to be written, whichever is larger. * * @param data The data to be written into pngBytes. * @param offset The starting point to write to. * @return The next place to be written to in the pngBytes array. */ protected int writeBytes(byte[] data, int offset) { this.maxPos = Math.max(this.maxPos, offset + data.length); if (data.length + offset > this.pngBytes.length) { this.pngBytes = resizeByteArray(this.pngBytes, this.pngBytes.length + Math.max(1000, data.length)); } System.arraycopy(data, 0, this.pngBytes, offset, data.length); return offset + data.length; } /** * Write an array of bytes into the pngBytes array, specifying number of * bytes to write. Note: This routine has the side effect of updating * maxPos, the largest element written in the array. * The array is resized by 1000 bytes or the length * of the data to be written, whichever is larger. * * @param data The data to be written into pngBytes. * @param nBytes The number of bytes to be written. * @param offset The starting point to write to. * @return The next place to be written to in the pngBytes array. */ protected int writeBytes(byte[] data, int nBytes, int offset) { this.maxPos = Math.max(this.maxPos, offset + nBytes); if (nBytes + offset > this.pngBytes.length) { this.pngBytes = resizeByteArray(this.pngBytes, this.pngBytes.length + Math.max(1000, nBytes)); } System.arraycopy(data, 0, this.pngBytes, offset, nBytes); return offset + nBytes; } /** * Write a two-byte integer into the pngBytes array at a given position. * * @param n The integer to be written into pngBytes. * @param offset The starting point to write to. * @return The next place to be written to in the pngBytes array. */ protected int writeInt2(int n, int offset) { byte[] temp = {(byte) ((n >> 8) & 0xff), (byte) (n & 0xff)}; return writeBytes(temp, offset); } /** * Write a four-byte integer into the pngBytes array at a given position. * * @param n The integer to be written into pngBytes. * @param offset The starting point to write to. * @return The next place to be written to in the pngBytes array. */ protected int writeInt4(int n, int offset) { byte[] temp = {(byte) ((n >> 24) & 0xff), (byte) ((n >> 16) & 0xff), (byte) ((n >> 8) & 0xff), (byte) (n & 0xff)}; return writeBytes(temp, offset); } /** * Write a single byte into the pngBytes array at a given position. * * @param b The integer to be written into pngBytes. * @param offset The starting point to write to. * @return The next place to be written to in the pngBytes array. */ protected int writeByte(int b, int offset) { byte[] temp = {(byte) b}; return writeBytes(temp, offset); } /** * Write a PNG "IHDR" chunk into the pngBytes array. */ protected void writeHeader() { int startPos = this.bytePos = writeInt4(13, this.bytePos); this.bytePos = writeBytes(IHDR, this.bytePos); this.width = this.image.getWidth(null); this.height = this.image.getHeight(null); this.bytePos = writeInt4(this.width, this.bytePos); this.bytePos = writeInt4(this.height, this.bytePos); this.bytePos = writeByte(8, this.bytePos); // bit depth this.bytePos = writeByte((this.encodeAlpha) ? 6 : 2, this.bytePos); // direct model this.bytePos = writeByte(0, this.bytePos); // compression method this.bytePos = writeByte(0, this.bytePos); // filter method this.bytePos = writeByte(0, this.bytePos); // no interlace this.crc.reset(); this.crc.update(this.pngBytes, startPos, this.bytePos - startPos); this.crcValue = this.crc.getValue(); this.bytePos = writeInt4((int) this.crcValue, this.bytePos); } /** * Perform "sub" filtering on the given row. * Uses temporary array leftBytes to store the original values * of the previous pixels. The array is 16 bytes long, which * will easily hold two-byte samples plus two-byte alpha. * * @param pixels The array holding the scan lines being built * @param startPos Starting position within pixels of bytes to be filtered. * @param width Width of a scanline in pixels. */ protected void filterSub(byte[] pixels, int startPos, int width) { int offset = this.bytesPerPixel; int actualStart = startPos + offset; int nBytes = width * this.bytesPerPixel; int leftInsert = offset; int leftExtract = 0; for (int i = actualStart; i < startPos + nBytes; i++) { this.leftBytes[leftInsert] = pixels[i]; pixels[i] = (byte) ((pixels[i] - this.leftBytes[leftExtract]) % 256); leftInsert = (leftInsert + 1) % 0x0f; leftExtract = (leftExtract + 1) % 0x0f; } } /** * Perform "up" filtering on the given row. * Side effect: refills the prior row with current row * * @param pixels The array holding the scan lines being built * @param startPos Starting position within pixels of bytes to be filtered. * @param width Width of a scanline in pixels. */ protected void filterUp(byte[] pixels, int startPos, int width) { final int nBytes = width * this.bytesPerPixel; for (int i = 0; i < nBytes; i++) { final byte currentByte = pixels[startPos + i]; pixels[startPos + i] = (byte) ((pixels[startPos + i] - this.priorRow[i]) % 256); this.priorRow[i] = currentByte; } } /** * Write the image data into the pngBytes array. * This will write one or more PNG "IDAT" chunks. In order * to conserve memory, this method grabs as many rows as will * fit into 32K bytes, or the whole image; whichever is less. * * * @return true if no errors; false if error grabbing pixels */ protected boolean writeImageData() { int rowsLeft = this.height; // number of rows remaining to write int startRow = 0; // starting row to process this time through int nRows; // how many rows to grab at a time byte[] scanLines; // the scan lines to be compressed int scanPos; // where we are in the scan lines int startPos; // where this line"s actual pixels start (used // for filtering) byte[] compressedLines; // the resultant compressed lines int nCompressed; // how big is the compressed area? //int depth; // color depth ( handle only 8 or 32 ) PixelGrabber pg; this.bytesPerPixel = (this.encodeAlpha) ? 4 : 3; Deflater scrunch = new Deflater(this.rupressionLevel); ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024); DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes, scrunch); try { while (rowsLeft > 0) { nRows = Math.min(32767 / (this.width * (this.bytesPerPixel + 1)), rowsLeft); nRows = Math.max(nRows, 1); int[] pixels = new int[this.width * nRows]; pg = new PixelGrabber(this.image, 0, startRow, this.width, nRows, pixels, 0, this.width); try { pg.grabPixels(); } catch (Exception e) { System.err.println("interrupted waiting for pixels!"); return false; } if ((pg.getStatus() & ImageObserver.ABORT) != 0) { System.err.println("image fetch aborted or errored"); return false; } /* * Create a data chunk. scanLines adds "nRows" for * the filter bytes. */ scanLines = new byte[this.width * nRows * this.bytesPerPixel + nRows]; if (this.filter == FILTER_SUB) { this.leftBytes = new byte[16]; } if (this.filter == FILTER_UP) { this.priorRow = new byte[this.width * this.bytesPerPixel]; } scanPos = 0; startPos = 1; for (int i = 0; i < this.width * nRows; i++) { if (i % this.width == 0) { scanLines[scanPos++] = (byte) this.filter; startPos = scanPos; } scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff); scanLines[scanPos++] = (byte) ((pixels[i] >> 8) & 0xff); scanLines[scanPos++] = (byte) ((pixels[i]) & 0xff); if (this.encodeAlpha) { scanLines[scanPos++] = (byte) ((pixels[i] >> 24) & 0xff); } if ((i % this.width == this.width - 1) && (this.filter != FILTER_NONE)) { if (this.filter == FILTER_SUB) { filterSub(scanLines, startPos, this.width); } if (this.filter == FILTER_UP) { filterUp(scanLines, startPos, this.width); } } } /* * Write these lines to the output area */ compBytes.write(scanLines, 0, scanPos); startRow += nRows; rowsLeft -= nRows; } compBytes.close(); /* * Write the compressed bytes */ compressedLines = outBytes.toByteArray(); nCompressed = compressedLines.length; this.crc.reset(); this.bytePos = writeInt4(nCompressed, this.bytePos); this.bytePos = writeBytes(IDAT, this.bytePos); this.crc.update(IDAT); this.bytePos = writeBytes(compressedLines, nCompressed, this.bytePos); this.crc.update(compressedLines, 0, nCompressed); this.crcValue = this.crc.getValue(); this.bytePos = writeInt4((int) this.crcValue, this.bytePos); scrunch.finish(); scrunch.end(); return true; } catch (IOException e) { System.err.println(e.toString()); return false; } } /** * Write a PNG "IEND" chunk into the pngBytes array. */ protected void writeEnd() { this.bytePos = writeInt4(0, this.bytePos); this.bytePos = writeBytes(IEND, this.bytePos); this.crc.reset(); this.crc.update(IEND); this.crcValue = this.crc.getValue(); this.bytePos = writeInt4((int) this.crcValue, this.bytePos); }
/** * Set the DPI for the X axis. * * @param xDpi The number of dots per inch */ public void setXDpi(int xDpi) { this.xDpi = Math.round(xDpi / INCH_IN_METER_UNIT); } /** * Get the DPI for the X axis. * * @return The number of dots per inch */ public int getXDpi() { return Math.round(this.xDpi * INCH_IN_METER_UNIT); } /** * Set the DPI for the Y axis. * * @param yDpi The number of dots per inch */ public void setYDpi(int yDpi) { this.yDpi = Math.round(yDpi / INCH_IN_METER_UNIT); } /** * Get the DPI for the Y axis. * * @return The number of dots per inch */ public int getYDpi() { return Math.round(this.yDpi * INCH_IN_METER_UNIT); } /** * Set the DPI resolution. * * @param xDpi The number of dots per inch for the X axis. * @param yDpi The number of dots per inch for the Y axis. */ public void setDpi(int xDpi, int yDpi) { this.xDpi = Math.round(xDpi / INCH_IN_METER_UNIT); this.yDpi = Math.round(yDpi / INCH_IN_METER_UNIT); } /** * Write a PNG "pHYs" chunk into the pngBytes array. */ protected void writeResolution() { if (this.xDpi > 0 && this.yDpi > 0) { final int startPos = this.bytePos = writeInt4(9, this.bytePos); this.bytePos = writeBytes(PHYS, this.bytePos); this.bytePos = writeInt4(this.xDpi, this.bytePos); this.bytePos = writeInt4(this.yDpi, this.bytePos); this.bytePos = writeByte(1, this.bytePos); // unit is the meter. this.crc.reset(); this.crc.update(this.pngBytes, startPos, this.bytePos - startPos); this.crcValue = this.crc.getValue(); this.bytePos = writeInt4((int) this.crcValue, this.bytePos); } }
}
</source>
PNG file format decoder
<source lang="java"> import java.awt.Graphics; import java.awt.Insets; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.IndexColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.zip.CRC32; import java.util.zip.InflaterInputStream; import javax.swing.JFrame; public class PNGDecoder {
public static void main(String[] args) throws Exception { String name = "logo.png"; if (args.length > 0) name = args[0]; InputStream in = PNGDecoder.class.getResourceAsStream(name); final BufferedImage image = PNGDecoder.decode(in); in.close(); JFrame f = new JFrame() { public void paint(Graphics g) { Insets insets = getInsets(); g.drawImage(image, insets.left, insets.top, null); } }; f.setVisible(true); Insets insets = f.getInsets(); f.setSize(image.getWidth() + insets.left + insets.right, image .getHeight() + insets.top + insets.bottom); } public static BufferedImage decode(InputStream in) throws IOException { DataInputStream dataIn = new DataInputStream(in); readSignature(dataIn); PNGData chunks = readChunks(dataIn); long widthLong = chunks.getWidth(); long heightLong = chunks.getHeight(); if (widthLong > Integer.MAX_VALUE || heightLong > Integer.MAX_VALUE) throw new IOException("That image is too wide or tall."); int width = (int) widthLong; int height = (int) heightLong; ColorModel cm = chunks.getColorModel(); WritableRaster raster = chunks.getRaster(); BufferedImage image = new BufferedImage(cm, raster, false, null); return image; } protected static void readSignature(DataInputStream in) throws IOException { long signature = in.readLong(); if (signature != 0x89504e470d0a1a0aL) throw new IOException("PNG signature not found!"); } protected static PNGData readChunks(DataInputStream in) throws IOException { PNGData chunks = new PNGData(); boolean trucking = true; while (trucking) { try { // Read the length. int length = in.readInt(); if (length < 0) throw new IOException("Sorry, that file is too long."); // Read the type. byte[] typeBytes = new byte[4]; in.readFully(typeBytes); // Read the data. byte[] data = new byte[length]; in.readFully(data); // Read the CRC. long crc = in.readInt() & 0x00000000ffffffffL; // Make it // unsigned. if (verifyCRC(typeBytes, data, crc) == false) throw new IOException("That file appears to be corrupted."); PNGChunk chunk = new PNGChunk(typeBytes, data); chunks.add(chunk); } catch (EOFException eofe) { trucking = false; } } return chunks; } protected static boolean verifyCRC(byte[] typeBytes, byte[] data, long crc) { CRC32 crc32 = new CRC32(); crc32.update(typeBytes); crc32.update(data); long calculated = crc32.getValue(); return (calculated == crc); }
} class PNGData {
private int mNumberOfChunks; private PNGChunk[] mChunks; public PNGData() { mNumberOfChunks = 0; mChunks = new PNGChunk[10]; } public void add(PNGChunk chunk) { mChunks[mNumberOfChunks++] = chunk; if (mNumberOfChunks >= mChunks.length) { PNGChunk[] largerArray = new PNGChunk[mChunks.length + 10]; System.arraycopy(mChunks, 0, largerArray, 0, mChunks.length); mChunks = largerArray; } } public long getWidth() { return getChunk("IHDR").getUnsignedInt(0); } public long getHeight() { return getChunk("IHDR").getUnsignedInt(4); } public short getBitsPerPixel() { return getChunk("IHDR").getUnsignedByte(8); } public short getColorType() { return getChunk("IHDR").getUnsignedByte(9); } public short getCompression() { return getChunk("IHDR").getUnsignedByte(10); } public short getFilter() { return getChunk("IHDR").getUnsignedByte(11); } public short getInterlace() { return getChunk("IHDR").getUnsignedByte(12); } public ColorModel getColorModel() { short colorType = getColorType(); int bitsPerPixel = getBitsPerPixel(); if (colorType == 3) { byte[] paletteData = getChunk("PLTE").getData(); int paletteLength = paletteData.length / 3; return new IndexColorModel(bitsPerPixel, paletteLength, paletteData, 0, false); } System.out.println("Unsupported color type: " + colorType); return null; } public WritableRaster getRaster() { int width = (int) getWidth(); int height = (int) getHeight(); int bitsPerPixel = getBitsPerPixel(); short colorType = getColorType(); if (colorType == 3) { byte[] imageData = getImageData(); DataBuffer db = new DataBufferByte(imageData, imageData.length); WritableRaster raster = Raster.createPackedRaster(db, width, height, bitsPerPixel, null); return raster; } else System.out.println("Unsupported color type!"); return null; } public byte[] getImageData() { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); // Write all the IDAT data into the array. for (int i = 0; i < mNumberOfChunks; i++) { PNGChunk chunk = mChunks[i]; if (chunk.getTypeString().equals("IDAT")) { out.write(chunk.getData()); } } out.flush(); // Now deflate the data. InflaterInputStream in = new InflaterInputStream( new ByteArrayInputStream(out.toByteArray())); ByteArrayOutputStream inflatedOut = new ByteArrayOutputStream(); int readLength; byte[] block = new byte[8192]; while ((readLength = in.read(block)) != -1) inflatedOut.write(block, 0, readLength); inflatedOut.flush(); byte[] imageData = inflatedOut.toByteArray(); // Compute the real length. int width = (int) getWidth(); int height = (int) getHeight(); int bitsPerPixel = getBitsPerPixel(); int length = width * height * bitsPerPixel / 8; byte[] prunedData = new byte[length]; // We can only deal with non-interlaced images. if (getInterlace() == 0) { int index = 0; for (int i = 0; i < length; i++) { if ((i * 8 / bitsPerPixel) % width == 0) { index++; // Skip the filter byte. } prunedData[i] = imageData[index++]; } } else System.out.println("Couldn"t undo interlacing."); return prunedData; } catch (IOException ioe) { } return null; } public PNGChunk getChunk(String type) { for (int i = 0; i < mNumberOfChunks; i++) if (mChunks[i].getTypeString().equals(type)) return mChunks[i]; return null; }
} class PNGChunk {
private byte[] mType; private byte[] mData; public PNGChunk(byte[] type, byte[] data) { mType = type; mData = data; } public String getTypeString() { try { return new String(mType, "UTF8"); } catch (UnsupportedEncodingException uee) { return ""; } } public byte[] getData() { return mData; } public long getUnsignedInt(int offset) { long value = 0; for (int i = 0; i < 4; i++) value += (mData[offset + i] & 0xff) << ((3 - i) * 8); return value; } public short getUnsignedByte(int offset) { return (short) (mData[offset] & 0x00ff); }
}
</source>
Saving a Generated Graphic to a PNG or JPEG File
<source lang="java"> import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.File; import javax.imageio.ImageIO; public class Main {
public static void main(String[] argv) throws Exception { int width = 100; int height = 100; BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = bufferedImage.createGraphics(); g2d.setColor(Color.white); g2d.fillRect(0, 0, width, height); g2d.setColor(Color.black); g2d.fillOval(0, 0, width, height); g2d.dispose(); RenderedImage rendImage = bufferedImage; File file = new File("newimage.png"); ImageIO.write(rendImage, "png", file); file = new File("newimage.jpg"); ImageIO.write(rendImage, "jpg", file); }
}
</source>