jdk/test/javax/imageio/plugins/tiff/MultiPageImageTIFFFieldTest.java
author bpb
Tue, 13 Dec 2016 12:02:37 -0800
changeset 42749 91fb907a8732
parent 40137 0d7d50ab241f
permissions -rw-r--r--
8154058: [TIFF] ignoreMetadata parameter of TIFFImageReader's setInput() method affects TIFFImageReadParam in non-obvious way Summary: Add readUnknownTags to TIFFImageReadParam and add ReadParamTest Reviewed-by: prr

/*
 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/**
 * @test
 * @bug     8152183 8148454
 * @author  a.stepanov
 * @summary check that TIFFields are derived properly for multi-page tiff
 * @run     main MultiPageImageTIFFFieldTest
 */

import java.awt.*;
import java.awt.color.*;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.*;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;
import javax.imageio.plugins.tiff.*;


public class MultiPageImageTIFFFieldTest {

    private final static String FILENAME = "test.tiff";
    private final static int W1 = 20, H1 = 40, W2 = 100, H2 = 15;
    private final static Color C1 = Color.BLACK, C2 = Color.RED;

    private final static int N_WIDTH  = BaselineTIFFTagSet.TAG_IMAGE_WIDTH;
    private final static int N_HEIGHT = BaselineTIFFTagSet.TAG_IMAGE_LENGTH;

    private static final String DESCRIPTION_1[] = {"Description-1", "abc ABC"};
    private static final String DESCRIPTION_2[] = {"Description-2", "1-2-3"};
    private final static int N_DESCRIPTION =
        BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION;

    private final static String EXIF_DATA_1[] = {"2001:01:01 00:00:01"};
    private final static String EXIF_DATA_2[] = {"2002:02:02 00:00:02"};
    private final static int N_EXIF = ExifTIFFTagSet.TAG_DATE_TIME_ORIGINAL;

    private final static String GPS_DATA[] = {
        ExifGPSTagSet.STATUS_MEASUREMENT_IN_PROGRESS};
    private final static int N_GPS = ExifGPSTagSet.TAG_GPS_STATUS;

    private final static short FAX_DATA =
        FaxTIFFTagSet.CLEAN_FAX_DATA_ERRORS_UNCORRECTED;
    private final static int N_FAX = FaxTIFFTagSet.TAG_CLEAN_FAX_DATA;

    private static final byte[] ICC_PROFILE_2 =
        ICC_ProfileRGB.getInstance(ColorSpace.CS_sRGB).getData();
    private static final int N_ICC = BaselineTIFFTagSet.TAG_ICC_PROFILE;

    private static final int N_BPS = BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE;

    private static final int
        COMPRESSION_1 = BaselineTIFFTagSet.COMPRESSION_DEFLATE,
        COMPRESSION_2 = BaselineTIFFTagSet.COMPRESSION_LZW;
    private static final int N_COMPRESSION = BaselineTIFFTagSet.TAG_COMPRESSION;

    private static final int
        GRAY_1 = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO,
        GRAY_2 = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO,
        RGB    = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;

    private static final int N_PHOTO =
        BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION;

    private ImageWriter getTIFFWriter() {

        java.util.Iterator<ImageWriter> writers =
            ImageIO.getImageWritersByFormatName("TIFF");
        if (!writers.hasNext()) {
            throw new RuntimeException("No writers available for TIFF format");
        }
        return writers.next();
    }

    private ImageReader getTIFFReader() {

        java.util.Iterator<ImageReader> readers =
            ImageIO.getImageReadersByFormatName("TIFF");
        if (!readers.hasNext()) {
            throw new RuntimeException("No readers available for TIFF format");
        }
        return readers.next();
    }

    private void addASCIIField(TIFFDirectory d,
                               String        name,
                               String        data[],
                               int           num) {

        d.addTIFFField(new TIFFField(
            new TIFFTag(name, num, 1 << TIFFTag.TIFF_ASCII),
                TIFFTag.TIFF_ASCII, data.length, data));
    }

    private void checkASCIIField(TIFFDirectory d,
                                 String        what,
                                 String        data[],
                                 int           num) {

        String notFound = what + " field was not found";
        check(d.containsTIFFField(num), notFound);
        TIFFField f = d.getTIFFField(num);
        check(f.getType() == TIFFTag.TIFF_ASCII, "field type != ASCII");
        check(f.getCount() == data.length, "invalid " + what + " data count");
        for (int i = 0; i < data.length; i++) {
            check(f.getValueAsString(i).equals(data[i]),
                "invalid " + what + " data");
        }
    }

    private void writeImage() throws Exception {

        OutputStream s = new BufferedOutputStream(new FileOutputStream(FILENAME));
        try (ImageOutputStream ios = ImageIO.createImageOutputStream(s)) {

            ImageWriter writer = getTIFFWriter();
            writer.setOutput(ios);

            BufferedImage img1 =
                new BufferedImage(W1, H1, BufferedImage.TYPE_BYTE_GRAY);
            Graphics g = img1.getGraphics();
            g.setColor(C1);
            g.fillRect(0, 0, W1, H1);
            g.dispose();

            BufferedImage img2 =
                new BufferedImage(W2, H2, BufferedImage.TYPE_INT_RGB);
            g = img2.getGraphics();
            g.setColor(C2);
            g.fillRect(0, 0, W2, H2);
            g.dispose();

            ImageWriteParam param1 = writer.getDefaultWriteParam();
            param1.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            param1.setCompressionType("Deflate");
            param1.setCompressionQuality(0.5f);

            ImageWriteParam param2 = writer.getDefaultWriteParam();
            param2.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            param2.setCompressionType("LZW");
            param2.setCompressionQuality(0.5f);

            IIOMetadata
                md1 = writer.getDefaultImageMetadata(
                    new ImageTypeSpecifier(img1), param1),
                md2 = writer.getDefaultImageMetadata(
                    new ImageTypeSpecifier(img2), param2);

            TIFFDirectory
                dir1 = TIFFDirectory.createFromMetadata(md1),
                dir2 = TIFFDirectory.createFromMetadata(md2);

            addASCIIField(dir1, "ImageDescription", DESCRIPTION_1, N_DESCRIPTION);
            addASCIIField(dir2, "ImageDescription", DESCRIPTION_2, N_DESCRIPTION);

            addASCIIField(dir1, "GPSStatus", GPS_DATA, N_GPS);
            addASCIIField(dir2, "GPSStatus", GPS_DATA, N_GPS);

            addASCIIField(dir1, "DateTimeOriginal", EXIF_DATA_1, N_EXIF);
            addASCIIField(dir2, "DateTimeOriginal", EXIF_DATA_2, N_EXIF);

            TIFFTag faxTag = new TIFFTag(
                "CleanFaxData", N_FAX, 1 << TIFFTag.TIFF_SHORT);
            dir1.addTIFFField(new TIFFField(faxTag, FAX_DATA));
            dir2.addTIFFField(new TIFFField(faxTag, FAX_DATA));

            dir2.addTIFFField(new TIFFField(
                new TIFFTag("ICC Profile", N_ICC, 1 << TIFFTag.TIFF_UNDEFINED),
                TIFFTag.TIFF_UNDEFINED, ICC_PROFILE_2.length, ICC_PROFILE_2));

            writer.prepareWriteSequence(null);
            writer.writeToSequence(
                new IIOImage(img1, null, dir1.getAsMetadata()), param1);
            writer.writeToSequence(
                new IIOImage(img2, null, dir2.getAsMetadata()), param2);
            writer.endWriteSequence();

            ios.flush();
            writer.dispose();
        }
        s.close();
    }

    private void checkBufferedImages(BufferedImage im1, BufferedImage im2) {

        check(im1.getWidth()  == W1, "invalid width for image 1");
        check(im1.getHeight() == H1, "invalid height for image 1");
        check(im2.getWidth()  == W2, "invalid width for image 2");
        check(im2.getHeight() == H2, "invalid height for image 2");

        Color
            c1 = new Color(im1.getRGB(W1 / 2, H1 / 2)),
            c2 = new Color(im2.getRGB(W2 / 2, H2 / 2));

        check(c1.equals(C1), "invalid image 1 color");
        check(c2.equals(C2), "invalid image 2 color");
    }

    private void readAndCheckImage() throws Exception {

        ImageReader reader = getTIFFReader();

        ImageInputStream s = ImageIO.createImageInputStream(new File(FILENAME));
        reader.setInput(s, false, false);

        int ni = reader.getNumImages(true);
        check(ni == 2, "invalid number of images");

        // check TIFFImageReadParam for multipage image
        TIFFImageReadParam
            param1 = new TIFFImageReadParam(), param2 = new TIFFImageReadParam();

        param1.addAllowedTagSet(ExifTIFFTagSet.getInstance());
        param1.addAllowedTagSet(ExifGPSTagSet.getInstance());

        param2.addAllowedTagSet(ExifTIFFTagSet.getInstance());
        param2.addAllowedTagSet(GeoTIFFTagSet.getInstance());

        // FaxTIFFTagSet is allowed by default
        param2.removeAllowedTagSet(FaxTIFFTagSet.getInstance());


        // read images and metadata
        IIOImage i1 = reader.readAll(0, param1), i2 = reader.readAll(1, param2);
        BufferedImage
            bi1 = (BufferedImage) i1.getRenderedImage(),
            bi2 = (BufferedImage) i2.getRenderedImage();

        // check rendered images, just in case
        checkBufferedImages(bi1, bi2);

        TIFFDirectory
            dir1 = TIFFDirectory.createFromMetadata(i1.getMetadata()),
            dir2 = TIFFDirectory.createFromMetadata(i2.getMetadata());

        // check ASCII fields
        checkASCIIField(
            dir1, "image 1 description", DESCRIPTION_1, N_DESCRIPTION);
        checkASCIIField(
            dir2, "image 2 description", DESCRIPTION_2, N_DESCRIPTION);

        checkASCIIField(dir1, "image 1 datetime", EXIF_DATA_1, N_EXIF);
        checkASCIIField(dir2, "image 2 datetime", EXIF_DATA_2, N_EXIF);

        // check sizes
        TIFFField f = dir1.getTIFFField(N_WIDTH);
        check((f.getCount() == 1) && (f.getAsInt(0) == W1),
            "invalid width field for image 1");
        f = dir2.getTIFFField(N_WIDTH);
        check((f.getCount() == 1) && (f.getAsInt(0) == W2),
            "invalid width field for image 2");

        f = dir1.getTIFFField(N_HEIGHT);
        check((f.getCount() == 1) && (f.getAsInt(0) == H1),
            "invalid height field for image 1");
        f = dir2.getTIFFField(N_HEIGHT);
        check((f.getCount() == 1) && (f.getAsInt(0) == H2),
            "invalid height field for image 2");

        // check fax data
        check(dir1.containsTIFFField(N_FAX), "image 2 TIFF directory " +
            "must contain clean fax data");
        f = dir1.getTIFFField(N_FAX);
        check(
            (f.getCount() == 1) && f.isIntegral() && (f.getAsInt(0) == FAX_DATA),
            "invalid clean fax data");

        check(!dir2.containsTIFFField(N_FAX), "image 2 TIFF directory " +
            "must not contain fax fields");

        // check GPS data
        checkASCIIField(dir1, "GPS status", GPS_DATA, N_GPS);

        check(!dir2.containsTIFFField(N_GPS), "image 2 TIFF directory " +
            "must not contain GPS fields");

        // check ICC profile data
        check(!dir1.containsTIFFField(N_ICC), "image 1 TIFF directory "
            + "must not contain ICC Profile field");
        check(dir2.containsTIFFField(N_ICC), "image 2 TIFF directory "
            + "must contain ICC Profile field");

        f = dir2.getTIFFField(N_ICC);
        check(f.getType() == TIFFTag.TIFF_UNDEFINED,
            "invalid ICC profile field type");
        int cnt = f.getCount();
        byte icc[] = f.getAsBytes();
        check((cnt == ICC_PROFILE_2.length) && (cnt == icc.length),
                "invalid ICC profile");
        for (int i = 0; i < cnt; i++) {
            check(icc[i] == ICC_PROFILE_2[i], "invalid ICC profile data");
        }

        // check component sizes
        check(dir1.getTIFFField(N_BPS).isIntegral() &&
              dir2.getTIFFField(N_BPS).isIntegral(),
              "invalid bits per sample type");
        int sz1[] = bi1.getColorModel().getComponentSize(),
            sz2[] = bi2.getColorModel().getComponentSize(),
            bps1[] = dir1.getTIFFField(N_BPS).getAsInts(),
            bps2[] = dir2.getTIFFField(N_BPS).getAsInts();

        check((bps1.length == sz1.length) && (bps2.length == sz2.length),
            "invalid component size count");

        for (int i = 0; i < bps1.length; i++) {
            check(bps1[i] == sz1[i], "image 1: invalid bits per sample data");
        }

        for (int i = 0; i < bps2.length; i++) {
            check(bps2[i] == sz2[i], "image 2: invalid bits per sample data");
        }

        // check compression data
        check(dir1.containsTIFFField(N_COMPRESSION) &&
              dir2.containsTIFFField(N_COMPRESSION),
              "compression info lost");
        f = dir1.getTIFFField(N_COMPRESSION);
        check(f.isIntegral() && (f.getCount() == 1) &&
            (f.getAsInt(0) == COMPRESSION_1), "invalid image 1 compression data");

        f = dir2.getTIFFField(N_COMPRESSION);
        check(f.isIntegral() && (f.getCount() == 1) &&
            (f.getAsInt(0) == COMPRESSION_2), "invalid image 2 compression data");

        // check photometric interpretation
        f = dir1.getTIFFField(N_PHOTO);
        check(f.isIntegral() && (f.getCount() == 1) &&
            ((f.getAsInt(0) == GRAY_1) || (f.getAsInt(0) == GRAY_2)),
            "invalid photometric interpretation for image 1");

        f = dir2.getTIFFField(N_PHOTO);
        check(f.isIntegral() && (f.getCount() == 1) && (f.getAsInt(0) == RGB),
            "invalid photometric interpretation for image 2");
    }

    public void run() {

        try {
            writeImage();
            readAndCheckImage();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void check(boolean ok, String msg) {

        if (!ok) { throw new RuntimeException(msg); }
    }

    public static void main(String[] args) {
        (new MultiPageImageTIFFFieldTest()).run();
    }
}