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