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