Fixed NullPointerException due to missing PhotometricInterpretation, now uses fallback as we do when reading.

This commit is contained in:
Harald Kuhr 2021-09-16 22:56:13 +02:00
parent 9d3f271867
commit 1f33afb5a1
3 changed files with 96 additions and 63 deletions

View File

@ -30,6 +30,22 @@
package com.twelvemonkeys.imageio.plugins.tiff; 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.AbstractMetadata;
import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry; 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.imageio.metadata.tiff.TIFFEntry;
import com.twelvemonkeys.lang.Validate; 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. * TIFFImageMetadata.
* *
@ -354,8 +357,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
// Handle ColorSpaceType (RGB/CMYK/YCbCr etc)... // Handle ColorSpaceType (RGB/CMYK/YCbCr etc)...
Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION); int photometricValue = getPhotometricInterpretationWithFallback(); // No default for this tag!
int photometricValue = getValueAsInt(photometricTag); // No default for this tag!
int numChannelsValue = getSamplesPerPixelWithFallback(); int numChannelsValue = getSamplesPerPixelWithFallback();
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType"); IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
@ -446,6 +448,13 @@ public final class TIFFImageMetadata extends AbstractMetadata {
return chroma; 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() { private int getSamplesPerPixelWithFallback() {
// SamplePerPixel defaults to 1, but we'll check BitsPerSample to be sure // SamplePerPixel defaults to 1, but we'll check BitsPerSample to be sure
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL); Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
@ -456,15 +465,19 @@ public final class TIFFImageMetadata extends AbstractMetadata {
: bitsPerSampleTag != null ? bitsPerSampleTag.valueCount() : 1; : bitsPerSampleTag != null ? bitsPerSampleTag.valueCount() : 1;
} }
private int getCompression() {
Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION);
return compressionTag == null
? TIFFBaseline.COMPRESSION_NONE
: getValueAsInt(compressionTag);
}
@Override @Override
protected IIOMetadataNode getStandardCompressionNode() { protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode compression = new IIOMetadataNode("Compression"); IIOMetadataNode compression = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null); IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null);
Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION); int compressionValue = getCompression();
int compressionValue = compressionTag == null
? TIFFBaseline.COMPRESSION_NONE
: getValueAsInt(compressionTag);
// Naming is identical to JAI ImageIO metadata as far as possible // Naming is identical to JAI ImageIO metadata as far as possible
switch (compressionValue) { switch (compressionValue) {

View File

@ -712,40 +712,49 @@ public final class TIFFImageReader extends ImageReaderBase {
private int getPhotometricInterpretationWithFallback() throws IIOException { 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. // 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); int interpretation = getValueAsIntWithDefault(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation", -1);
if (interpretation == -1) { if (interpretation == -1) {
int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE); int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1); int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1);
Entry extraSamplesEntry = currentIFD.getEntryById(TIFF.TAG_EXTRA_SAMPLES); Entry extraSamples = currentIFD.getEntryById(TIFF.TAG_EXTRA_SAMPLES);
int extraSamples = extraSamplesEntry == null ? 0 : extraSamplesEntry.valueCount(); 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); processWarningOccurred("Missing PhotometricInterpretation, determining fallback: " + interpretation);
} }
return 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 { private int getOpaqueSamplesPerPixel(final int photometricInterpretation) throws IIOException {
switch (photometricInterpretation) { switch (photometricInterpretation) {
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO: case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:

View File

@ -29,19 +29,14 @@
*/ */
package com.twelvemonkeys.imageio.plugins.tiff; package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.imageio.metadata.Directory; import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
import com.twelvemonkeys.imageio.metadata.Entry; import static org.junit.Assert.*;
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 org.junit.Test; import java.io.IOException;
import org.w3c.dom.Element; import java.net.URL;
import org.w3c.dom.Node; import java.util.Collections;
import org.w3c.dom.NodeList; import java.util.HashSet;
import java.util.Set;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.metadata.IIOInvalidTreeException; import javax.imageio.metadata.IIOInvalidTreeException;
@ -50,14 +45,20 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.IIORegistry; import javax.imageio.spi.IIORegistry;
import javax.imageio.stream.ImageInputStream; 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 org.junit.Test;
import static org.junit.Assert.*; 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. * TIFFImageMetadataTest.
@ -565,6 +566,16 @@ public class TIFFImageMetadataTest {
assertNotNull(standardTree); 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 // TODO: Test that failed set leaves metadata unchanged
private void assertSingleNodeWithValue(final NodeList fields, final int tag, int type, final String... expectedValue) { private void assertSingleNodeWithValue(final NodeList fields, final int tag, int type, final String... expectedValue) {