PNGDecoder.java Source code

Java tutorial

Introduction

Here is the source code for PNGDecoder.java

Source

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