1 /*
2  * Copyright (c) 2016, 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     8152183 8148454
27  * @author  a.stepanov
28  * @summary check that TIFFields are derived properly for multi-page tiff
29  * @run     main MultiPageImageTIFFFieldTest
30  */
31 
32 import java.awt.*;
33 import java.awt.color.*;
34 import java.awt.image.BufferedImage;
35 import java.io.*;
36 import javax.imageio.*;
37 import javax.imageio.metadata.*;
38 import javax.imageio.stream.*;
39 import javax.imageio.plugins.tiff.*;
40 
41 
42 public class MultiPageImageTIFFFieldTest {
43 
44     private final static String FILENAME = "test.tiff";
45     private final static int W1 = 20, H1 = 40, W2 = 100, H2 = 15;
46     private final static Color C1 = Color.BLACK, C2 = Color.RED;
47 
48     private final static int N_WIDTH  = BaselineTIFFTagSet.TAG_IMAGE_WIDTH;
49     private final static int N_HEIGHT = BaselineTIFFTagSet.TAG_IMAGE_LENGTH;
50 
51     private static final String DESCRIPTION_1[] = {"Description-1", "abc ABC"};
52     private static final String DESCRIPTION_2[] = {"Description-2", "1-2-3"};
53     private final static int N_DESCRIPTION =
54         BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION;
55 
56     private final static String EXIF_DATA_1[] = {"2001:01:01 00:00:01"};
57     private final static String EXIF_DATA_2[] = {"2002:02:02 00:00:02"};
58     private final static int N_EXIF = ExifTIFFTagSet.TAG_DATE_TIME_ORIGINAL;
59 
60     private final static String GPS_DATA[] = {
61         ExifGPSTagSet.STATUS_MEASUREMENT_IN_PROGRESS};
62     private final static int N_GPS = ExifGPSTagSet.TAG_GPS_STATUS;
63 
64     private final static short FAX_DATA =
65         FaxTIFFTagSet.CLEAN_FAX_DATA_ERRORS_UNCORRECTED;
66     private final static int N_FAX = FaxTIFFTagSet.TAG_CLEAN_FAX_DATA;
67 
68     private static final byte[] ICC_PROFILE_2 =
69         ICC_ProfileRGB.getInstance(ColorSpace.CS_sRGB).getData();
70     private static final int N_ICC = BaselineTIFFTagSet.TAG_ICC_PROFILE;
71 
72     private static final int N_BPS = BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE;
73 
74     private static final int
75         COMPRESSION_1 = BaselineTIFFTagSet.COMPRESSION_DEFLATE,
76         COMPRESSION_2 = BaselineTIFFTagSet.COMPRESSION_LZW;
77     private static final int N_COMPRESSION = BaselineTIFFTagSet.TAG_COMPRESSION;
78 
79     private static final int
80         GRAY_1 = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO,
81         GRAY_2 = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO,
82         RGB    = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
83 
84     private static final int N_PHOTO =
85         BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION;
86 
getTIFFWriter()87     private ImageWriter getTIFFWriter() {
88 
89         java.util.Iterator<ImageWriter> writers =
90             ImageIO.getImageWritersByFormatName("TIFF");
91         if (!writers.hasNext()) {
92             throw new RuntimeException("No writers available for TIFF format");
93         }
94         return writers.next();
95     }
96 
getTIFFReader()97     private ImageReader getTIFFReader() {
98 
99         java.util.Iterator<ImageReader> readers =
100             ImageIO.getImageReadersByFormatName("TIFF");
101         if (!readers.hasNext()) {
102             throw new RuntimeException("No readers available for TIFF format");
103         }
104         return readers.next();
105     }
106 
addASCIIField(TIFFDirectory d, String name, String data[], int num)107     private void addASCIIField(TIFFDirectory d,
108                                String        name,
109                                String        data[],
110                                int           num) {
111 
112         d.addTIFFField(new TIFFField(
113             new TIFFTag(name, num, 1 << TIFFTag.TIFF_ASCII),
114                 TIFFTag.TIFF_ASCII, data.length, data));
115     }
116 
checkASCIIField(TIFFDirectory d, String what, String data[], int num)117     private void checkASCIIField(TIFFDirectory d,
118                                  String        what,
119                                  String        data[],
120                                  int           num) {
121 
122         String notFound = what + " field was not found";
123         check(d.containsTIFFField(num), notFound);
124         TIFFField f = d.getTIFFField(num);
125         check(f.getType() == TIFFTag.TIFF_ASCII, "field type != ASCII");
126         check(f.getCount() == data.length, "invalid " + what + " data count");
127         for (int i = 0; i < data.length; i++) {
128             check(f.getValueAsString(i).equals(data[i]),
129                 "invalid " + what + " data");
130         }
131     }
132 
writeImage()133     private void writeImage() throws Exception {
134 
135         OutputStream s = new BufferedOutputStream(new FileOutputStream(FILENAME));
136         try (ImageOutputStream ios = ImageIO.createImageOutputStream(s)) {
137 
138             ImageWriter writer = getTIFFWriter();
139             writer.setOutput(ios);
140 
141             BufferedImage img1 =
142                 new BufferedImage(W1, H1, BufferedImage.TYPE_BYTE_GRAY);
143             Graphics g = img1.getGraphics();
144             g.setColor(C1);
145             g.fillRect(0, 0, W1, H1);
146             g.dispose();
147 
148             BufferedImage img2 =
149                 new BufferedImage(W2, H2, BufferedImage.TYPE_INT_RGB);
150             g = img2.getGraphics();
151             g.setColor(C2);
152             g.fillRect(0, 0, W2, H2);
153             g.dispose();
154 
155             ImageWriteParam param1 = writer.getDefaultWriteParam();
156             param1.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
157             param1.setCompressionType("Deflate");
158             param1.setCompressionQuality(0.5f);
159 
160             ImageWriteParam param2 = writer.getDefaultWriteParam();
161             param2.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
162             param2.setCompressionType("LZW");
163             param2.setCompressionQuality(0.5f);
164 
165             IIOMetadata
166                 md1 = writer.getDefaultImageMetadata(
167                     new ImageTypeSpecifier(img1), param1),
168                 md2 = writer.getDefaultImageMetadata(
169                     new ImageTypeSpecifier(img2), param2);
170 
171             TIFFDirectory
172                 dir1 = TIFFDirectory.createFromMetadata(md1),
173                 dir2 = TIFFDirectory.createFromMetadata(md2);
174 
175             addASCIIField(dir1, "ImageDescription", DESCRIPTION_1, N_DESCRIPTION);
176             addASCIIField(dir2, "ImageDescription", DESCRIPTION_2, N_DESCRIPTION);
177 
178             addASCIIField(dir1, "GPSStatus", GPS_DATA, N_GPS);
179             addASCIIField(dir2, "GPSStatus", GPS_DATA, N_GPS);
180 
181             addASCIIField(dir1, "DateTimeOriginal", EXIF_DATA_1, N_EXIF);
182             addASCIIField(dir2, "DateTimeOriginal", EXIF_DATA_2, N_EXIF);
183 
184             TIFFTag faxTag = new TIFFTag(
185                 "CleanFaxData", N_FAX, 1 << TIFFTag.TIFF_SHORT);
186             dir1.addTIFFField(new TIFFField(faxTag, FAX_DATA));
187             dir2.addTIFFField(new TIFFField(faxTag, FAX_DATA));
188 
189             dir2.addTIFFField(new TIFFField(
190                 new TIFFTag("ICC Profile", N_ICC, 1 << TIFFTag.TIFF_UNDEFINED),
191                 TIFFTag.TIFF_UNDEFINED, ICC_PROFILE_2.length, ICC_PROFILE_2));
192 
193             writer.prepareWriteSequence(null);
194             writer.writeToSequence(
195                 new IIOImage(img1, null, dir1.getAsMetadata()), param1);
196             writer.writeToSequence(
197                 new IIOImage(img2, null, dir2.getAsMetadata()), param2);
198             writer.endWriteSequence();
199 
200             ios.flush();
201             writer.dispose();
202         }
203         s.close();
204     }
205 
checkBufferedImages(BufferedImage im1, BufferedImage im2)206     private void checkBufferedImages(BufferedImage im1, BufferedImage im2) {
207 
208         check(im1.getWidth()  == W1, "invalid width for image 1");
209         check(im1.getHeight() == H1, "invalid height for image 1");
210         check(im2.getWidth()  == W2, "invalid width for image 2");
211         check(im2.getHeight() == H2, "invalid height for image 2");
212 
213         Color
214             c1 = new Color(im1.getRGB(W1 / 2, H1 / 2)),
215             c2 = new Color(im2.getRGB(W2 / 2, H2 / 2));
216 
217         check(c1.equals(C1), "invalid image 1 color");
218         check(c2.equals(C2), "invalid image 2 color");
219     }
220 
readAndCheckImage()221     private void readAndCheckImage() throws Exception {
222 
223         ImageReader reader = getTIFFReader();
224 
225         ImageInputStream s = ImageIO.createImageInputStream(new File(FILENAME));
226         reader.setInput(s, false, false);
227 
228         int ni = reader.getNumImages(true);
229         check(ni == 2, "invalid number of images");
230 
231         // check TIFFImageReadParam for multipage image
232         TIFFImageReadParam
233             param1 = new TIFFImageReadParam(), param2 = new TIFFImageReadParam();
234 
235         param1.addAllowedTagSet(ExifTIFFTagSet.getInstance());
236         param1.addAllowedTagSet(ExifGPSTagSet.getInstance());
237 
238         param2.addAllowedTagSet(ExifTIFFTagSet.getInstance());
239         param2.addAllowedTagSet(GeoTIFFTagSet.getInstance());
240 
241         // FaxTIFFTagSet is allowed by default
242         param2.removeAllowedTagSet(FaxTIFFTagSet.getInstance());
243 
244 
245         // read images and metadata
246         IIOImage i1 = reader.readAll(0, param1), i2 = reader.readAll(1, param2);
247         BufferedImage
248             bi1 = (BufferedImage) i1.getRenderedImage(),
249             bi2 = (BufferedImage) i2.getRenderedImage();
250 
251         // check rendered images, just in case
252         checkBufferedImages(bi1, bi2);
253 
254         TIFFDirectory
255             dir1 = TIFFDirectory.createFromMetadata(i1.getMetadata()),
256             dir2 = TIFFDirectory.createFromMetadata(i2.getMetadata());
257 
258         // check ASCII fields
259         checkASCIIField(
260             dir1, "image 1 description", DESCRIPTION_1, N_DESCRIPTION);
261         checkASCIIField(
262             dir2, "image 2 description", DESCRIPTION_2, N_DESCRIPTION);
263 
264         checkASCIIField(dir1, "image 1 datetime", EXIF_DATA_1, N_EXIF);
265         checkASCIIField(dir2, "image 2 datetime", EXIF_DATA_2, N_EXIF);
266 
267         // check sizes
268         TIFFField f = dir1.getTIFFField(N_WIDTH);
269         check((f.getCount() == 1) && (f.getAsInt(0) == W1),
270             "invalid width field for image 1");
271         f = dir2.getTIFFField(N_WIDTH);
272         check((f.getCount() == 1) && (f.getAsInt(0) == W2),
273             "invalid width field for image 2");
274 
275         f = dir1.getTIFFField(N_HEIGHT);
276         check((f.getCount() == 1) && (f.getAsInt(0) == H1),
277             "invalid height field for image 1");
278         f = dir2.getTIFFField(N_HEIGHT);
279         check((f.getCount() == 1) && (f.getAsInt(0) == H2),
280             "invalid height field for image 2");
281 
282         // check fax data
283         check(dir1.containsTIFFField(N_FAX), "image 2 TIFF directory " +
284             "must contain clean fax data");
285         f = dir1.getTIFFField(N_FAX);
286         check(
287             (f.getCount() == 1) && f.isIntegral() && (f.getAsInt(0) == FAX_DATA),
288             "invalid clean fax data");
289 
290         check(!dir2.containsTIFFField(N_FAX), "image 2 TIFF directory " +
291             "must not contain fax fields");
292 
293         // check GPS data
294         checkASCIIField(dir1, "GPS status", GPS_DATA, N_GPS);
295 
296         check(!dir2.containsTIFFField(N_GPS), "image 2 TIFF directory " +
297             "must not contain GPS fields");
298 
299         // check ICC profile data
300         check(!dir1.containsTIFFField(N_ICC), "image 1 TIFF directory "
301             + "must not contain ICC Profile field");
302         check(dir2.containsTIFFField(N_ICC), "image 2 TIFF directory "
303             + "must contain ICC Profile field");
304 
305         f = dir2.getTIFFField(N_ICC);
306         check(f.getType() == TIFFTag.TIFF_UNDEFINED,
307             "invalid ICC profile field type");
308         int cnt = f.getCount();
309         byte icc[] = f.getAsBytes();
310         check((cnt == ICC_PROFILE_2.length) && (cnt == icc.length),
311                 "invalid ICC profile");
312         for (int i = 0; i < cnt; i++) {
313             check(icc[i] == ICC_PROFILE_2[i], "invalid ICC profile data");
314         }
315 
316         // check component sizes
317         check(dir1.getTIFFField(N_BPS).isIntegral() &&
318               dir2.getTIFFField(N_BPS).isIntegral(),
319               "invalid bits per sample type");
320         int sz1[] = bi1.getColorModel().getComponentSize(),
321             sz2[] = bi2.getColorModel().getComponentSize(),
322             bps1[] = dir1.getTIFFField(N_BPS).getAsInts(),
323             bps2[] = dir2.getTIFFField(N_BPS).getAsInts();
324 
325         check((bps1.length == sz1.length) && (bps2.length == sz2.length),
326             "invalid component size count");
327 
328         for (int i = 0; i < bps1.length; i++) {
329             check(bps1[i] == sz1[i], "image 1: invalid bits per sample data");
330         }
331 
332         for (int i = 0; i < bps2.length; i++) {
333             check(bps2[i] == sz2[i], "image 2: invalid bits per sample data");
334         }
335 
336         // check compression data
337         check(dir1.containsTIFFField(N_COMPRESSION) &&
338               dir2.containsTIFFField(N_COMPRESSION),
339               "compression info lost");
340         f = dir1.getTIFFField(N_COMPRESSION);
341         check(f.isIntegral() && (f.getCount() == 1) &&
342             (f.getAsInt(0) == COMPRESSION_1), "invalid image 1 compression data");
343 
344         f = dir2.getTIFFField(N_COMPRESSION);
345         check(f.isIntegral() && (f.getCount() == 1) &&
346             (f.getAsInt(0) == COMPRESSION_2), "invalid image 2 compression data");
347 
348         // check photometric interpretation
349         f = dir1.getTIFFField(N_PHOTO);
350         check(f.isIntegral() && (f.getCount() == 1) &&
351             ((f.getAsInt(0) == GRAY_1) || (f.getAsInt(0) == GRAY_2)),
352             "invalid photometric interpretation for image 1");
353 
354         f = dir2.getTIFFField(N_PHOTO);
355         check(f.isIntegral() && (f.getCount() == 1) && (f.getAsInt(0) == RGB),
356             "invalid photometric interpretation for image 2");
357     }
358 
run()359     public void run() {
360 
361         try {
362             writeImage();
363             readAndCheckImage();
364         } catch (Exception e) {
365             throw new RuntimeException(e);
366         }
367     }
368 
check(boolean ok, String msg)369     private void check(boolean ok, String msg) {
370 
371         if (!ok) { throw new RuntimeException(msg); }
372     }
373 
main(String[] args)374     public static void main(String[] args) {
375         (new MultiPageImageTIFFFieldTest()).run();
376     }
377 }
378