Java/2D Graphics GUI/PNG File
Содержание
Draw an Image and save to png
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();
}
}
}
Encodes a java.awt.Image into PNG format
/*
* 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 <code>Image</code>s to <code>BufferedImage</code>s.
*/
private static class ImageToBufferedImage {
/**
* Converts an <code>Image</code> to a <code>BufferedImage</code>.
* If the image is already a <code>BufferedImage</code>, the original is returned.
*
* @param image the image to convert
* @return the image as a <code>BufferedImage</code>
*/
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 the <code>Image</code>
* @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();
}
}
/** <code>SubFilter</code> singleton. */
public static final Filter SUB_FILTER = new SubFilter();
/** <code>UpFilter</code> singleton. */
public static final Filter UP_FILTER = new UpFilter();
/** <code>AverageFilter</code> singleton. */
public static final Filter AVERAGE_FILTER = new AverageFilter();
/** <code>PaethFilter</code> 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 image <code>Raster</code> transfer type. */
private int transferType;
/** The image <code>Raster</code> data. */
private Raster raster;
/** The source image bits-per-pixel. */
private int inputBpp;
/** The encoded image bits-per-pixel. */
private int outputBpp;
/** The <code>Translator</code> 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:
* <ul>
* <li>SUB_FILTER</li>
* <li>UP_FILTER</li>
* <li>AVERAGE_FILTER</li>
* <li>PAETH_FILTER</li>
* </ul>
* 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());
}
}
PNG Decoder
/*
* 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);
}
}
PNG Encoder
/*
* 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.<br>
* 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);
}
}
PngEncoder takes a Java Image object and creates a byte string which can be saved as a PNG file
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.
*
* <p>Thanks to Jay Denny at KeyPoint Software
* http://www.keypoint.ru/
* who let me develop this code on company time.</p>
*
* <p>You may contact me with (probably very-much-needed) improvements,
* comments, and bug fixes at:</p>
*
* <p><code>david@catcode.ru</code></p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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
* <code>http://www.gnu.org/copyleft/lesser.html</code></p>
*
* @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);
}
}
}
PNG file format decoder
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);
}
}
Saving a Generated Graphic to a PNG or JPEG File
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);
}
}