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