Bug 6541476; State: 8-Fix Available, bug; Priority: 4-Low http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6541476
PNGMetadata is inconsitent in what data types get stored in its various iTXt_* lists. Method mergeNativeTree used different data types than the rest of the code for the following two attributes: 1. iTXt_compressionFlag uses Integer, but Boolean in mergeNativeTree 2. iTXt_compressionMethod uses Integer, but String in mergeNativeTree The attached patch addresses this by: 1. Changing all Lists to compile time type checked generics. Now the code compiles with -Xlint:unchecked 2. Cast the boolean compressionFlag to an integer before adding 3. Request the compressionMethod as an integer attribute 4. Change the comment about the compressionMethod datatype in the DTD 5. Provide a simple test case The compressionMethod of the iTXtEntry element is worth a closer look. While compressionMethod attributes of other elements in the native PNG metadata format use enum types in the DTD, this one has been defined as String in the spec and represented as integers on export so far. I believe it would be better to turn it into an enum as well, probably in a way which would retain backward and forward compatibility. However, I would consider this a separate issue, and discuss it in a separate thread. Therefore I'd deem this patch here ready to be applied as a self comtained fix for a single issue, even though I would like for another fix to change the DTD again before OpenJDK7 gets released. The Evaluation of CR 6541476 also mentions some issues reading UTF8 data for iTXt chunks. This I'd also consider a separate issue, and would address it in a separate fix, along with a separate test case to reproduce the issue in the first place. The attached patch is from my mercurial patch queue. Once you consider it ready for inclusion, I will commit it locally and export a mercurial patch instead. Greetings, Martin von Gagern
diff --git a/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java b/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java --- a/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java +++ b/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java @@ -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<String> iTXt_keyword = new ArrayList<String>(); + public ArrayList<Integer> iTXt_compressionFlag = new ArrayList<Integer>(); + public ArrayList<Integer> iTXt_compressionMethod = new ArrayList<Integer>(); + public ArrayList<String> iTXt_languageTag = new ArrayList<String>(); + public ArrayList<String> iTXt_translatedKeyword = new ArrayList<String>(); + public ArrayList<String> iTXt_text = new ArrayList<String>(); // pHYs chunk public boolean pHYs_present; @@ -211,8 +211,8 @@ public int sRGB_renderingIntent; // tEXt chunk - public ArrayList tEXt_keyword = new ArrayList(); // 1-79 char Strings - public ArrayList tEXt_text = new ArrayList(); // Strings + public ArrayList<String> tEXt_keyword = new ArrayList<String>(); // 1-79 characters + public ArrayList<String> tEXt_text = new ArrayList<String>(); // tIME chunk public boolean tIME_present; @@ -235,13 +235,13 @@ public int tRNS_blue; // zTXt chunk - public ArrayList zTXt_keyword = new ArrayList(); // Strings - public ArrayList zTXt_compressionMethod = new ArrayList(); // Integers - public ArrayList zTXt_text = new ArrayList(); // Strings + public ArrayList<String> zTXt_keyword = new ArrayList<String>(); + public ArrayList<Integer> zTXt_compressionMethod = new ArrayList<Integer>(); + public ArrayList<String> zTXt_text = new ArrayList<String>(); // Unknown chunks - public ArrayList unknownChunkType = new ArrayList(); // Strings - public ArrayList unknownChunkData = new ArrayList(); // byte arrays + public ArrayList<String> unknownChunkType = new ArrayList<String>(); + public ArrayList<byte[]> unknownChunkData = new ArrayList<byte[]>(); public PNGMetadata() { super(true, @@ -426,21 +426,14 @@ return false; } - private ArrayList cloneBytesArrayList(ArrayList in) { + private ArrayList<byte[]> cloneBytesArrayList(ArrayList<byte[]> in) { if (in == null) { return null; } else { - ArrayList list = new ArrayList(in.size()); - Iterator iter = in.iterator(); - while (iter.hasNext()) { - Object o = iter.next(); - if (o == null) { - list.add(null); - } else { - list.add(((byte[])o).clone()); - } + ArrayList<byte[]> list = new ArrayList<byte[]>(in.size()); + for (byte[] chunk: in) { + list.add(chunk); } - return list; } } @@ -600,16 +593,16 @@ 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("keyword", iTXt_keyword.get(i)); + val = iTXt_compressionFlag.get(i); iTXt_node.setAttribute("compressionFlag", val.toString()); - val = (Integer)iTXt_compressionMethod.get(i); + val = iTXt_compressionMethod.get(i); iTXt_node.setAttribute("compressionMethod", val.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); } @@ -1041,7 +1034,7 @@ node.setAttribute("value", (String)iTXt_text.get(i)); node.setAttribute("language", (String)iTXt_languageTag.get(i)); - if (((Integer)iTXt_compressionFlag.get(i)).intValue() == 1) { + if (iTXt_compressionFlag.get(i).intValue() == 1) { node.setAttribute("compression", "deflate"); } else { node.setAttribute("compression", "none"); @@ -1427,10 +1420,10 @@ boolean compressionFlag = getBooleanAttribute(iTXt_node, "compressionFlag"); - iTXt_compressionFlag.add(new Boolean(compressionFlag)); + iTXt_compressionFlag.add(compressionFlag ? 1 : 0); - String compressionMethod = - getAttribute(iTXt_node, "compressionMethod"); + int compressionMethod = + getIntAttribute(iTXt_node, "compressionMethod"); iTXt_compressionMethod.add(compressionMethod); String languageTag = @@ -1996,24 +1989,24 @@ 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<String>(); + iTXt_compressionFlag = new ArrayList<Integer>(); + iTXt_compressionMethod = new ArrayList<Integer>(); + iTXt_languageTag = new ArrayList<String>(); + iTXt_translatedKeyword = new ArrayList<String>(); + iTXt_text = new ArrayList<String>(); pHYs_present = false; sBIT_present = false; sPLT_present = false; sRGB_present = false; - tEXt_keyword = new ArrayList(); - tEXt_text = new ArrayList(); + tEXt_keyword = new ArrayList<String>(); + tEXt_text = new ArrayList<String>(); tIME_present = false; tRNS_present = false; - zTXt_keyword = new ArrayList(); - zTXt_compressionMethod = new ArrayList(); - zTXt_text = new ArrayList(); - unknownChunkType = new ArrayList(); - unknownChunkData = new ArrayList(); + zTXt_keyword = new ArrayList<String>(); + zTXt_compressionMethod = new ArrayList<Integer>(); + zTXt_text = new ArrayList<String>(); + unknownChunkType = new ArrayList<String>(); + unknownChunkData = new ArrayList<byte[]>(); } } diff --git a/src/share/classes/javax/imageio/metadata/doc-files/png_metadata.html b/src/share/classes/javax/imageio/metadata/doc-files/png_metadata.html --- a/src/share/classes/javax/imageio/metadata/doc-files/png_metadata.html +++ b/src/share/classes/javax/imageio/metadata/doc-files/png_metadata.html @@ -292,7 +292,7 @@ #REQUIRED> <!ATTLIST "iTXtEntry" "compressionMethod" #CDATA #REQUIRED> <!-- The compression method used to store this iTXt entry --> - <!-- Data type: String --> + <!-- Data type: Integer --> <!ATTLIST "iTXtEntry" "languageTag" #CDATA #REQUIRED> <!-- The ISO tag describing the language this iTXt entry --> <!-- Data type: String --> diff --git a/test/javax/imageio/plugins/png/ItxtWriteTest.java b/test/javax/imageio/plugins/png/ItxtWriteTest.java new file mode 100644 --- /dev/null +++ b/test/javax/imageio/plugins/png/ItxtWriteTest.java @@ -0,0 +1,53 @@ +/** + * @test + * @bug 6541476 + * @summary Write a PNG file including an iTXt chunk + */ + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.bootstrap.DOMImplementationRegistry; + +public class ItxtWriteTest { + + public static void main(String[] args) 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(); + OutputStream 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, text, entry; + root = doc.getDocumentElement(); + root.appendChild(text = doc.createElement("iTXt")); + text.appendChild(entry = doc.createElement("iTXtEntry")); + entry.setAttribute("keyword", "Comment"); + entry.setAttribute("compressionFlag", "true"); + entry.setAttribute("compressionMethod", "0"); + entry.setAttribute("languageTag", "de-DE"); + entry.setAttribute("translatedKeyword", "Kommentar"); + entry.setAttribute("text", "Dies ist ein Kommentar"); + meta.mergeTree(format, root); + iw.write(new IIOImage(img, null, meta)); + iw.dispose(); + } + +}