de.matthiasmann.twl.utils.PNGDecoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pngdecoder Show documentation
Show all versions of pngdecoder Show documentation
TWL's fast PNGDecoder. This is a subset of the TWL.jar and contains the same classes in the same package.
The newest version!
/*
* Copyright (c) 2008-2010, Matthias Mann
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Matthias Mann nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
* * Redistributions in source or binary form must keep the original package
* and class name.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.matthiasmann.twl.utils;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
/**
* A PNGDecoder. The slick PNG decoder is based on this class :)
*
* @author Matthias Mann
*/
public class PNGDecoder {
public enum Format {
ALPHA(1, true),
LUMINANCE(1, false),
LUMINANCE_ALPHA(2, true),
RGB(3, false),
RGBA(4, true),
BGRA(4, true),
ABGR(4, true);
final int numComponents;
final boolean hasAlpha;
private Format(int numComponents, boolean hasAlpha) {
this.numComponents = numComponents;
this.hasAlpha = hasAlpha;
}
public int getNumComponents() {
return numComponents;
}
public boolean isHasAlpha() {
return hasAlpha;
}
}
private static final byte[] SIGNATURE = {(byte)137, 80, 78, 71, 13, 10, 26, 10};
private static final int IHDR = 0x49484452;
private static final int PLTE = 0x504C5445;
private static final int tRNS = 0x74524E53;
private static final int IDAT = 0x49444154;
private static final int IEND = 0x49454E44;
private static final byte COLOR_GREYSCALE = 0;
private static final byte COLOR_TRUECOLOR = 2;
private static final byte COLOR_INDEXED = 3;
private static final byte COLOR_GREYALPHA = 4;
private static final byte COLOR_TRUEALPHA = 6;
private final InputStream input;
private final CRC32 crc;
private final byte[] buffer;
private int chunkLength;
private int chunkType;
private int chunkRemaining;
private int width;
private int height;
private int bitdepth;
private int colorType;
private int bytesPerPixel;
private byte[] palette;
private byte[] paletteA;
private byte[] transPixel;
public PNGDecoder(InputStream input) throws IOException {
this.input = input;
this.crc = new CRC32();
this.buffer = new byte[4096];
readFully(buffer, 0, SIGNATURE.length);
if(!checkSignature(buffer)) {
throw new IOException("Not a valid PNG file");
}
openChunk(IHDR);
readIHDR();
closeChunk();
searchIDAT: for(;;) {
openChunk();
switch (chunkType) {
case IDAT:
break searchIDAT;
case PLTE:
readPLTE();
break;
case tRNS:
readtRNS();
break;
}
closeChunk();
}
if(colorType == COLOR_INDEXED && palette == null) {
throw new IOException("Missing PLTE chunk");
}
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
/**
* Checks if the image has a real alpha channel.
* This method does not check for the presence of a tRNS chunk.
*
* @return true if the image has an alpha channel
* @see #hasAlpha()
*/
public boolean hasAlphaChannel() {
return colorType == COLOR_TRUEALPHA || colorType == COLOR_GREYALPHA;
}
/**
* Checks if the image has transparency information either from
* an alpha channel or from a tRNS chunk.
*
* @return true if the image has transparency
* @see #hasAlphaChannel()
* @see #overwriteTRNS(byte, byte, byte)
*/
public boolean hasAlpha() {
return hasAlphaChannel() ||
paletteA != null || transPixel != null;
}
public boolean isRGB() {
return colorType == COLOR_TRUEALPHA ||
colorType == COLOR_TRUECOLOR ||
colorType == COLOR_INDEXED;
}
/**
* Overwrites the tRNS chunk entry to make a selected color transparent.
* This can only be invoked when the image has no alpha channel.
* Calling this method causes {@link #hasAlpha()} to return true.
*
* @param r the red component of the color to make transparent
* @param g the green component of the color to make transparent
* @param b the blue component of the color to make transparent
* @throws UnsupportedOperationException if the tRNS chunk data can't be set
* @see #hasAlphaChannel()
*/
public void overwriteTRNS(byte r, byte g, byte b) {
if(hasAlphaChannel()) {
throw new UnsupportedOperationException("image has an alpha channel");
}
byte[] pal = this.palette;
if(pal == null) {
transPixel = new byte[] { 0, r, 0, g, 0, b };
} else {
paletteA = new byte[pal.length/3];
for(int i=0,j=0 ; i> 1)] & 255;
switch(n-i) {
default: dst[i+1] = (byte)(val & 15);
case 1: dst[i ] = (byte)(val >> 4);
}
}
}
private void expand2(byte[] src, byte[] dst) {
for(int i=1,n=dst.length ; i> 2)] & 255;
switch(n-i) {
default: dst[i+3] = (byte)((val ) & 3);
case 3: dst[i+2] = (byte)((val >> 2) & 3);
case 2: dst[i+1] = (byte)((val >> 4) & 3);
case 1: dst[i ] = (byte)((val >> 6) );
}
}
}
private void expand1(byte[] src, byte[] dst) {
for(int i=1,n=dst.length ; i> 3)] & 255;
switch(n-i) {
default: dst[i+7] = (byte)((val ) & 1);
case 7: dst[i+6] = (byte)((val >> 1) & 1);
case 6: dst[i+5] = (byte)((val >> 2) & 1);
case 5: dst[i+4] = (byte)((val >> 3) & 1);
case 4: dst[i+3] = (byte)((val >> 4) & 1);
case 3: dst[i+2] = (byte)((val >> 5) & 1);
case 2: dst[i+1] = (byte)((val >> 6) & 1);
case 1: dst[i ] = (byte)((val >> 7) );
}
}
}
private void unfilter(byte[] curLine, byte[] prevLine) throws IOException {
switch (curLine[0]) {
case 0: // none
break;
case 1:
unfilterSub(curLine);
break;
case 2:
unfilterUp(curLine, prevLine);
break;
case 3:
unfilterAverage(curLine, prevLine);
break;
case 4:
unfilterPaeth(curLine, prevLine);
break;
default:
throw new IOException("invalide filter type in scanline: " + curLine[0]);
}
}
private void unfilterSub(byte[] curLine) {
final int bpp = this.bytesPerPixel;
for(int i=bpp+1,n=curLine.length ; i>> 1);
}
for(int n=curLine.length ; i>> 1);
}
}
private void unfilterPaeth(byte[] curLine, byte[] prevLine) {
final int bpp = this.bytesPerPixel;
int i;
for(i=1 ; i<=bpp ; ++i) {
curLine[i] += prevLine[i];
}
for(int n=curLine.length ; i 256 || (chunkLength % 3) != 0) {
throw new IOException("PLTE chunk has wrong length");
}
palette = new byte[paletteEntries*3];
readChunk(palette, 0, palette.length);
}
private void readtRNS() throws IOException {
switch (colorType) {
case COLOR_GREYSCALE:
checkChunkLength(2);
transPixel = new byte[2];
readChunk(transPixel, 0, 2);
break;
case COLOR_TRUECOLOR:
checkChunkLength(6);
transPixel = new byte[6];
readChunk(transPixel, 0, 6);
break;
case COLOR_INDEXED:
if(palette == null) {
throw new IOException("tRNS chunk without PLTE chunk");
}
paletteA = new byte[palette.length/3];
Arrays.fill(paletteA, (byte)0xFF);
readChunk(paletteA, 0, paletteA.length);
break;
default:
// just ignore it
}
}
private void closeChunk() throws IOException {
if(chunkRemaining > 0) {
// just skip the rest and the CRC
skip(chunkRemaining + 4);
} else {
readFully(buffer, 0, 4);
int expectedCrc = readInt(buffer, 0);
int computedCrc = (int)crc.getValue();
if(computedCrc != expectedCrc) {
throw new IOException("Invalid CRC");
}
}
chunkRemaining = 0;
chunkLength = 0;
chunkType = 0;
}
private void openChunk() throws IOException {
readFully(buffer, 0, 8);
chunkLength = readInt(buffer, 0);
chunkType = readInt(buffer, 4);
chunkRemaining = chunkLength;
crc.reset();
crc.update(buffer, 4, 4); // only chunkType
}
private void openChunk(int expected) throws IOException {
openChunk();
if(chunkType != expected) {
throw new IOException("Expected chunk: " + Integer.toHexString(expected));
}
}
private void checkChunkLength(int expected) throws IOException {
if(chunkLength != expected) {
throw new IOException("Chunk has wrong size");
}
}
private int readChunk(byte[] buffer, int offset, int length) throws IOException {
if(length > chunkRemaining) {
length = chunkRemaining;
}
readFully(buffer, offset, length);
crc.update(buffer, offset, length);
chunkRemaining -= length;
return length;
}
private void refillInflater(Inflater inflater) throws IOException {
while(chunkRemaining == 0) {
closeChunk();
openChunk(IDAT);
}
int read = readChunk(buffer, 0, buffer.length);
inflater.setInput(buffer, 0, read);
}
private void readChunkUnzip(Inflater inflater, byte[] buffer, int offset, int length) throws IOException {
assert(buffer != this.buffer);
try {
do {
int read = inflater.inflate(buffer, offset, length);
if(read <= 0) {
if(inflater.finished()) {
throw new EOFException();
}
if(inflater.needsInput()) {
refillInflater(inflater);
} else {
throw new IOException("Can't inflate " + length + " bytes");
}
} else {
offset += read;
length -= read;
}
} while(length > 0);
} catch (DataFormatException ex) {
throw (IOException)(new IOException("inflate error").initCause(ex));
}
}
private void readFully(byte[] buffer, int offset, int length) throws IOException {
do {
int read = input.read(buffer, offset, length);
if(read < 0) {
throw new EOFException();
}
offset += read;
length -= read;
} while(length > 0);
}
private int readInt(byte[] buffer, int offset) {
return
((buffer[offset ] ) << 24) |
((buffer[offset+1] & 255) << 16) |
((buffer[offset+2] & 255) << 8) |
((buffer[offset+3] & 255) );
}
private void skip(long amount) throws IOException {
while(amount > 0) {
long skipped = input.skip(amount);
if(skipped < 0) {
throw new EOFException();
}
amount -= skipped;
}
}
private static boolean checkSignature(byte[] buffer) {
for(int i=0 ; i