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

com.sun.javafx.iio.png.PNGImageLoader Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.iio.png;

import com.sun.javafx.geom.Point2D;
import com.sun.javafx.geom.Rectangle;
import com.sun.javafx.iio.ImageFrame;
import com.sun.javafx.iio.ImageMetadata;
import com.sun.javafx.iio.ImageStorage;
import com.sun.javafx.iio.ImageStorage.ImageType;
import com.sun.javafx.iio.common.ImageLoaderImpl;
import com.sun.javafx.iio.common.ImageTools;
import com.sun.javafx.iio.common.PushbroomScaler;
import com.sun.javafx.iio.common.ScalerFactory;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipException;

/**
 * For future version(s):
 * - Re-order content per Java Coding Style (review)
 * - Try to remove non-CLDC code, or non-CDC at worst
 * - Short/Int reads for two types of endianness
 * - Metadata parsing
 * - Post-IDAT metadata
 * - Test multiple IDAT chunk case
 * - Test metadata post-IDAT
 */
//
// PNG format:
//
// Magic header
// IHDR chunk
// ... chunks other than IDAT, IEND...
// PLTE
// ... chunks other than IDAT, IEND...
// IDAT0
// [IDAT1]
// [IDAT...]
// ... chunks other than IEND...
// IEND
//
public class PNGImageLoader extends ImageLoaderImpl {
    /*
     * Note: The following chunk type constants are autogenerated.  Each
     * one is derived from the ASCII values of its 4-character name.  For
     * example, IHDR_TYPE is calculated as follows:
     *            ('I' << 24) | ('H' << 16) | ('D' << 8) | 'R'
     */

    // Critical chunks
    static final int IHDR_TYPE = 0x49484452;
    static final int PLTE_TYPE = 0x504c5445;
    static final int IDAT_TYPE = 0x49444154;
    static final int IEND_TYPE = 0x49454e44;
    // Ancillary chunks
    static final int bKGD_TYPE = 0x624b4744;
    static final int cHRM_TYPE = 0x6348524d;
    static final int gAMA_TYPE = 0x67414d41;
    static final int hIST_TYPE = 0x68495354;
    static final int iCCP_TYPE = 0x69434350;
    static final int iTXt_TYPE = 0x69545874;
    static final int pHYs_TYPE = 0x70485973;
    static final int sBIT_TYPE = 0x73424954;
    static final int sPLT_TYPE = 0x73504c54;
    static final int sRGB_TYPE = 0x73524742;
    static final int tEXt_TYPE = 0x74455874;
    static final int tIME_TYPE = 0x74494d45;
    static final int tRNS_TYPE = 0x74524e53;
    static final int zTXt_TYPE = 0x7a545874;
    static final int PNG_COLOR_GRAY = 0;
    static final int PNG_COLOR_RGB = 2;
    static final int PNG_COLOR_PALETTE = 3;
    static final int PNG_COLOR_GRAY_ALPHA = 4;
    static final int PNG_COLOR_RGB_ALPHA = 6;
    // The number of bands by PNG color type
    static final int[] inputBandsForColorType = {
        1, // gray
        -1, // unused
        3, // rgb
        1, // palette
        2, // gray + alpha
        -1, // unused
        4 // rgb + alpha
    };
    static final int PNG_FILTER_NONE = 0;
    static final int PNG_FILTER_SUB = 1;
    static final int PNG_FILTER_UP = 2;
    static final int PNG_FILTER_AVERAGE = 3;
    static final int PNG_FILTER_PAETH = 4;
    static final int[] adam7XOffset = {0, 4, 0, 2, 0, 1, 0};
    static final int[] adam7YOffset = {0, 0, 4, 0, 2, 0, 1};
    static final int[] adam7XSubsampling = {8, 8, 4, 4, 2, 2, 1, 1};
    static final int[] adam7YSubsampling = {8, 8, 8, 4, 4, 2, 2, 1};

    /**
     * Constants indicating how to process each row of decoded pixels.
     */
    private static enum ROW_PROCESS {

        /**
         * Copy the decoded pixels to the destination.
         */
        COPY,
        /**
         * Convert the decoded pixels to the destination ImageType.
         */
        CONVERT,
        /**
         * Downscale the decoded pixels.
         */
        DOWNSCALE,
        /**
         * Convert the decoded pixels to the destination type and then downscale.
         */
        CONVERT_DOWNSCALE
    };
    private InputStream stream;
    private boolean gotHeader = false;
    private boolean gotMetadata = false;
    private PNGIDATChunkInputStream IDATStream;
    int sourceXSubsampling = -1;
    int sourceYSubsampling = -1;
    int sourceMinProgressivePass = 0;
    int sourceMaxProgressivePass = 6;
    private PNGImageMetadata metadata;
    private DataInputStream pixelStream;
    private ImageType sourceType;
    private ImageType destType;
    private int numDestBands;
    private ImageFrame theImage;
    byte[][] thePalette;
    private PushbroomScaler scaler;
    private boolean isInterlaced;
    private boolean isConverting;
    private boolean isScaling;
    // Variables used in decodePass()
    private ROW_PROCESS rowProc;
    byte[] curr = null;
    byte[] prior = null;
    byte[] passRow = null;
    byte[] rescaledBuf = null;
    short[] shortData = null;
    byte[] convertedBuf = null;
    private int pixelsDone;
    private int totalPixels;

    public PNGImageLoader(InputStream input) throws IOException {
        super(PNGDescriptor.getInstance());
        if(input == null) {
            throw new IllegalArgumentException("input == null!");
        }
        this.stream = input;
        readHeader();
    }

    public void abort() {
        // no-op
    }

    public void dispose() {
        // no-op
    }

    public ImageFrame load(int imageIndex, int width, int height, boolean preserveAspectRatio, boolean smooth) throws IOException {
        if (imageIndex != 0) {
            return null;
        }

        readImage(width, height, preserveAspectRatio, smooth);

        return theImage;
    }

    private String readNullTerminatedString(String charset, int maxLen) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int b;
        int count = 0;
        while ((maxLen > count++) && ((b = stream.read()) != 0)) {
            if (b == -1) {
                throw new EOFException();
            }
            baos.write(b);
        }
        return new String(baos.toByteArray(), charset);
    }

    private synchronized void readHeader() throws IOException {
        if (gotHeader) {
            return;
        }
        if (stream == null) {
            throw new IllegalStateException("Input source not set!");
        }

        byte[] signature = new byte[8];
        ImageTools.readFully(stream, signature);

        if (signature[0] != (byte) 137 ||
                signature[1] != (byte) 80 ||
                signature[2] != (byte) 78 ||
                signature[3] != (byte) 71 ||
                signature[4] != (byte) 13 ||
                signature[5] != (byte) 10 ||
                signature[6] != (byte) 26 ||
                signature[7] != (byte) 10) {
            throw new IOException("Bad PNG signature!");
        }

        int IHDR_length = (stream.read() << 24) | (stream.read() << 16) |
                (stream.read() << 8) | stream.read();
        if (IHDR_length != 13) {
            throw new IOException("Bad length for IHDR chunk!");
        }
        int IHDR_type = (stream.read() << 24) | (stream.read() << 16) |
                (stream.read() << 8) | stream.read();
        if (IHDR_type != IHDR_TYPE) {
            throw new IOException("Bad type for IHDR chunk!");
        }

        this.metadata = new PNGImageMetadata();

        int width = (stream.read() << 24) | (stream.read() << 16) |
                (stream.read() << 8) | stream.read();
        int height = (stream.read() << 24) | (stream.read() << 16) |
                (stream.read() << 8) | stream.read();

        // Re-use signature array to bulk-read these unsigned byte values
        //stream.read(signature, 0, 5);
        ImageTools.readFully(stream, signature, 0, 5);
        int bitDepth = signature[0] & 0xff;
        int colorType = signature[1] & 0xff;
        int compressionMethod = signature[2] & 0xff;
        int filterMethod = signature[3] & 0xff;
        int interlaceMethod = signature[4] & 0xff;

        // Skip IHDR CRC
        stream.skip(4);

//            stream.flushBefore(input.getStreamPosition());

        if (width == 0) {
            throw new IOException("Image width == 0!");
        }
        if (height == 0) {
            throw new IOException("Image height == 0!");
        }
        if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 &&
                bitDepth != 8 && bitDepth != 16) {
            throw new IOException("Bit depth must be 1, 2, 4, 8, or 16!");
        }
        if (colorType != 0 && colorType != 2 && colorType != 3 &&
                colorType != 4 && colorType != 6) {
            throw new IOException("Color type must be 0, 2, 3, 4, or 6!");
        }
        if (colorType == PNG_COLOR_PALETTE && bitDepth == 16) {
            throw new IOException("Bad color type/bit depth combination!");
        }
        if ((colorType == PNG_COLOR_RGB ||
                colorType == PNG_COLOR_RGB_ALPHA ||
                colorType == PNG_COLOR_GRAY_ALPHA) &&
                (bitDepth != 8 && bitDepth != 16)) {
            throw new IOException("Bad color type/bit depth combination!");
        }
        if (compressionMethod != 0) {
            throw new IOException("Unknown compression method (not 0)!");
        }
        if (filterMethod != 0) {
            throw new IOException("Unknown filter method (not 0)!");
        }
        if (interlaceMethod != 0 && interlaceMethod != 1) {
            throw new IOException("Unknown interlace method (not 0 or 1)!");
        }

        switch (colorType) {
            case PNG_COLOR_GRAY:
//                System.out.println("GRAY");
                sourceType = ImageStorage.ImageType.GRAY;
                break;
            case PNG_COLOR_RGB:
//                System.out.println("RGB");
                sourceType = ImageStorage.ImageType.RGB;
                break;
            case PNG_COLOR_PALETTE:
//                System.out.println("PALETTE");
                sourceType = ImageStorage.ImageType.PALETTE;
                break;
            case PNG_COLOR_GRAY_ALPHA:
//                System.out.println("GRAY_ALPHA");
                sourceType = ImageStorage.ImageType.GRAY_ALPHA;
                break;
            case PNG_COLOR_RGB_ALPHA:
//                System.out.println("RGB_ALPHA");
                sourceType = ImageStorage.ImageType.RGBA;
                break;
            default:
                throw new UnsupportedOperationException("Unhandled color type");
        }

        metadata.IHDR_present = true;
        metadata.IHDR_width = width;
        metadata.IHDR_height = height;
        metadata.IHDR_bitDepth = bitDepth;
        metadata.IHDR_colorType = colorType;
        metadata.IHDR_compressionMethod = compressionMethod;
        metadata.IHDR_filterMethod = filterMethod;
        metadata.IHDR_interlaceMethod = interlaceMethod;
        this.isInterlaced = metadata.IHDR_interlaceMethod != 0;
        gotHeader = true;
    }

    private void parse_PLTE_chunk(int chunkLength) throws IOException {
        if (metadata.PLTE_present) {
            emitWarning(
                    "A PNG image may not contain more than one PLTE chunk.\n" +
                    "The chunk wil be ignored.");
            return;
        } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY ||
                metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
            emitWarning(
                    "A PNG gray or gray alpha image cannot have a PLTE chunk.\n" +
                    "The chunk wil be ignored.");
            return;
        }

        byte[] palette = new byte[chunkLength];
        ImageTools.readFully(stream, palette);

        int numEntries = chunkLength / 3;
        if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) {
            int maxEntries = 1 << metadata.IHDR_bitDepth;
            if (numEntries > maxEntries) {
                emitWarning(
                        "PLTE chunk contains too many entries for bit depth, ignoring extras.");
                numEntries = maxEntries;
            }
            numEntries = Math.min(numEntries, maxEntries);
        }

        // Round array sizes up to 2^2^n
        int paletteEntries;
        if (numEntries > 16) {
            paletteEntries = 256;
        } else if (numEntries > 4) {
            paletteEntries = 16;
        } else if (numEntries > 2) {
            paletteEntries = 4;
        } else {
            paletteEntries = 2;
        }

        metadata.PLTE_present = true;
        metadata.PLTE_red = new byte[paletteEntries];
        metadata.PLTE_green = new byte[paletteEntries];
        metadata.PLTE_blue = new byte[paletteEntries];

        int index = 0;
        for (int i = 0; i < numEntries; i++) {
            metadata.PLTE_red[i] = palette[index++];
            metadata.PLTE_green[i] = palette[index++];
            metadata.PLTE_blue[i] = palette[index++];
        }
    }

    private void parse_bKGD_chunk() throws IOException {
//        if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) {
//            metadata.bKGD_colorType = PNG_COLOR_PALETTE;
//            metadata.bKGD_index = stream.readUnsignedByte();
//        } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY ||
//                metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
//            metadata.bKGD_colorType = PNG_COLOR_GRAY;
//            metadata.bKGD_gray = stream.readUnsignedShort();
//        } else { // RGB or RGB_ALPHA
//            metadata.bKGD_colorType = PNG_COLOR_RGB;
//            metadata.bKGD_red = stream.readUnsignedShort();
//            metadata.bKGD_green = stream.readUnsignedShort();
//            metadata.bKGD_blue = stream.readUnsignedShort();
//        }
//
//        metadata.bKGD_present = true;
    }

    private void parse_cHRM_chunk() throws IOException {
//        metadata.cHRM_whitePointX = stream.readInt();
//        metadata.cHRM_whitePointY = stream.readInt();
//        metadata.cHRM_redX = stream.readInt();
//        metadata.cHRM_redY = stream.readInt();
//        metadata.cHRM_greenX = stream.readInt();
//        metadata.cHRM_greenY = stream.readInt();
//        metadata.cHRM_blueX = stream.readInt();
//        metadata.cHRM_blueY = stream.readInt();
//
//        metadata.cHRM_present = true;
    }

    private void parse_gAMA_chunk() throws IOException {
//        int gamma = stream.readInt();
//        metadata.gAMA_gamma = gamma;
//
//        metadata.gAMA_present = true;
    }

    private void parse_hIST_chunk(int chunkLength) throws IOException {
//        if (!metadata.PLTE_present) {
//            throw new IIOException("hIST chunk without prior PLTE chunk!");
//        }
//
//        /* According to PNG specification length of
//         * hIST chunk is specified in bytes and
//         * hIST chunk consists of 2 byte elements
//         * (so we expect length is even).
//         */
//        metadata.hIST_histogram = new char[chunkLength / 2];
//        stream.readFully(metadata.hIST_histogram,
//                0, metadata.hIST_histogram.length);
//
//        metadata.hIST_present = true;
    }

    private void parse_iCCP_chunk(int chunkLength) throws IOException {
//        String keyword = readNullTerminatedString("ISO-8859-1", 80);
//        metadata.iCCP_profileName = keyword;
//
//        metadata.iCCP_compressionMethod = stream.readUnsignedByte();
//
//        byte[] compressedProfile =
//                new byte[chunkLength - keyword.length() - 2];
//        stream.readFully(compressedProfile);
//        metadata.iCCP_compressedProfile = compressedProfile;
//
//        metadata.iCCP_present = true;
    }

    private void parse_iTXt_chunk(int chunkLength) throws IOException {
//        long chunkStart = stream.getStreamPosition();
//
//        String keyword = readNullTerminatedString("ISO-8859-1", 80);
//        metadata.iTXt_keyword.add(keyword);
//
//        int compressionFlag = stream.readUnsignedByte();
//        metadata.iTXt_compressionFlag.add(Boolean.millis(compressionFlag == 1));
//
//        int compressionMethod = stream.readUnsignedByte();
//        metadata.iTXt_compressionMethod.add(Integer.millis(compressionMethod));
//
//        String languageTag = readNullTerminatedString("UTF8", 80);
//        metadata.iTXt_languageTag.add(languageTag);
//
//        long pos = stream.getStreamPosition();
//        int maxLen = (int) (chunkStart + chunkLength - pos);
//        String translatedKeyword =
//                readNullTerminatedString("UTF8", maxLen);
//        metadata.iTXt_translatedKeyword.add(translatedKeyword);
//
//        String text;
//        pos = stream.getStreamPosition();
//        byte[] b = new byte[(int) (chunkStart + chunkLength - pos)];
//        stream.readFully(b);
//
//        if (compressionFlag == 1) { // Decompress the text
//            text = new String(inflate(b), "UTF8");
//        } else {
//            text = new String(b, "UTF8");
//        }
//        metadata.iTXt_text.add(text);
    }

    private void parse_pHYs_chunk() throws IOException {
//        metadata.pHYs_pixelsPerUnitXAxis = stream.readInt();
//        metadata.pHYs_pixelsPerUnitYAxis = stream.readInt();
//        metadata.pHYs_unitSpecifier = stream.readUnsignedByte();
//
//        metadata.pHYs_present = true;
    }

    private void parse_sBIT_chunk() throws IOException {
//        int colorType = metadata.IHDR_colorType;
//        if (colorType == PNG_COLOR_GRAY ||
//                colorType == PNG_COLOR_GRAY_ALPHA) {
//            metadata.sBIT_grayBits = stream.readUnsignedByte();
//        } else if (colorType == PNG_COLOR_RGB ||
//                colorType == PNG_COLOR_PALETTE ||
//                colorType == PNG_COLOR_RGB_ALPHA) {
//            metadata.sBIT_redBits = stream.readUnsignedByte();
//            metadata.sBIT_greenBits = stream.readUnsignedByte();
//            metadata.sBIT_blueBits = stream.readUnsignedByte();
//        }
//
//        if (colorType == PNG_COLOR_GRAY_ALPHA ||
//                colorType == PNG_COLOR_RGB_ALPHA) {
//            metadata.sBIT_alphaBits = stream.readUnsignedByte();
//        }
//
//        metadata.sBIT_colorType = colorType;
//        metadata.sBIT_present = true;
        }

    private void parse_sPLT_chunk(int chunkLength) throws IOException {
//        metadata.sPLT_paletteName = readNullTerminatedString("ISO-8859-1", 80);
//        chunkLength -= metadata.sPLT_paletteName.length() + 1;
//
//        int sampleDepth = stream.readUnsignedByte();
//        metadata.sPLT_sampleDepth = sampleDepth;
//
//        int numEntries = chunkLength / (4 * (sampleDepth / 8) + 2);
//        metadata.sPLT_red = new int[numEntries];
//        metadata.sPLT_green = new int[numEntries];
//        metadata.sPLT_blue = new int[numEntries];
//        metadata.sPLT_alpha = new int[numEntries];
//        metadata.sPLT_frequency = new int[numEntries];
//
//        if (sampleDepth == 8) {
//            for (int i = 0; i < numEntries; i++) {
//                metadata.sPLT_red[i] = stream.readUnsignedByte();
//                metadata.sPLT_green[i] = stream.readUnsignedByte();
//                metadata.sPLT_blue[i] = stream.readUnsignedByte();
//                metadata.sPLT_alpha[i] = stream.readUnsignedByte();
//                metadata.sPLT_frequency[i] = stream.readUnsignedShort();
//            }
//        } else if (sampleDepth == 16) {
//            for (int i = 0; i < numEntries; i++) {
//                metadata.sPLT_red[i] = stream.readUnsignedShort();
//                metadata.sPLT_green[i] = stream.readUnsignedShort();
//                metadata.sPLT_blue[i] = stream.readUnsignedShort();
//                metadata.sPLT_alpha[i] = stream.readUnsignedShort();
//                metadata.sPLT_frequency[i] = stream.readUnsignedShort();
//            }
//        } else {
//            throw new IIOException("sPLT sample depth not 8 or 16!");
//        }
//
//        metadata.sPLT_present = true;
    }

    private void parse_sRGB_chunk() throws IOException {
//        metadata.sRGB_renderingIntent = stream.readUnsignedByte();
//
//        metadata.sRGB_present = true;
    }

    private void parse_tEXt_chunk(int chunkLength) throws IOException {
//        String keyword = readNullTerminatedString("ISO-8859-1", 80);
//        metadata.tEXt_keyword.add(keyword);
//
//        byte[] b = new byte[chunkLength - keyword.length() - 1];
//        stream.readFully(b);
//        metadata.tEXt_text.add(new String(b, "ISO-8859-1"));
    }

    private void parse_tIME_chunk() throws IOException {
//        metadata.tIME_year = stream.readUnsignedShort();
//        metadata.tIME_month = stream.readUnsignedByte();
//        metadata.tIME_day = stream.readUnsignedByte();
//        metadata.tIME_hour = stream.readUnsignedByte();
//        metadata.tIME_minute = stream.readUnsignedByte();
//        metadata.tIME_second = stream.readUnsignedByte();
//
//        metadata.tIME_present = true;
    }

    private void parse_tRNS_chunk(int chunkLength) throws IOException {
        int colorType = metadata.IHDR_colorType;
        if (colorType == PNG_COLOR_PALETTE) {
            if (!metadata.PLTE_present) {
                emitWarning(
                        "tRNS chunk without prior PLTE chunk, ignoring it.");
                return;
            }

            // Alpha table may have fewer entries than RGB palette
            int maxEntries = metadata.PLTE_red.length;
            int numEntries = chunkLength;
            if (numEntries > maxEntries) {
                emitWarning(
                        "tRNS chunk has more entries than prior PLTE chunk, ignoring extras.");
                numEntries = maxEntries;
            }
            metadata.tRNS_alpha = new byte[numEntries];
            metadata.tRNS_colorType = PNG_COLOR_PALETTE;
            ImageTools.readFully(stream, metadata.tRNS_alpha, 0, numEntries);
            stream.skip(chunkLength - numEntries);
        } else if (colorType == PNG_COLOR_GRAY) {
            if (chunkLength != 2) {
                emitWarning(
                        "tRNS chunk for gray image must have length 2, ignoring chunk.");
                stream.skip(chunkLength);
                return;
            }
            metadata.tRNS_gray = (stream.read() << 8) | stream.read();
            metadata.tRNS_colorType = PNG_COLOR_GRAY;
        } else if (colorType == PNG_COLOR_RGB) {
            if (chunkLength != 6) {
                emitWarning(
                        "tRNS chunk for RGB image must have length 6, ignoring chunk.");
                stream.skip(chunkLength);
                return;
            }
            metadata.tRNS_red = (stream.read() << 8) | stream.read();
            metadata.tRNS_green = (stream.read() << 8) | stream.read();
            metadata.tRNS_blue = (stream.read() << 8) | stream.read();
            metadata.tRNS_colorType = PNG_COLOR_RGB;
        } else {
            emitWarning(
                    "Gray+Alpha and RGBS images may not have a tRNS chunk, ignoring it.");
            return;
        }

        metadata.tRNS_present = true;
    }

//    private static byte[] inflate(byte[] b) throws IOException {
//        InputStream bais = new ByteArrayInputStream(b);
//        InputStream iis = new InflaterInputStream(bais);
//        ByteArrayOutputStream baos = new ByteArrayOutputStream();
//
//        int c;
//        try {
//            while ((c = iis.read()) != -1) {
//                baos.write(c);
//            }
//        } finally {
//            iis.close();
//        }
//        return baos.toByteArray();
//    }
    private void parse_zTXt_chunk(int chunkLength) throws IOException {
//        String keyword = readNullTerminatedString("ISO-8859-1", 80);
//        metadata.zTXt_keyword.add(keyword);
//
//        int method = stream.readUnsignedByte();
//        metadata.zTXt_compressionMethod.add(new Integer(method));
//
//        byte[] b = new byte[chunkLength - keyword.length() - 2];
//        stream.readFully(b);
//        metadata.zTXt_text.add(new String(inflate(b), "ISO-8859-1"));
    }

    private synchronized void readMetadata() throws IOException {
        if (gotMetadata) {
            return;
        }

        readHeader();

        /*
         * Optimization: We can skip the remaining metadata if the
         * ignoreMetadata flag is set, and only if this is not a palette
         * image (in that case, we need to read the metadata to get the
         * tRNS chunk, which is needed for the getImageTypes() method).
         */
        int colorType = metadata.IHDR_colorType;
//        if (ignoreMetadata && colorType != PNG_COLOR_PALETTE) {
        if (colorType != PNG_COLOR_PALETTE) {
            try {
                while (true) {
                    int chunkLength = (stream.read() << 24) | (stream.read() << 16) |
                            (stream.read() << 8) | stream.read();
                    int chunkType = (stream.read() << 24) | (stream.read() << 16) |
                            (stream.read() << 8) | stream.read();

                    if (chunkType == IDAT_TYPE) {
                        IDATStream = new PNGIDATChunkInputStream(stream, chunkLength);
//                        // We've reached the image data
//                        stream.skipBytes(-8);
//                        imageStartPosition = stream.getStreamPosition();
                        break;
                    } else {
                        // Skip the chunk plus the 4 CRC bytes that follow
                        stream.skip(chunkLength + 4);
                    }
                }
            } catch (IOException e) {
                IOException ex = new IOException("Error skipping PNG metadata");
                ex.initCause(e);
                throw ex;
            }

            gotMetadata = true;
            return;
        }

        try {
            loop:
            while (true) {
                int chunkLength = (stream.read() << 24) | (stream.read() << 16) |
                        (stream.read() << 8) | stream.read();
                int chunkType = (stream.read() << 24) | (stream.read() << 16) |
                        (stream.read() << 8) | stream.read();

                switch (chunkType) {
                    case IDAT_TYPE:
                        IDATStream = new PNGIDATChunkInputStream(stream, chunkLength);
//                        // If chunk type is 'IDAT', we've reached the image data.
//                        stream.skipBytes(-8);
//                        imageStartPosition = stream.getStreamPosition();
                        break loop;
                    case PLTE_TYPE:
                        parse_PLTE_chunk(chunkLength);
                        break;
//                    case bKGD_TYPE:
//                        parse_bKGD_chunk();
//                        break;
//                    case cHRM_TYPE:
//                        parse_cHRM_chunk();
//                        break;
//                    case gAMA_TYPE:
//                        parse_gAMA_chunk();
//                        break;
//                    case hIST_TYPE:
//                        parse_hIST_chunk(chunkLength);
//                        break;
//                    case iCCP_TYPE:
//                        parse_iCCP_chunk(chunkLength);
//                        break;
//                    case iTXt_TYPE:
//                        parse_iTXt_chunk(chunkLength);
//                        break;
//                    case pHYs_TYPE:
//                        parse_pHYs_chunk();
//                        break;
//                    case sBIT_TYPE:
//                        parse_sBIT_chunk();
//                        break;
//                    case sPLT_TYPE:
//                        parse_sPLT_chunk(chunkLength);
//                        break;
//                    case sRGB_TYPE:
//                        parse_sRGB_chunk();
//                        break;
//                    case tEXt_TYPE:
//                        parse_tEXt_chunk(chunkLength);
//                        break;
//                    case tIME_TYPE:
//                        parse_tIME_chunk();
//                        break;
                    case tRNS_TYPE:
                        parse_tRNS_chunk(chunkLength);
                        break;
//                    case zTXt_TYPE:
//                        parse_zTXt_chunk(chunkLength);
//                        break;
                    default:
//                        // Read an unknown chunk
//                        byte[] b = new byte[chunkLength];
//                        stream.read(b);
//
//                        StringBuilder chunkName = new StringBuilder(4);
//                        chunkName.append((char) (chunkType >>> 24));
//                        chunkName.append((char) ((chunkType >> 16) & 0xff));
//                        chunkName.append((char) ((chunkType >> 8) & 0xff));
//                        chunkName.append((char) (chunkType & 0xff));
//
//                        int ancillaryBit = chunkType >>> 28;
//                        if (ancillaryBit == 0) {
////                            emitWarning(
////                                    "Encountered unknown chunk with critical bit set!");
//                        }
//
//                        metadata.unknownChunkType.add(chunkName.toString());
//                        metadata.unknownChunkData.add(b);
                        // Skip over an unknown chunk
                        stream.skip(chunkLength);
                        break;
                }

                int chunkCRC = (stream.read() << 24) | (stream.read() << 16) |
                        (stream.read() << 8) | stream.read();
//                stream.flushBefore(stream.getStreamPosition());
            }
        } catch (IOException e) {
            IOException ex = new IOException("Error reading PNG metadata");
            ex.initCause(e);
            throw ex;
        }

        if (metadata.PLTE_present) {
            /*
             * The PLTE chunk spec says:
             *
             * The number of palette entries must not exceed the range that
             * can be represented in the image bit depth (for example, 2^4 = 16
             * for a bit depth of 4). It is permissible to have fewer entries
             * than the bit depth would allow. In that case, any out-of-range
             * pixel value found in the image data is an error.
             *
             * http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.PLTE
             *
             * Consequently, the case when the palette length is smaller than
             * 2^bitDepth is legal in the view of PNG spec.
             */
            if (metadata.tRNS_present) {
                thePalette = new byte[4][];
            } else {
                thePalette = new byte[3][];
            }
            thePalette[0] = metadata.PLTE_red;
            thePalette[1] = metadata.PLTE_green;
            thePalette[2] = metadata.PLTE_blue;

            int plength = 1 << metadata.IHDR_bitDepth;
            if (metadata.PLTE_red.length < plength) {
                thePalette[0] = new byte[plength];
                System.arraycopy(metadata.PLTE_red, 0,
                        thePalette[0], 0, metadata.PLTE_red.length);
                Arrays.fill(thePalette[0], metadata.PLTE_red.length, plength,
                        metadata.PLTE_red[metadata.PLTE_red.length - 1]);
            }
            if (metadata.PLTE_green.length < plength) {
                thePalette[1] = new byte[plength];
                System.arraycopy(metadata.PLTE_green, 0,
                        thePalette[1], 0, metadata.PLTE_green.length);
                Arrays.fill(thePalette[1], metadata.PLTE_green.length, plength,
                        metadata.PLTE_green[metadata.PLTE_green.length - 1]);
            }
            if (metadata.PLTE_red.length < plength) {
                thePalette[2] = new byte[plength];
                System.arraycopy(metadata.PLTE_blue, 0,
                        thePalette[2], 0, metadata.PLTE_blue.length);
                Arrays.fill(thePalette[2], metadata.PLTE_blue.length, plength,
                        metadata.PLTE_blue[metadata.PLTE_blue.length - 1]);
            }

            if (metadata.tRNS_present && (metadata.tRNS_alpha != null)) {
                sourceType = ImageStorage.ImageType.PALETTE_ALPHA;
                if (metadata.tRNS_alpha.length >= metadata.PLTE_red.length) {
                    thePalette[3] = metadata.tRNS_alpha;
                } else {
                    thePalette[3] = new byte[metadata.PLTE_red.length];
                    System.arraycopy(metadata.tRNS_alpha, 0, thePalette[3], 0,
                            metadata.tRNS_alpha.length);
                    Arrays.fill(thePalette[3],
                            metadata.tRNS_alpha.length,
                            thePalette[3].length, (byte) 255);
                }
            }
        }

        gotMetadata = true;
    }

    // Data filtering methods
    private static void decodeSubFilter(byte[] curr, int coff, int count,
            int bpp) {
        for (int i = bpp; i < count; i++) {
            int val;

            val = curr[i + coff] & 0xff;
            val += curr[i + coff - bpp] & 0xff;

            curr[i + coff] = (byte) val;
        }
    }

    private static void decodeUpFilter(byte[] curr, int coff,
            byte[] prev, int poff,
            int count) {
        for (int i = 0; i < count; i++) {
            int raw = curr[i + coff] & 0xff;
            int prior = prev[i + poff] & 0xff;

            curr[i + coff] = (byte) (raw + prior);
        }
    }

    private static void decodeAverageFilter(byte[] curr, int coff,
            byte[] prev, int poff,
            int count, int bpp) {
        int raw, priorPixel, priorRow;

        for (int i = 0; i < bpp; i++) {
            raw = curr[i + coff] & 0xff;
            priorRow = prev[i + poff] & 0xff;

            curr[i + coff] = (byte) (raw + priorRow / 2);
        }

        for (int i = bpp; i < count; i++) {
            raw = curr[i + coff] & 0xff;
            priorPixel = curr[i + coff - bpp] & 0xff;
            priorRow = prev[i + poff] & 0xff;

            curr[i + coff] = (byte) (raw + (priorPixel + priorRow) / 2);
        }
    }

    private static int paethPredictor(int a, int b, int c) {
        int p = a + b - c;
        int pa = Math.abs(p - a);
        int pb = Math.abs(p - b);
        int pc = Math.abs(p - c);

        if ((pa <= pb) && (pa <= pc)) {
            return a;
        } else if (pb <= pc) {
            return b;
        } else {
            return c;
        }
    }

    private static void decodePaethFilter(byte[] curr, int coff,
            byte[] prev, int poff,
            int count, int bpp) {
        int raw, priorPixel, priorRow, priorRowPixel;

        for (int i = 0; i < bpp; i++) {
            raw = curr[i + coff] & 0xff;
            priorRow = prev[i + poff] & 0xff;

            curr[i + coff] = (byte) (raw + priorRow);
        }

        for (int i = bpp; i < count; i++) {
            raw = curr[i + coff] & 0xff;
            priorPixel = curr[i + coff - bpp] & 0xff;
            priorRow = prev[i + poff] & 0xff;
            priorRowPixel = prev[i + poff - bpp] & 0xff;

            curr[i + coff] = (byte) (raw + paethPredictor(priorPixel,
                    priorRow,
                    priorRowPixel));
        }
    }
//    private static final int[][] bandOffsets = {
//        null,
//        {0}, // G
//        {0, 1}, // GA in GA order
//        {0, 1, 2}, // RGB in RGB order
//        {0, 1, 2, 3} // RGBA in RGBA order
//    };

//    private WritableRaster createRaster(int width, int height, int bands,
//            int scanlineStride,
//            int bitDepth) {
//
//        DataBuffer dataBuffer;
//        WritableRaster ras = null;
//        Point origin = new Point(0, 0);
//        if ((bitDepth < 8) && (bands == 1)) {
//            dataBuffer = new DataBufferByte(height * scanlineStride);
//            ras = Raster.createPackedRaster(dataBuffer,
//                    width, height,
//                    bitDepth,
//                    origin);
//        } else if (bitDepth <= 8) {
//            dataBuffer = new DataBufferByte(height * scanlineStride);
//            ras = Raster.createInterleavedRaster(dataBuffer,
//                    width, height,
//                    scanlineStride,
//                    bands,
//                    bandOffsets[bands],
//                    origin);
//        } else {
//            dataBuffer = new DataBufferUShort(height * scanlineStride);
//            ras = Raster.createInterleavedRaster(dataBuffer,
//                    width, height,
//                    scanlineStride,
//                    bands,
//                    bandOffsets[bands],
//                    origin);
//        }
//
//        return ras;
//    }
    private void skipPass(int passWidth, int passHeight)
            throws IOException {
        if ((passWidth == 0) || (passHeight == 0)) {
            return;
        }

        int inputBands = inputBandsForColorType[metadata.IHDR_colorType];
        int bytesPerRow = (inputBands * passWidth * metadata.IHDR_bitDepth + 7) / 8;

        // Read the image row-by-row
        for (int srcY = 0; srcY < passHeight; srcY++) {
            // Skip filter byte and the remaining row bytes
            pixelStream.skipBytes(1 + bytesPerRow);

            // If read has been aborted, just return
            // processReadAborted will be called later
//            if (abortRequested()) {
//                return;
//            }
        }
    }

    private void updateImageProgress(int newPixels) {
        pixelsDone += newPixels;
        updateImageProgress(100.0F * pixelsDone / totalPixels);
    }

    private void decodePass(int passNum,
            int xStart, int yStart,
            int xStep, int yStep,
            int passWidth, int passHeight) throws IOException {

        if ((passWidth == 0) || (passHeight == 0)) {
            return;
        }

//        WritableRaster imRas = theBufferedImage.getWritableTile(0, 0);
        byte[] imPixels = null;
        Buffer bufferNIO = this.theImage.getImageData();
        if (bufferNIO instanceof ByteBuffer) {
            imPixels = ((ByteBuffer) bufferNIO).array();
        } else {
            throw new UnsupportedOperationException("Only ByteBuffer is supported!");
        }

        int dstMinX = 0;
        int dstMaxX = metadata.IHDR_width - 1;//imRas.getWidth() - 1;
        int dstMinY = 0;
        int dstMaxY = metadata.IHDR_height - 1;//imRas.getHeight() - 1;

        // Determine which pixels will be updated in this pass
//        int[] vals =
//                ReaderUtil.computeUpdatedPixels(sourceRegion,
//                destinationOffset,
//                dstMinX, dstMinY,
//                dstMaxX, dstMaxY,
//                sourceXSubsampling,
//                sourceYSubsampling,
//                xStart, yStart,
//                passWidth, passHeight,
//                xStep, yStep);
//        int updateMinX = vals[0];
//        int updateMinY = vals[1];
//        int updateWidth = vals[2];
//        int updateXStep = vals[4];
//        int updateYStep = vals;[5];
        int[] vals =
                ImageTools.computeUpdatedPixels(new Rectangle(metadata.IHDR_width, metadata.IHDR_height),
                new Point2D(),
                dstMinX, dstMinY,
                dstMaxX, dstMaxY,
                sourceXSubsampling,
                sourceYSubsampling,
                xStart, yStart,
                passWidth, passHeight,
                xStep, yStep);
        int updateMinX = vals[0];
        int updateMinY = vals[1];
        int updateWidth = vals[2];
        int updateXStep = vals[4];
        int updateYStep = vals[5];
//        int updateMinX = 0;
//        int updateMinY = 0;
//        int updateWidth = passWidth;
//        int updateXStep = 1;
//        int updateYStep = 1;

        int bitDepth = metadata.IHDR_bitDepth;
        int inputBands = inputBandsForColorType[metadata.IHDR_colorType];
        int bytesPerPixel = (bitDepth == 16) ? 2 : 1;
        bytesPerPixel *= inputBands;
        int pixelsPerByte = 8/bitDepth;
        int byteMask = 0x01;

        switch(bitDepth){
            case 8:
                byteMask = 0xff;
            break;
            case 4:
                byteMask = 0x0f;
            break;
            case 2:
                byteMask = 0x03;
            break;
            case 1:
                byteMask = 0x01;
            break;
        }

        int bytesPerRow = (inputBands * passWidth * bitDepth + 7) / 8;
        int eltsPerRow = (bitDepth == 16) ? bytesPerRow / 2 : bytesPerRow;

        // If no pixels need updating, just skip the input data
        if (updateWidth == 0) {
            for (int srcY = 0; srcY < passHeight; srcY++) {
                // Update count of pixels read
                updateImageProgress(passWidth);
                // Skip filter byte and the remaining row bytes
                pixelStream.skipBytes(1 + bytesPerRow);
            }
            return;
        }

        // Backwards map from destination pixels
        // (dstX = updateMinX + k*updateXStep)
        // to source pixels (sourceX), and then
        // to offset and skip in passRow (srcX and srcXStep)
        int sourceX = updateMinX * sourceXSubsampling;
        int srcX = (sourceX - xStart) / xStep;

        // Compute the step factor in the source
        int srcXStep = updateXStep * sourceXSubsampling / xStep;

        if (curr == null || curr.length < bytesPerRow) {
            curr = new byte[bytesPerRow];
            prior = new byte[bytesPerRow];

//        // Create a 1-row tall Raster to hold the data
//        WritableRaster passRow = createRaster(passWidth, 1, inputBands,
//                eltsPerRow,
//                bitDepth);
            passRow = new byte[bytesPerRow];
            if (bitDepth == 16) {
//            throw new UnsupportedOperationException("Bit depth > 8 not supported!");
                shortData = new short[bytesPerRow / 2];
            }
        }

        byte[] byteData = bitDepth != 16 ? passRow : null;
        if(rowProc == ROW_PROCESS.CONVERT_DOWNSCALE) {
            int destBytesPerRow = updateWidth*numDestBands;
            if(convertedBuf == null || convertedBuf.length < destBytesPerRow) {
                convertedBuf = new byte[destBytesPerRow];
            }
        }

        // Create an array suitable for holding one pixel
//        int[] ps = passRow.getPixel(0, 0, (int[]) null);

//        DataBuffer dataBuffer = passRow.getDataBuffer();
//        int type = dataBuffer.getDataType();
//        if (type == DataBuffer.TYPE_BYTE) {
//            byteData = ((DataBufferByte) dataBuffer).getData();
//        } else {
//            shortData = ((DataBufferUShort) dataBuffer).getData();
//        }
//        if (bitDepth == 16) {
////            throw new UnsupportedOperationException("Bit depth > 8 not supported!");
//            shortData = new short[bytesPerRow / 2];
//        } else {
//            byteData = passRow;
//        }

//        processPassStarted(theBufferedImage,
//                passNum,
//                sourceMinProgressivePass,
//                sourceMaxProgressivePass,
//                updateMinX, updateMinY,
//                updateXStep, updateYStep,
//                destinationBands);

        // Handle source and destination bands
//        if (sourceBands != null) {
//            passRow = passRow.createWritableChild(0, 0,
//                    passRow.getWidth(), 1,
//                    0, 0,
//                    sourceBands);
//        }
//        if (destinationBands != null) {
//            imRas = imRas.createWritableChild(0, 0,
//                    imRas.getWidth(),
//                    imRas.getHeight(),
//                    0, 0,
//                    destinationBands);
//        }

        // Determine if all of the relevant output bands have the
        // same bit depth as the source data
        boolean adjustBitDepths = false;
//        int[] outputSampleSize = imRas.getSampleModel().getSampleSize();
        int[] outputSampleSize = new int[inputBands];
        for (int b = 0; b < inputBands; b++) {
            outputSampleSize[b] = 8; // NOTE: output bit depth hard-coded.
        }
        int numBands = outputSampleSize.length;
        for (int b = 0; b < numBands; b++) {
            if (outputSampleSize[b] != bitDepth) {
                adjustBitDepths = true;
                break;
            }
        }

        // If the bit depths differ, create a lookup table per band to perform
        // the conversion
        int[][] scale = null;
        if (adjustBitDepths) {
            int maxInSample = (1 << bitDepth) - 1;
            int halfMaxInSample = maxInSample / 2;
            scale = new int[numBands][];
            for (int b = 0; b < numBands; b++) {
                int maxOutSample = (1 << outputSampleSize[b]) - 1;
                scale[b] = new int[maxInSample + 1];
                for (int s = 0; s <= maxInSample; s++) {
                    scale[b][s] =
                            (s * maxOutSample + halfMaxInSample) / maxInSample;
                }
            }
        }

//        // Limit passRow to relevant area for the case where we
//        // will can setRect to copy a contiguous span
//        boolean useSetRect = srcXStep == 1 &&
//                updateXStep == 1 &&
//                !adjustBitDepths &&
//                (imRas instanceof ByteInterleavedRaster);
//
//        if (useSetRect) {
//            passRow = passRow.createWritableChild(srcX, 0,
//                    updateWidth, 1,
//                    0, 0,
//                    null);
//        }
        boolean useSetRect = srcXStep == 1 &&
                updateXStep == 1 &&
                !adjustBitDepths;

        int rescaledStride;
        if(useSetRect) {
            rescaledBuf = null;
            rescaledStride = 0;
        } else {
            if (rowProc == ROW_PROCESS.COPY) {
                rescaledBuf = imPixels;
                rescaledStride = theImage.getStride();
            } else {
                int len = metadata.IHDR_width * inputBands;
                if(rescaledBuf == null || rescaledBuf.length < len) {
                    rescaledBuf = new byte[metadata.IHDR_width * inputBands];
                }
                rescaledStride = 0;
            }
        }

        // Decode the (sub)image row-by-row
        for (int srcY = 0; srcY < passHeight; srcY++) {
            // Update count of pixels read
            updateImageProgress(passWidth);

            // Read the filter type byte and a row of data
            int filter = pixelStream.read();
            try {
                // Swap curr and prior
                byte[] tmp = prior;
                prior = curr;
                curr = tmp;

                pixelStream.readFully(curr, 0, bytesPerRow);
            } catch (ZipException ze) {
                IOException ex = new IOException("Error deflating compressed PNG data!");
                ex.initCause(ze);
                throw ex;
            }

            switch (filter) {
                case PNG_FILTER_NONE:
                    break;
                case PNG_FILTER_SUB:
                    decodeSubFilter(curr, 0, bytesPerRow, bytesPerPixel);
                    break;
                case PNG_FILTER_UP:
                    decodeUpFilter(curr, 0, prior, 0, bytesPerRow);
                    break;
                case PNG_FILTER_AVERAGE:
                    decodeAverageFilter(curr, 0, prior, 0, bytesPerRow,
                            bytesPerPixel);
                    break;
                case PNG_FILTER_PAETH:
                    decodePaethFilter(curr, 0, prior, 0, bytesPerRow,
                            bytesPerPixel);
                    break;
                default:
                    throw new IOException("Unknown row filter type (= " +
                            filter + ")!");
            }

            // Copy data into passRow byte by byte
            if (bitDepth < 16) {
                System.arraycopy(curr, 0, byteData, 0, bytesPerRow);
            } else {
                int idx = 0;
                for (int j = 0; j < eltsPerRow; j++) {
                    shortData[j] =
                            (short) ((curr[idx] << 8) | (curr[idx + 1] & 0xff));
                    idx += 2;
                }
            }

            // True Y position in source
            int sourceY = srcY * yStep + yStart;
            if ((sourceY >= 0) && (sourceY < metadata.IHDR_height)) {

                int dstY = sourceY / sourceYSubsampling;
                if (dstY < dstMinY) {
                    continue;
                }
                if (dstY > dstMaxY) {
                    break;
                }

//                if (useSetRect) {
//                    imRas.setRect(updateMinX, dstY, passRow);
//                } else {
                if (useSetRect) {
                    switch (rowProc) {
                        case COPY:
                            System.arraycopy(passRow, 0,
                                    imPixels, dstY * theImage.getStride(),
                                    updateWidth * numBands);
                            break;
                        case CONVERT:
                            ImageTools.convert(updateWidth, 1, sourceType,
                            passRow, 0, 0,
                            imPixels, dstY * theImage.getStride(), theImage.getStride(),
                            thePalette, 0, false);
                            break;
                        case DOWNSCALE:
                            scaler.putSourceScanline(passRow, 0);
                            break;
                        case CONVERT_DOWNSCALE:
                            assert passRow.length <= passWidth*numBands;
                            ImageTools.convert(updateWidth, 1, sourceType,
                            passRow, 0, 0,
                            convertedBuf, 0, 0,
                            thePalette, 0, false);
                            scaler.putSourceScanline(convertedBuf, 0);
                            break;
                        default:
                            assert false;
                    }
//                    ImageTools.convert(updateWidth, 1, sourceType,
//                            passRow, 0, 0,
//                            imPixels, dstY * theImage.getStride(), 0,
//                            palette);
                } else {
                    int newSrcX = srcX;
                    int rowOffset = dstY * rescaledStride + updateMinX * numBands;
                    int pixelStep = (updateXStep - 1) * numBands;
                    if (adjustBitDepths) {
                        if (bitDepth == 16) {
                            for (int dstX = updateMinX;
                                    dstX < updateMinX + updateWidth;
                                    dstX += updateXStep) {
                                for (int i = 0, j = newSrcX * inputBands; i < inputBands; i++) {
                                    int index = shortData[j++] & 0xffff;
                                    rescaledBuf[rowOffset++] = (byte) scale[i][index];
                                }
                                rowOffset += pixelStep;
                                newSrcX += srcXStep;
                            }
                        } else {
                            for (int dstX = updateMinX;
                                    dstX < updateMinX + (updateWidth);
                                    dstX += pixelsPerByte * inputBands) {
                                for (int i = 0, j = newSrcX * inputBands; i < inputBands; i++) {
                                    byte data = byteData[j++];
                                    for(int k = 0; k < pixelsPerByte; k++) {
                                        //shift in order to grab the correct bits.
                                        int shiftedByte = ((data >> ((pixelsPerByte-1 - k) * bitDepth)) & 0xff);
                                        int val = (shiftedByte & byteMask);
                                        if(rowOffset < updateWidth )
                                            rescaledBuf[rowOffset++] = (byte) (scale[i][val] & byteMask);
                                    }
                                }
                                newSrcX += srcXStep;
                            }
                        }
                    } else if (!adjustBitDepths) {
                        if (bitDepth == 16) {
                            for (int dstX = updateMinX;
                                    dstX < updateMinX + updateWidth;
                                    dstX += updateXStep) {
                                for (int i = 0, j = newSrcX * inputBands; i < inputBands; i++) {
                                    rescaledBuf[rowOffset++] = (byte) ((shortData[j++] & 0xffff) >> 8);
                                }
                                rowOffset += pixelStep;
                                newSrcX += srcXStep;
                            }
                        } else {
                            for (int dstX = updateMinX;
                                    dstX < updateMinX + updateWidth;
                                    dstX += updateXStep) {
                                for (int i = 0, j = newSrcX * inputBands; i < inputBands; i++) {
                                    rescaledBuf[rowOffset++] = byteData[j++];
                                }
                                rowOffset += pixelStep;
                                newSrcX += srcXStep;
                            }
                        }
                    }

                    if(!isInterlaced) {
                        switch (rowProc) {
                            case COPY:
                                // rescaled data are already in imPixels
                                break;
                            case CONVERT:
                                ImageTools.convert(updateWidth, 1, sourceType,
                                        rescaledBuf, updateMinX * numBands, rescaledStride,
                                        imPixels, dstY * theImage.getStride() + updateMinX * numBands, theImage.getStride(),
                                        thePalette, 0, false);
                                break;
                            case DOWNSCALE:
                                scaler.putSourceScanline(rescaledBuf, 0);
                                break;
                            case CONVERT_DOWNSCALE:
                                ImageTools.convert(updateWidth, 1, sourceType,
                                        rescaledBuf, updateMinX * numBands, rescaledStride,
                                        convertedBuf, updateMinX * numBands, 0,
                                        thePalette, 0, false);
                                scaler.putSourceScanline(convertedBuf, 0);
                                break;
                            default:
                                assert false;
                        }
                    }
                }

//                processImageUpdate(theBufferedImage,
//                        updateMinX, dstY,
//                        updateWidth, 1,
//                        updateXStep, updateYStep,
//                        destinationBands);

                // If read has been aborted, just return
                // processReadAborted will be called later
//                if (abortRequested()) {
//                    return;
//                }
            }
        }

//        processPassComplete(theBufferedImage);
    }

    private void decodeImage(int destWidth, int destHeight,
            boolean smooth) throws IOException {
        int width = metadata.IHDR_width;
        int height = metadata.IHDR_height;

        this.pixelsDone = 0;
        this.totalPixels = width * height;

//        clearAbortRequest();

        // Send 0% message.
        updateImageProgress(0);

        if (metadata.IHDR_interlaceMethod == 0) {
            decodePass(0, 0, 0, 1, 1, width, height);
        } else {
            for (int i = 0; i <= sourceMaxProgressivePass; i++) {
                int XOffset = adam7XOffset[i];
                int YOffset = adam7YOffset[i];
                int XSubsampling = adam7XSubsampling[i];
                int YSubsampling = adam7YSubsampling[i];
                int xbump = adam7XSubsampling[i + 1] - 1;
                int ybump = adam7YSubsampling[i + 1] - 1;

                if (i >= sourceMinProgressivePass) {
                    decodePass(i,
                            XOffset,
                            YOffset,
                            XSubsampling,
                            YSubsampling,
                            (width + xbump) / XSubsampling,
                            (height + ybump) / YSubsampling);
                } else {
                    skipPass((width + xbump) / XSubsampling,
                            (height + ybump) / YSubsampling);
                }

                // If read has been aborted, just return
                // processReadAborted will be called later
//                if (abortRequested()) {
//                    return;
//                }
            }

            // Interlaced images must be converted and downscaled after decoding
            if (isScaling) {
                // Downscale the image.
                ByteBuffer bb = (ByteBuffer) theImage.getImageData();
                byte[] b = bb.array();
                int stride = theImage.getStride();
                int offset = 0;
                byte[] scanline = new byte[width * numDestBands];
                for (int y = 0; y < height; y++) {
                    ImageTools.convert(width, 1, sourceType,
                            b, offset, stride, scanline, 0, 0,
                            thePalette, 0, false);
                    if (scaler.putSourceScanline(scanline, 0)) {
                        break;
                    }
                    offset += stride;
                }
//                theImage = new ImageFrame(destType, scaler.getDestination(),
//                        destWidth, destHeight, destWidth * numDestBands,
//                        null, null);
            } else if(isConverting) {
                theImage = ImageTools.convertImageFrame(theImage);
            }
        }

        if(isScaling) {
            theImage = new ImageFrame(destType, scaler.getDestination(),
                        destWidth, destHeight, destWidth * numDestBands,
                        null, null);
        }
    }

    private synchronized void readImage(int destWidth, int destHeight,
            boolean preserveAspectRatio, boolean smooth) throws IOException {
        if (theImage != null) {
            return;
        }

        readMetadata();

        int width = metadata.IHDR_width;
        int height = metadata.IHDR_height;

        // Determine output image dimensions.
        int[] widthHeight = ImageTools.computeDimensions(width, height, destWidth, destHeight, preserveAspectRatio);
        destWidth = widthHeight[0];
        destHeight = widthHeight[1];

        // Set destination type.
        destType = ImageTools.getConvertedType(sourceType);
        numDestBands = ImageStorage.getNumBands(destType);

        // Set logic flags.
        this.isConverting = destType != sourceType;
        this.isScaling = destWidth != width || destHeight != height;

        // Set row processing flag for use in decodePass().
        if ((!isConverting && !isScaling) ||
                (isInterlaced && (isConverting || isScaling))) {
            // non-interlaced, unconverted, unscaled
            // interlaced, unconverted, unscaled
            // interlaced, unconverted, scaled
            // interlaced, converted, unscaled
            // interlaced, converted, scaled
            this.rowProc = ROW_PROCESS.COPY;
        } else if (isConverting && !isScaling) {
            // non-interlaced, converted, unscaled
            this.rowProc = ROW_PROCESS.CONVERT;
        } else {
            assert !isInterlaced;
            if (!isConverting && isScaling) {
                // non-interlaced, unconverted, scaled
                this.rowProc = ROW_PROCESS.DOWNSCALE;
            } else {
                // non-interlaced, converted, scaled
                this.rowProc = ROW_PROCESS.CONVERT_DOWNSCALE;
            }
        }

        // Init default values
        sourceXSubsampling = 1;
        sourceYSubsampling = 1;
        sourceMinProgressivePass = 0;
        sourceMaxProgressivePass = 6;
//        sourceBands = null;
//        destinationBands = null;
//        destinationOffset = new Point(0, 0);

        // If an ImageReadParam is available, get values from it
//        if (param != null) {
//            sourceXSubsampling = param.getSourceXSubsampling();
//            sourceYSubsampling = param.getSourceYSubsampling();
//
//            sourceMinProgressivePass =
//                    Math.max(param.getSourceMinProgressivePass(), 0);
//            sourceMaxProgressivePass =
//                    Math.min(param.getSourceMaxProgressivePass(), 6);
//
//            sourceBands = param.getSourceBands();
//            destinationBands = param.getDestinationBands();
//            destinationOffset = param.getDestinationOffset();
//        }
        Inflater inf = null;
        try {
//            stream.seek(imageStartPosition);
//
//            Enumeration e = new PNGImageDataEnumeration(stream);
            InputStream is; // = new ObservableListInputStream(e);

            /* InflaterInputStream uses an Inflater instance which consumes
             * native (non-GC visible) resources. This is normally implicitly
             * freed when the stream is closed. However since the
             * InflaterInputStream wraps a client-supplied input stream,
             * we cannot close it.
             * But the app may depend on GC finalization to close the stream.
             * Therefore to ensure timely freeing of native resources we
             * explicitly create the Inflater instance and free its resources
             * when we are done with the InflaterInputStream by calling
             * inf.end();
             */
            inf = new Inflater();
            is = new InflaterInputStream(IDATStream, inf);
            is = new BufferedInputStream(is);
            this.pixelStream = new DataInputStream(is);

            scaler = ScalerFactory.createScaler(width, height, ImageStorage.getNumBands(destType),
                    destWidth, destHeight, smooth);

            //            theBufferedImage = getImageTypes(0).next().createBufferedImage(width, height);
            Float gamma = metadata.gAMA_present ? (float) metadata.gAMA_gamma : null;
            Integer backgroundIndex = null;
            Integer backgroundColor = null;

            if (metadata.bKGD_present) {
                if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) {
                    backgroundIndex = metadata.bKGD_index;
                    metadata.bKGD_colorType = PNG_COLOR_PALETTE;
                } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY ||
                        metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
                    int gray = metadata.bKGD_gray & 0xff;
                    backgroundColor = new Integer((gray << 16) | (gray << 8) | gray |
                                ((255 & 0xff) << 24));
                } else { // RGB or RGB_ALPHA
                    backgroundColor = new Integer(((metadata.bKGD_red & 0xff) << 16) |
                                ((metadata.bKGD_green & 0xff) << 8) |
                                (metadata.bKGD_blue & 0xff) |
                                ((255 & 0xff) << 24));
                }
            }

            ImageMetadata theMetadata = new ImageMetadata(gamma, true,
                    backgroundIndex, backgroundColor, null, null,
                    metadata.IHDR_width, metadata.IHDR_height,
                    null, null, null);
            updateImageMetadata(theMetadata);

            if (rowProc == ROW_PROCESS.COPY) {
                int numSourceBands = ImageStorage.getNumBands(sourceType);
                int bytesPerRow = width * numSourceBands;
                theImage = new ImageFrame(sourceType,
                        ByteBuffer.wrap(new byte[height * bytesPerRow]),
                        width, height, bytesPerRow, thePalette, theMetadata);
            } else {
                int bytesPerRow = destWidth * numDestBands;
                theImage = new ImageFrame(destType,
                        ByteBuffer.wrap(new byte[destHeight * bytesPerRow]),
                        destWidth, destHeight, bytesPerRow, thePalette, theMetadata);
            }
//            theImage = ImageTools.createPrismImage(sourceType, width, height);

//            Rectangle destRegion = new Rectangle(0, 0, 0, 0);
//            sourceRegion = new Rectangle(0, 0, 0, 0);
//            computeRegions(param, width, height,
//                    theBufferedImage,
//                    sourceRegion, destRegion);
//            destinationOffset.setLocation(destRegion.getLocation());

            // At this point the header has been read and we know
            // how many bands are in the image, so perform checking
            // of the read param.
//            int colorType = metadata.IHDR_colorType;
//            checkReadParamBandSettings(param,
//                    inputBandsForColorType[colorType],
//                    theBufferedImage.getSampleModel().getNumBands());
//
//            processImageStarted(0);
            decodeImage(destWidth, destHeight, smooth);
//            if (abortRequested()) {
//                processReadAborted();
//            } else {
//                processImageComplete();
//            }
        } catch (IOException e) {
            IOException ex = new IOException("Error reading PNG image data");
            ex.initCause(e);
            throw ex;
        } finally {
            if (inf != null) {
                inf.end();
            }
        }
    }
//
//    public int getNumImages(boolean allowSearch) throws IIOException {
//        if (stream == null) {
//            throw new IllegalStateException("No input source set!");
//        }
//        if (seekForwardOnly && allowSearch) {
//            throw new IllegalStateException
//                ("seekForwardOnly and allowSearch can't both be true!");
//        }
//        return 1;
//    }
//
//    public int getWidth(int imageIndex) throws IIOException {
//        if (imageIndex != 0) {
//            throw new IndexOutOfBoundsException("imageIndex != 0!");
//        }
//
//        readHeader();
//
//        return metadata.IHDR_width;
//    }
//
//    public int getHeight(int imageIndex) throws IIOException {
//        if (imageIndex != 0) {
//            throw new IndexOutOfBoundsException("imageIndex != 0!");
//        }
//
//        readHeader();
//
//        return metadata.IHDR_height;
//    }
//
//    public Iterator getImageTypes(int imageIndex)
//            throws IOException // IIOException
//    {
//        if (imageIndex != 0) {
//            throw new IndexOutOfBoundsException("imageIndex != 0!");
//        }
//
//        readHeader();
//
//        ArrayList l =
//                new ArrayList(1);
//
//        ColorSpace rgb;
//        ColorSpace gray;
//        int[] bandOffsets;
//
//        int bitDepth = metadata.IHDR_bitDepth;
//        int colorType = metadata.IHDR_colorType;
//
//        int dataType;
//        if (bitDepth <= 8) {
//            dataType = DataBuffer.TYPE_BYTE;
//        } else {
//            dataType = DataBuffer.TYPE_USHORT;
//        }
//
//        switch (colorType) {
//            case PNG_COLOR_GRAY:
//                // Packed grayscale
//                l.add(ImageTypeSpecifier.createGrayscale(bitDepth,
//                        dataType,
//                        false));
//                break;
//
//            case PNG_COLOR_RGB:
//                if (bitDepth == 8) {
//                    // some standard types of buffered images
//                    // which can be used as destination
//                    l.add(ImageTypeSpecifier.createFromBufferedImageType(
//                            BufferedImage.TYPE_3BYTE_BGR));
//
//                    l.add(ImageTypeSpecifier.createFromBufferedImageType(
//                            BufferedImage.TYPE_INT_RGB));
//
//                    l.add(ImageTypeSpecifier.createFromBufferedImageType(
//                            BufferedImage.TYPE_INT_BGR));
//
//                }
//                // Component R, G, B
//                rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
//                bandOffsets = new int[3];
//                bandOffsets[0] = 0;
//                bandOffsets[1] = 1;
//                bandOffsets[2] = 2;
//                l.add(ImageTypeSpecifier.createInterleaved(rgb,
//                        bandOffsets,
//                        dataType,
//                        false,
//                        false));
//                break;
//
//            case PNG_COLOR_PALETTE:
//                readMetadata(); // Need tRNS chunk
//
//                /*
//                 * The PLTE chunk spec says:
//                 *
//                 * The number of palette entries must not exceed the range that
//                 * can be represented in the image bit depth (for example, 2^4 = 16
//                 * for a bit depth of 4). It is permissible to have fewer entries
//                 * than the bit depth would allow. In that case, any out-of-range
//                 * pixel value found in the image data is an error.
//                 *
//                 * http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.PLTE
//                 *
//                 * Consequently, the case when the palette length is smaller than
//                 * 2^bitDepth is legal in the view of PNG spec.
//                 *
//                 * However the spec of createIndexed() method demands the exact
//                 * equality of the palette lengh and number of possible palette
//                 * entries (2^bitDepth).
//                 *
//                 * {@link javax.imageio.ImageTypeSpecifier.html#createIndexed}
//                 *
//                 * In order to avoid this contradiction we need to extend the
//                 * palette arrays to the limit defined by the bitDepth.
//                 */
//
//                int plength = 1 << bitDepth;
//
//                byte[] red = metadata.PLTE_red;
//                byte[] green = metadata.PLTE_green;
//                byte[] blue = metadata.PLTE_blue;
//
//                if (metadata.PLTE_red.length < plength) {
//                    red = new byte[plength];
//                    System.arraycopy(metadata.PLTE_red, 0, red, 0, plength);
//                    Arrays.fill(red, metadata.PLTE_red.length, plength,
//                            metadata.PLTE_red[metadata.PLTE_red.length - 1]);
//
//                    green = new byte[plength];
//                    System.arraycopy(metadata.PLTE_green, 0, green, 0, plength);
//                    Arrays.fill(green, metadata.PLTE_green.length, plength,
//                            metadata.PLTE_green[metadata.PLTE_green.length - 1]);
//
//                    blue = new byte[plength];
//                    System.arraycopy(metadata.PLTE_blue, 0, blue, 0, plength);
//                    Arrays.fill(blue, metadata.PLTE_blue.length, plength,
//                            metadata.PLTE_blue[metadata.PLTE_blue.length - 1]);
//
//                }
//
//                // Alpha from tRNS chunk may have fewer entries than
//                // the RGB LUTs from the PLTE chunk; if so, pad with
//                // 255.
//                byte[] alpha = null;
//                if (metadata.tRNS_present && (metadata.tRNS_alpha != null)) {
//                    if (metadata.tRNS_alpha.length == red.length) {
//                        alpha = metadata.tRNS_alpha;
//                    } else {
//                        alpha = new byte[red.length];
//                        System.arraycopy(metadata.tRNS_alpha, 0, alpha, 0, red.length);
//                        Arrays.fill(alpha,
//                                metadata.tRNS_alpha.length,
//                                red.length, (byte) 255);
//                    }
//                }
//
//                l.add(ImageTypeSpecifier.createIndexed(red, green,
//                        blue, alpha,
//                        bitDepth,
//                        DataBuffer.TYPE_BYTE));
//                break;
//
//            case PNG_COLOR_GRAY_ALPHA:
//                // Component G, A
//                gray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
//                bandOffsets = new int[2];
//                bandOffsets[0] = 0;
//                bandOffsets[1] = 1;
//                l.add(ImageTypeSpecifier.createInterleaved(gray,
//                        bandOffsets,
//                        dataType,
//                        true,
//                        false));
//                break;
//
//            case PNG_COLOR_RGB_ALPHA:
//                if (bitDepth == 8) {
//                    // some standard types of buffered images
//                    // wich can be used as destination
//                    l.add(ImageTypeSpecifier.createFromBufferedImageType(
//                            BufferedImage.TYPE_4BYTE_ABGR));
//
//                    l.add(ImageTypeSpecifier.createFromBufferedImageType(
//                            BufferedImage.TYPE_INT_ARGB));
//                }
//
//                // Component R, G, B, A (non-premultiplied)
//                rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
//                bandOffsets = new int[4];
//                bandOffsets[0] = 0;
//                bandOffsets[1] = 1;
//                bandOffsets[2] = 2;
//                bandOffsets[3] = 3;
//
//                l.add(ImageTypeSpecifier.createInterleaved(rgb,
//                        bandOffsets,
//                        dataType,
//                        true,
//                        false));
//                break;
//
//            default:
//                break;
//        }
//
//        return l.iterator();
//    }
//
//    /*
//     * Super class implementation uses first element
//     * of image types list as raw image type.
//     *
//     * Also, super implementation uses first element of this list
//     * as default destination type image read param does not specify
//     * anything other.
//     *
//     * However, in case of RGB and RGBA color types, raw image type
//     * produces buffered image of custom type. It causes some
//     * performance degradation of subsequent rendering operations.
//     *
//     * To resolve this contradiction we put standard image types
//     * at the first positions of image types list (to produce standard
//     * images by default) and put raw image type (which is custom)
//     * at the last position of this list.
//     *
//     * After this changes we should override getRawImageType()
//     * to return last element of image types list.
//     */
//    public ImageTypeSpecifier getRawImageType(int imageIndex)
//      throws IOException {
//
//        Iterator types = getImageTypes(imageIndex);
//        ImageTypeSpecifier raw = null;
//        do {
//            raw = types.next();
//        } while (types.hasNext());
//        return raw;
//    }
//
//    public ImageReadParam getDefaultReadParam() {
//        return new ImageReadParam();
//    }
//
//    public IIOMetadata getStreamMetadata()
//        throws IIOException {
//        return null;
//    }
//
//    public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
//        if (imageIndex != 0) {
//            throw new IndexOutOfBoundsException("imageIndex != 0!");
//        }
//        readMetadata();
//        return metadata;
//    }
//
//    public BufferedImage read(int imageIndex, ImageReadParam param)
//        throws IIOException {
//        if (imageIndex != 0) {
//            throw new IndexOutOfBoundsException("imageIndex != 0!");
//        }
//
//        readImage(param);
//        return theBufferedImage;
//    }
//
//    public void reset() {
//        super.reset();
//        resetStreamSettings();
//    }
//
//    private void resetStreamSettings() {
//        gotHeader = false;
//        gotMetadata = false;
//        metadata = null;
//        pixelStream = null;
//    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy