Mercurial > hg > release > icedtea6-1.7
view patches/openjdk/6782079-png_metadata_oom.patch @ 2021:3eec8b65af65
Backport S6687968, S6541476, S6782079
iTXt chunk handling for png now works correctly.
author | Deepak Bhole <dbhole@redhat.com> |
---|---|
date | Tue, 04 Jan 2011 08:50:49 -0500 |
parents | |
children |
line wrap: on
line source
diff -urN openjdk.orig/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java --- openjdk.orig/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java 2010-12-24 11:22:28.802101947 -0500 +++ openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java 2010-12-24 11:24:47.272101847 -0500 @@ -37,6 +37,7 @@ import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.DataInputStream; +import java.io.EOFException; import java.io.InputStream; import java.io.IOException; import java.io.SequenceInputStream; @@ -59,7 +60,7 @@ import java.io.ByteArrayOutputStream; import sun.awt.image.ByteInterleavedRaster; -class PNGImageDataEnumeration implements Enumeration { +class PNGImageDataEnumeration implements Enumeration<InputStream> { boolean firstTime = true; ImageInputStream stream; @@ -72,7 +73,7 @@ int type = stream.readInt(); // skip chunk type } - public Object nextElement() { + public InputStream nextElement() { try { firstTime = false; ImageInputStream iis = new SubImageInputStream(stream, length); @@ -209,25 +210,17 @@ resetStreamSettings(); } - private String readNullTerminatedString(String charset) throws IOException { + private String readNullTerminatedString(String charset, int maxLen) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int b; - while ((b = stream.read()) != 0) { + 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 String readNullTerminatedString() throws IOException { - StringBuilder b = new StringBuilder(); - int c; - - while ((c = stream.read()) != 0) { - b.append((char)c); - } - return b.toString(); - } - private void readHeader() throws IIOException { if (gotHeader) { return; @@ -436,7 +429,7 @@ } private void parse_iCCP_chunk(int chunkLength) throws IOException { - String keyword = readNullTerminatedString(); + String keyword = readNullTerminatedString("ISO-8859-1", 80); metadata.iCCP_profileName = keyword; metadata.iCCP_compressionMethod = stream.readUnsignedByte(); @@ -452,7 +445,7 @@ private void parse_iTXt_chunk(int chunkLength) throws IOException { long chunkStart = stream.getStreamPosition(); - String keyword = readNullTerminatedString(); + String keyword = readNullTerminatedString("ISO-8859-1", 80); metadata.iTXt_keyword.add(keyword); int compressionFlag = stream.readUnsignedByte(); @@ -461,15 +454,17 @@ int compressionMethod = stream.readUnsignedByte(); metadata.iTXt_compressionMethod.add(Integer.valueOf(compressionMethod)); - String languageTag = readNullTerminatedString("UTF8"); + String languageTag = readNullTerminatedString("UTF8", 80); metadata.iTXt_languageTag.add(languageTag); + long pos = stream.getStreamPosition(); + int maxLen = (int)(chunkStart + chunkLength - pos); String translatedKeyword = - readNullTerminatedString("UTF8"); + readNullTerminatedString("UTF8", maxLen); metadata.iTXt_translatedKeyword.add(translatedKeyword); String text; - long pos = stream.getStreamPosition(); + pos = stream.getStreamPosition(); byte[] b = new byte[(int)(chunkStart + chunkLength - pos)]; stream.readFully(b); @@ -513,7 +508,7 @@ private void parse_sPLT_chunk(int chunkLength) throws IOException, IIOException { - metadata.sPLT_paletteName = readNullTerminatedString(); + metadata.sPLT_paletteName = readNullTerminatedString("ISO-8859-1", 80); chunkLength -= metadata.sPLT_paletteName.length() + 1; int sampleDepth = stream.readUnsignedByte(); @@ -556,12 +551,12 @@ } private void parse_tEXt_chunk(int chunkLength) throws IOException { - String keyword = readNullTerminatedString(); + 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)); + metadata.tEXt_text.add(new String(b, "ISO-8859-1")); } private void parse_tIME_chunk() throws IOException { @@ -642,7 +637,7 @@ } private void parse_zTXt_chunk(int chunkLength) throws IOException { - String keyword = readNullTerminatedString(); + String keyword = readNullTerminatedString("ISO-8859-1", 80); metadata.zTXt_keyword.add(keyword); int method = stream.readUnsignedByte(); @@ -650,7 +645,7 @@ byte[] b = new byte[chunkLength - keyword.length() - 2]; stream.readFully(b); - metadata.zTXt_text.add(new String(inflate(b))); + metadata.zTXt_text.add(new String(inflate(b), "ISO-8859-1")); } private void readMetadata() throws IIOException { @@ -1265,7 +1260,7 @@ try { stream.seek(imageStartPosition); - Enumeration e = new PNGImageDataEnumeration(stream); + Enumeration<InputStream> e = new PNGImageDataEnumeration(stream); InputStream is = new SequenceInputStream(e); /* InflaterInputStream uses an Inflater instance which consumes diff -urN openjdk.orig/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java.orig openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java.orig --- openjdk.orig/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java.orig 1969-12-31 19:00:00.000000000 -0500 +++ openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java.orig 2010-12-24 11:22:50.400102016 -0500 @@ -0,0 +1,1599 @@ +/* + * Copyright (c) 2000, 2006, 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.imageio.plugins.png; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferUShort; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.SequenceInputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; +import javax.imageio.IIOException; +import javax.imageio.ImageReader; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import com.sun.imageio.plugins.common.InputStreamAdapter; +import com.sun.imageio.plugins.common.ReaderUtil; +import com.sun.imageio.plugins.common.SubImageInputStream; +import java.io.ByteArrayOutputStream; +import sun.awt.image.ByteInterleavedRaster; + +class PNGImageDataEnumeration implements Enumeration { + + boolean firstTime = true; + ImageInputStream stream; + int length; + + public PNGImageDataEnumeration(ImageInputStream stream) + throws IOException { + this.stream = stream; + this.length = stream.readInt(); + int type = stream.readInt(); // skip chunk type + } + + public Object nextElement() { + try { + firstTime = false; + ImageInputStream iis = new SubImageInputStream(stream, length); + return new InputStreamAdapter(iis); + } catch (IOException e) { + return null; + } + } + + public boolean hasMoreElements() { + if (firstTime) { + return true; + } + + try { + int crc = stream.readInt(); + this.length = stream.readInt(); + int type = stream.readInt(); + if (type == PNGImageReader.IDAT_TYPE) { + return true; + } else { + return false; + } + } catch (IOException e) { + return false; + } + } +} + +/** + */ +public class PNGImageReader extends ImageReader { + + /* + * 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 }; + + private static final boolean debug = true; + + ImageInputStream stream = null; + + boolean gotHeader = false; + boolean gotMetadata = false; + + ImageReadParam lastParam = null; + + long imageStartPosition = -1L; + + Rectangle sourceRegion = null; + int sourceXSubsampling = -1; + int sourceYSubsampling = -1; + int sourceMinProgressivePass = 0; + int sourceMaxProgressivePass = 6; + int[] sourceBands = null; + int[] destinationBands = null; + Point destinationOffset = new Point(0, 0); + + PNGMetadata metadata = new PNGMetadata(); + + DataInputStream pixelStream = null; + + BufferedImage theImage = null; + + // The number of source pixels processed + int pixelsDone = 0; + + // The total number of pixels in the source image + int totalPixels; + + public PNGImageReader(ImageReaderSpi originatingProvider) { + super(originatingProvider); + } + + public void setInput(Object input, + boolean seekForwardOnly, + boolean ignoreMetadata) { + super.setInput(input, seekForwardOnly, ignoreMetadata); + this.stream = (ImageInputStream)input; // Always works + + // Clear all values based on the previous stream contents + resetStreamSettings(); + } + + private String readNullTerminatedString(String charset) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int b; + while ((b = stream.read()) != 0) { + baos.write(b); + } + return new String(baos.toByteArray(), charset); + } + + private String readNullTerminatedString() throws IOException { + StringBuilder b = new StringBuilder(); + int c; + + while ((c = stream.read()) != 0) { + b.append((char)c); + } + return b.toString(); + } + + private void readHeader() throws IIOException { + if (gotHeader) { + return; + } + if (stream == null) { + throw new IllegalStateException("Input source not set!"); + } + + try { + byte[] signature = new byte[8]; + stream.readFully(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 IIOException("Bad PNG signature!"); + } + + int IHDR_length = stream.readInt(); + if (IHDR_length != 13) { + throw new IIOException("Bad length for IHDR chunk!"); + } + int IHDR_type = stream.readInt(); + if (IHDR_type != IHDR_TYPE) { + throw new IIOException("Bad type for IHDR chunk!"); + } + + this.metadata = new PNGMetadata(); + + int width = stream.readInt(); + int height = stream.readInt(); + + // Re-use signature array to bulk-read these unsigned byte values + stream.readFully(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.skipBytes(4); + + stream.flushBefore(stream.getStreamPosition()); + + if (width == 0) { + throw new IIOException("Image width == 0!"); + } + if (height == 0) { + throw new IIOException("Image height == 0!"); + } + if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && + bitDepth != 8 && bitDepth != 16) { + throw new IIOException("Bit depth must be 1, 2, 4, 8, or 16!"); + } + if (colorType != 0 && colorType != 2 && colorType != 3 && + colorType != 4 && colorType != 6) { + throw new IIOException("Color type must be 0, 2, 3, 4, or 6!"); + } + if (colorType == PNG_COLOR_PALETTE && bitDepth == 16) { + throw new IIOException("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 IIOException("Bad color type/bit depth combination!"); + } + if (compressionMethod != 0) { + throw new IIOException("Unknown compression method (not 0)!"); + } + if (filterMethod != 0) { + throw new IIOException("Unknown filter method (not 0)!"); + } + if (interlaceMethod != 0 && interlaceMethod != 1) { + throw new IIOException("Unknown interlace method (not 0 or 1)!"); + } + + 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; + gotHeader = true; + } catch (IOException e) { + throw new IIOException("I/O error reading PNG header!", e); + } + } + + private void parse_PLTE_chunk(int chunkLength) throws IOException { + if (metadata.PLTE_present) { + processWarningOccurred( +"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) { + processWarningOccurred( +"A PNG gray or gray alpha image cannot have a PLTE chunk.\n" + +"The chunk wil be ignored."); + return; + } + + byte[] palette = new byte[chunkLength]; + stream.readFully(palette); + + int numEntries = chunkLength/3; + if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) { + int maxEntries = 1 << metadata.IHDR_bitDepth; + if (numEntries > maxEntries) { + processWarningOccurred( +"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, + IIOException + { + 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(); + 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(); + metadata.iTXt_keyword.add(keyword); + + int compressionFlag = stream.readUnsignedByte(); + metadata.iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag == 1)); + + int compressionMethod = stream.readUnsignedByte(); + metadata.iTXt_compressionMethod.add(Integer.valueOf(compressionMethod)); + + String languageTag = readNullTerminatedString("UTF8"); + metadata.iTXt_languageTag.add(languageTag); + + String translatedKeyword = + readNullTerminatedString("UTF8"); + metadata.iTXt_translatedKeyword.add(translatedKeyword); + + String text; + long 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, IIOException { + metadata.sPLT_paletteName = readNullTerminatedString(); + 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(); + metadata.tEXt_keyword.add(keyword); + + byte[] b = new byte[chunkLength - keyword.length() - 1]; + stream.readFully(b); + metadata.tEXt_text.add(new String(b)); + } + + 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) { + processWarningOccurred( +"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) { + processWarningOccurred( +"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; + stream.read(metadata.tRNS_alpha, 0, numEntries); + stream.skipBytes(chunkLength - numEntries); + } else if (colorType == PNG_COLOR_GRAY) { + if (chunkLength != 2) { + processWarningOccurred( +"tRNS chunk for gray image must have length 2, ignoring chunk."); + stream.skipBytes(chunkLength); + return; + } + metadata.tRNS_gray = stream.readUnsignedShort(); + metadata.tRNS_colorType = PNG_COLOR_GRAY; + } else if (colorType == PNG_COLOR_RGB) { + if (chunkLength != 6) { + processWarningOccurred( +"tRNS chunk for RGB image must have length 6, ignoring chunk."); + stream.skipBytes(chunkLength); + return; + } + metadata.tRNS_red = stream.readUnsignedShort(); + metadata.tRNS_green = stream.readUnsignedShort(); + metadata.tRNS_blue = stream.readUnsignedShort(); + metadata.tRNS_colorType = PNG_COLOR_RGB; + } else { + processWarningOccurred( +"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(); + 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))); + } + + private void readMetadata() throws IIOException { + 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) { + try { + while (true) { + int chunkLength = stream.readInt(); + int chunkType = stream.readInt(); + + if (chunkType == IDAT_TYPE) { + // 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.skipBytes(chunkLength + 4); + } + } + } catch (IOException e) { + throw new IIOException("Error skipping PNG metadata", e); + } + + gotMetadata = true; + return; + } + + try { + loop: while (true) { + int chunkLength = stream.readInt(); + int chunkType = stream.readInt(); + + switch (chunkType) { + case IDAT_TYPE: + // 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.readFully(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) { + processWarningOccurred( +"Encountered unknown chunk with critical bit set!"); + } + + metadata.unknownChunkType.add(chunkName.toString()); + metadata.unknownChunkData.add(b); + break; + } + + int chunkCRC = stream.readInt(); + stream.flushBefore(stream.getStreamPosition()); + } + } catch (IOException e) { + throw new IIOException("Error reading PNG metadata", e); + } + + 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, IIOException { + 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; + processImageProgress(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 = theImage.getWritableTile(0, 0); + int dstMinX = imRas.getMinX(); + int dstMaxX = dstMinX + imRas.getWidth() - 1; + int dstMinY = imRas.getMinY(); + int dstMaxY = dstMinY + 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 bitDepth = metadata.IHDR_bitDepth; + int inputBands = inputBandsForColorType[metadata.IHDR_colorType]; + int bytesPerPixel = (bitDepth == 16) ? 2 : 1; + bytesPerPixel *= inputBands; + + 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 - destinationOffset.x)*sourceXSubsampling + + sourceRegion.x; + int srcX = (sourceX - xStart)/xStep; + + // Compute the step factor in the source + int srcXStep = updateXStep*sourceXSubsampling/xStep; + + byte[] byteData = null; + short[] shortData = null; + byte[] curr = new byte[bytesPerRow]; + byte[] prior = new byte[bytesPerRow]; + + // Create a 1-row tall Raster to hold the data + WritableRaster passRow = createRaster(passWidth, 1, inputBands, + eltsPerRow, + bitDepth); + + // 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(); + } + + processPassStarted(theImage, + 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 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); + } + + // 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 (java.util.zip.ZipException ze) { + // TODO - throw a more meaningful exception + throw ze; + } + + 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 IIOException("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 >= sourceRegion.y) && + (sourceY < sourceRegion.y + sourceRegion.height) && + (((sourceY - sourceRegion.y) % + sourceYSubsampling) == 0)) { + + int dstY = destinationOffset.y + + (sourceY - sourceRegion.y)/sourceYSubsampling; + if (dstY < dstMinY) { + continue; + } + if (dstY > dstMaxY) { + break; + } + + if (useSetRect) { + imRas.setRect(updateMinX, dstY, passRow); + } else { + int newSrcX = srcX; + + for (int dstX = updateMinX; + dstX < updateMinX + updateWidth; + dstX += updateXStep) { + + passRow.getPixel(newSrcX, 0, ps); + if (adjustBitDepths) { + for (int b = 0; b < numBands; b++) { + ps[b] = scale[b][ps[b]]; + } + } + imRas.setPixel(dstX, dstY, ps); + newSrcX += srcXStep; + } + } + + processImageUpdate(theImage, + updateMinX, dstY, + updateWidth, 1, + updateXStep, updateYStep, + destinationBands); + + // If read has been aborted, just return + // processReadAborted will be called later + if (abortRequested()) { + return; + } + } + } + + processPassComplete(theImage); + } + + private void decodeImage() + throws IOException, IIOException { + int width = metadata.IHDR_width; + int height = metadata.IHDR_height; + + this.pixelsDone = 0; + this.totalPixels = width*height; + + clearAbortRequest(); + + 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; + } + } + } + } + + private void readImage(ImageReadParam param) throws IIOException { + readMetadata(); + + int width = metadata.IHDR_width; + int height = metadata.IHDR_height; + + // 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 SequenceInputStream(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(is, inf); + is = new BufferedInputStream(is); + this.pixelStream = new DataInputStream(is); + + theImage = getDestination(param, + getImageTypes(0), + width, + height); + + Rectangle destRegion = new Rectangle(0, 0, 0, 0); + sourceRegion = new Rectangle(0, 0, 0, 0); + computeRegions(param, width, height, + theImage, + 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], + theImage.getSampleModel().getNumBands()); + + processImageStarted(0); + decodeImage(); + if (abortRequested()) { + processReadAborted(); + } else { + processImageComplete(); + } + } catch (IOException e) { + throw new IIOException("Error reading PNG image data", e); + } 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<ImageTypeSpecifier> getImageTypes(int imageIndex) + throws IIOException + { + if (imageIndex != 0) { + throw new IndexOutOfBoundsException("imageIndex != 0!"); + } + + readHeader(); + + ArrayList<ImageTypeSpecifier> l = + new ArrayList<ImageTypeSpecifier>(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 = Arrays.copyOf(metadata.PLTE_red, plength); + Arrays.fill(red, metadata.PLTE_red.length, plength, + metadata.PLTE_red[metadata.PLTE_red.length - 1]); + + green = Arrays.copyOf(metadata.PLTE_green, plength); + Arrays.fill(green, metadata.PLTE_green.length, plength, + metadata.PLTE_green[metadata.PLTE_green.length - 1]); + + blue = Arrays.copyOf(metadata.PLTE_blue, 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 = Arrays.copyOf(metadata.tRNS_alpha, 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<ImageTypeSpecifier> 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 theImage; + } + + public void reset() { + super.reset(); + resetStreamSettings(); + } + + private void resetStreamSettings() { + gotHeader = false; + gotMetadata = false; + metadata = null; + pixelStream = null; + } +} diff -urN openjdk.orig/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java --- openjdk.orig/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java 2010-12-24 11:22:28.803101963 -0500 +++ openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java 2010-12-24 11:24:47.273101870 -0500 @@ -674,13 +674,8 @@ private byte[] deflate(byte[] b) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DeflaterOutputStream dos = new DeflaterOutputStream(baos); - - int len = b.length; - for (int i = 0; i < len; i++) { - dos.write((int)(0xff & b[i])); - } + dos.write(b); dos.close(); - return baos.toByteArray(); } @@ -736,7 +731,7 @@ cs.writeByte(compressionMethod); String text = (String)textIter.next(); - cs.write(deflate(text.getBytes())); + cs.write(deflate(text.getBytes("ISO-8859-1"))); cs.finish(); } } diff -urN openjdk.orig/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java --- openjdk.orig/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java 2010-12-24 11:22:28.803101963 -0500 +++ openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java 2010-12-24 11:24:47.274101892 -0500 @@ -213,8 +213,8 @@ public int sRGB_renderingIntent; // tEXt chunk - public ArrayList tEXt_keyword = new ArrayList(); // 1-79 char Strings - public ArrayList tEXt_text = new ArrayList(); // Strings + public ArrayList<String> tEXt_keyword = new ArrayList<String>(); // 1-79 characters + public ArrayList<String> tEXt_text = new ArrayList<String>(); // tIME chunk public boolean tIME_present; @@ -237,13 +237,13 @@ public int tRNS_blue; // zTXt chunk - public ArrayList zTXt_keyword = new ArrayList(); // Strings - public ArrayList zTXt_compressionMethod = new ArrayList(); // Integers - public ArrayList zTXt_text = new ArrayList(); // Strings + public ArrayList<String> zTXt_keyword = new ArrayList<String>(); + public ArrayList<Integer> zTXt_compressionMethod = new ArrayList<Integer>(); + public ArrayList<String> zTXt_text = new ArrayList<String>(); // Unknown chunks - public ArrayList unknownChunkType = new ArrayList(); // Strings - public ArrayList unknownChunkData = new ArrayList(); // byte arrays + public ArrayList<String> unknownChunkType = new ArrayList<String>(); + public ArrayList<byte[]> unknownChunkData = new ArrayList<byte[]>(); public PNGMetadata() { super(true, @@ -428,21 +428,14 @@ return false; } - private ArrayList cloneBytesArrayList(ArrayList in) { + private ArrayList<byte[]> cloneBytesArrayList(ArrayList<byte[]> in) { if (in == null) { return null; } else { - ArrayList list = new ArrayList(in.size()); - Iterator iter = in.iterator(); - while (iter.hasNext()) { - Object o = iter.next(); - if (o == null) { - list.add(null); - } else { - list.add(((byte[])o).clone()); - } + ArrayList<byte[]> list = new ArrayList<byte[]>(in.size()); + for (byte[] b: in) { + list.add((b == null) ? null : (byte[])b.clone()); } - return list; } } @@ -2000,14 +1993,14 @@ sBIT_present = false; sPLT_present = false; sRGB_present = false; - tEXt_keyword = new ArrayList(); - tEXt_text = new ArrayList(); + tEXt_keyword = new ArrayList<String>(); + tEXt_text = new ArrayList<String>(); tIME_present = false; tRNS_present = false; - zTXt_keyword = new ArrayList(); - zTXt_compressionMethod = new ArrayList(); - zTXt_text = new ArrayList(); - unknownChunkType = new ArrayList(); - unknownChunkData = new ArrayList(); + zTXt_keyword = new ArrayList<String>(); + zTXt_compressionMethod = new ArrayList<Integer>(); + zTXt_text = new ArrayList<String>(); + unknownChunkType = new ArrayList<String>(); + unknownChunkData = new ArrayList<byte[]>(); } } diff -urN openjdk.orig/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java.orig openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java.orig --- openjdk.orig/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java.orig 1969-12-31 19:00:00.000000000 -0500 +++ openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java.orig 2010-12-24 11:22:50.401102014 -0500 @@ -0,0 +1,2013 @@ +/* + * Copyright (c) 2000, 2001, 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.imageio.plugins.png; + +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.SampleModel; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.StringTokenizer; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; +import org.w3c.dom.Node; + +/** + */ +public class PNGMetadata extends IIOMetadata implements Cloneable { + + // package scope + public static final String + nativeMetadataFormatName = "javax_imageio_png_1.0"; + + protected static final String nativeMetadataFormatClassName + = "com.sun.imageio.plugins.png.PNGMetadataFormat"; + + // Color types for IHDR chunk + public static final String[] IHDR_colorTypeNames = { + "Grayscale", null, "RGB", "Palette", + "GrayAlpha", null, "RGBAlpha" + }; + + public static final int[] IHDR_numChannels = { + 1, 0, 3, 3, 2, 0, 4 + }; + + // Bit depths for IHDR chunk + public static final String[] IHDR_bitDepths = { + "1", "2", "4", "8", "16" + }; + + // Compression methods for IHDR chunk + public static final String[] IHDR_compressionMethodNames = { + "deflate" + }; + + // Filter methods for IHDR chunk + public static final String[] IHDR_filterMethodNames = { + "adaptive" + }; + + // Interlace methods for IHDR chunk + public static final String[] IHDR_interlaceMethodNames = { + "none", "adam7" + }; + + // Compression methods for iCCP chunk + public static final String[] iCCP_compressionMethodNames = { + "deflate" + }; + + // Compression methods for zTXt chunk + public static final String[] zTXt_compressionMethodNames = { + "deflate" + }; + + // "Unknown" unit for pHYs chunk + public static final int PHYS_UNIT_UNKNOWN = 0; + + // "Meter" unit for pHYs chunk + public static final int PHYS_UNIT_METER = 1; + + // Unit specifiers for pHYs chunk + public static final String[] unitSpecifierNames = { + "unknown", "meter" + }; + + // Rendering intents for sRGB chunk + public static final String[] renderingIntentNames = { + "Perceptual", // 0 + "Relative colorimetric", // 1 + "Saturation", // 2 + "Absolute colorimetric" // 3 + + }; + + // Color space types for Chroma->ColorSpaceType node + public static final String[] colorSpaceTypeNames = { + "GRAY", null, "RGB", "RGB", + "GRAY", null, "RGB" + }; + + // IHDR chunk + public boolean IHDR_present; + public int IHDR_width; + public int IHDR_height; + public int IHDR_bitDepth; + public int IHDR_colorType; + public int IHDR_compressionMethod; + public int IHDR_filterMethod; + public int IHDR_interlaceMethod; // 0 == none, 1 == adam7 + + // PLTE chunk + public boolean PLTE_present; + public byte[] PLTE_red; + public byte[] PLTE_green; + public byte[] PLTE_blue; + + // If non-null, used to reorder palette entries during encoding in + // order to minimize the size of the tRNS chunk. Thus an index of + // 'i' in the source should be encoded as index 'PLTE_order[i]'. + // PLTE_order will be null unless 'initialize' is called with an + // IndexColorModel image type. + public int[] PLTE_order = null; + + // bKGD chunk + // If external (non-PNG sourced) data has red = green = blue, + // always store it as gray and promote when writing + public boolean bKGD_present; + public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE + public int bKGD_index; + public int bKGD_gray; + public int bKGD_red; + public int bKGD_green; + public int bKGD_blue; + + // cHRM chunk + public boolean cHRM_present; + public int cHRM_whitePointX; + public int cHRM_whitePointY; + public int cHRM_redX; + public int cHRM_redY; + public int cHRM_greenX; + public int cHRM_greenY; + public int cHRM_blueX; + public int cHRM_blueY; + + // gAMA chunk + public boolean gAMA_present; + public int gAMA_gamma; + + // hIST chunk + public boolean hIST_present; + public char[] hIST_histogram; + + // iCCP chunk + public boolean iCCP_present; + public String iCCP_profileName; + public int iCCP_compressionMethod; + public byte[] iCCP_compressedProfile; + + // iTXt chunk + public ArrayList<String> iTXt_keyword = new ArrayList<String>(); + public ArrayList<Boolean> iTXt_compressionFlag = new ArrayList<Boolean>(); + public ArrayList<Integer> iTXt_compressionMethod = new ArrayList<Integer>(); + public ArrayList<String> iTXt_languageTag = new ArrayList<String>(); + public ArrayList<String> iTXt_translatedKeyword = new ArrayList<String>(); + public ArrayList<String> iTXt_text = new ArrayList<String>(); + + // pHYs chunk + public boolean pHYs_present; + public int pHYs_pixelsPerUnitXAxis; + public int pHYs_pixelsPerUnitYAxis; + public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter + + // sBIT chunk + public boolean sBIT_present; + public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA + public int sBIT_grayBits; + public int sBIT_redBits; + public int sBIT_greenBits; + public int sBIT_blueBits; + public int sBIT_alphaBits; + + // sPLT chunk + public boolean sPLT_present; + public String sPLT_paletteName; // 1-79 characters + public int sPLT_sampleDepth; // 8 or 16 + public int[] sPLT_red; + public int[] sPLT_green; + public int[] sPLT_blue; + public int[] sPLT_alpha; + public int[] sPLT_frequency; + + // sRGB chunk + public boolean sRGB_present; + public int sRGB_renderingIntent; + + // tEXt chunk + public ArrayList tEXt_keyword = new ArrayList(); // 1-79 char Strings + public ArrayList tEXt_text = new ArrayList(); // Strings + + // tIME chunk + public boolean tIME_present; + public int tIME_year; + public int tIME_month; + public int tIME_day; + public int tIME_hour; + public int tIME_minute; + public int tIME_second; + + // tRNS chunk + // If external (non-PNG sourced) data has red = green = blue, + // always store it as gray and promote when writing + public boolean tRNS_present; + public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE + public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc. + public int tRNS_gray; + public int tRNS_red; + public int tRNS_green; + public int tRNS_blue; + + // zTXt chunk + public ArrayList zTXt_keyword = new ArrayList(); // Strings + public ArrayList zTXt_compressionMethod = new ArrayList(); // Integers + public ArrayList zTXt_text = new ArrayList(); // Strings + + // Unknown chunks + public ArrayList unknownChunkType = new ArrayList(); // Strings + public ArrayList unknownChunkData = new ArrayList(); // byte arrays + + public PNGMetadata() { + super(true, + nativeMetadataFormatName, + nativeMetadataFormatClassName, + null, null); + } + + public PNGMetadata(IIOMetadata metadata) { + // TODO -- implement + } + + /** + * Sets the IHDR_bitDepth and IHDR_colorType variables. + * The <code>numBands</code> parameter is necessary since + * we may only be writing a subset of the image bands. + */ + public void initialize(ImageTypeSpecifier imageType, int numBands) { + ColorModel colorModel = imageType.getColorModel(); + SampleModel sampleModel = imageType.getSampleModel(); + + // Initialize IHDR_bitDepth + int[] sampleSize = sampleModel.getSampleSize(); + int bitDepth = sampleSize[0]; + // Choose max bit depth over all channels + // Fixes bug 4413109 + for (int i = 1; i < sampleSize.length; i++) { + if (sampleSize[i] > bitDepth) { + bitDepth = sampleSize[i]; + } + } + // Multi-channel images must have a bit depth of 8 or 16 + if (sampleSize.length > 1 && bitDepth < 8) { + bitDepth = 8; + } + + // Round bit depth up to a power of 2 + if (bitDepth > 2 && bitDepth < 4) { + bitDepth = 4; + } else if (bitDepth > 4 && bitDepth < 8) { + bitDepth = 8; + } else if (bitDepth > 8 && bitDepth < 16) { + bitDepth = 16; + } else if (bitDepth > 16) { + throw new RuntimeException("bitDepth > 16!"); + } + IHDR_bitDepth = bitDepth; + + // Initialize IHDR_colorType + if (colorModel instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)colorModel; + int size = icm.getMapSize(); + + byte[] reds = new byte[size]; + icm.getReds(reds); + byte[] greens = new byte[size]; + icm.getGreens(greens); + byte[] blues = new byte[size]; + icm.getBlues(blues); + + // Determine whether the color tables are actually a gray ramp + // if the color type has not been set previously + boolean isGray = false; + if (!IHDR_present || + (IHDR_colorType != PNGImageReader.PNG_COLOR_PALETTE)) { + isGray = true; + int scale = 255/((1 << IHDR_bitDepth) - 1); + for (int i = 0; i < size; i++) { + byte red = reds[i]; + if ((red != (byte)(i*scale)) || + (red != greens[i]) || + (red != blues[i])) { + isGray = false; + break; + } + } + } + + // Determine whether transparency exists + boolean hasAlpha = colorModel.hasAlpha(); + + byte[] alpha = null; + if (hasAlpha) { + alpha = new byte[size]; + icm.getAlphas(alpha); + } + + /* + * NB: PNG_COLOR_GRAY_ALPHA color type may be not optimal for images + * contained more than 1024 pixels (or even than 768 pixels in case of + * single transparent pixel in palette). + * For such images alpha samples in raster will occupy more space than + * it is required to store palette so it could be reasonable to + * use PNG_COLOR_PALETTE color type for large images. + */ + + if (isGray && hasAlpha && (bitDepth == 8 || bitDepth == 16)) { + IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA; + } else if (isGray && !hasAlpha) { + IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY; + } else { + IHDR_colorType = PNGImageReader.PNG_COLOR_PALETTE; + PLTE_present = true; + PLTE_order = null; + PLTE_red = (byte[])reds.clone(); + PLTE_green = (byte[])greens.clone(); + PLTE_blue = (byte[])blues.clone(); + + if (hasAlpha) { + tRNS_present = true; + tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE; + + PLTE_order = new int[alpha.length]; + + // Reorder the palette so that non-opaque entries + // come first. Since the tRNS chunk does not have + // to store trailing 255's, this can save a + // considerable amount of space when encoding + // images with only one transparent pixel value, + // e.g., images from GIF sources. + + byte[] newAlpha = new byte[alpha.length]; + + // Scan for non-opaque entries and assign them + // positions starting at 0. + int newIndex = 0; + for (int i = 0; i < alpha.length; i++) { + if (alpha[i] != (byte)255) { + PLTE_order[i] = newIndex; + newAlpha[newIndex] = alpha[i]; + ++newIndex; + } + } + int numTransparent = newIndex; + + // Scan for opaque entries and assign them + // positions following the non-opaque entries. + for (int i = 0; i < alpha.length; i++) { + if (alpha[i] == (byte)255) { + PLTE_order[i] = newIndex++; + } + } + + // Reorder the palettes + byte[] oldRed = PLTE_red; + byte[] oldGreen = PLTE_green; + byte[] oldBlue = PLTE_blue; + int len = oldRed.length; // All have the same length + PLTE_red = new byte[len]; + PLTE_green = new byte[len]; + PLTE_blue = new byte[len]; + for (int i = 0; i < len; i++) { + PLTE_red[PLTE_order[i]] = oldRed[i]; + PLTE_green[PLTE_order[i]] = oldGreen[i]; + PLTE_blue[PLTE_order[i]] = oldBlue[i]; + } + + // Copy only the transparent entries into tRNS_alpha + tRNS_alpha = new byte[numTransparent]; + System.arraycopy(newAlpha, 0, + tRNS_alpha, 0, numTransparent); + } + } + } else { + if (numBands == 1) { + IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY; + } else if (numBands == 2) { + IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA; + } else if (numBands == 3) { + IHDR_colorType = PNGImageReader.PNG_COLOR_RGB; + } else if (numBands == 4) { + IHDR_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA; + } else { + throw new RuntimeException("Number of bands not 1-4!"); + } + } + + IHDR_present = true; + } + + public boolean isReadOnly() { + return false; + } + + private ArrayList cloneBytesArrayList(ArrayList in) { + if (in == null) { + return null; + } else { + ArrayList list = new ArrayList(in.size()); + Iterator iter = in.iterator(); + while (iter.hasNext()) { + Object o = iter.next(); + if (o == null) { + list.add(null); + } else { + list.add(((byte[])o).clone()); + } + } + + return list; + } + } + + // Deep clone + public Object clone() { + PNGMetadata metadata; + try { + metadata = (PNGMetadata)super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + + // unknownChunkData needs deep clone + metadata.unknownChunkData = + cloneBytesArrayList(this.unknownChunkData); + + return metadata; + } + + public Node getAsTree(String formatName) { + if (formatName.equals(nativeMetadataFormatName)) { + return getNativeTree(); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + return getStandardTree(); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + private Node getNativeTree() { + IIOMetadataNode node = null; // scratch node + IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); + + // IHDR + if (IHDR_present) { + IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR"); + IHDR_node.setAttribute("width", Integer.toString(IHDR_width)); + IHDR_node.setAttribute("height", Integer.toString(IHDR_height)); + IHDR_node.setAttribute("bitDepth", + Integer.toString(IHDR_bitDepth)); + IHDR_node.setAttribute("colorType", + IHDR_colorTypeNames[IHDR_colorType]); + // IHDR_compressionMethod must be 0 in PNG 1.1 + IHDR_node.setAttribute("compressionMethod", + IHDR_compressionMethodNames[IHDR_compressionMethod]); + // IHDR_filterMethod must be 0 in PNG 1.1 + IHDR_node.setAttribute("filterMethod", + IHDR_filterMethodNames[IHDR_filterMethod]); + IHDR_node.setAttribute("interlaceMethod", + IHDR_interlaceMethodNames[IHDR_interlaceMethod]); + root.appendChild(IHDR_node); + } + + // PLTE + if (PLTE_present) { + IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE"); + int numEntries = PLTE_red.length; + for (int i = 0; i < numEntries; i++) { + IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", + Integer.toString(PLTE_red[i] & 0xff)); + entry.setAttribute("green", + Integer.toString(PLTE_green[i] & 0xff)); + entry.setAttribute("blue", + Integer.toString(PLTE_blue[i] & 0xff)); + PLTE_node.appendChild(entry); + } + + root.appendChild(PLTE_node); + } + + // bKGD + if (bKGD_present) { + IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD"); + + if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) { + node = new IIOMetadataNode("bKGD_Palette"); + node.setAttribute("index", Integer.toString(bKGD_index)); + } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) { + node = new IIOMetadataNode("bKGD_Grayscale"); + node.setAttribute("gray", Integer.toString(bKGD_gray)); + } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_RGB) { + node = new IIOMetadataNode("bKGD_RGB"); + node.setAttribute("red", Integer.toString(bKGD_red)); + node.setAttribute("green", Integer.toString(bKGD_green)); + node.setAttribute("blue", Integer.toString(bKGD_blue)); + } + bKGD_node.appendChild(node); + + root.appendChild(bKGD_node); + } + + // cHRM + if (cHRM_present) { + IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM"); + cHRM_node.setAttribute("whitePointX", + Integer.toString(cHRM_whitePointX)); + cHRM_node.setAttribute("whitePointY", + Integer.toString(cHRM_whitePointY)); + cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX)); + cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY)); + cHRM_node.setAttribute("greenX", Integer.toString(cHRM_greenX)); + cHRM_node.setAttribute("greenY", Integer.toString(cHRM_greenY)); + cHRM_node.setAttribute("blueX", Integer.toString(cHRM_blueX)); + cHRM_node.setAttribute("blueY", Integer.toString(cHRM_blueY)); + + root.appendChild(cHRM_node); + } + + // gAMA + if (gAMA_present) { + IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA"); + gAMA_node.setAttribute("value", Integer.toString(gAMA_gamma)); + + root.appendChild(gAMA_node); + } + + // hIST + if (hIST_present) { + IIOMetadataNode hIST_node = new IIOMetadataNode("hIST"); + + for (int i = 0; i < hIST_histogram.length; i++) { + IIOMetadataNode hist = + new IIOMetadataNode("hISTEntry"); + hist.setAttribute("index", Integer.toString(i)); + hist.setAttribute("value", + Integer.toString(hIST_histogram[i])); + hIST_node.appendChild(hist); + } + + root.appendChild(hIST_node); + } + + // iCCP + if (iCCP_present) { + IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP"); + iCCP_node.setAttribute("profileName", iCCP_profileName); + iCCP_node.setAttribute("compressionMethod", + iCCP_compressionMethodNames[iCCP_compressionMethod]); + + Object profile = iCCP_compressedProfile; + if (profile != null) { + profile = ((byte[])profile).clone(); + } + iCCP_node.setUserObject(profile); + + root.appendChild(iCCP_node); + } + + // iTXt + if (iTXt_keyword.size() > 0) { + IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt"); + for (int i = 0; i < iTXt_keyword.size(); i++) { + IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry"); + iTXt_node.setAttribute("keyword", iTXt_keyword.get(i)); + iTXt_node.setAttribute("compressionFlag", + iTXt_compressionFlag.get(i) ? "1" : "0"); + iTXt_node.setAttribute("compressionMethod", + iTXt_compressionMethod.get(i).toString()); + iTXt_node.setAttribute("languageTag", + iTXt_languageTag.get(i)); + iTXt_node.setAttribute("translatedKeyword", + iTXt_translatedKeyword.get(i)); + iTXt_node.setAttribute("text", iTXt_text.get(i)); + + iTXt_parent.appendChild(iTXt_node); + } + + root.appendChild(iTXt_parent); + } + + // pHYs + if (pHYs_present) { + IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs"); + pHYs_node.setAttribute("pixelsPerUnitXAxis", + Integer.toString(pHYs_pixelsPerUnitXAxis)); + pHYs_node.setAttribute("pixelsPerUnitYAxis", + Integer.toString(pHYs_pixelsPerUnitYAxis)); + pHYs_node.setAttribute("unitSpecifier", + unitSpecifierNames[pHYs_unitSpecifier]); + + root.appendChild(pHYs_node); + } + + // sBIT + if (sBIT_present) { + IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT"); + + if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY) { + node = new IIOMetadataNode("sBIT_Grayscale"); + node.setAttribute("gray", + Integer.toString(sBIT_grayBits)); + } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { + node = new IIOMetadataNode("sBIT_GrayAlpha"); + node.setAttribute("gray", + Integer.toString(sBIT_grayBits)); + node.setAttribute("alpha", + Integer.toString(sBIT_alphaBits)); + } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB) { + node = new IIOMetadataNode("sBIT_RGB"); + node.setAttribute("red", + Integer.toString(sBIT_redBits)); + node.setAttribute("green", + Integer.toString(sBIT_greenBits)); + node.setAttribute("blue", + Integer.toString(sBIT_blueBits)); + } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { + node = new IIOMetadataNode("sBIT_RGBAlpha"); + node.setAttribute("red", + Integer.toString(sBIT_redBits)); + node.setAttribute("green", + Integer.toString(sBIT_greenBits)); + node.setAttribute("blue", + Integer.toString(sBIT_blueBits)); + node.setAttribute("alpha", + Integer.toString(sBIT_alphaBits)); + } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_PALETTE) { + node = new IIOMetadataNode("sBIT_Palette"); + node.setAttribute("red", + Integer.toString(sBIT_redBits)); + node.setAttribute("green", + Integer.toString(sBIT_greenBits)); + node.setAttribute("blue", + Integer.toString(sBIT_blueBits)); + } + sBIT_node.appendChild(node); + + root.appendChild(sBIT_node); + } + + // sPLT + if (sPLT_present) { + IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT"); + + sPLT_node.setAttribute("name", sPLT_paletteName); + sPLT_node.setAttribute("sampleDepth", + Integer.toString(sPLT_sampleDepth)); + + int numEntries = sPLT_red.length; + for (int i = 0; i < numEntries; i++) { + IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", Integer.toString(sPLT_red[i])); + entry.setAttribute("green", Integer.toString(sPLT_green[i])); + entry.setAttribute("blue", Integer.toString(sPLT_blue[i])); + entry.setAttribute("alpha", Integer.toString(sPLT_alpha[i])); + entry.setAttribute("frequency", + Integer.toString(sPLT_frequency[i])); + sPLT_node.appendChild(entry); + } + + root.appendChild(sPLT_node); + } + + // sRGB + if (sRGB_present) { + IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB"); + sRGB_node.setAttribute("renderingIntent", + renderingIntentNames[sRGB_renderingIntent]); + + root.appendChild(sRGB_node); + } + + // tEXt + if (tEXt_keyword.size() > 0) { + IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt"); + for (int i = 0; i < tEXt_keyword.size(); i++) { + IIOMetadataNode tEXt_node = new IIOMetadataNode("tEXtEntry"); + tEXt_node.setAttribute("keyword" , (String)tEXt_keyword.get(i)); + tEXt_node.setAttribute("value" , (String)tEXt_text.get(i)); + + tEXt_parent.appendChild(tEXt_node); + } + + root.appendChild(tEXt_parent); + } + + // tIME + if (tIME_present) { + IIOMetadataNode tIME_node = new IIOMetadataNode("tIME"); + tIME_node.setAttribute("year", Integer.toString(tIME_year)); + tIME_node.setAttribute("month", Integer.toString(tIME_month)); + tIME_node.setAttribute("day", Integer.toString(tIME_day)); + tIME_node.setAttribute("hour", Integer.toString(tIME_hour)); + tIME_node.setAttribute("minute", Integer.toString(tIME_minute)); + tIME_node.setAttribute("second", Integer.toString(tIME_second)); + + root.appendChild(tIME_node); + } + + // tRNS + if (tRNS_present) { + IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS"); + + if (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE) { + node = new IIOMetadataNode("tRNS_Palette"); + + for (int i = 0; i < tRNS_alpha.length; i++) { + IIOMetadataNode entry = + new IIOMetadataNode("tRNS_PaletteEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("alpha", + Integer.toString(tRNS_alpha[i] & 0xff)); + node.appendChild(entry); + } + } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) { + node = new IIOMetadataNode("tRNS_Grayscale"); + node.setAttribute("gray", Integer.toString(tRNS_gray)); + } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) { + node = new IIOMetadataNode("tRNS_RGB"); + node.setAttribute("red", Integer.toString(tRNS_red)); + node.setAttribute("green", Integer.toString(tRNS_green)); + node.setAttribute("blue", Integer.toString(tRNS_blue)); + } + tRNS_node.appendChild(node); + + root.appendChild(tRNS_node); + } + + // zTXt + if (zTXt_keyword.size() > 0) { + IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt"); + for (int i = 0; i < zTXt_keyword.size(); i++) { + IIOMetadataNode zTXt_node = new IIOMetadataNode("zTXtEntry"); + zTXt_node.setAttribute("keyword", (String)zTXt_keyword.get(i)); + + int cm = ((Integer)zTXt_compressionMethod.get(i)).intValue(); + zTXt_node.setAttribute("compressionMethod", + zTXt_compressionMethodNames[cm]); + + zTXt_node.setAttribute("text", (String)zTXt_text.get(i)); + + zTXt_parent.appendChild(zTXt_node); + } + + root.appendChild(zTXt_parent); + } + + // Unknown chunks + if (unknownChunkType.size() > 0) { + IIOMetadataNode unknown_parent = + new IIOMetadataNode("UnknownChunks"); + for (int i = 0; i < unknownChunkType.size(); i++) { + IIOMetadataNode unknown_node = + new IIOMetadataNode("UnknownChunk"); + unknown_node.setAttribute("type", + (String)unknownChunkType.get(i)); + unknown_node.setUserObject((byte[])unknownChunkData.get(i)); + + unknown_parent.appendChild(unknown_node); + } + + root.appendChild(unknown_parent); + } + + return root; + } + + private int getNumChannels() { + // Determine number of channels + // Be careful about palette color with transparency + int numChannels = IHDR_numChannels[IHDR_colorType]; + if (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE && + tRNS_present && tRNS_colorType == IHDR_colorType) { + numChannels = 4; + } + return numChannels; + } + + public IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("ColorSpaceType"); + node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]); + chroma_node.appendChild(node); + + node = new IIOMetadataNode("NumChannels"); + node.setAttribute("value", Integer.toString(getNumChannels())); + chroma_node.appendChild(node); + + if (gAMA_present) { + node = new IIOMetadataNode("Gamma"); + node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F)); + chroma_node.appendChild(node); + } + + node = new IIOMetadataNode("BlackIsZero"); + node.setAttribute("value", "true"); + chroma_node.appendChild(node); + + if (PLTE_present) { + boolean hasAlpha = tRNS_present && + (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE); + + node = new IIOMetadataNode("Palette"); + for (int i = 0; i < PLTE_red.length; i++) { + IIOMetadataNode entry = + new IIOMetadataNode("PaletteEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", + Integer.toString(PLTE_red[i] & 0xff)); + entry.setAttribute("green", + Integer.toString(PLTE_green[i] & 0xff)); + entry.setAttribute("blue", + Integer.toString(PLTE_blue[i] & 0xff)); + if (hasAlpha) { + int alpha = (i < tRNS_alpha.length) ? + (tRNS_alpha[i] & 0xff) : 255; + entry.setAttribute("alpha", Integer.toString(alpha)); + } + node.appendChild(entry); + } + chroma_node.appendChild(node); + } + + if (bKGD_present) { + if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) { + node = new IIOMetadataNode("BackgroundIndex"); + node.setAttribute("value", Integer.toString(bKGD_index)); + } else { + node = new IIOMetadataNode("BackgroundColor"); + int r, g, b; + + if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) { + r = g = b = bKGD_gray; + } else { + r = bKGD_red; + g = bKGD_green; + b = bKGD_blue; + } + node.setAttribute("red", Integer.toString(r)); + node.setAttribute("green", Integer.toString(g)); + node.setAttribute("blue", Integer.toString(b)); + } + chroma_node.appendChild(node); + } + + return chroma_node; + } + + public IIOMetadataNode getStandardCompressionNode() { + IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("CompressionTypeName"); + node.setAttribute("value", "deflate"); + compression_node.appendChild(node); + + node = new IIOMetadataNode("Lossless"); + node.setAttribute("value", "true"); + compression_node.appendChild(node); + + node = new IIOMetadataNode("NumProgressiveScans"); + node.setAttribute("value", + (IHDR_interlaceMethod == 0) ? "1" : "7"); + compression_node.appendChild(node); + + return compression_node; + } + + private String repeat(String s, int times) { + if (times == 1) { + return s; + } + StringBuffer sb = new StringBuffer((s.length() + 1)*times - 1); + sb.append(s); + for (int i = 1; i < times; i++) { + sb.append(" "); + sb.append(s); + } + return sb.toString(); + } + + public IIOMetadataNode getStandardDataNode() { + IIOMetadataNode data_node = new IIOMetadataNode("Data"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("PlanarConfiguration"); + node.setAttribute("value", "PixelInterleaved"); + data_node.appendChild(node); + + node = new IIOMetadataNode("SampleFormat"); + node.setAttribute("value", + IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE ? + "Index" : "UnsignedIntegral"); + data_node.appendChild(node); + + String bitDepth = Integer.toString(IHDR_bitDepth); + node = new IIOMetadataNode("BitsPerSample"); + node.setAttribute("value", repeat(bitDepth, getNumChannels())); + data_node.appendChild(node); + + if (sBIT_present) { + node = new IIOMetadataNode("SignificantBitsPerSample"); + String sbits; + if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY || + sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { + sbits = Integer.toString(sBIT_grayBits); + } else { // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB || + // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA + sbits = Integer.toString(sBIT_redBits) + " " + + Integer.toString(sBIT_greenBits) + " " + + Integer.toString(sBIT_blueBits); + } + + if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA || + sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { + sbits += " " + Integer.toString(sBIT_alphaBits); + } + + node.setAttribute("value", sbits); + data_node.appendChild(node); + } + + // SampleMSB + + return data_node; + } + + public IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("PixelAspectRatio"); + float ratio = pHYs_present ? + (float)pHYs_pixelsPerUnitXAxis/pHYs_pixelsPerUnitYAxis : 1.0F; + node.setAttribute("value", Float.toString(ratio)); + dimension_node.appendChild(node); + + node = new IIOMetadataNode("ImageOrientation"); + node.setAttribute("value", "Normal"); + dimension_node.appendChild(node); + + if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) { + node = new IIOMetadataNode("HorizontalPixelSize"); + node.setAttribute("value", + Float.toString(1000.0F/pHYs_pixelsPerUnitXAxis)); + dimension_node.appendChild(node); + + node = new IIOMetadataNode("VerticalPixelSize"); + node.setAttribute("value", + Float.toString(1000.0F/pHYs_pixelsPerUnitYAxis)); + dimension_node.appendChild(node); + } + + return dimension_node; + } + + public IIOMetadataNode getStandardDocumentNode() { + if (!tIME_present) { + return null; + } + + IIOMetadataNode document_node = new IIOMetadataNode("Document"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("ImageModificationTime"); + node.setAttribute("year", Integer.toString(tIME_year)); + node.setAttribute("month", Integer.toString(tIME_month)); + node.setAttribute("day", Integer.toString(tIME_day)); + node.setAttribute("hour", Integer.toString(tIME_hour)); + node.setAttribute("minute", Integer.toString(tIME_minute)); + node.setAttribute("second", Integer.toString(tIME_second)); + document_node.appendChild(node); + + return document_node; + } + + public IIOMetadataNode getStandardTextNode() { + int numEntries = tEXt_keyword.size() + + iTXt_keyword.size() + zTXt_keyword.size(); + if (numEntries == 0) { + return null; + } + + IIOMetadataNode text_node = new IIOMetadataNode("Text"); + IIOMetadataNode node = null; // scratch node + + for (int i = 0; i < tEXt_keyword.size(); i++) { + node = new IIOMetadataNode("TextEntry"); + node.setAttribute("keyword", (String)tEXt_keyword.get(i)); + node.setAttribute("value", (String)tEXt_text.get(i)); + node.setAttribute("encoding", "ISO-8859-1"); + node.setAttribute("compression", "none"); + + text_node.appendChild(node); + } + + for (int i = 0; i < iTXt_keyword.size(); i++) { + node = new IIOMetadataNode("TextEntry"); + node.setAttribute("keyword", iTXt_keyword.get(i)); + node.setAttribute("value", iTXt_text.get(i)); + node.setAttribute("language", + iTXt_languageTag.get(i)); + if (iTXt_compressionFlag.get(i)) { + node.setAttribute("compression", "deflate"); + } else { + node.setAttribute("compression", "none"); + } + + text_node.appendChild(node); + } + + for (int i = 0; i < zTXt_keyword.size(); i++) { + node = new IIOMetadataNode("TextEntry"); + node.setAttribute("keyword", (String)zTXt_keyword.get(i)); + node.setAttribute("value", (String)zTXt_text.get(i)); + node.setAttribute("compression", "deflate"); + + text_node.appendChild(node); + } + + return text_node; + } + + public IIOMetadataNode getStandardTransparencyNode() { + IIOMetadataNode transparency_node = + new IIOMetadataNode("Transparency"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("Alpha"); + boolean hasAlpha = + (IHDR_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) || + (IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) || + (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE && + tRNS_present && + (tRNS_colorType == IHDR_colorType) && + (tRNS_alpha != null)); + node.setAttribute("value", hasAlpha ? "nonpremultipled" : "none"); + transparency_node.appendChild(node); + + if (tRNS_present) { + node = new IIOMetadataNode("TransparentColor"); + if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) { + node.setAttribute("value", + Integer.toString(tRNS_red) + " " + + Integer.toString(tRNS_green) + " " + + Integer.toString(tRNS_blue)); + } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) { + node.setAttribute("value", Integer.toString(tRNS_gray)); + } + transparency_node.appendChild(node); + } + + return transparency_node; + } + + // Shorthand for throwing an IIOInvalidTreeException + private void fatal(Node node, String reason) + throws IIOInvalidTreeException { + throw new IIOInvalidTreeException(reason, node); + } + + // Get an integer-valued attribute + private String getStringAttribute(Node node, String name, + String defaultValue, boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + return attr.getNodeValue(); + } + + + // Get an integer-valued attribute + private int getIntAttribute(Node node, String name, + int defaultValue, boolean required) + throws IIOInvalidTreeException { + String value = getStringAttribute(node, name, null, required); + if (value == null) { + return defaultValue; + } + return Integer.parseInt(value); + } + + // Get a float-valued attribute + private float getFloatAttribute(Node node, String name, + float defaultValue, boolean required) + throws IIOInvalidTreeException { + String value = getStringAttribute(node, name, null, required); + if (value == null) { + return defaultValue; + } + return Float.parseFloat(value); + } + + // Get a required integer-valued attribute + private int getIntAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getIntAttribute(node, name, -1, true); + } + + // Get a required float-valued attribute + private float getFloatAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getFloatAttribute(node, name, -1.0F, true); + } + + // Get a boolean-valued attribute + private boolean getBooleanAttribute(Node node, String name, + boolean defaultValue, + boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + String value = attr.getNodeValue(); + if (value.equals("true")) { + return true; + } else if (value.equals("false")) { + return false; + } else { + fatal(node, "Attribute " + name + " must be 'true' or 'false'!"); + return false; + } + } + + // Get a required boolean-valued attribute + private boolean getBooleanAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getBooleanAttribute(node, name, false, true); + } + + // Get an enumerated attribute as an index into a String array + private int getEnumeratedAttribute(Node node, + String name, String[] legalNames, + int defaultValue, boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + String value = attr.getNodeValue(); + for (int i = 0; i < legalNames.length; i++) { + if (value.equals(legalNames[i])) { + return i; + } + } + + fatal(node, "Illegal value for attribute " + name + "!"); + return -1; + } + + // Get a required enumerated attribute as an index into a String array + private int getEnumeratedAttribute(Node node, + String name, String[] legalNames) + throws IIOInvalidTreeException { + return getEnumeratedAttribute(node, name, legalNames, -1, true); + } + + // Get a String-valued attribute + private String getAttribute(Node node, String name, + String defaultValue, boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + return attr.getNodeValue(); + } + + // Get a required String-valued attribute + private String getAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getAttribute(node, name, null, true); + } + + public void mergeTree(String formatName, Node root) + throws IIOInvalidTreeException { + if (formatName.equals(nativeMetadataFormatName)) { + if (root == null) { + throw new IllegalArgumentException("root == null!"); + } + mergeNativeTree(root); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + if (root == null) { + throw new IllegalArgumentException("root == null!"); + } + mergeStandardTree(root); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + private void mergeNativeTree(Node root) + throws IIOInvalidTreeException { + Node node = root; + if (!node.getNodeName().equals(nativeMetadataFormatName)) { + fatal(node, "Root must be " + nativeMetadataFormatName); + } + + node = node.getFirstChild(); + while (node != null) { + String name = node.getNodeName(); + + if (name.equals("IHDR")) { + IHDR_width = getIntAttribute(node, "width"); + IHDR_height = getIntAttribute(node, "height"); + IHDR_bitDepth = getEnumeratedAttribute(node, "bitDepth", + IHDR_bitDepths); + IHDR_colorType = getEnumeratedAttribute(node, "colorType", + IHDR_colorTypeNames); + IHDR_compressionMethod = + getEnumeratedAttribute(node, "compressionMethod", + IHDR_compressionMethodNames); + IHDR_filterMethod = + getEnumeratedAttribute(node, + "filterMethod", + IHDR_filterMethodNames); + IHDR_interlaceMethod = + getEnumeratedAttribute(node, "interlaceMethod", + IHDR_interlaceMethodNames); + IHDR_present = true; + } else if (name.equals("PLTE")) { + byte[] red = new byte[256]; + byte[] green = new byte[256]; + byte[] blue = new byte[256]; + int maxindex = -1; + + Node PLTE_entry = node.getFirstChild(); + if (PLTE_entry == null) { + fatal(node, "Palette has no entries!"); + } + + while (PLTE_entry != null) { + if (!PLTE_entry.getNodeName().equals("PLTEEntry")) { + fatal(node, + "Only a PLTEEntry may be a child of a PLTE!"); + } + + int index = getIntAttribute(PLTE_entry, "index"); + if (index < 0 || index > 255) { + fatal(node, + "Bad value for PLTEEntry attribute index!"); + } + if (index > maxindex) { + maxindex = index; + } + red[index] = + (byte)getIntAttribute(PLTE_entry, "red"); + green[index] = + (byte)getIntAttribute(PLTE_entry, "green"); + blue[index] = + (byte)getIntAttribute(PLTE_entry, "blue"); + + PLTE_entry = PLTE_entry.getNextSibling(); + } + + int numEntries = maxindex + 1; + PLTE_red = new byte[numEntries]; + PLTE_green = new byte[numEntries]; + PLTE_blue = new byte[numEntries]; + System.arraycopy(red, 0, PLTE_red, 0, numEntries); + System.arraycopy(green, 0, PLTE_green, 0, numEntries); + System.arraycopy(blue, 0, PLTE_blue, 0, numEntries); + PLTE_present = true; + } else if (name.equals("bKGD")) { + bKGD_present = false; // Guard against partial overwrite + Node bKGD_node = node.getFirstChild(); + if (bKGD_node == null) { + fatal(node, "bKGD node has no children!"); + } + String bKGD_name = bKGD_node.getNodeName(); + if (bKGD_name.equals("bKGD_Palette")) { + bKGD_index = getIntAttribute(bKGD_node, "index"); + bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE; + } else if (bKGD_name.equals("bKGD_Grayscale")) { + bKGD_gray = getIntAttribute(bKGD_node, "gray"); + bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY; + } else if (bKGD_name.equals("bKGD_RGB")) { + bKGD_red = getIntAttribute(bKGD_node, "red"); + bKGD_green = getIntAttribute(bKGD_node, "green"); + bKGD_blue = getIntAttribute(bKGD_node, "blue"); + bKGD_colorType = PNGImageReader.PNG_COLOR_RGB; + } else { + fatal(node, "Bad child of a bKGD node!"); + } + if (bKGD_node.getNextSibling() != null) { + fatal(node, "bKGD node has more than one child!"); + } + + bKGD_present = true; + } else if (name.equals("cHRM")) { + cHRM_whitePointX = getIntAttribute(node, "whitePointX"); + cHRM_whitePointY = getIntAttribute(node, "whitePointY"); + cHRM_redX = getIntAttribute(node, "redX"); + cHRM_redY = getIntAttribute(node, "redY"); + cHRM_greenX = getIntAttribute(node, "greenX"); + cHRM_greenY = getIntAttribute(node, "greenY"); + cHRM_blueX = getIntAttribute(node, "blueX"); + cHRM_blueY = getIntAttribute(node, "blueY"); + + cHRM_present = true; + } else if (name.equals("gAMA")) { + gAMA_gamma = getIntAttribute(node, "value"); + gAMA_present = true; + } else if (name.equals("hIST")) { + char[] hist = new char[256]; + int maxindex = -1; + + Node hIST_entry = node.getFirstChild(); + if (hIST_entry == null) { + fatal(node, "hIST node has no children!"); + } + + while (hIST_entry != null) { + if (!hIST_entry.getNodeName().equals("hISTEntry")) { + fatal(node, + "Only a hISTEntry may be a child of a hIST!"); + } + + int index = getIntAttribute(hIST_entry, "index"); + if (index < 0 || index > 255) { + fatal(node, + "Bad value for histEntry attribute index!"); + } + if (index > maxindex) { + maxindex = index; + } + hist[index] = + (char)getIntAttribute(hIST_entry, "value"); + + hIST_entry = hIST_entry.getNextSibling(); + } + + int numEntries = maxindex + 1; + hIST_histogram = new char[numEntries]; + System.arraycopy(hist, 0, hIST_histogram, 0, numEntries); + + hIST_present = true; + } else if (name.equals("iCCP")) { + iCCP_profileName = getAttribute(node, "profileName"); + iCCP_compressionMethod = + getEnumeratedAttribute(node, "compressionMethod", + iCCP_compressionMethodNames); + Object compressedProfile = + ((IIOMetadataNode)node).getUserObject(); + if (compressedProfile == null) { + fatal(node, "No ICCP profile present in user object!"); + } + if (!(compressedProfile instanceof byte[])) { + fatal(node, "User object not a byte array!"); + } + + iCCP_compressedProfile = + (byte[])((byte[])compressedProfile).clone(); + + iCCP_present = true; + } else if (name.equals("iTXt")) { + Node iTXt_node = node.getFirstChild(); + while (iTXt_node != null) { + if (!iTXt_node.getNodeName().equals("iTXtEntry")) { + fatal(node, + "Only an iTXtEntry may be a child of an iTXt!"); + } + + String keyword = getAttribute(iTXt_node, "keyword"); + iTXt_keyword.add(keyword); + + boolean compressionFlag = + getBooleanAttribute(iTXt_node, "compressionFlag"); + iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag)); + + String compressionMethod = + getAttribute(iTXt_node, "compressionMethod"); + iTXt_compressionMethod.add(Integer.valueOf(compressionMethod)); + + String languageTag = + getAttribute(iTXt_node, "languageTag"); + iTXt_languageTag.add(languageTag); + + String translatedKeyword = + getAttribute(iTXt_node, "translatedKeyword"); + iTXt_translatedKeyword.add(translatedKeyword); + + String text = getAttribute(iTXt_node, "text"); + iTXt_text.add(text); + + iTXt_node = iTXt_node.getNextSibling(); + } + } else if (name.equals("pHYs")) { + pHYs_pixelsPerUnitXAxis = + getIntAttribute(node, "pixelsPerUnitXAxis"); + pHYs_pixelsPerUnitYAxis = + getIntAttribute(node, "pixelsPerUnitYAxis"); + pHYs_unitSpecifier = + getEnumeratedAttribute(node, "unitSpecifier", + unitSpecifierNames); + + pHYs_present = true; + } else if (name.equals("sBIT")) { + sBIT_present = false; // Guard against partial overwrite + Node sBIT_node = node.getFirstChild(); + if (sBIT_node == null) { + fatal(node, "sBIT node has no children!"); + } + String sBIT_name = sBIT_node.getNodeName(); + if (sBIT_name.equals("sBIT_Grayscale")) { + sBIT_grayBits = getIntAttribute(sBIT_node, "gray"); + sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY; + } else if (sBIT_name.equals("sBIT_GrayAlpha")) { + sBIT_grayBits = getIntAttribute(sBIT_node, "gray"); + sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha"); + sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA; + } else if (sBIT_name.equals("sBIT_RGB")) { + sBIT_redBits = getIntAttribute(sBIT_node, "red"); + sBIT_greenBits = getIntAttribute(sBIT_node, "green"); + sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); + sBIT_colorType = PNGImageReader.PNG_COLOR_RGB; + } else if (sBIT_name.equals("sBIT_RGBAlpha")) { + sBIT_redBits = getIntAttribute(sBIT_node, "red"); + sBIT_greenBits = getIntAttribute(sBIT_node, "green"); + sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); + sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha"); + sBIT_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA; + } else if (sBIT_name.equals("sBIT_Palette")) { + sBIT_redBits = getIntAttribute(sBIT_node, "red"); + sBIT_greenBits = getIntAttribute(sBIT_node, "green"); + sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); + sBIT_colorType = PNGImageReader.PNG_COLOR_PALETTE; + } else { + fatal(node, "Bad child of an sBIT node!"); + } + if (sBIT_node.getNextSibling() != null) { + fatal(node, "sBIT node has more than one child!"); + } + + sBIT_present = true; + } else if (name.equals("sPLT")) { + sPLT_paletteName = getAttribute(node, "name"); + sPLT_sampleDepth = getIntAttribute(node, "sampleDepth"); + + int[] red = new int[256]; + int[] green = new int[256]; + int[] blue = new int[256]; + int[] alpha = new int[256]; + int[] frequency = new int[256]; + int maxindex = -1; + + Node sPLT_entry = node.getFirstChild(); + if (sPLT_entry == null) { + fatal(node, "sPLT node has no children!"); + } + + while (sPLT_entry != null) { + if (!sPLT_entry.getNodeName().equals("sPLTEntry")) { + fatal(node, + "Only an sPLTEntry may be a child of an sPLT!"); + } + + int index = getIntAttribute(sPLT_entry, "index"); + if (index < 0 || index > 255) { + fatal(node, + "Bad value for PLTEEntry attribute index!"); + } + if (index > maxindex) { + maxindex = index; + } + red[index] = getIntAttribute(sPLT_entry, "red"); + green[index] = getIntAttribute(sPLT_entry, "green"); + blue[index] = getIntAttribute(sPLT_entry, "blue"); + alpha[index] = getIntAttribute(sPLT_entry, "alpha"); + frequency[index] = + getIntAttribute(sPLT_entry, "frequency"); + + sPLT_entry = sPLT_entry.getNextSibling(); + } + + int numEntries = maxindex + 1; + sPLT_red = new int[numEntries]; + sPLT_green = new int[numEntries]; + sPLT_blue = new int[numEntries]; + sPLT_alpha = new int[numEntries]; + sPLT_frequency = new int[numEntries]; + System.arraycopy(red, 0, sPLT_red, 0, numEntries); + System.arraycopy(green, 0, sPLT_green, 0, numEntries); + System.arraycopy(blue, 0, sPLT_blue, 0, numEntries); + System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries); + System.arraycopy(frequency, 0, + sPLT_frequency, 0, numEntries); + + sPLT_present = true; + } else if (name.equals("sRGB")) { + sRGB_renderingIntent = + getEnumeratedAttribute(node, "renderingIntent", + renderingIntentNames); + + sRGB_present = true; + } else if (name.equals("tEXt")) { + Node tEXt_node = node.getFirstChild(); + while (tEXt_node != null) { + if (!tEXt_node.getNodeName().equals("tEXtEntry")) { + fatal(node, + "Only an tEXtEntry may be a child of an tEXt!"); + } + + String keyword = getAttribute(tEXt_node, "keyword"); + tEXt_keyword.add(keyword); + + String text = getAttribute(tEXt_node, "value"); + tEXt_text.add(text); + + tEXt_node = tEXt_node.getNextSibling(); + } + } else if (name.equals("tIME")) { + tIME_year = getIntAttribute(node, "year"); + tIME_month = getIntAttribute(node, "month"); + tIME_day = getIntAttribute(node, "day"); + tIME_hour = getIntAttribute(node, "hour"); + tIME_minute = getIntAttribute(node, "minute"); + tIME_second = getIntAttribute(node, "second"); + + tIME_present = true; + } else if (name.equals("tRNS")) { + tRNS_present = false; // Guard against partial overwrite + Node tRNS_node = node.getFirstChild(); + if (tRNS_node == null) { + fatal(node, "tRNS node has no children!"); + } + String tRNS_name = tRNS_node.getNodeName(); + if (tRNS_name.equals("tRNS_Palette")) { + byte[] alpha = new byte[256]; + int maxindex = -1; + + Node tRNS_paletteEntry = tRNS_node.getFirstChild(); + if (tRNS_paletteEntry == null) { + fatal(node, "tRNS_Palette node has no children!"); + } + while (tRNS_paletteEntry != null) { + if (!tRNS_paletteEntry.getNodeName().equals( + "tRNS_PaletteEntry")) { + fatal(node, + "Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!"); + } + int index = + getIntAttribute(tRNS_paletteEntry, "index"); + if (index < 0 || index > 255) { + fatal(node, + "Bad value for tRNS_PaletteEntry attribute index!"); + } + if (index > maxindex) { + maxindex = index; + } + alpha[index] = + (byte)getIntAttribute(tRNS_paletteEntry, + "alpha"); + + tRNS_paletteEntry = + tRNS_paletteEntry.getNextSibling(); + } + + int numEntries = maxindex + 1; + tRNS_alpha = new byte[numEntries]; + tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE; + System.arraycopy(alpha, 0, tRNS_alpha, 0, numEntries); + } else if (tRNS_name.equals("tRNS_Grayscale")) { + tRNS_gray = getIntAttribute(tRNS_node, "gray"); + tRNS_colorType = PNGImageReader.PNG_COLOR_GRAY; + } else if (tRNS_name.equals("tRNS_RGB")) { + tRNS_red = getIntAttribute(tRNS_node, "red"); + tRNS_green = getIntAttribute(tRNS_node, "green"); + tRNS_blue = getIntAttribute(tRNS_node, "blue"); + tRNS_colorType = PNGImageReader.PNG_COLOR_RGB; + } else { + fatal(node, "Bad child of a tRNS node!"); + } + if (tRNS_node.getNextSibling() != null) { + fatal(node, "tRNS node has more than one child!"); + } + + tRNS_present = true; + } else if (name.equals("zTXt")) { + Node zTXt_node = node.getFirstChild(); + while (zTXt_node != null) { + if (!zTXt_node.getNodeName().equals("zTXtEntry")) { + fatal(node, + "Only an zTXtEntry may be a child of an zTXt!"); + } + + String keyword = getAttribute(zTXt_node, "keyword"); + zTXt_keyword.add(keyword); + + int compressionMethod = + getEnumeratedAttribute(zTXt_node, "compressionMethod", + zTXt_compressionMethodNames); + zTXt_compressionMethod.add(new Integer(compressionMethod)); + + String text = getAttribute(zTXt_node, "text"); + zTXt_text.add(text); + + zTXt_node = zTXt_node.getNextSibling(); + } + } else if (name.equals("UnknownChunks")) { + Node unknown_node = node.getFirstChild(); + while (unknown_node != null) { + if (!unknown_node.getNodeName().equals("UnknownChunk")) { + fatal(node, + "Only an UnknownChunk may be a child of an UnknownChunks!"); + } + String chunkType = getAttribute(unknown_node, "type"); + Object chunkData = + ((IIOMetadataNode)unknown_node).getUserObject(); + + if (chunkType.length() != 4) { + fatal(unknown_node, + "Chunk type must be 4 characters!"); + } + if (chunkData == null) { + fatal(unknown_node, + "No chunk data present in user object!"); + } + if (!(chunkData instanceof byte[])) { + fatal(unknown_node, + "User object not a byte array!"); + } + unknownChunkType.add(chunkType); + unknownChunkData.add(((byte[])chunkData).clone()); + + unknown_node = unknown_node.getNextSibling(); + } + } else { + fatal(node, "Unknown child of root node!"); + } + + node = node.getNextSibling(); + } + } + + private boolean isISOLatin(String s) { + int len = s.length(); + for (int i = 0; i < len; i++) { + if (s.charAt(i) > 255) { + return false; + } + } + return true; + } + + private void mergeStandardTree(Node root) + throws IIOInvalidTreeException { + Node node = root; + if (!node.getNodeName() + .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { + fatal(node, "Root must be " + + IIOMetadataFormatImpl.standardMetadataFormatName); + } + + node = node.getFirstChild(); + while (node != null) { + String name = node.getNodeName(); + + if (name.equals("Chroma")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("Gamma")) { + float gamma = getFloatAttribute(child, "value"); + gAMA_present = true; + gAMA_gamma = (int)(gamma*100000 + 0.5); + } else if (childName.equals("Palette")) { + byte[] red = new byte[256]; + byte[] green = new byte[256]; + byte[] blue = new byte[256]; + int maxindex = -1; + + Node entry = child.getFirstChild(); + while (entry != null) { + int index = getIntAttribute(entry, "index"); + if (index >= 0 && index <= 255) { + red[index] = + (byte)getIntAttribute(entry, "red"); + green[index] = + (byte)getIntAttribute(entry, "green"); + blue[index] = + (byte)getIntAttribute(entry, "blue"); + if (index > maxindex) { + maxindex = index; + } + } + entry = entry.getNextSibling(); + } + + int numEntries = maxindex + 1; + PLTE_red = new byte[numEntries]; + PLTE_green = new byte[numEntries]; + PLTE_blue = new byte[numEntries]; + System.arraycopy(red, 0, PLTE_red, 0, numEntries); + System.arraycopy(green, 0, PLTE_green, 0, numEntries); + System.arraycopy(blue, 0, PLTE_blue, 0, numEntries); + PLTE_present = true; + } else if (childName.equals("BackgroundIndex")) { + bKGD_present = true; + bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE; + bKGD_index = getIntAttribute(child, "value"); + } else if (childName.equals("BackgroundColor")) { + int red = getIntAttribute(child, "red"); + int green = getIntAttribute(child, "green"); + int blue = getIntAttribute(child, "blue"); + if (red == green && red == blue) { + bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY; + bKGD_gray = red; + } else { + bKGD_red = red; + bKGD_green = green; + bKGD_blue = blue; + } + bKGD_present = true; + } +// } else if (childName.equals("ColorSpaceType")) { +// } else if (childName.equals("NumChannels")) { + + child = child.getNextSibling(); + } + } else if (name.equals("Compression")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("NumProgressiveScans")) { + // Use Adam7 if NumProgressiveScans > 1 + int scans = getIntAttribute(child, "value"); + IHDR_interlaceMethod = (scans > 1) ? 1 : 0; +// } else if (childName.equals("CompressionTypeName")) { +// } else if (childName.equals("Lossless")) { +// } else if (childName.equals("BitRate")) { + } + child = child.getNextSibling(); + } + } else if (name.equals("Data")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("BitsPerSample")) { + String s = getAttribute(child, "value"); + StringTokenizer t = new StringTokenizer(s); + int maxBits = -1; + while (t.hasMoreTokens()) { + int bits = Integer.parseInt(t.nextToken()); + if (bits > maxBits) { + maxBits = bits; + } + } + if (maxBits < 1) { + maxBits = 1; + } + if (maxBits == 3) maxBits = 4; + if (maxBits > 4 || maxBits < 8) { + maxBits = 8; + } + if (maxBits > 8) { + maxBits = 16; + } + IHDR_bitDepth = maxBits; + } else if (childName.equals("SignificantBitsPerSample")) { + String s = getAttribute(child, "value"); + StringTokenizer t = new StringTokenizer(s); + int numTokens = t.countTokens(); + if (numTokens == 1) { + sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY; + sBIT_grayBits = Integer.parseInt(t.nextToken()); + } else if (numTokens == 2) { + sBIT_colorType = + PNGImageReader.PNG_COLOR_GRAY_ALPHA; + sBIT_grayBits = Integer.parseInt(t.nextToken()); + sBIT_alphaBits = Integer.parseInt(t.nextToken()); + } else if (numTokens == 3) { + sBIT_colorType = PNGImageReader.PNG_COLOR_RGB; + sBIT_redBits = Integer.parseInt(t.nextToken()); + sBIT_greenBits = Integer.parseInt(t.nextToken()); + sBIT_blueBits = Integer.parseInt(t.nextToken()); + } else if (numTokens == 4) { + sBIT_colorType = + PNGImageReader.PNG_COLOR_RGB_ALPHA; + sBIT_redBits = Integer.parseInt(t.nextToken()); + sBIT_greenBits = Integer.parseInt(t.nextToken()); + sBIT_blueBits = Integer.parseInt(t.nextToken()); + sBIT_alphaBits = Integer.parseInt(t.nextToken()); + } + if (numTokens >= 1 && numTokens <= 4) { + sBIT_present = true; + } +// } else if (childName.equals("PlanarConfiguration")) { +// } else if (childName.equals("SampleFormat")) { +// } else if (childName.equals("SampleMSB")) { + } + child = child.getNextSibling(); + } + } else if (name.equals("Dimension")) { + boolean gotWidth = false; + boolean gotHeight = false; + boolean gotAspectRatio = false; + + float width = -1.0F; + float height = -1.0F; + float aspectRatio = -1.0F; + + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("PixelAspectRatio")) { + aspectRatio = getFloatAttribute(child, "value"); + gotAspectRatio = true; + } else if (childName.equals("HorizontalPixelSize")) { + width = getFloatAttribute(child, "value"); + gotWidth = true; + } else if (childName.equals("VerticalPixelSize")) { + height = getFloatAttribute(child, "value"); + gotHeight = true; +// } else if (childName.equals("ImageOrientation")) { +// } else if +// (childName.equals("HorizontalPhysicalPixelSpacing")) { +// } else if +// (childName.equals("VerticalPhysicalPixelSpacing")) { +// } else if (childName.equals("HorizontalPosition")) { +// } else if (childName.equals("VerticalPosition")) { +// } else if (childName.equals("HorizontalPixelOffset")) { +// } else if (childName.equals("VerticalPixelOffset")) { + } + child = child.getNextSibling(); + } + + if (gotWidth && gotHeight) { + pHYs_present = true; + pHYs_unitSpecifier = 1; + pHYs_pixelsPerUnitXAxis = (int)(width*1000 + 0.5F); + pHYs_pixelsPerUnitYAxis = (int)(height*1000 + 0.5F); + } else if (gotAspectRatio) { + pHYs_present = true; + pHYs_unitSpecifier = 0; + + // Find a reasonable rational approximation + int denom = 1; + for (; denom < 100; denom++) { + int num = (int)(aspectRatio*denom); + if (Math.abs(num/denom - aspectRatio) < 0.001) { + break; + } + } + pHYs_pixelsPerUnitXAxis = (int)(aspectRatio*denom); + pHYs_pixelsPerUnitYAxis = denom; + } + } else if (name.equals("Document")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("ImageModificationTime")) { + tIME_present = true; + tIME_year = getIntAttribute(child, "year"); + tIME_month = getIntAttribute(child, "month"); + tIME_day = getIntAttribute(child, "day"); + tIME_hour = + getIntAttribute(child, "hour", 0, false); + tIME_minute = + getIntAttribute(child, "minute", 0, false); + tIME_second = + getIntAttribute(child, "second", 0, false); +// } else if (childName.equals("SubimageInterpretation")) { +// } else if (childName.equals("ImageCreationTime")) { + } + child = child.getNextSibling(); + } + } else if (name.equals("Text")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("TextEntry")) { + String keyword = getAttribute(child, "keyword"); + String value = getAttribute(child, "value"); + String encoding = getAttribute(child, "encoding"); + String language = getAttribute(child, "language"); + String compression = + getAttribute(child, "compression"); + + if (isISOLatin(value)) { + if (compression.equals("zip")) { + // Use a zTXt node + zTXt_keyword.add(keyword); + zTXt_text.add(value); + zTXt_compressionMethod.add(new Integer(0)); + } else { + // Use a tEXt node + tEXt_keyword.add(keyword); + tEXt_text.add(value); + } + } else { + // Use an iTXt node + iTXt_keyword.add(keyword); + iTXt_compressionFlag.add(Boolean.valueOf(compression.equals("zip"))); + iTXt_compressionMethod.add(Integer.valueOf(0)); + iTXt_languageTag.add(language); + iTXt_translatedKeyword.add(keyword); // fake it + iTXt_text.add(value); + } + } + child = child.getNextSibling(); + } +// } else if (name.equals("Transparency")) { +// Node child = node.getFirstChild(); +// while (child != null) { +// String childName = child.getNodeName(); +// if (childName.equals("Alpha")) { +// } else if (childName.equals("TransparentIndex")) { +// } else if (childName.equals("TransparentColor")) { +// } else if (childName.equals("TileTransparencies")) { +// } else if (childName.equals("TileOpacities")) { +// } +// child = child.getNextSibling(); +// } +// } else { +// // fatal(node, "Unknown child of root node!"); + } + + node = node.getNextSibling(); + } + } + + // Reset all instance variables to their initial state + public void reset() { + IHDR_present = false; + PLTE_present = false; + bKGD_present = false; + cHRM_present = false; + gAMA_present = false; + hIST_present = false; + iCCP_present = false; + iTXt_keyword = new ArrayList<String>(); + iTXt_compressionFlag = new ArrayList<Boolean>(); + iTXt_compressionMethod = new ArrayList<Integer>(); + iTXt_languageTag = new ArrayList<String>(); + iTXt_translatedKeyword = new ArrayList<String>(); + iTXt_text = new ArrayList<String>(); + pHYs_present = false; + sBIT_present = false; + sPLT_present = false; + sRGB_present = false; + tEXt_keyword = new ArrayList(); + tEXt_text = new ArrayList(); + tIME_present = false; + tRNS_present = false; + zTXt_keyword = new ArrayList(); + zTXt_compressionMethod = new ArrayList(); + zTXt_text = new ArrayList(); + unknownChunkType = new ArrayList(); + unknownChunkData = new ArrayList(); + } +} diff -urN openjdk.orig/jdk/test/javax/imageio/plugins/png/ItxtUtf8Test.java openjdk/jdk/test/javax/imageio/plugins/png/ItxtUtf8Test.java --- openjdk.orig/jdk/test/javax/imageio/plugins/png/ItxtUtf8Test.java 1969-12-31 19:00:00.000000000 -0500 +++ openjdk/jdk/test/javax/imageio/plugins/png/ItxtUtf8Test.java 2010-12-24 11:25:19.144101998 -0500 @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2008, 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. + * + * 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. + */ + +/** + * @test + * @bug 6541476 6782079 + * @summary Write and read a PNG file including an non-latin1 iTXt chunk + * Test also verifies that trunkated png images does not cause + * an OoutOfMemory error. + * + * @run main ItxtUtf8Test + * + * @run main/othervm/timeout=10 -Xmx2m ItxtUtf8Test truncate + */ + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.MemoryCacheImageInputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.bootstrap.DOMImplementationRegistry; + +public class ItxtUtf8Test { + + public static final String + TEXT = "\u24c9\u24d4\u24e7\u24e3" + + "\ud835\udc13\ud835\udc1e\ud835\udc31\ud835\udc2d" + + "\u24c9\u24d4\u24e7\u24e3", // a repetition for compression + VERBATIM = "\u24e5\u24d4\u24e1\u24d1\u24d0\u24e3\u24d8\u24dc", + COMPRESSED = "\u24d2\u24de\u24dc\u24df\u24e1\u24d4\u24e2\u24e2\u24d4\u24d3"; + + public static final byte[] + VBYTES = { + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x56, // chunk length + (byte)0x69, (byte)0x54, (byte)0x58, (byte)0x74, // chunk type "iTXt" + (byte)0x76, (byte)0x65, (byte)0x72, (byte)0x62, + (byte)0x61, (byte)0x74, (byte)0x69, (byte)0x6d, // keyword "verbatim" + (byte)0x00, // separator terminating keyword + (byte)0x00, // compression flag + (byte)0x00, // compression method, must be zero + (byte)0x78, (byte)0x2d, (byte)0x63, (byte)0x69, + (byte)0x72, (byte)0x63, (byte)0x6c, (byte)0x65, + (byte)0x64, // language tag "x-circled" + (byte)0x00, // separator terminating language tag + (byte)0xe2, (byte)0x93, (byte)0xa5, // '\u24e5' + (byte)0xe2, (byte)0x93, (byte)0x94, // '\u24d4' + (byte)0xe2, (byte)0x93, (byte)0xa1, // '\u24e1' + (byte)0xe2, (byte)0x93, (byte)0x91, // '\u24d1' + (byte)0xe2, (byte)0x93, (byte)0x90, // '\u24d0' + (byte)0xe2, (byte)0x93, (byte)0xa3, // '\u24e3' + (byte)0xe2, (byte)0x93, (byte)0x98, // '\u24d8' + (byte)0xe2, (byte)0x93, (byte)0x9c, // '\u24dc' + (byte)0x00, // separator terminating the translated keyword + (byte)0xe2, (byte)0x93, (byte)0x89, // '\u24c9' + (byte)0xe2, (byte)0x93, (byte)0x94, // '\u24d4' + (byte)0xe2, (byte)0x93, (byte)0xa7, // '\u24e7' + (byte)0xe2, (byte)0x93, (byte)0xa3, // '\u24e3' + (byte)0xf0, (byte)0x9d, (byte)0x90, (byte)0x93, // '\ud835\udc13' + (byte)0xf0, (byte)0x9d, (byte)0x90, (byte)0x9e, // '\ud835\udc1e' + (byte)0xf0, (byte)0x9d, (byte)0x90, (byte)0xb1, // '\ud835\udc31' + (byte)0xf0, (byte)0x9d, (byte)0x90, (byte)0xad, // '\ud835\udc2d' + (byte)0xe2, (byte)0x93, (byte)0x89, // '\u24c9' + (byte)0xe2, (byte)0x93, (byte)0x94, // '\u24d4' + (byte)0xe2, (byte)0x93, (byte)0xa7, // '\u24e7' + (byte)0xe2, (byte)0x93, (byte)0xa3, // '\u24e3' + (byte)0xb5, (byte)0xcc, (byte)0x97, (byte)0x56 // CRC + }, + CBYTES = { + // we don't want to check the chunk length, + // as this might depend on implementation. + (byte)0x69, (byte)0x54, (byte)0x58, (byte)0x74, // chunk type "iTXt" + (byte)0x63, (byte)0x6f, (byte)0x6d, (byte)0x70, + (byte)0x72, (byte)0x65, (byte)0x73, (byte)0x73, + (byte)0x65, (byte)0x64, // keyword "compressed" + (byte)0x00, // separator terminating keyword + (byte)0x01, // compression flag + (byte)0x00, // compression method, 0=deflate + (byte)0x78, (byte)0x2d, (byte)0x63, (byte)0x69, + (byte)0x72, (byte)0x63, (byte)0x6c, (byte)0x65, + (byte)0x64, // language tag "x-circled" + (byte)0x00, // separator terminating language tag + // we don't want to check the actual compressed data, + // as this might depend on implementation. + }; +/* +*/ + + public static void main(String[] args) throws Exception { + List argList = Arrays.asList(args); + if (argList.contains("truncate")) { + try { + runTest(false, true); + throw new AssertionError("Expect an error for truncated file"); + } + catch (IIOException e) { + // expected an error for a truncated image file. + } + } + else { + runTest(argList.contains("dump"), false); + } + } + + public static void runTest(boolean dump, boolean truncate) + throws Exception + { + String format = "javax_imageio_png_1.0"; + BufferedImage img = + new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB); + ImageWriter iw = ImageIO.getImageWritersByMIMEType("image/png").next(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ImageOutputStream ios = new MemoryCacheImageOutputStream(os); + iw.setOutput(ios); + IIOMetadata meta = + iw.getDefaultImageMetadata(new ImageTypeSpecifier(img), null); + DOMImplementationRegistry registry; + registry = DOMImplementationRegistry.newInstance(); + DOMImplementation impl = registry.getDOMImplementation("XML 3.0"); + Document doc = impl.createDocument(null, format, null); + Element root, itxt, entry; + root = doc.getDocumentElement(); + root.appendChild(itxt = doc.createElement("iTXt")); + itxt.appendChild(entry = doc.createElement("iTXtEntry")); + entry.setAttribute("keyword", "verbatim"); + entry.setAttribute("compressionFlag", "false"); + entry.setAttribute("compressionMethod", "0"); + entry.setAttribute("languageTag", "x-circled"); + entry.setAttribute("translatedKeyword", VERBATIM); + entry.setAttribute("text", TEXT); + itxt.appendChild(entry = doc.createElement("iTXtEntry")); + entry.setAttribute("keyword", "compressed"); + entry.setAttribute("compressionFlag", "true"); + entry.setAttribute("compressionMethod", "0"); + entry.setAttribute("languageTag", "x-circled"); + entry.setAttribute("translatedKeyword", COMPRESSED); + entry.setAttribute("text", TEXT); + meta.mergeTree(format, root); + iw.write(new IIOImage(img, null, meta)); + iw.dispose(); + + byte[] bytes = os.toByteArray(); + if (dump) + System.out.write(bytes); + if (findBytes(VBYTES, bytes) < 0) + throw new AssertionError("verbatim block not found"); + if (findBytes(CBYTES, bytes) < 0) + throw new AssertionError("compressed block not found"); + int length = bytes.length; + if (truncate) + length = findBytes(VBYTES, bytes) + 32; + + ImageReader ir = ImageIO.getImageReader(iw); + ByteArrayInputStream is = new ByteArrayInputStream(bytes, 0, length); + ImageInputStream iis = new MemoryCacheImageInputStream(is); + ir.setInput(iis); + meta = ir.getImageMetadata(0); + Node node = meta.getAsTree(format); + for (node = node.getFirstChild(); + !"iTXt".equals(node.getNodeName()); + node = node.getNextSibling()); + boolean verbatimSeen = false, compressedSeen = false; + for (node = node.getFirstChild(); + node != null; + node = node.getNextSibling()) { + entry = (Element)node; + String keyword = entry.getAttribute("keyword"); + String translatedKeyword = entry.getAttribute("translatedKeyword"); + String text = entry.getAttribute("text"); + if ("verbatim".equals(keyword)) { + if (verbatimSeen) throw new AssertionError("Duplicate"); + verbatimSeen = true; + if (!VERBATIM.equals(translatedKeyword)) + throw new AssertionError("Wrong translated keyword"); + if (!TEXT.equals(text)) + throw new AssertionError("Wrong text"); + } + else if ("compressed".equals(keyword)) { + if (compressedSeen) throw new AssertionError("Duplicate"); + compressedSeen = true; + if (!COMPRESSED.equals(translatedKeyword)) + throw new AssertionError("Wrong translated keyword"); + if (!TEXT.equals(text)) + throw new AssertionError("Wrong text"); + } + else { + throw new AssertionError("Unexpected keyword"); + } + } + if (!(verbatimSeen && compressedSeen)) + throw new AssertionError("Missing chunk"); + } + + private static final int findBytes(byte[] needle, byte[] haystack) { + HAYSTACK: for (int h = 0; h <= haystack.length - needle.length; ++h) { + for (int n = 0; n < needle.length; ++n) { + if (needle[n] != haystack[h + n]) { + continue HAYSTACK; + } + } + return h; + } + return -1; + } + +}