com.sun.javafx.iio.png.PNGImageLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openjfx-78-backport Show documentation
Show all versions of openjfx-78-backport Show documentation
This is a backport of OpenJFX 8 to run on Java 7.
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;
// }
}