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