# HG changeset patch # User bae # Date 1225561338 -10800 # Node ID 861400729115dc091393c0cc8f8bcb72e3e75628 # Parent 95c41a86eac909379ea46b7f894b1b2ac87068fc 6541476: PNG imageio plugin incorrectly handles iTXt chunk Reviewed-by: igor, prr diff -r 95c41a86eac9 -r 861400729115 jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java --- a/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java Wed Oct 29 01:52:22 2008 +0300 +++ b/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java Sat Nov 01 20:42:18 2008 +0300 @@ -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 { @@ -207,6 +207,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; @@ -445,26 +454,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); } @@ -613,20 +623,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 { @@ -638,7 +648,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 -r 95c41a86eac9 -r 861400729115 jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java --- a/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java Wed Oct 29 01:52:22 2008 +0300 +++ b/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java Sat Nov 01 20:42:18 2008 +0300 @@ -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 -r 95c41a86eac9 -r 861400729115 jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java --- a/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java Wed Oct 29 01:52:22 2008 +0300 +++ b/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java Sat Nov 01 20:42:18 2008 +0300 @@ -174,12 +174,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; @@ -597,19 +597,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); } @@ -1037,11 +1035,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"); @@ -1427,11 +1425,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"); @@ -1950,13 +1948,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); @@ -1993,12 +1988,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 -r 95c41a86eac9 -r 861400729115 jdk/test/javax/imageio/plugins/png/ITXtTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/javax/imageio/plugins/png/ITXtTest.java Sat Nov 01 20:42:18 2008 +0300 @@ -0,0 +1,236 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.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; + } +}