# HG changeset patch # User Deepak Bhole # Date 1294149049 18000 # Node ID 3eec8b65af65d2c505c1a94dd56b8b4cc7604436 # Parent c39dead41c57183e1dfbfb3865090658076765a8 Backport S6687968, S6541476, S6782079 iTXt chunk handling for png now works correctly. diff -r c39dead41c57 -r 3eec8b65af65 ChangeLog --- a/ChangeLog Thu Dec 23 15:52:54 2010 +0100 +++ b/ChangeLog Tue Jan 04 08:50:49 2011 -0500 @@ -1,4 +1,18 @@ +2010-12-24 Deepak Bhole + + Backport S6687968, S6541476, S6782079 + * Makefile.am: Add missing "\" to the patch list, update to include + new patches. + * NEWS: Updated. + * patches/openjdk/6687968-pngimagereader_mem_leak.patch: Backport to allow + S6541476 fix to apply cleanly. + * patches/openjdk/6541476-png-iTXt-chunk.patch: Backport to fix iTXt chunk + handling for png images. Also addresses RH665355. + * patches/openjdk/6782079-png_metadata_oom.patch: Backport to fix + potential OOM error when reading metadata on truncated pngs. + 2010-12-23 Jiri Vanek + * NEWS: Updated with rh.bugzilas. * patches/fonts-rhel.patch:repaired rhel 6 fonts configuration. diff -r c39dead41c57 -r 3eec8b65af65 Makefile.am --- a/Makefile.am Thu Dec 23 15:52:54 2010 +0100 +++ b/Makefile.am Tue Jan 04 08:50:49 2011 -0500 @@ -380,7 +380,10 @@ patches/openjdk/6438179-systray_check.patch \ patches/openjdk/4356282-opentype.patch \ patches/openjdk/6954424-opentype_javadoc.patch \ - patches/openjdk/6795356-proxylazyvalue-leak.patch + patches/openjdk/6795356-proxylazyvalue-leak.patch \ + patches/openjdk/6687968-pngimagereader_mem_leak.patch \ + patches/openjdk/6541476-png-iTXt-chunk.patch \ + patches/openjdk/6782079-png_metadata_oom.patch if WITH_ALT_HSBUILD ICEDTEA_PATCHES += patches/hotspot/$(HSBUILD)/openjdk-6886353-ignore_deoptimizealot.patch \ diff -r c39dead41c57 -r 3eec8b65af65 NEWS --- a/NEWS Thu Dec 23 15:52:54 2010 +0100 +++ b/NEWS Tue Jan 04 08:50:49 2011 -0500 @@ -18,6 +18,9 @@ - S6967433: dashed lines broken when using scaling transforms. - S6976265: No STROKE_CONTROL - S6967434, PR450, RH530642: Round joins/caps of scaled up lines have poor quality. + - S6687968: PNGImageReader leaks native memory through an Inflater + - S6541476, RH665355: PNG imageio plugin incorrectly handles iTXt chunk + - S6782079: PNG: reading metadata may cause OOM on truncated images * Fixes: - S7003777, RH647674: JTextPane produces incorrect content after parsing the html text - RH647157,RH582455: Update fontconfig files for rhel 6 diff -r c39dead41c57 -r 3eec8b65af65 patches/openjdk/6541476-png-iTXt-chunk.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/patches/openjdk/6541476-png-iTXt-chunk.patch Tue Jan 04 08:50:49 2011 -0500 @@ -0,0 +1,549 @@ +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-23 17:11:16.193446425 -0500 ++++ openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java 2010-12-23 17:11:29.399447037 -0500 +@@ -44,7 +44,6 @@ + import java.util.Arrays; + import java.util.Enumeration; + import java.util.Iterator; +-import java.util.List; + import java.util.zip.Inflater; + import java.util.zip.InflaterInputStream; + import javax.imageio.IIOException; +@@ -57,6 +56,7 @@ + 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 { +@@ -209,6 +209,15 @@ + 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; +@@ -447,26 +456,27 @@ + metadata.iTXt_keyword.add(keyword); + + int compressionFlag = stream.readUnsignedByte(); +- metadata.iTXt_compressionFlag.add(new Integer(compressionFlag)); ++ metadata.iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag == 1)); + + int compressionMethod = stream.readUnsignedByte(); +- metadata.iTXt_compressionMethod.add(new Integer(compressionMethod)); ++ metadata.iTXt_compressionMethod.add(Integer.valueOf(compressionMethod)); + +- String languageTag = readNullTerminatedString(); ++ String languageTag = readNullTerminatedString("UTF8"); + metadata.iTXt_languageTag.add(languageTag); + +- String translatedKeyword = stream.readUTF(); ++ String translatedKeyword = ++ readNullTerminatedString("UTF8"); + metadata.iTXt_translatedKeyword.add(translatedKeyword); +- stream.skipBytes(1); // Null separator + + String text; ++ long pos = stream.getStreamPosition(); ++ byte[] b = new byte[(int)(chunkStart + chunkLength - pos)]; ++ stream.readFully(b); ++ + if (compressionFlag == 1) { // Decompress the text +- long pos = stream.getStreamPosition(); +- byte[] b = new byte[(int)(chunkStart + chunkLength - pos)]; +- stream.readFully(b); +- text = inflate(b); ++ text = new String(inflate(b), "UTF8"); + } else { +- text = stream.readUTF(); ++ text = new String(b, "UTF8"); + } + metadata.iTXt_text.add(text); + } +@@ -615,20 +625,20 @@ + metadata.tRNS_present = true; + } + +- private static String inflate(byte[] b) throws IOException { ++ private static byte[] inflate(byte[] b) throws IOException { + InputStream bais = new ByteArrayInputStream(b); + InputStream iis = new InflaterInputStream(bais); ++ ByteArrayOutputStream baos = new ByteArrayOutputStream(); + +- StringBuilder sb = new StringBuilder(80); + int c; + try { + while ((c = iis.read()) != -1) { +- sb.append((char)c); ++ baos.write(c); + } + } finally { + iis.close(); + } +- return sb.toString(); ++ return baos.toByteArray(); + } + + private void parse_zTXt_chunk(int chunkLength) throws IOException { +@@ -640,7 +650,7 @@ + + byte[] b = new byte[chunkLength - keyword.length() - 2]; + stream.readFully(b); +- metadata.zTXt_text.add(inflate(b)); ++ metadata.zTXt_text.add(new String(inflate(b))); + } + + private void readMetadata() throws IIOException { +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-23 17:11:16.193446425 -0500 ++++ openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java 2010-12-23 17:11:29.400446993 -0500 +@@ -671,13 +671,13 @@ + } + } + +- private byte[] deflate(String s) throws IOException { ++ private byte[] deflate(byte[] b) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DeflaterOutputStream dos = new DeflaterOutputStream(baos); + +- int len = s.length(); ++ int len = b.length; + for (int i = 0; i < len; i++) { +- dos.write((int)s.charAt(i)); ++ dos.write((int)(0xff & b[i])); + } + dos.close(); + +@@ -685,38 +685,37 @@ + } + + private void write_iTXt() throws IOException { +- Iterator keywordIter = metadata.iTXt_keyword.iterator(); +- Iterator flagIter = metadata.iTXt_compressionFlag.iterator(); +- Iterator methodIter = metadata.iTXt_compressionMethod.iterator(); +- Iterator languageIter = metadata.iTXt_languageTag.iterator(); +- Iterator translatedKeywordIter = ++ Iterator keywordIter = metadata.iTXt_keyword.iterator(); ++ Iterator flagIter = metadata.iTXt_compressionFlag.iterator(); ++ Iterator methodIter = metadata.iTXt_compressionMethod.iterator(); ++ Iterator languageIter = metadata.iTXt_languageTag.iterator(); ++ Iterator translatedKeywordIter = + metadata.iTXt_translatedKeyword.iterator(); +- Iterator textIter = metadata.iTXt_text.iterator(); ++ Iterator textIter = metadata.iTXt_text.iterator(); + + while (keywordIter.hasNext()) { + ChunkStream cs = new ChunkStream(PNGImageReader.iTXt_TYPE, stream); +- String keyword = (String)keywordIter.next(); +- cs.writeBytes(keyword); ++ ++ cs.writeBytes(keywordIter.next()); + cs.writeByte(0); + +- int flag = ((Integer)flagIter.next()).intValue(); +- cs.writeByte(flag); +- int method = ((Integer)methodIter.next()).intValue(); +- cs.writeByte(method); ++ Boolean compressed = flagIter.next(); ++ cs.writeByte(compressed ? 1 : 0); + +- String languageTag = (String)languageIter.next(); +- cs.writeBytes(languageTag); ++ cs.writeByte(methodIter.next().intValue()); ++ ++ cs.writeBytes(languageIter.next()); + cs.writeByte(0); + +- String translatedKeyword = (String)translatedKeywordIter.next(); +- cs.writeBytes(translatedKeyword); ++ ++ cs.write(translatedKeywordIter.next().getBytes("UTF8")); + cs.writeByte(0); + +- String text = (String)textIter.next(); +- if (flag == 1) { +- cs.write(deflate(text)); ++ String text = textIter.next(); ++ if (compressed) { ++ cs.write(deflate(text.getBytes("UTF8"))); + } else { +- cs.writeUTF(text); ++ cs.write(text.getBytes("UTF8")); + } + cs.finish(); + } +@@ -737,7 +736,7 @@ + cs.writeByte(compressionMethod); + + String text = (String)textIter.next(); +- cs.write(deflate(text)); ++ cs.write(deflate(text.getBytes())); + 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-06-21 17:15:11.000000000 -0400 ++++ openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java 2010-12-23 17:11:29.401446953 -0500 +@@ -176,12 +176,12 @@ + public byte[] iCCP_compressedProfile; + + // iTXt chunk +- public ArrayList iTXt_keyword = new ArrayList(); // Strings +- public ArrayList iTXt_compressionFlag = new ArrayList(); // Integers +- public ArrayList iTXt_compressionMethod = new ArrayList(); // Integers +- public ArrayList iTXt_languageTag = new ArrayList(); // Strings +- public ArrayList iTXt_translatedKeyword = new ArrayList(); // Strings +- public ArrayList iTXt_text = new ArrayList(); // Strings ++ public ArrayList iTXt_keyword = new ArrayList(); ++ public ArrayList iTXt_compressionFlag = new ArrayList(); ++ public ArrayList iTXt_compressionMethod = new ArrayList(); ++ public ArrayList iTXt_languageTag = new ArrayList(); ++ public ArrayList iTXt_translatedKeyword = new ArrayList(); ++ public ArrayList iTXt_text = new ArrayList(); + + // pHYs chunk + public boolean pHYs_present; +@@ -599,19 +599,17 @@ + if (iTXt_keyword.size() > 0) { + IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt"); + for (int i = 0; i < iTXt_keyword.size(); i++) { +- Integer val; +- + IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry"); +- iTXt_node.setAttribute("keyword", (String)iTXt_keyword.get(i)); +- val = (Integer)iTXt_compressionFlag.get(i); +- iTXt_node.setAttribute("compressionFlag", val.toString()); +- val = (Integer)iTXt_compressionMethod.get(i); +- iTXt_node.setAttribute("compressionMethod", val.toString()); ++ 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", +- (String)iTXt_languageTag.get(i)); ++ iTXt_languageTag.get(i)); + iTXt_node.setAttribute("translatedKeyword", +- (String)iTXt_translatedKeyword.get(i)); +- iTXt_node.setAttribute("text", (String)iTXt_text.get(i)); ++ iTXt_translatedKeyword.get(i)); ++ iTXt_node.setAttribute("text", iTXt_text.get(i)); + + iTXt_parent.appendChild(iTXt_node); + } +@@ -1039,11 +1037,11 @@ + + for (int i = 0; i < iTXt_keyword.size(); i++) { + node = new IIOMetadataNode("TextEntry"); +- node.setAttribute("keyword", (String)iTXt_keyword.get(i)); +- node.setAttribute("value", (String)iTXt_text.get(i)); ++ node.setAttribute("keyword", iTXt_keyword.get(i)); ++ node.setAttribute("value", iTXt_text.get(i)); + node.setAttribute("language", +- (String)iTXt_languageTag.get(i)); +- if (((Integer)iTXt_compressionFlag.get(i)).intValue() == 1) { ++ iTXt_languageTag.get(i)); ++ if (iTXt_compressionFlag.get(i)) { + node.setAttribute("compression", "deflate"); + } else { + node.setAttribute("compression", "none"); +@@ -1429,11 +1427,11 @@ + + boolean compressionFlag = + getBooleanAttribute(iTXt_node, "compressionFlag"); +- iTXt_compressionFlag.add(new Boolean(compressionFlag)); ++ iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag)); + + String compressionMethod = + getAttribute(iTXt_node, "compressionMethod"); +- iTXt_compressionMethod.add(compressionMethod); ++ iTXt_compressionMethod.add(Integer.valueOf(compressionMethod)); + + String languageTag = + getAttribute(iTXt_node, "languageTag"); +@@ -1952,13 +1950,10 @@ + tEXt_text.add(value); + } + } else { +- int flag = compression.equals("zip") ? +- 1 : 0; +- + // Use an iTXt node + iTXt_keyword.add(keyword); +- iTXt_compressionFlag.add(new Integer(flag)); +- iTXt_compressionMethod.add(new Integer(0)); ++ 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); +@@ -1995,12 +1990,12 @@ + gAMA_present = false; + hIST_present = false; + iCCP_present = false; +- iTXt_keyword = new ArrayList(); +- iTXt_compressionFlag = new ArrayList(); +- iTXt_compressionMethod = new ArrayList(); +- iTXt_languageTag = new ArrayList(); +- iTXt_translatedKeyword = new ArrayList(); +- iTXt_text = new ArrayList(); ++ iTXt_keyword = new ArrayList(); ++ iTXt_compressionFlag = new ArrayList(); ++ iTXt_compressionMethod = new ArrayList(); ++ iTXt_languageTag = new ArrayList(); ++ iTXt_translatedKeyword = new ArrayList(); ++ iTXt_text = new ArrayList(); + pHYs_present = false; + sBIT_present = false; + sPLT_present = false; +diff -urN openjdk.orig/jdk/test/javax/imageio/plugins/png/ITXtTest.java openjdk/jdk/test/javax/imageio/plugins/png/ITXtTest.java +--- openjdk.orig/jdk/test/javax/imageio/plugins/png/ITXtTest.java 1969-12-31 19:00:00.000000000 -0500 ++++ openjdk/jdk/test/javax/imageio/plugins/png/ITXtTest.java 2010-12-23 17:12:58.538446639 -0500 +@@ -0,0 +1,236 @@ ++/* ++ * 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 ++ * @summary Test verifies that ImageIO PNG plugin correcly handles the ++ * iTxt chunk (International textual data). ++ * ++ * @run main ITXtTest ++ */ ++ ++ ++import java.awt.Color; ++import java.awt.Graphics2D; ++import java.awt.image.BufferedImage; ++import java.io.File; ++ ++import javax.imageio.ImageIO; ++import javax.imageio.ImageReader; ++import javax.imageio.IIOImage; ++import javax.imageio.ImageTypeSpecifier; ++import javax.imageio.ImageWriter; ++import javax.imageio.metadata.IIOMetadata; ++import javax.imageio.metadata.IIOMetadataNode; ++import javax.imageio.stream.ImageOutputStream; ++import javax.imageio.stream.ImageInputStream; ++ ++import org.w3c.dom.Node; ++ ++public class ITXtTest { ++ static public void main(String args[]) { ++ ITXtTest t_en = new ITXtTest(); ++ t_en.description = "xml - en"; ++ t_en.keyword = "XML:com.adobe.xmp"; ++ t_en.isCompressed = false; ++ t_en.compression = 0; ++ t_en.language = "en"; ++ t_en.trasKeyword = "XML:com.adobe.xmp"; ++ t_en.text = "Something"; ++ ++ doTest(t_en); ++ ++ // check compression case ++ t_en.isCompressed = true; ++ t_en.description = "xml - en - compressed"; ++ ++ doTest(t_en); ++ ++ ITXtTest t_ru = new ITXtTest(); ++ t_ru.description = "xml - ru"; ++ t_ru.keyword = "XML:com.adobe.xmp"; ++ t_ru.isCompressed = false; ++ t_ru.compression = 0; ++ t_ru.language = "ru"; ++ t_ru.trasKeyword = "\u0410\u0410\u0410\u0410\u0410 XML"; ++ t_ru.text = "\u042A\u042F\u042F\u042F\u042F\u042F\u042F"; ++ ++ doTest(t_ru); ++ ++ t_ru.isCompressed = true; ++ t_ru.description = "xml - ru - compressed"; ++ ++ doTest(t_ru); ++ } ++ ++ ++ String description; ++ ++ String keyword; ++ boolean isCompressed; ++ int compression; ++ String language; ++ String trasKeyword; ++ String text; ++ ++ ++ public IIOMetadataNode getNode() { ++ IIOMetadataNode iTXt = new IIOMetadataNode("iTXt"); ++ IIOMetadataNode iTXtEntry = new IIOMetadataNode("iTXtEntry"); ++ iTXtEntry.setAttribute("keyword", keyword); ++ iTXtEntry.setAttribute("compressionFlag", ++ isCompressed ? "true" : "false"); ++ iTXtEntry.setAttribute("compressionMethod", ++ Integer.toString(compression)); ++ iTXtEntry.setAttribute("languageTag", language); ++ iTXtEntry.setAttribute("translatedKeyword", ++ trasKeyword); ++ iTXtEntry.setAttribute("text", text); ++ iTXt.appendChild(iTXtEntry); ++ return iTXt; ++ } ++ ++ public static ITXtTest getFromNode(IIOMetadataNode n) { ++ ITXtTest t = new ITXtTest(); ++ ++ if (!"iTXt".equals(n.getNodeName())) { ++ throw new RuntimeException("Invalid node"); ++ } ++ IIOMetadataNode e = (IIOMetadataNode)n.getFirstChild(); ++ if (!"iTXtEntry".equals(e.getNodeName())) { ++ throw new RuntimeException("Invalid entry node"); ++ } ++ t.keyword = e.getAttribute("keyword"); ++ t.isCompressed = ++ (Integer.valueOf(e.getAttribute("compressionFlag")).intValue() == 1); ++ t.compression = ++ Integer.valueOf(e.getAttribute("compressionMethod")).intValue(); ++ t.language = e.getAttribute("languageTag"); ++ t.trasKeyword = e.getAttribute("translatedKeyword"); ++ t.text = e.getAttribute("text"); ++ ++ return t; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (! (o instanceof ITXtTest)) { ++ return false; ++ } ++ ITXtTest t = (ITXtTest)o; ++ if (!keyword.equals(t.keyword)) { return false; } ++ if (isCompressed != t.isCompressed) { return false; } ++ if (compression != t.compression) { return false; } ++ if (!language.equals(t.language)) { return false; } ++ if (!trasKeyword.equals(t.trasKeyword)) { return false; } ++ if (!text.equals(t.text)) { return false; } ++ ++ return true; ++ } ++ ++ ++ ++ private static void doTest(ITXtTest src) { ++ ++ System.out.println("Test: " + src.description); ++ ++ File file = new File("test.png"); ++ ++ writeTo(file, src); ++ ITXtTest dst = readFrom(file); ++ ++ if (dst == null || !dst.equals(src)) { ++ throw new RuntimeException("Test failed."); ++ } ++ ++ System.out.println("Test passed."); ++ } ++ ++ private static void writeTo(File f, ITXtTest t) { ++ BufferedImage src = createBufferedImage(); ++ try { ++ ImageOutputStream imageOutputStream = ++ ImageIO.createImageOutputStream(f); ++ ++ ImageTypeSpecifier imageTypeSpecifier = ++ new ImageTypeSpecifier(src); ++ ImageWriter imageWriter = ++ ImageIO.getImageWritersByFormatName("PNG").next(); ++ ++ imageWriter.setOutput(imageOutputStream); ++ ++ IIOMetadata m = ++ imageWriter.getDefaultImageMetadata(imageTypeSpecifier, null); ++ ++ String format = m.getNativeMetadataFormatName(); ++ Node root = m.getAsTree(format); ++ ++ IIOMetadataNode iTXt = t.getNode(); ++ root.appendChild(iTXt); ++ m.setFromTree(format, root); ++ ++ imageWriter.write(new IIOImage(src, null, m)); ++ imageOutputStream.close(); ++ System.out.println("Writing done."); ++ } catch (Throwable e) { ++ throw new RuntimeException("Writing test failed.", e); ++ } ++ } ++ ++ private static ITXtTest readFrom(File f) { ++ try { ++ ImageInputStream iis = ImageIO.createImageInputStream(f); ++ ImageReader r = ImageIO.getImageReaders(iis).next(); ++ r.setInput(iis); ++ ++ IIOImage dst = r.readAll(0, null); ++ ++ // look for iTXt node ++ IIOMetadata m = dst.getMetadata(); ++ Node root = m.getAsTree(m.getNativeMetadataFormatName()); ++ Node n = root.getFirstChild(); ++ while (n != null && !"iTXt".equals(n.getNodeName())) { ++ n = n.getNextSibling(); ++ } ++ if (n == null) { ++ throw new RuntimeException("No iTXt node!"); ++ } ++ ITXtTest t = ITXtTest.getFromNode((IIOMetadataNode)n); ++ return t; ++ } catch (Throwable e) { ++ throw new RuntimeException("Reading test failed.", e); ++ } ++ } ++ ++ private static BufferedImage createBufferedImage() { ++ BufferedImage image = new BufferedImage(128, 128, ++ BufferedImage.TYPE_4BYTE_ABGR_PRE); ++ Graphics2D graph = image.createGraphics(); ++ graph.setPaintMode(); ++ graph.setColor(Color.orange); ++ graph.fillRect(32, 32, 64, 64); ++ graph.dispose(); ++ return image; ++ } ++} diff -r c39dead41c57 -r 3eec8b65af65 patches/openjdk/6687968-pngimagereader_mem_leak.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/patches/openjdk/6687968-pngimagereader_mem_leak.patch Tue Jan 04 08:50:49 2011 -0500 @@ -0,0 +1,126 @@ +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-06-21 17:15:11.000000000 -0400 ++++ openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java 2010-12-23 15:12:24.471446202 -0500 +@@ -618,10 +618,15 @@ + private static String inflate(byte[] b) throws IOException { + InputStream bais = new ByteArrayInputStream(b); + InputStream iis = new InflaterInputStream(bais); ++ + StringBuilder sb = new StringBuilder(80); + int c; +- while ((c = iis.read()) != -1) { +- sb.append((char)c); ++ try { ++ while ((c = iis.read()) != -1) { ++ sb.append((char)c); ++ } ++ } finally { ++ iis.close(); + } + return sb.toString(); + } +@@ -1246,13 +1251,26 @@ + destinationBands = param.getDestinationBands(); + destinationOffset = param.getDestinationOffset(); + } +- ++ Inflater inf = null; + try { + stream.seek(imageStartPosition); + + Enumeration e = new PNGImageDataEnumeration(stream); + InputStream is = new SequenceInputStream(e); +- is = new InflaterInputStream(is, new Inflater()); ++ ++ /* 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); + +@@ -1285,6 +1303,10 @@ + } + } catch (IOException e) { + throw new IIOException("Error reading PNG image data", e); ++ } finally { ++ if (inf != null) { ++ inf.end(); ++ } + } + } + +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-06-21 17:15:11.000000000 -0400 ++++ openjdk/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java 2010-12-23 15:12:24.472446225 -0500 +@@ -244,13 +244,17 @@ + } + + public void finish() throws IOException { +- if (!def.finished()) { +- def.finish(); +- while (!def.finished()) { +- deflate(); ++ try { ++ if (!def.finished()) { ++ def.finish(); ++ while (!def.finished()) { ++ deflate(); ++ } + } ++ finishChunk(); ++ } finally { ++ def.end(); + } +- finishChunk(); + } + + protected void finalize() throws Throwable { +@@ -928,23 +932,24 @@ + // Use sourceXOffset, etc. + private void write_IDAT(RenderedImage image) throws IOException { + IDATOutputStream ios = new IDATOutputStream(stream, 32768); +- +- if (metadata.IHDR_interlaceMethod == 1) { +- for (int i = 0; i < 7; i++) { +- encodePass(ios, image, +- PNGImageReader.adam7XOffset[i], +- PNGImageReader.adam7YOffset[i], +- PNGImageReader.adam7XSubsampling[i], +- PNGImageReader.adam7YSubsampling[i]); +- if (abortRequested()) { +- break; ++ try { ++ if (metadata.IHDR_interlaceMethod == 1) { ++ for (int i = 0; i < 7; i++) { ++ encodePass(ios, image, ++ PNGImageReader.adam7XOffset[i], ++ PNGImageReader.adam7YOffset[i], ++ PNGImageReader.adam7XSubsampling[i], ++ PNGImageReader.adam7YSubsampling[i]); ++ if (abortRequested()) { ++ break; ++ } + } ++ } else { ++ encodePass(ios, image, 0, 0, 1, 1); + } +- } else { +- encodePass(ios, image, 0, 0, 1, 1); ++ } finally { ++ ios.finish(); + } +- +- ios.finish(); + } + + private void writeIEND() throws IOException { diff -r c39dead41c57 -r 3eec8b65af65 patches/openjdk/6782079-png_metadata_oom.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/patches/openjdk/6782079-png_metadata_oom.patch Tue Jan 04 08:50:49 2011 -0500 @@ -0,0 +1,4122 @@ +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 { + + 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 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 getImageTypes(int imageIndex) ++ throws IIOException ++ { ++ if (imageIndex != 0) { ++ throw new IndexOutOfBoundsException("imageIndex != 0!"); ++ } ++ ++ readHeader(); ++ ++ ArrayList l = ++ new ArrayList(1); ++ ++ ColorSpace rgb; ++ ColorSpace gray; ++ int[] bandOffsets; ++ ++ int bitDepth = metadata.IHDR_bitDepth; ++ int colorType = metadata.IHDR_colorType; ++ ++ int dataType; ++ if (bitDepth <= 8) { ++ dataType = DataBuffer.TYPE_BYTE; ++ } else { ++ dataType = DataBuffer.TYPE_USHORT; ++ } ++ ++ switch (colorType) { ++ case PNG_COLOR_GRAY: ++ // Packed grayscale ++ l.add(ImageTypeSpecifier.createGrayscale(bitDepth, ++ dataType, ++ false)); ++ break; ++ ++ case PNG_COLOR_RGB: ++ if (bitDepth == 8) { ++ // some standard types of buffered images ++ // which can be used as destination ++ l.add(ImageTypeSpecifier.createFromBufferedImageType( ++ BufferedImage.TYPE_3BYTE_BGR)); ++ ++ l.add(ImageTypeSpecifier.createFromBufferedImageType( ++ BufferedImage.TYPE_INT_RGB)); ++ ++ l.add(ImageTypeSpecifier.createFromBufferedImageType( ++ BufferedImage.TYPE_INT_BGR)); ++ ++ } ++ // Component R, G, B ++ rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB); ++ bandOffsets = new int[3]; ++ bandOffsets[0] = 0; ++ bandOffsets[1] = 1; ++ bandOffsets[2] = 2; ++ l.add(ImageTypeSpecifier.createInterleaved(rgb, ++ bandOffsets, ++ dataType, ++ false, ++ false)); ++ break; ++ ++ case PNG_COLOR_PALETTE: ++ readMetadata(); // Need tRNS chunk ++ ++ /* ++ * The PLTE chunk spec says: ++ * ++ * The number of palette entries must not exceed the range that ++ * can be represented in the image bit depth (for example, 2^4 = 16 ++ * for a bit depth of 4). It is permissible to have fewer entries ++ * than the bit depth would allow. In that case, any out-of-range ++ * pixel value found in the image data is an error. ++ * ++ * http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.PLTE ++ * ++ * Consequently, the case when the palette length is smaller than ++ * 2^bitDepth is legal in the view of PNG spec. ++ * ++ * However the spec of createIndexed() method demands the exact ++ * equality of the palette lengh and number of possible palette ++ * entries (2^bitDepth). ++ * ++ * {@link javax.imageio.ImageTypeSpecifier.html#createIndexed} ++ * ++ * In order to avoid this contradiction we need to extend the ++ * palette arrays to the limit defined by the bitDepth. ++ */ ++ ++ int plength = 1 << bitDepth; ++ ++ byte[] red = metadata.PLTE_red; ++ byte[] green = metadata.PLTE_green; ++ byte[] blue = metadata.PLTE_blue; ++ ++ if (metadata.PLTE_red.length < plength) { ++ red = 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 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 tEXt_keyword = new ArrayList(); // 1-79 characters ++ public ArrayList tEXt_text = new ArrayList(); + + // 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 zTXt_keyword = new ArrayList(); ++ public ArrayList zTXt_compressionMethod = new ArrayList(); ++ public ArrayList zTXt_text = new ArrayList(); + + // Unknown chunks +- public ArrayList unknownChunkType = new ArrayList(); // Strings +- public ArrayList unknownChunkData = new ArrayList(); // byte arrays ++ public ArrayList unknownChunkType = new ArrayList(); ++ public ArrayList unknownChunkData = new ArrayList(); + + public PNGMetadata() { + super(true, +@@ -428,21 +428,14 @@ + return false; + } + +- private ArrayList cloneBytesArrayList(ArrayList in) { ++ 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()); +- } ++ ArrayList list = new ArrayList(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(); ++ 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(); ++ zTXt_keyword = new ArrayList(); ++ zTXt_compressionMethod = new ArrayList(); ++ zTXt_text = new ArrayList(); ++ unknownChunkType = new ArrayList(); ++ unknownChunkData = new ArrayList(); + } + } +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 iTXt_keyword = new ArrayList(); ++ public ArrayList iTXt_compressionFlag = new ArrayList(); ++ public ArrayList iTXt_compressionMethod = new ArrayList(); ++ public ArrayList iTXt_languageTag = new ArrayList(); ++ public ArrayList iTXt_translatedKeyword = new ArrayList(); ++ public ArrayList iTXt_text = new ArrayList(); ++ ++ // 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 numBands 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(); ++ iTXt_compressionFlag = new ArrayList(); ++ iTXt_compressionMethod = new ArrayList(); ++ iTXt_languageTag = new ArrayList(); ++ iTXt_translatedKeyword = new ArrayList(); ++ iTXt_text = new ArrayList(); ++ 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; ++ } ++ ++}