All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mathiewz.slick.opengl.PNGDecoder Maven / Gradle / Ivy

Go to download

The main purpose of this libraryis to modernize and maintain the slick2D library.

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.
 * 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.github.mathiewz.slick.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 static Format ALPHA = new Format(1, true);
    public static Format LUMINANCE = new Format(1, false);
    public static Format LUMINANCE_ALPHA = new Format(2, true);
    public static Format RGB = new Format(3, false);
    public static Format RGBA = new Format(4, true);
    public static Format BGRA = new Format(4, true);
    public static Format ABGR = new Format(4, true);

    public static class Format {

        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;
        crc = new CRC32();
        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;
    }

    public boolean hasAlpha() {
        return colorType == COLOR_TRUEALPHA || paletteA != null || transPixel != null;
    }

    public boolean isRGB() {
        return colorType == COLOR_TRUEALPHA || colorType == COLOR_TRUECOLOR || colorType == COLOR_INDEXED;
    }

    /**
     * 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:
                if (fmt == ABGR || fmt == RGBA || fmt == BGRA || fmt == RGB) {
                    return fmt;
                }

                return RGB;
            case COLOR_TRUEALPHA:
                if (fmt == ABGR || fmt == RGBA || fmt == BGRA || fmt == RGB) {
                    return fmt;
                }

                return RGBA;
            case COLOR_GREYSCALE:
                if (fmt == LUMINANCE || fmt == ALPHA) {
                    return fmt;
                }

                return LUMINANCE;
            case COLOR_GREYALPHA:
                return LUMINANCE_ALPHA;
            case COLOR_INDEXED:
                if (fmt == ABGR || fmt == RGBA || fmt == BGRA) {
                    return fmt;
                }

                return RGBA;
            default:
                throw new UnsupportedOperationException("Not yet implemented");
        }
    }

    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:
                        if (fmt == ABGR) {
                            copyRGBtoABGR(buffer, curLine);
                        } else if (fmt == RGBA) {
                            copyRGBtoRGBA(buffer, curLine);
                        } else if (fmt == BGRA) {
                            copyRGBtoBGRA(buffer, curLine);
                        } else if (fmt == RGB) {
                            copy(buffer, curLine);
                        } else {
                            throw new UnsupportedOperationException("Unsupported format for this image");
                        }
                        break;
                    case COLOR_TRUEALPHA:
                        if (fmt == ABGR) {
                            copyRGBAtoABGR(buffer, curLine);
                        } else if (fmt == RGBA) {
                            copy(buffer, curLine);
                        } else if (fmt == BGRA) {
                            copyRGBAtoBGRA(buffer, curLine);
                            break;
                        } else if (fmt == RGB) {
                            copyRGBAtoRGB(buffer, curLine);
                            break;
                        } else {
                            throw new UnsupportedOperationException("Unsupported format for this image");
                        }
                        break;
                    case COLOR_GREYSCALE:
                        if (fmt == LUMINANCE || fmt == ALPHA) {
                            copy(buffer, curLine);
                        } else {
                            throw new UnsupportedOperationException("Unsupported format for this image");
                        }
                        break;
                    case COLOR_GREYALPHA:
                        if (fmt == LUMINANCE_ALPHA) {
                            copy(buffer, curLine);
                        } else {
                            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");
                        }
                        if (fmt == ABGR) {
                            copyPALtoABGR(buffer, palLine);
                        } else if (fmt == RGBA) {
                            copyPALtoRGBA(buffer, palLine);
                        } else if (fmt == BGRA) {
                            copyPALtoBGRA(buffer, palLine);
                        } else {
                            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();
        }
    }

    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 + 0]).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 = 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 = 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 = 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 {
        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;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy