From 1f33afb5a1bffefe7e62a244aff7e50017813e20 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 16 Sep 2021 22:56:13 +0200 Subject: [PATCH] Fixed NullPointerException due to missing PhotometricInterpretation, now uses fallback as we do when reading. --- .../plugins/tiff/TIFFImageMetadata.java | 51 ++++++++++------ .../imageio/plugins/tiff/TIFFImageReader.java | 59 +++++++++++-------- .../plugins/tiff/TIFFImageMetadataTest.java | 49 +++++++++------ 3 files changed, 96 insertions(+), 63 deletions(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadata.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadata.java index e857a63a..41d45c40 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadata.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadata.java @@ -30,6 +30,22 @@ package com.twelvemonkeys.imageio.plugins.tiff; +import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader.guessPhotometricInterpretation; + +import java.lang.reflect.Array; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + import com.twelvemonkeys.imageio.AbstractMetadata; import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Entry; @@ -39,19 +55,6 @@ import com.twelvemonkeys.imageio.metadata.tiff.TIFF; import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry; import com.twelvemonkeys.lang.Validate; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import javax.imageio.metadata.IIOInvalidTreeException; -import javax.imageio.metadata.IIOMetadataFormatImpl; -import javax.imageio.metadata.IIOMetadataNode; -import java.lang.reflect.Array; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; - /** * TIFFImageMetadata. * @@ -354,8 +357,7 @@ public final class TIFFImageMetadata extends AbstractMetadata { IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); // Handle ColorSpaceType (RGB/CMYK/YCbCr etc)... - Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION); - int photometricValue = getValueAsInt(photometricTag); // No default for this tag! + int photometricValue = getPhotometricInterpretationWithFallback(); // No default for this tag! int numChannelsValue = getSamplesPerPixelWithFallback(); IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType"); @@ -446,6 +448,13 @@ public final class TIFFImageMetadata extends AbstractMetadata { return chroma; } + private int getPhotometricInterpretationWithFallback() { + Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION); + + return photometricTag != null ? getValueAsInt(photometricTag) + : guessPhotometricInterpretation(getCompression(), getSamplesPerPixelWithFallback(), ifd.getEntryById(TIFF.TAG_EXTRA_SAMPLES), ifd.getEntryById(TIFF.TAG_COLOR_MAP)); + } + private int getSamplesPerPixelWithFallback() { // SamplePerPixel defaults to 1, but we'll check BitsPerSample to be sure Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL); @@ -456,15 +465,19 @@ public final class TIFFImageMetadata extends AbstractMetadata { : bitsPerSampleTag != null ? bitsPerSampleTag.valueCount() : 1; } + private int getCompression() { + Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION); + return compressionTag == null + ? TIFFBaseline.COMPRESSION_NONE + : getValueAsInt(compressionTag); + } + @Override protected IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression = new IIOMetadataNode("Compression"); IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null); - Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION); - int compressionValue = compressionTag == null - ? TIFFBaseline.COMPRESSION_NONE - : getValueAsInt(compressionTag); + int compressionValue = getCompression(); // Naming is identical to JAI ImageIO metadata as far as possible switch (compressionValue) { diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index ad6ae785..2bca8d13 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -712,40 +712,49 @@ public final class TIFFImageReader extends ImageReaderBase { private int getPhotometricInterpretationWithFallback() throws IIOException { // PhotometricInterpretation is a required tag, but as it can be guessed this does a fallback that is similar to JAI ImageIO. int interpretation = getValueAsIntWithDefault(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation", -1); + if (interpretation == -1) { int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE); int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1); - Entry extraSamplesEntry = currentIFD.getEntryById(TIFF.TAG_EXTRA_SAMPLES); - int extraSamples = extraSamplesEntry == null ? 0 : extraSamplesEntry.valueCount(); + Entry extraSamples = currentIFD.getEntryById(TIFF.TAG_EXTRA_SAMPLES); + Entry colorMap = currentIFD.getEntryById(TIFF.TAG_COLOR_MAP); + + interpretation = guessPhotometricInterpretation(compression, samplesPerPixel, extraSamples, colorMap); - if (compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE - || compression == TIFFExtension.COMPRESSION_CCITT_T4 - || compression == TIFFExtension.COMPRESSION_CCITT_T6) { - interpretation = TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO; - } - else if (currentIFD.getEntryById(TIFF.TAG_COLOR_MAP) != null) { - interpretation = TIFFBaseline.PHOTOMETRIC_PALETTE; - } - else if ((samplesPerPixel - extraSamples) == 3) { - if (compression == TIFFExtension.COMPRESSION_JPEG - || compression == TIFFExtension.COMPRESSION_OLD_JPEG) { - interpretation = TIFFExtension.PHOTOMETRIC_YCBCR; - } - else { - interpretation = TIFFBaseline.PHOTOMETRIC_RGB; - } - } - else if ((samplesPerPixel - extraSamples) == 4) { - interpretation = TIFFExtension.PHOTOMETRIC_SEPARATED; - } - else { - interpretation = TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO; - } processWarningOccurred("Missing PhotometricInterpretation, determining fallback: " + interpretation); } + return interpretation; } + static int guessPhotometricInterpretation(int compression, int samplesPerPixel, Entry extraSamples, Entry colorMap) { + int extraSamplesCount = extraSamples == null ? 0 : extraSamples.valueCount(); + + if (compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE + || compression == TIFFExtension.COMPRESSION_CCITT_T4 + || compression == TIFFExtension.COMPRESSION_CCITT_T6) { + return TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO; + } + else if (colorMap != null) { + return TIFFBaseline.PHOTOMETRIC_PALETTE; + } + else if ((samplesPerPixel - extraSamplesCount) == 3) { + if (compression == TIFFExtension.COMPRESSION_JPEG + || compression == TIFFExtension.COMPRESSION_OLD_JPEG) { + return TIFFExtension.PHOTOMETRIC_YCBCR; + } + else { + return TIFFBaseline.PHOTOMETRIC_RGB; + } + } + else if ((samplesPerPixel - extraSamplesCount) == 4) { + return TIFFExtension.PHOTOMETRIC_SEPARATED; + } + else { + return TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO; + } + } + private int getOpaqueSamplesPerPixel(final int photometricInterpretation) throws IIOException { switch (photometricInterpretation) { case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO: diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadataTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadataTest.java index 0fce4b62..f081e929 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadataTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadataTest.java @@ -29,19 +29,14 @@ */ package com.twelvemonkeys.imageio.plugins.tiff; -import com.twelvemonkeys.imageio.metadata.Directory; -import com.twelvemonkeys.imageio.metadata.Entry; -import com.twelvemonkeys.imageio.metadata.tiff.Rational; -import com.twelvemonkeys.imageio.metadata.tiff.TIFF; -import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry; -import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader; -import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi; -import com.twelvemonkeys.lang.StringUtil; +import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME; +import static org.junit.Assert.*; -import org.junit.Test; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import javax.imageio.ImageIO; import javax.imageio.metadata.IIOInvalidTreeException; @@ -50,14 +45,20 @@ import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.spi.IIORegistry; import javax.imageio.stream.ImageInputStream; -import java.io.IOException; -import java.net.URL; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME; -import static org.junit.Assert.*; +import org.junit.Test; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.Entry; +import com.twelvemonkeys.imageio.metadata.tiff.Rational; +import com.twelvemonkeys.imageio.metadata.tiff.TIFF; +import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry; +import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader; +import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi; +import com.twelvemonkeys.lang.StringUtil; /** * TIFFImageMetadataTest. @@ -565,6 +566,16 @@ public class TIFFImageMetadataTest { assertNotNull(standardTree); } + + @Test + public void testGuessMissingPhotometric() throws IOException { + IIOMetadata metadata = createMetadata("/tiff/guessPhotometric/group4.tif"); + + // Test that we don't blow up with a NPE due to missing photometric + IIOMetadataNode standardTree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + assertNotNull(standardTree); + } + // TODO: Test that failed set leaves metadata unchanged private void assertSingleNodeWithValue(final NodeList fields, final int tag, int type, final String... expectedValue) {