Roi Atalla avatar Roi Atalla committed 8d3f650

Moved PNGDecoder to its own util package.

Comments (0)

Files changed (2)

src/com/ra4king/opengl/PNGDecoder.java

-/*
- * 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 com.ra4king.opengl;
-
-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 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.
-     * <p>This can only be invoked when the image has no alpha channel.</p>
-     * <p>Calling this method causes {@link #hasAlpha()} to return true.</p>
-     *
-     * @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<pal.length ; i+=3,j++) {
-                if(pal[i] != r || pal[i+1] != g || pal[i+2] != b) {
-                    paletteA[j] = (byte)0xFF;
-                }
-            }
-        }
-    }
-
-    /**
-     * Computes the implemented format conversion for the desired format.
-     *
-     * @param fmt the desired format
-     * @return format which best matches the desired format
-     * @throws UnsupportedOperationException if this PNG file can't be decoded
-     */
-    public Format decideTextureFormat(Format fmt) {
-        switch (colorType) {
-        case COLOR_TRUECOLOR:
-            switch (fmt) {
-            case ABGR:
-            case RGBA:
-            case BGRA:
-            case RGB: return fmt;
-            default: return Format.RGB;
-            }
-        case COLOR_TRUEALPHA:
-            switch (fmt) {
-            case ABGR:
-            case RGBA:
-            case BGRA:
-            case RGB: return fmt;
-            default: return Format.RGBA;
-            }
-        case COLOR_GREYSCALE:
-            switch (fmt) {
-            case LUMINANCE:
-            case ALPHA: return fmt;
-            default: return Format.LUMINANCE;
-            }
-        case COLOR_GREYALPHA:
-            return Format.LUMINANCE_ALPHA;
-        case COLOR_INDEXED:
-            switch (fmt) {
-            case ABGR:
-            case RGBA:
-            case BGRA: return fmt;
-            default: return Format.RGBA;
-            }
-        default:
-            throw new UnsupportedOperationException("Not yet implemented");
-        }
-    }
-
-    /**
-     * Decodes the image into the specified buffer. The first line is placed at
-     * the current position. After decode the buffer position is at the end of
-     * the last line.
-     *
-     * @param buffer the buffer
-     * @param stride the stride in bytes from start of a line to start of the next line, can be negative.
-     * @param fmt the target format into which the image should be decoded.
-     * @throws IOException if a read or data error occurred
-     * @throws IllegalArgumentException if the start position of a line falls outside the buffer
-     * @throws UnsupportedOperationException if the image can't be decoded into the desired format
-     */
-    public void decode(ByteBuffer buffer, int stride, Format fmt) throws IOException {
-        final int offset = buffer.position();
-        final int lineSize = ((width * bitdepth + 7) / 8) * bytesPerPixel;
-        byte[] curLine = new byte[lineSize+1];
-        byte[] prevLine = new byte[lineSize+1];
-        byte[] palLine = (bitdepth < 8) ? new byte[width+1] : null;
-        
-        final Inflater inflater = new Inflater();
-        try {
-            for(int y=0 ; y<height ; y++) {
-                readChunkUnzip(inflater, curLine, 0, curLine.length);
-                unfilter(curLine, prevLine);
-
-                buffer.position(offset + y*stride);
-
-                switch (colorType) {
-                case COLOR_TRUECOLOR:
-                    switch (fmt) {
-                    case ABGR: copyRGBtoABGR(buffer, curLine); break;
-                    case RGBA: copyRGBtoRGBA(buffer, curLine); break;
-                    case BGRA: copyRGBtoBGRA(buffer, curLine); break;
-                    case RGB: copy(buffer, curLine); break;
-                    default: throw new UnsupportedOperationException("Unsupported format for this image");
-                    }
-                    break;
-                case COLOR_TRUEALPHA:
-                    switch (fmt) {
-                    case ABGR: copyRGBAtoABGR(buffer, curLine); break;
-                    case RGBA: copy(buffer, curLine); break;
-                    case BGRA: copyRGBAtoBGRA(buffer, curLine); break;
-                    case RGB: copyRGBAtoRGB(buffer, curLine); break;
-                    default: throw new UnsupportedOperationException("Unsupported format for this image");
-                    }
-                    break;
-                case COLOR_GREYSCALE:
-                    switch (fmt) {
-                    case LUMINANCE:
-                    case ALPHA: copy(buffer, curLine); break;
-                    default: throw new UnsupportedOperationException("Unsupported format for this image");
-                    }
-                    break;
-                case COLOR_GREYALPHA:
-                    switch (fmt) {
-                    case LUMINANCE_ALPHA: copy(buffer, curLine); break;
-                    default: throw new UnsupportedOperationException("Unsupported format for this image");
-                    }
-                    break;
-                case COLOR_INDEXED:
-                    switch(bitdepth) {
-                        case 8: palLine = curLine; break;
-                        case 4: expand4(curLine, palLine); break;
-                        case 2: expand2(curLine, palLine); break;
-                        case 1: expand1(curLine, palLine); break;
-                        default: throw new UnsupportedOperationException("Unsupported bitdepth for this image");
-                    }
-                    switch (fmt) {
-                    case ABGR: copyPALtoABGR(buffer, palLine); break;
-                    case RGBA: copyPALtoRGBA(buffer, palLine); break;
-                    case BGRA: copyPALtoBGRA(buffer, palLine); break;
-                    default: throw new UnsupportedOperationException("Unsupported format for this image");
-                    }
-                    break;
-                default:
-                    throw new UnsupportedOperationException("Not yet implemented");
-                }
-
-                byte[] tmp = curLine;
-                curLine = prevLine;
-                prevLine = tmp;
-            }
-        } finally {
-            inflater.end();
-        }
-    }
-
-    /**
-     * Decodes the image into the specified buffer. The last line is placed at
-     * the current position. After decode the buffer position is at the end of
-     * the first line.
-     *
-     * @param buffer the buffer
-     * @param stride the stride in bytes from start of a line to start of the next line, must be positive.
-     * @param fmt the target format into which the image should be decoded.
-     * @throws IOException if a read or data error occurred
-     * @throws IllegalArgumentException if the start position of a line falls outside the buffer
-     * @throws UnsupportedOperationException if the image can't be decoded into the desired format
-     */
-    public void decodeFlipped(ByteBuffer buffer, int stride, Format fmt) throws IOException {
-        if(stride <= 0) {
-            throw new IllegalArgumentException("stride");
-        }
-        int pos = buffer.position();
-        int posDelta = (height-1) * stride;
-        buffer.position(pos + posDelta);
-        decode(buffer, -stride, fmt);
-        buffer.position(buffer.position() + posDelta);
-    }
-    
-    private void copy(ByteBuffer buffer, byte[] curLine) {
-        buffer.put(curLine, 1, curLine.length-1);
-    }
-
-    private void copyRGBtoABGR(ByteBuffer buffer, byte[] curLine) {
-        if(transPixel != null) {
-            byte tr = transPixel[1];
-            byte tg = transPixel[3];
-            byte tb = transPixel[5];
-            for(int i=1,n=curLine.length ; i<n ; i+=3) {
-                byte r = curLine[i];
-                byte g = curLine[i+1];
-                byte b = curLine[i+2];
-                byte a = (byte)0xFF;
-                if(r==tr && g==tg && b==tb) {
-                    a = 0;
-                }
-                buffer.put(a).put(b).put(g).put(r);
-            }
-        } else {
-            for(int i=1,n=curLine.length ; i<n ; i+=3) {
-                buffer.put((byte)0xFF).put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]);
-            }
-        }
-    }
-
-    private void copyRGBtoRGBA(ByteBuffer buffer, byte[] curLine) {
-        if(transPixel != null) {
-            byte tr = transPixel[1];
-            byte tg = transPixel[3];
-            byte tb = transPixel[5];
-            for(int i=1,n=curLine.length ; i<n ; i+=3) {
-                byte r = curLine[i];
-                byte g = curLine[i+1];
-                byte b = curLine[i+2];
-                byte a = (byte)0xFF;
-                if(r==tr && g==tg && b==tb) {
-                    a = 0;
-                }
-                buffer.put(r).put(g).put(b).put(a);
-            }
-        } else {
-            for(int i=1,n=curLine.length ; i<n ; i+=3) {
-                buffer.put(curLine[i]).put(curLine[i+1]).put(curLine[i+2]).put((byte)0xFF);
-            }
-        }
-    }
-
-    private void copyRGBtoBGRA(ByteBuffer buffer, byte[] curLine) {
-        if(transPixel != null) {
-            byte tr = transPixel[1];
-            byte tg = transPixel[3];
-            byte tb = transPixel[5];
-            for(int i=1,n=curLine.length ; i<n ; i+=3) {
-                byte r = curLine[i];
-                byte g = curLine[i+1];
-                byte b = curLine[i+2];
-                byte a = (byte)0xFF;
-                if(r==tr && g==tg && b==tb) {
-                    a = 0;
-                }
-                buffer.put(b).put(g).put(r).put(a);
-            }
-        } else {
-            for(int i=1,n=curLine.length ; i<n ; i+=3) {
-                buffer.put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]).put((byte)0xFF);
-            }
-        }
-    }
-
-    private void copyRGBAtoABGR(ByteBuffer buffer, byte[] curLine) {
-        for(int i=1,n=curLine.length ; i<n ; i+=4) {
-            buffer.put(curLine[i+3]).put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]);
-        }
-    }
-
-    private void copyRGBAtoBGRA(ByteBuffer buffer, byte[] curLine) {
-        for(int i=1,n=curLine.length ; i<n ; i+=4) {
-            buffer.put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]).put(curLine[i+3]);
-        }
-    }
-
-    private void copyRGBAtoRGB(ByteBuffer buffer, byte[] curLine) {
-        for(int i=1,n=curLine.length ; i<n ; i+=4) {
-            buffer.put(curLine[i]).put(curLine[i+1]).put(curLine[i+2]);
-        }
-    }
-
-    private void copyPALtoABGR(ByteBuffer buffer, byte[] curLine) {
-        if(paletteA != null) {
-            for(int i=1,n=curLine.length ; i<n ; i+=1) {
-                int idx = curLine[i] & 255;
-                byte r = palette[idx*3 + 0];
-                byte g = palette[idx*3 + 1];
-                byte b = palette[idx*3 + 2];
-                byte a = paletteA[idx];
-                buffer.put(a).put(b).put(g).put(r);
-            }
-        } else {
-            for(int i=1,n=curLine.length ; i<n ; i+=1) {
-                int idx = curLine[i] & 255;
-                byte r = palette[idx*3 + 0];
-                byte g = palette[idx*3 + 1];
-                byte b = palette[idx*3 + 2];
-                byte a = (byte)0xFF;
-                buffer.put(a).put(b).put(g).put(r);
-            }
-        }
-    }
-
-    private void copyPALtoRGBA(ByteBuffer buffer, byte[] curLine) {
-        if(paletteA != null) {
-            for(int i=1,n=curLine.length ; i<n ; i+=1) {
-                int idx = curLine[i] & 255;
-                byte r = palette[idx*3 + 0];
-                byte g = palette[idx*3 + 1];
-                byte b = palette[idx*3 + 2];
-                byte a = paletteA[idx];
-                buffer.put(r).put(g).put(b).put(a);
-            }
-        } else {
-            for(int i=1,n=curLine.length ; i<n ; i+=1) {
-                int idx = curLine[i] & 255;
-                byte r = palette[idx*3 + 0];
-                byte g = palette[idx*3 + 1];
-                byte b = palette[idx*3 + 2];
-                byte a = (byte)0xFF;
-                buffer.put(r).put(g).put(b).put(a);
-            }
-        }
-    }
-
-    private void copyPALtoBGRA(ByteBuffer buffer, byte[] curLine) {
-        if(paletteA != null) {
-            for(int i=1,n=curLine.length ; i<n ; i+=1) {
-                int idx = curLine[i] & 255;
-                byte r = palette[idx*3 + 0];
-                byte g = palette[idx*3 + 1];
-                byte b = palette[idx*3 + 2];
-                byte a = paletteA[idx];
-                buffer.put(b).put(g).put(r).put(a);
-            }
-        } else {
-            for(int i=1,n=curLine.length ; i<n ; i+=1) {
-                int idx = curLine[i] & 255;
-                byte r = palette[idx*3 + 0];
-                byte g = palette[idx*3 + 1];
-                byte b = palette[idx*3 + 2];
-                byte a = (byte)0xFF;
-                buffer.put(b).put(g).put(r).put(a);
-            }
-        }
-    }
-
-    private void expand4(byte[] src, byte[] dst) {
-        for(int i=1,n=dst.length ; i<n ; i+=2) {
-            int val = src[1 + (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<n ; i+=4) {
-            int val = src[1 + (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<n ; i+=8) {
-            int val = src[1 + (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<n ; ++i) {
-            curLine[i] += curLine[i-bpp];
-        }
-    }
-    
-    private void unfilterUp(byte[] curLine, byte[] prevLine) {
-        for(int i=1,n=curLine.length ; i<n ; ++i) {
-            curLine[i] += prevLine[i];
-        }
-    }
-    
-    private void unfilterAverage(byte[] curLine, byte[] prevLine) {
-        final int bpp = this.bytesPerPixel;
-        
-        int i;
-        for(i=1 ; i<=bpp ; ++i) {
-            curLine[i] += (byte)((prevLine[i] & 0xFF) >>> 1);
-        }
-        for(int n=curLine.length ; i<n ; ++i) {
-            curLine[i] += (byte)(((prevLine[i] & 0xFF) + (curLine[i - bpp] & 0xFF)) >>> 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<n ; ++i) {
-            int a = curLine[i - bpp] & 255;
-            int b = prevLine[i] & 255;
-            int c = prevLine[i - bpp] & 255;
-            int p = a + b - c;
-            int pa = p - a; if(pa < 0) pa = -pa;
-            int pb = p - b; if(pb < 0) pb = -pb;
-            int pc = p - c; if(pc < 0) pc = -pc;
-            if(pa<=pb && pa<=pc)
-                c = a;
-            else if(pb<=pc)
-                c = b;
-            curLine[i] += (byte)c;
-        }
-    }
-      
-    private void readIHDR() throws IOException {
-        checkChunkLength(13);
-        readChunk(buffer, 0, 13);
-        width = readInt(buffer, 0);
-        height = readInt(buffer, 4);
-        bitdepth = buffer[8] & 255;
-        colorType = buffer[9] & 255;
-        
-        switch (colorType) {
-        case COLOR_GREYSCALE:
-            if(bitdepth != 8) {
-                throw new IOException("Unsupported bit depth: " + bitdepth);
-            }
-            bytesPerPixel = 1;
-            break;
-        case COLOR_GREYALPHA:
-            if(bitdepth != 8) {
-                throw new IOException("Unsupported bit depth: " + bitdepth);
-            }
-            bytesPerPixel = 2;
-            break;
-        case COLOR_TRUECOLOR:
-            if(bitdepth != 8) {
-                throw new IOException("Unsupported bit depth: " + bitdepth);
-            }
-            bytesPerPixel = 3;
-            break;
-        case COLOR_TRUEALPHA:
-            if(bitdepth != 8) {
-                throw new IOException("Unsupported bit depth: " + bitdepth);
-            }
-            bytesPerPixel = 4;
-            break;
-        case COLOR_INDEXED:
-            switch(bitdepth) {
-            case 8:
-            case 4:
-            case 2:
-            case 1:
-                bytesPerPixel = 1;
-                break;
-            default:
-                throw new IOException("Unsupported bit depth: " + bitdepth);
-            }
-            break;
-        default:
-            throw new IOException("unsupported color format: " + colorType);
-        }
-        
-        if(buffer[10] != 0) {
-            throw new IOException("unsupported compression method");
-        }
-        if(buffer[11] != 0) {
-            throw new IOException("unsupported filtering method");
-        }
-        if(buffer[12] != 0) {
-            throw new IOException("unsupported interlace method");
-        }
-    }
-
-    private void readPLTE() throws IOException {
-        int paletteEntries = chunkLength / 3;
-        if(paletteEntries < 1 || paletteEntries > 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<SIGNATURE.length ; i++) {
-            if(buffer[i] != SIGNATURE[i]) {
-                return false;
-            }
-        }
-        return true;
-    }
-}

src/com/ra4king/opengl/util/PNGDecoder.java

+/*
+ * 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 com.ra4king.opengl.util;
+
+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 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.
+     * <p>This can only be invoked when the image has no alpha channel.</p>
+     * <p>Calling this method causes {@link #hasAlpha()} to return true.</p>
+     *
+     * @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<pal.length ; i+=3,j++) {
+                if(pal[i] != r || pal[i+1] != g || pal[i+2] != b) {
+                    paletteA[j] = (byte)0xFF;
+                }
+            }
+        }
+    }
+
+    /**
+     * Computes the implemented format conversion for the desired format.
+     *
+     * @param fmt the desired format
+     * @return format which best matches the desired format
+     * @throws UnsupportedOperationException if this PNG file can't be decoded
+     */
+    public Format decideTextureFormat(Format fmt) {
+        switch (colorType) {
+        case COLOR_TRUECOLOR:
+            switch (fmt) {
+            case ABGR:
+            case RGBA:
+            case BGRA:
+            case RGB: return fmt;
+            default: return Format.RGB;
+            }
+        case COLOR_TRUEALPHA:
+            switch (fmt) {
+            case ABGR:
+            case RGBA:
+            case BGRA:
+            case RGB: return fmt;
+            default: return Format.RGBA;
+            }
+        case COLOR_GREYSCALE:
+            switch (fmt) {
+            case LUMINANCE:
+            case ALPHA: return fmt;
+            default: return Format.LUMINANCE;
+            }
+        case COLOR_GREYALPHA:
+            return Format.LUMINANCE_ALPHA;
+        case COLOR_INDEXED:
+            switch (fmt) {
+            case ABGR:
+            case RGBA:
+            case BGRA: return fmt;
+            default: return Format.RGBA;
+            }
+        default:
+            throw new UnsupportedOperationException("Not yet implemented");
+        }
+    }
+
+    /**
+     * Decodes the image into the specified buffer. The first line is placed at
+     * the current position. After decode the buffer position is at the end of
+     * the last line.
+     *
+     * @param buffer the buffer
+     * @param stride the stride in bytes from start of a line to start of the next line, can be negative.
+     * @param fmt the target format into which the image should be decoded.
+     * @throws IOException if a read or data error occurred
+     * @throws IllegalArgumentException if the start position of a line falls outside the buffer
+     * @throws UnsupportedOperationException if the image can't be decoded into the desired format
+     */
+    public void decode(ByteBuffer buffer, int stride, Format fmt) throws IOException {
+        final int offset = buffer.position();
+        final int lineSize = ((width * bitdepth + 7) / 8) * bytesPerPixel;
+        byte[] curLine = new byte[lineSize+1];
+        byte[] prevLine = new byte[lineSize+1];
+        byte[] palLine = (bitdepth < 8) ? new byte[width+1] : null;
+        
+        final Inflater inflater = new Inflater();
+        try {
+            for(int y=0 ; y<height ; y++) {
+                readChunkUnzip(inflater, curLine, 0, curLine.length);
+                unfilter(curLine, prevLine);
+
+                buffer.position(offset + y*stride);
+
+                switch (colorType) {
+                case COLOR_TRUECOLOR:
+                    switch (fmt) {
+                    case ABGR: copyRGBtoABGR(buffer, curLine); break;
+                    case RGBA: copyRGBtoRGBA(buffer, curLine); break;
+                    case BGRA: copyRGBtoBGRA(buffer, curLine); break;
+                    case RGB: copy(buffer, curLine); break;
+                    default: throw new UnsupportedOperationException("Unsupported format for this image");
+                    }
+                    break;
+                case COLOR_TRUEALPHA:
+                    switch (fmt) {
+                    case ABGR: copyRGBAtoABGR(buffer, curLine); break;
+                    case RGBA: copy(buffer, curLine); break;
+                    case BGRA: copyRGBAtoBGRA(buffer, curLine); break;
+                    case RGB: copyRGBAtoRGB(buffer, curLine); break;
+                    default: throw new UnsupportedOperationException("Unsupported format for this image");
+                    }
+                    break;
+                case COLOR_GREYSCALE:
+                    switch (fmt) {
+                    case LUMINANCE:
+                    case ALPHA: copy(buffer, curLine); break;
+                    default: throw new UnsupportedOperationException("Unsupported format for this image");
+                    }
+                    break;
+                case COLOR_GREYALPHA:
+                    switch (fmt) {
+                    case LUMINANCE_ALPHA: copy(buffer, curLine); break;
+                    default: throw new UnsupportedOperationException("Unsupported format for this image");
+                    }
+                    break;
+                case COLOR_INDEXED:
+                    switch(bitdepth) {
+                        case 8: palLine = curLine; break;
+                        case 4: expand4(curLine, palLine); break;
+                        case 2: expand2(curLine, palLine); break;
+                        case 1: expand1(curLine, palLine); break;
+                        default: throw new UnsupportedOperationException("Unsupported bitdepth for this image");
+                    }
+                    switch (fmt) {
+                    case ABGR: copyPALtoABGR(buffer, palLine); break;
+                    case RGBA: copyPALtoRGBA(buffer, palLine); break;
+                    case BGRA: copyPALtoBGRA(buffer, palLine); break;
+                    default: throw new UnsupportedOperationException("Unsupported format for this image");
+                    }
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Not yet implemented");
+                }
+
+                byte[] tmp = curLine;
+                curLine = prevLine;
+                prevLine = tmp;
+            }
+        } finally {
+            inflater.end();
+        }
+    }
+
+    /**
+     * Decodes the image into the specified buffer. The last line is placed at
+     * the current position. After decode the buffer position is at the end of
+     * the first line.
+     *
+     * @param buffer the buffer
+     * @param stride the stride in bytes from start of a line to start of the next line, must be positive.
+     * @param fmt the target format into which the image should be decoded.
+     * @throws IOException if a read or data error occurred
+     * @throws IllegalArgumentException if the start position of a line falls outside the buffer
+     * @throws UnsupportedOperationException if the image can't be decoded into the desired format
+     */
+    public void decodeFlipped(ByteBuffer buffer, int stride, Format fmt) throws IOException {
+        if(stride <= 0) {
+            throw new IllegalArgumentException("stride");
+        }
+        int pos = buffer.position();
+        int posDelta = (height-1) * stride;
+        buffer.position(pos + posDelta);
+        decode(buffer, -stride, fmt);
+        buffer.position(buffer.position() + posDelta);
+    }
+    
+    private void copy(ByteBuffer buffer, byte[] curLine) {
+        buffer.put(curLine, 1, curLine.length-1);
+    }
+
+    private void copyRGBtoABGR(ByteBuffer buffer, byte[] curLine) {
+        if(transPixel != null) {
+            byte tr = transPixel[1];
+            byte tg = transPixel[3];
+            byte tb = transPixel[5];
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                byte r = curLine[i];
+                byte g = curLine[i+1];
+                byte b = curLine[i+2];
+                byte a = (byte)0xFF;
+                if(r==tr && g==tg && b==tb) {
+                    a = 0;
+                }
+                buffer.put(a).put(b).put(g).put(r);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                buffer.put((byte)0xFF).put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]);
+            }
+        }
+    }
+
+    private void copyRGBtoRGBA(ByteBuffer buffer, byte[] curLine) {
+        if(transPixel != null) {
+            byte tr = transPixel[1];
+            byte tg = transPixel[3];
+            byte tb = transPixel[5];
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                byte r = curLine[i];
+                byte g = curLine[i+1];
+                byte b = curLine[i+2];
+                byte a = (byte)0xFF;
+                if(r==tr && g==tg && b==tb) {
+                    a = 0;
+                }
+                buffer.put(r).put(g).put(b).put(a);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                buffer.put(curLine[i]).put(curLine[i+1]).put(curLine[i+2]).put((byte)0xFF);
+            }
+        }
+    }
+
+    private void copyRGBtoBGRA(ByteBuffer buffer, byte[] curLine) {
+        if(transPixel != null) {
+            byte tr = transPixel[1];
+            byte tg = transPixel[3];
+            byte tb = transPixel[5];
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                byte r = curLine[i];
+                byte g = curLine[i+1];
+                byte b = curLine[i+2];
+                byte a = (byte)0xFF;
+                if(r==tr && g==tg && b==tb) {
+                    a = 0;
+                }
+                buffer.put(b).put(g).put(r).put(a);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                buffer.put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]).put((byte)0xFF);
+            }
+        }
+    }
+
+    private void copyRGBAtoABGR(ByteBuffer buffer, byte[] curLine) {
+        for(int i=1,n=curLine.length ; i<n ; i+=4) {
+            buffer.put(curLine[i+3]).put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]);
+        }
+    }
+
+    private void copyRGBAtoBGRA(ByteBuffer buffer, byte[] curLine) {
+        for(int i=1,n=curLine.length ; i<n ; i+=4) {
+            buffer.put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]).put(curLine[i+3]);
+        }
+    }
+
+    private void copyRGBAtoRGB(ByteBuffer buffer, byte[] curLine) {
+        for(int i=1,n=curLine.length ; i<n ; i+=4) {
+            buffer.put(curLine[i]).put(curLine[i+1]).put(curLine[i+2]);
+        }
+    }
+
+    private void copyPALtoABGR(ByteBuffer buffer, byte[] curLine) {
+        if(paletteA != null) {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = paletteA[idx];
+                buffer.put(a).put(b).put(g).put(r);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = (byte)0xFF;
+                buffer.put(a).put(b).put(g).put(r);
+            }
+        }
+    }
+
+    private void copyPALtoRGBA(ByteBuffer buffer, byte[] curLine) {
+        if(paletteA != null) {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = paletteA[idx];
+                buffer.put(r).put(g).put(b).put(a);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = (byte)0xFF;
+                buffer.put(r).put(g).put(b).put(a);
+            }
+        }
+    }
+
+    private void copyPALtoBGRA(ByteBuffer buffer, byte[] curLine) {
+        if(paletteA != null) {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = paletteA[idx];
+                buffer.put(b).put(g).put(r).put(a);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = (byte)0xFF;
+                buffer.put(b).put(g).put(r).put(a);
+            }
+        }
+    }
+
+    private void expand4(byte[] src, byte[] dst) {
+        for(int i=1,n=dst.length ; i<n ; i+=2) {
+            int val = src[1 + (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<n ; i+=4) {
+            int val = src[1 + (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<n ; i+=8) {
+            int val = src[1 + (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<n ; ++i) {
+            curLine[i] += curLine[i-bpp];
+        }
+    }
+    
+    private void unfilterUp(byte[] curLine, byte[] prevLine) {
+        for(int i=1,n=curLine.length ; i<n ; ++i) {
+            curLine[i] += prevLine[i];
+        }
+    }
+    
+    private void unfilterAverage(byte[] curLine, byte[] prevLine) {
+        final int bpp = this.bytesPerPixel;
+        
+        int i;
+        for(i=1 ; i<=bpp ; ++i) {
+            curLine[i] += (byte)((prevLine[i] & 0xFF) >>> 1);
+        }
+        for(int n=curLine.length ; i<n ; ++i) {
+            curLine[i] += (byte)(((prevLine[i] & 0xFF) + (curLine[i - bpp] & 0xFF)) >>> 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<n ; ++i) {
+            int a = curLine[i - bpp] & 255;
+            int b = prevLine[i] & 255;
+            int c = prevLine[i - bpp] & 255;
+            int p = a + b - c;
+            int pa = p - a; if(pa < 0) pa = -pa;
+            int pb = p - b; if(pb < 0) pb = -pb;
+            int pc = p - c; if(pc < 0) pc = -pc;
+            if(pa<=pb && pa<=pc)
+                c = a;
+            else if(pb<=pc)
+                c = b;
+            curLine[i] += (byte)c;
+        }
+    }
+      
+    private void readIHDR() throws IOException {
+        checkChunkLength(13);
+        readChunk(buffer, 0, 13);
+        width = readInt(buffer, 0);
+        height = readInt(buffer, 4);
+        bitdepth = buffer[8] & 255;
+        colorType = buffer[9] & 255;
+        
+        switch (colorType) {
+        case COLOR_GREYSCALE:
+            if(bitdepth != 8) {
+                throw new IOException("Unsupported bit depth: " + bitdepth);
+            }
+            bytesPerPixel = 1;
+            break;
+        case COLOR_GREYALPHA:
+            if(bitdepth != 8) {
+                throw new IOException("Unsupported bit depth: " + bitdepth);
+            }
+            bytesPerPixel = 2;
+            break;
+        case COLOR_TRUECOLOR:
+            if(bitdepth != 8) {
+                throw new IOException("Unsupported bit depth: " + bitdepth);
+            }
+            bytesPerPixel = 3;
+            break;
+        case COLOR_TRUEALPHA:
+            if(bitdepth != 8) {
+                throw new IOException("Unsupported bit depth: " + bitdepth);
+            }
+            bytesPerPixel = 4;
+            break;
+        case COLOR_INDEXED:
+            switch(bitdepth) {
+            case 8:
+            case 4:
+            case 2:
+            case 1:
+                bytesPerPixel = 1;
+                break;
+            default:
+                throw new IOException("Unsupported bit depth: " + bitdepth);
+            }
+            break;
+        default:
+            throw new IOException("unsupported color format: " + colorType);
+        }
+        
+        if(buffer[10] != 0) {
+            throw new IOException("unsupported compression method");
+        }
+        if(buffer[11] != 0) {
+            throw new IOException("unsupported filtering method");
+        }
+        if(buffer[12] != 0) {
+            throw new IOException("unsupported interlace method");
+        }
+    }
+
+    private void readPLTE() throws IOException {
+        int paletteEntries = chunkLength / 3;
+        if(paletteEntries < 1 || paletteEntries > 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<SIGNATURE.length ; i++) {
+            if(buffer[i] != SIGNATURE[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.