/* * 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. * * 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 slim.texture.io; 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; import slim.texture.Texture; /** * 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