Java tutorial
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); } }