Java PNGDecoder

资料:Java PNGDecoder

/*
 * 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

你可能感兴趣的:(java)