1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 8164971 8187113 27 * @summary The test decodes a png file and checks if the metadata contains 28 * image creation time. In addition, the test also merges the custom 29 * metadata tree (both standard and native) and succeeds when the 30 * metadata contains expected image creation time. 31 * @run main PngCreationTimeTest 32 */ 33 import java.io.IOException; 34 import java.io.File; 35 import java.nio.file.Files; 36 import java.util.Iterator; 37 import java.awt.Graphics2D; 38 import java.awt.Color; 39 import java.awt.image.BufferedImage; 40 import javax.imageio.ImageIO; 41 import javax.imageio.IIOImage; 42 import javax.imageio.ImageTypeSpecifier; 43 import javax.imageio.stream.ImageInputStream; 44 import javax.imageio.stream.ImageOutputStream; 45 import javax.imageio.ImageReadParam; 46 import javax.imageio.ImageReader; 47 import javax.imageio.ImageWriter; 48 import javax.imageio.metadata.IIOMetadata; 49 import javax.imageio.metadata.IIOMetadataNode; 50 import org.w3c.dom.Node; 51 import org.w3c.dom.NamedNodeMap; 52 53 public class PngCreationTimeTest { 54 // Members 55 private static IIOMetadata pngMetadata = null; 56 initializeTest()57 public static void initializeTest() throws IOException { 58 Iterator<ImageReader> iterR = null; 59 ImageReader pngImageReader = null; 60 BufferedImage decImage = null; 61 ImageInputStream imageStream = null; 62 String fileName = "duke.png"; 63 String separator = System.getProperty("file.separator"); 64 String dirPath = System.getProperty("test.src", "."); 65 String filePath = dirPath + separator + fileName; 66 File file = null; 67 68 try { 69 // Open the required file and check if file exists. 70 file = new File(filePath); 71 if (file != null && !file.exists()) { 72 reportExceptionAndFail("Test Failed. Required image file was" 73 + " not found."); 74 } 75 76 // Get PNG image reader 77 iterR = ImageIO.getImageReadersBySuffix("PNG"); 78 if (iterR.hasNext()) { 79 pngImageReader = iterR.next(); 80 ImageReadParam param = pngImageReader.getDefaultReadParam(); 81 imageStream = ImageIO.createImageInputStream(file); 82 if (imageStream != null) { 83 // Last argument informs reader not to ignore metadata 84 pngImageReader.setInput(imageStream, 85 false, 86 false); 87 decImage = pngImageReader.read(0, param); 88 pngMetadata = pngImageReader.getImageMetadata(0); 89 if (pngMetadata != null) { 90 // Check if the metadata contains creation time 91 testImageMetadata(pngMetadata); 92 } else { 93 reportExceptionAndFail("Test Failed. Reader could not" 94 + " generate image metadata."); 95 } 96 } else { 97 reportExceptionAndFail("Test Failed. Could not initialize" 98 + " image input stream."); 99 } 100 } else { 101 reportExceptionAndFail("Test Failed. Required image reader" 102 + " was not found."); 103 } 104 } finally { 105 // Release ther resources 106 if (imageStream != null) { 107 imageStream.close(); 108 } 109 if (pngImageReader != null) { 110 pngImageReader.dispose(); 111 } 112 } 113 } 114 testImageMetadata(IIOMetadata metadata)115 public static void testImageMetadata(IIOMetadata metadata) { 116 /* 117 * The source file contains Creation Time in its text chunk. Upon 118 * successful decoding, the Standard/Document/ImageCreationTime 119 * should exist in the metadata. 120 */ 121 if (metadata != null) { 122 Node keyNode = findNode(metadata.getAsTree("javax_imageio_1.0"), 123 "ImageCreationTime"); 124 if (keyNode == null) { 125 reportExceptionAndFail("Test Failed: Could not find image" 126 + " creation time in the metadata."); 127 } 128 } 129 } 130 testSaveCreationTime()131 public static void testSaveCreationTime() throws IOException { 132 File file = null; 133 Iterator<ImageWriter> iterW = null; 134 Iterator<ImageReader> iterR = null; 135 ImageWriter pngImageWriter = null; 136 ImageReader pngImageReader = null; 137 ImageInputStream inputStream = null; 138 ImageOutputStream outputStream = null; 139 try { 140 // Create a simple image and fill with a color 141 int imageSize = 200; 142 BufferedImage buffImage = new BufferedImage(imageSize, imageSize, 143 BufferedImage.TYPE_INT_ARGB); 144 Graphics2D g2d = buffImage.createGraphics(); 145 g2d.setColor(Color.red); 146 g2d.fillRect(0, 0, imageSize, imageSize); 147 148 // Create a temporary file for the output png image 149 String fileName = "RoundTripTest"; 150 file = File.createTempFile(fileName, ".png"); 151 if (file == null) { 152 reportExceptionAndFail("Test Failed. Could not create a" 153 + " temporary file for round trip test."); 154 } 155 156 // Create a PNG writer and write test image with metadata 157 iterW = ImageIO.getImageWritersBySuffix("PNG"); 158 if (iterW.hasNext()) { 159 pngImageWriter = iterW.next(); 160 outputStream = ImageIO.createImageOutputStream(file); 161 if (outputStream != null) { 162 pngImageWriter.setOutput(outputStream); 163 164 // Get the default metadata & add image creation time to it. 165 ImageTypeSpecifier imgType = 166 ImageTypeSpecifier.createFromRenderedImage(buffImage); 167 IIOMetadata metadata = 168 pngImageWriter.getDefaultImageMetadata(imgType, null); 169 IIOMetadataNode root = createStandardMetadataNodeTree(); 170 metadata.mergeTree("javax_imageio_1.0", root); 171 172 // Write a png image using buffImage & metadata 173 IIOImage iioImage = new IIOImage(buffImage, null, metadata); 174 pngImageWriter.write(iioImage); 175 } else { 176 reportExceptionAndFail("Test Failed. Could not initialize" 177 + " image output stream for round trip test."); 178 } 179 } else { 180 reportExceptionAndFail("Test Failed. Could not find required" 181 + " image writer for the round trip test."); 182 } 183 184 // Create a PNG reader and check if metadata was written 185 iterR = ImageIO.getImageReadersBySuffix("PNG"); 186 if (iterR.hasNext()) { 187 pngImageReader = iterR.next(); 188 inputStream = ImageIO.createImageInputStream(file); 189 if (inputStream != null) { 190 // Read the image and get the metadata 191 pngImageReader.setInput(inputStream, false, false); 192 pngImageReader.read(0); 193 IIOMetadata imgMetadata = 194 pngImageReader.getImageMetadata(0); 195 196 // Test if the metadata contains creation time. 197 testImageMetadata(imgMetadata); 198 } else { 199 reportExceptionAndFail("Test Failed. Could not initialize" 200 + " image input stream for round trip test."); 201 } 202 } else { 203 reportExceptionAndFail("Test Failed. Cound not find the" 204 + " required image reader."); 205 } 206 } finally { 207 // Release the resources held 208 if (inputStream != null) { 209 inputStream.close(); 210 } 211 if (outputStream != null) { 212 outputStream.close(); 213 } 214 if (pngImageWriter != null) { 215 pngImageWriter.dispose(); 216 } 217 if (pngImageReader != null) { 218 pngImageReader.dispose(); 219 } 220 // Delete the temp file as well 221 if (file != null) { 222 Files.delete(file.toPath()); 223 } 224 } 225 } 226 reportExceptionAndFail(String message)227 public static void reportExceptionAndFail(String message) { 228 // A common method to report exception. 229 throw new RuntimeException(message); 230 } 231 testMergeNativeTree()232 public static void testMergeNativeTree() { 233 // Merge a custom native metadata tree and inspect creation time 234 if (pngMetadata != null) { 235 try { 236 IIOMetadataNode root = createNativeMetadataNodeTree(); 237 238 /* 239 * Merge the native metadata tree created. The data should 240 * reflect in Standard/Document/ImageCreationTime Node 241 */ 242 pngMetadata.mergeTree("javax_imageio_png_1.0", root); 243 Node keyNode = findNode(pngMetadata.getAsTree("javax_imageio_1.0"), 244 "ImageCreationTime"); 245 if (keyNode != null) { 246 // Query the attributes of the node and check for the value 247 NamedNodeMap attrMap = keyNode.getAttributes(); 248 String attrValue = attrMap.getNamedItem("year") 249 .getNodeValue(); 250 int decYear = Integer.parseInt(attrValue); 251 if (decYear != 2014) { 252 // Throw exception. Incorrect year value observed 253 reportExceptionAndFail("Test Failed: Incorrect" 254 + " creation time value observed."); 255 } 256 } else { 257 // Throw exception. 258 reportExceptionAndFail("Test Failed: Image creation" 259 + " time doesn't exist in metadata."); 260 } 261 } catch (IOException ex) { 262 // Throw exception. 263 reportExceptionAndFail("Test Failed: While executing" 264 + " mergeTree on metadata."); 265 } 266 } 267 } 268 testMergeStandardTree()269 public static void testMergeStandardTree() { 270 // Merge a standard metadata tree and inspect creation time 271 if (pngMetadata != null) { 272 try { 273 IIOMetadataNode root = createStandardMetadataNodeTree(); 274 275 /* 276 * Merge the standard metadata tree created. The data should 277 * correctly reflect in the native tree 278 */ 279 pngMetadata.mergeTree("javax_imageio_1.0", root); 280 Node keyNode = findNode(pngMetadata.getAsTree("javax_imageio_png_1.0"), 281 "tEXtEntry"); 282 // Last text entry would contain the merged information 283 while (keyNode != null && keyNode.getNextSibling() != null) { 284 keyNode = keyNode.getNextSibling(); 285 } 286 287 if (keyNode != null) { 288 // Query the attributes of the node and check for the value 289 NamedNodeMap attrMap = keyNode.getAttributes(); 290 String attrValue = attrMap.getNamedItem("value") 291 .getNodeValue(); 292 if (!attrValue.contains("2016")) { 293 // Throw exception. Incorrect year value observed 294 throw new RuntimeException("Test Failed: Incorrect" 295 + " creation time value observed."); 296 } 297 } else { 298 // Throw exception. 299 reportExceptionAndFail("Test Failed: Image creation" 300 + " time doesn't exist in metadata."); 301 } 302 } catch (IOException ex) { 303 // Throw exception. 304 reportExceptionAndFail("Test Failed: While executing" 305 + " mergeTree on metadata."); 306 } 307 } 308 } 309 createNativeMetadataNodeTree()310 public static IIOMetadataNode createNativeMetadataNodeTree() { 311 // Create a text node to hold tEXtEntries 312 IIOMetadataNode tEXtNode = new IIOMetadataNode("tEXt"); 313 314 // Create tEXt entry to hold random date time 315 IIOMetadataNode randomTimeEntry = new IIOMetadataNode("tEXtEntry"); 316 randomTimeEntry.setAttribute("keyword", "Creation Time"); 317 randomTimeEntry.setAttribute("value", "21 Dec 2015,Monday"); 318 tEXtNode.appendChild(randomTimeEntry); 319 320 // Create a tEXt entry to hold time in RFC1123 format 321 IIOMetadataNode rfcTextEntry = new IIOMetadataNode("tEXtEntry"); 322 rfcTextEntry.setAttribute("keyword", "Creation Time"); 323 rfcTextEntry.setAttribute("value", "Mon, 21 Dec 2015 09:04:30 +0530"); 324 tEXtNode.appendChild(rfcTextEntry); 325 326 // Create a tEXt entry to hold time in ISO format 327 IIOMetadataNode isoTextEntry = new IIOMetadataNode("tEXtEntry"); 328 isoTextEntry.setAttribute("keyword", "Creation Time"); 329 isoTextEntry.setAttribute("value", "2014-12-21T09:04:30+05:30"); 330 tEXtNode.appendChild(isoTextEntry); 331 332 // Create a root node append the text node 333 IIOMetadataNode root = new IIOMetadataNode("javax_imageio_png_1.0"); 334 root.appendChild(tEXtNode); 335 336 return root; 337 } 338 createStandardMetadataNodeTree()339 public static IIOMetadataNode createStandardMetadataNodeTree() { 340 /* 341 * Create standard metadata tree with creation time in 342 * Standard(Root)/Document/ImageCreationTime node 343 */ 344 IIOMetadataNode createTimeNode = new IIOMetadataNode("ImageCreationTime"); 345 createTimeNode.setAttribute("year", "2016"); 346 createTimeNode.setAttribute("month", "12"); 347 createTimeNode.setAttribute("day", "21"); 348 createTimeNode.setAttribute("hour", "18"); 349 createTimeNode.setAttribute("minute", "30"); 350 createTimeNode.setAttribute("second", "00"); 351 352 // Create the Document node 353 IIOMetadataNode documentNode = new IIOMetadataNode("Document"); 354 documentNode.appendChild(createTimeNode); 355 356 // Create a root node append the Document node 357 IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0"); 358 root.appendChild(documentNode); 359 360 return root; 361 } 362 findNode(Node root, String nodeName)363 public static Node findNode(Node root, String nodeName) { 364 // Return value 365 Node retVal = null; 366 367 if (root != null ) { 368 // Check if the name of root node matches the key 369 String name = root.getNodeName(); 370 if (name.equalsIgnoreCase(nodeName)) { 371 return root; 372 } 373 374 // Process all children 375 Node node = root.getFirstChild(); 376 while (node != null) { 377 retVal = findNode(node, nodeName); 378 if (retVal != null ) { 379 // We found the node. Stop the search 380 break; 381 } 382 node = node.getNextSibling(); 383 } 384 } 385 386 return retVal; 387 } 388 main(String[] args)389 public static void main(String[] args) throws IOException { 390 /* 391 * Initialize the test by decoding a PNG image that has creation 392 * time in one of its text chunks and check if the metadata returned 393 * contains image creation time. 394 */ 395 initializeTest(); 396 397 /* 398 * Test the round trip usecase. Write a PNG file with "Creation Time" 399 * in text chunk and decode the same to check if the creation time 400 * was indeed written to the PNG file. 401 */ 402 testSaveCreationTime(); 403 404 /* 405 * Modify the metadata by merging a standard metadata tree and inspect 406 * the value in the native tree 407 */ 408 testMergeNativeTree(); 409 410 /* 411 * Modify the metadata by merging a native metadata tree and inspect 412 * the value in the standard tree. 413 */ 414 testMergeStandardTree(); 415 } 416 } 417