TIFFImageMetadataTest.java
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.tiff;
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
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;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.IIORegistry;
import javax.imageio.stream.ImageInputStream;
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.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: TIFFImageMetadataTest.java,v 1.0 30/07/15 harald.kuhr Exp$
*/
public class TIFFImageMetadataTest {
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
ImageIO.setUseCache(false);
}
// TODO: Candidate super method
private URL getClassLoaderResource(final String resource) {
return getClass().getResource(resource);
}
// TODO: Candidate abstract super method
private IIOMetadata createMetadata(final String resource) throws IOException {
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource(resource))) {
Directory ifd = new TIFFReader().read(input);
// System.err.println("ifd: " + ifd);
return new TIFFImageMetadata(ifd);
}
}
@Test
public void testMetadataStandardFormat() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/smallliz.tif");
Node root = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
// Root: "javax_imageio_1.0"
assertNotNull(root);
assertEquals(IIOMetadataFormatImpl.standardMetadataFormatName, root.getNodeName());
assertEquals(6, root.getChildNodes().getLength());
// "Chroma"
Node chroma = root.getFirstChild();
assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getChildNodes().getLength());
Node colorSpaceType = chroma.getFirstChild();
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
assertEquals("YCbCr", ((Element) colorSpaceType).getAttribute("name"));
Node numChannels = colorSpaceType.getNextSibling();
assertEquals("NumChannels", numChannels.getNodeName());
assertEquals("3", ((Element) numChannels).getAttribute("value"));
Node blackIsZero = numChannels.getNextSibling();
assertEquals("BlackIsZero", blackIsZero.getNodeName());
assertEquals(0, blackIsZero.getAttributes().getLength());
// "Compression"
Node compression = chroma.getNextSibling();
assertEquals("Compression", compression.getNodeName());
assertEquals(2, compression.getChildNodes().getLength());
Node compressionTypeName = compression.getFirstChild();
assertEquals("CompressionTypeName", compressionTypeName.getNodeName());
assertEquals("Old JPEG", ((Element) compressionTypeName).getAttribute("value"));
Node lossless = compressionTypeName.getNextSibling();
assertEquals("Lossless", lossless.getNodeName());
assertEquals("FALSE", ((Element) lossless).getAttribute("value"));
// "Data"
Node data = compression.getNextSibling();
assertEquals("Data", data.getNodeName());
assertEquals(4, data.getChildNodes().getLength());
Node planarConfiguration = data.getFirstChild();
assertEquals("PlanarConfiguration", planarConfiguration.getNodeName());
assertEquals("PixelInterleaved", ((Element) planarConfiguration).getAttribute("value"));
Node sampleFormat = planarConfiguration.getNextSibling();
assertEquals("SampleFormat", sampleFormat.getNodeName());
assertEquals("UnsignedIntegral", ((Element) sampleFormat).getAttribute("value"));
Node bitsPerSample = sampleFormat.getNextSibling();
assertEquals("BitsPerSample", bitsPerSample.getNodeName());
assertEquals("8 8 8", ((Element) bitsPerSample).getAttribute("value"));
Node sampleMSB = bitsPerSample.getNextSibling();
assertEquals("SampleMSB", sampleMSB.getNodeName());
assertEquals("0 0 0", ((Element) sampleMSB).getAttribute("value"));
// "Dimension"
Node dimension = data.getNextSibling();
assertEquals("Dimension", dimension.getNodeName());
assertEquals(3, dimension.getChildNodes().getLength());
Node pixelAspectRatio = dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("1.0", ((Element) pixelAspectRatio).getAttribute("value"));
Node horizontalPixelSize = pixelAspectRatio.getNextSibling();
assertEquals("HorizontalPixelSize", horizontalPixelSize.getNodeName());
assertEquals("0.254", ((Element) horizontalPixelSize).getAttribute("value"));
Node verticalPixelSize = horizontalPixelSize.getNextSibling();
assertEquals("VerticalPixelSize", verticalPixelSize.getNodeName());
assertEquals("0.254", ((Element) verticalPixelSize).getAttribute("value"));
// "Document"
Node document = dimension.getNextSibling();
assertEquals("Document", document.getNodeName());
assertEquals(1, document.getChildNodes().getLength());
Node formatVersion = document.getFirstChild();
assertEquals("FormatVersion", formatVersion.getNodeName());
assertEquals("6.0", ((Element) formatVersion).getAttribute("value"));
// "Text"
Node text = document.getNextSibling();
assertEquals("Text", text.getNodeName());
assertEquals(1, text.getChildNodes().getLength());
// NOTE: Could be multiple "TextEntry" elements, with different "keyword" attributes
Node textEntry = text.getFirstChild();
assertEquals("TextEntry", textEntry.getNodeName());
assertEquals("Software", ((Element) textEntry).getAttribute("keyword"));
assertEquals("HP IL v1.1", ((Element) textEntry).getAttribute("value"));
}
@Test
public void testMetadataStandardFormat_CCITT300dpi() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/CCITT-G4-300dpi-StripByteCounts0.tif");
Node root = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
// Root: "javax_imageio_1.0"
assertNotNull(root);
assertEquals(IIOMetadataFormatImpl.standardMetadataFormatName, root.getNodeName());
assertEquals(5, root.getChildNodes().getLength());
// "Chroma"
Node chroma = root.getFirstChild();
assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getChildNodes().getLength());
Node colorSpaceType = chroma.getFirstChild();
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
assertEquals("GRAY", ((Element) colorSpaceType).getAttribute("name"));
Node numChannels = colorSpaceType.getNextSibling();
assertEquals("NumChannels", numChannels.getNodeName());
assertEquals("1", ((Element) numChannels).getAttribute("value"));
Node blackIsZero = numChannels.getNextSibling();
assertEquals("BlackIsZero", blackIsZero.getNodeName());
assertEquals("FALSE", ((Element) blackIsZero).getAttribute("value"));
// "Compression"
Node compression = chroma.getNextSibling();
assertEquals("Compression", compression.getNodeName());
assertEquals(2, compression.getChildNodes().getLength());
Node compressionTypeName = compression.getFirstChild();
assertEquals("CompressionTypeName", compressionTypeName.getNodeName());
assertEquals("CCITT T.6", ((Element) compressionTypeName).getAttribute("value"));
Node lossless = compressionTypeName.getNextSibling();
assertEquals("Lossless", lossless.getNodeName());
assertEquals(0, lossless.getAttributes().getLength());
// "Data"
Node data = compression.getNextSibling();
assertEquals("Data", data.getNodeName());
assertEquals(4, data.getChildNodes().getLength());
Node planarConfiguration = data.getFirstChild();
assertEquals("PlanarConfiguration", planarConfiguration.getNodeName());
assertEquals("PixelInterleaved", ((Element) planarConfiguration).getAttribute("value"));
Node sampleFormat = planarConfiguration.getNextSibling();
assertEquals("SampleFormat", sampleFormat.getNodeName());
assertEquals("UnsignedIntegral", ((Element) sampleFormat).getAttribute("value"));
Node bitsPerSample = sampleFormat.getNextSibling();
assertEquals("BitsPerSample", bitsPerSample.getNodeName());
assertEquals("1", ((Element) bitsPerSample).getAttribute("value"));
Node sampleMSB = bitsPerSample.getNextSibling();
assertEquals("SampleMSB", sampleMSB.getNodeName());
assertEquals("0", ((Element) sampleMSB).getAttribute("value"));
// "Dimension"
Node dimension = data.getNextSibling();
assertEquals("Dimension", dimension.getNodeName());
assertEquals(4, dimension.getChildNodes().getLength());
Node pixelAspectRatio = dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("1.0", ((Element) pixelAspectRatio).getAttribute("value"));
Node imageOrientation = pixelAspectRatio.getNextSibling();
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", ((Element) imageOrientation).getAttribute("value"));
Node horizontalPixelSize = imageOrientation.getNextSibling();
assertEquals("HorizontalPixelSize", horizontalPixelSize.getNodeName());
assertEquals("0.08466666666666667", ((Element) horizontalPixelSize).getAttribute("value"));
Node verticalPixelSize = horizontalPixelSize.getNextSibling();
assertEquals("VerticalPixelSize", verticalPixelSize.getNodeName());
assertEquals("0.08466666666666667", ((Element) verticalPixelSize).getAttribute("value"));
// "Document"
Node document = dimension.getNextSibling();
assertEquals("Document", document.getNodeName());
assertEquals(1, document.getChildNodes().getLength());
Node formatVersion = document.getFirstChild();
assertEquals("FormatVersion", formatVersion.getNodeName());
assertEquals("6.0", ((Element) formatVersion).getAttribute("value"));
// No more elements
assertNull(document.getNextSibling());
}
@Test
public void testMetadataNativeFormat() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/quad-lzw.tif");
Node root = metadata.getAsTree(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
// Root: "com_sun_media_imageio_plugins_tiff_image_1.0"
assertNotNull(root);
assertEquals(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, root.getNodeName());
assertEquals(1, root.getChildNodes().getLength());
// IFD: "TIFFIFD"
Node ifd = root.getFirstChild();
assertEquals("TIFFIFD", ifd.getNodeName());
NodeList entries = ifd.getChildNodes();
assertEquals(13, entries.getLength());
String[] stripOffsets = {
"8", "150", "292", "434", "576", "718", "860", "1002", "1144", "1286",
"1793", "3823", "7580", "12225", "17737", "23978", "30534", "36863", "42975", "49180",
"55361", "61470", "67022", "71646", "74255", "75241", "75411", "75553", "75695", "75837",
"75979", "76316", "77899", "80466", "84068", "88471", "93623", "99105", "104483", "109663",
"114969", "120472", "126083", "131289", "135545", "138810", "140808", "141840", "141982", "142124",
"142266", "142408", "142615", "144074", "146327", "149721", "154066", "158927", "164022", "169217",
"174409", "179657", "185166", "190684", "196236", "201560", "206064", "209497", "211612", "212419",
"212561", "212703", "212845", "212987", "213129", "213271", "213413"
};
String[] stripByteCounts = {
"142", "142", "142", "142", "142", "142", "142", "142", "142", "507",
"2030", "3757", "4645", "5512", "6241", "6556", "6329", "6112", "6205", "6181",
"6109", "5552", "4624", "2609", "986", "170", "142", "142", "142", "142",
"337", "1583", "2567", "3602", "4403", "5152", "5482", "5378", "5180", "5306",
"5503", "5611", "5206", "4256", "3265", "1998", "1032", "142", "142", "142",
"142", "207", "1459", "2253", "3394", "4345", "4861", "5095", "5195", "5192",
"5248", "5509", "5518", "5552", "5324", "4504", "3433", "2115", "807", "142",
"142", "142", "142", "142", "142", "142", "128"
};
// The 13 entries
assertSingleNodeWithValue(entries, TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, "512");
assertSingleNodeWithValue(entries, TIFF.TAG_IMAGE_HEIGHT, TIFF.TYPE_SHORT, "384");
assertSingleNodeWithValue(entries, TIFF.TAG_BITS_PER_SAMPLE, TIFF.TYPE_SHORT, "8", "8", "8");
assertSingleNodeWithValue(entries, TIFF.TAG_COMPRESSION, TIFF.TYPE_SHORT, "5");
assertSingleNodeWithValue(entries, TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, "2");
assertSingleNodeWithValue(entries, TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffsets);
assertSingleNodeWithValue(entries, TIFF.TAG_SAMPLES_PER_PIXEL, TIFF.TYPE_SHORT, "3");
assertSingleNodeWithValue(entries, TIFF.TAG_ROWS_PER_STRIP, TIFF.TYPE_LONG, "5");
assertSingleNodeWithValue(entries, TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCounts);
assertSingleNodeWithValue(entries, TIFF.TAG_PLANAR_CONFIGURATION, TIFF.TYPE_SHORT, "1");
assertSingleNodeWithValue(entries, TIFF.TAG_X_POSITION, TIFF.TYPE_RATIONAL, "0/1");
assertSingleNodeWithValue(entries, TIFF.TAG_Y_POSITION, TIFF.TYPE_RATIONAL, "0/1");
assertSingleNodeWithValue(entries, 32995, TIFF.TYPE_SHORT, "0"); // Matteing tag, obsoleted by ExtraSamples tag in TIFF 6.0
}
@Test
public void testTreeDetached() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
Node nativeTree = metadata.getAsTree(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
assertNotNull(nativeTree);
Node nativeTree2 = metadata.getAsTree(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
assertNotNull(nativeTree2);
assertNotSame(nativeTree, nativeTree2);
assertNodeEquals("Unmodified trees differs", nativeTree, nativeTree2); // Both not modified
// Modify one of the trees
Node ifdNode = nativeTree2.getFirstChild();
ifdNode.removeChild(ifdNode.getFirstChild());
IIOMetadataNode tiffField = new IIOMetadataNode("TIFFField");
ifdNode.appendChild(tiffField);
assertNodeNotEquals("Modified tree does not differ", nativeTree, nativeTree2);
}
@Test
public void testMergeTree() throws IOException {
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
Node nativeTree = metadata.getAsTree(nativeFormat);
assertNotNull(nativeTree);
IIOMetadataNode newTree = new IIOMetadataNode("com_sun_media_imageio_plugins_tiff_image_1.0");
IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD");
newTree.appendChild(ifdNode);
createTIFFFieldNode(ifdNode, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, TIFFBaseline.RESOLUTION_UNIT_DPI);
createTIFFFieldNode(ifdNode, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, new Rational(300));
createTIFFFieldNode(ifdNode, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, new Rational(30001, 100));
metadata.mergeTree(nativeFormat, newTree);
Directory ifd = metadata.getIFD();
assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
assertEquals(new Rational(30001, 100), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
assertEquals(TIFFBaseline.RESOLUTION_UNIT_DPI, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
Node mergedTree = metadata.getAsTree(nativeFormat);
NodeList fields = mergedTree.getFirstChild().getChildNodes();
// Validate there's one and only one resolution unit, x res and y res
// Validate resolution unit == 1, x res & y res
assertSingleNodeWithValue(fields, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, String.valueOf(TIFFBaseline.RESOLUTION_UNIT_DPI));
assertSingleNodeWithValue(fields, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, "300/1");
assertSingleNodeWithValue(fields, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, "30001/100");
}
@Test
public void testMergeTreeStandardFormat() throws IOException {
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/zackthecat.tif");
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
Node standardTree = metadata.getAsTree(standardFormat);
assertNotNull(standardTree);
IIOMetadataNode newTree = new IIOMetadataNode(standardFormat);
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
newTree.appendChild(dimensionNode);
IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
dimensionNode.appendChild(horizontalPixelSize);
horizontalPixelSize.setAttribute("value", String.valueOf(300 / 25.4));
IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
dimensionNode.appendChild(verticalPixelSize);
verticalPixelSize.setAttribute("value", String.valueOf(300 / 25.4));
IIOMetadataNode orientation = new IIOMetadataNode("ImageOrientation");
dimensionNode.appendChild(orientation);
orientation.setAttribute("value", "FlipV");
metadata.mergeTree(standardFormat, newTree);
Directory ifd = metadata.getIFD();
assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
assertNotNull(ifd.getEntryById(TIFF.TAG_ORIENTATION));
assertEquals(TIFFExtension.ORIENTATION_BOTLEFT, ((Number) ifd.getEntryById(TIFF.TAG_ORIENTATION).getValue()).intValue());
// Should keep DPI as unit
assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
assertEquals(TIFFBaseline.RESOLUTION_UNIT_DPI, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
}
@Test
public void testMergeTreeStandardFormatAspectOnly() throws IOException {
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
Node standardTree = metadata.getAsTree(standardFormat);
assertNotNull(standardTree);
IIOMetadataNode newTree = new IIOMetadataNode(standardFormat);
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
newTree.appendChild(dimensionNode);
IIOMetadataNode aspectRatio = new IIOMetadataNode("PixelAspectRatio");
dimensionNode.appendChild(aspectRatio);
aspectRatio.setAttribute("value", String.valueOf(3f / 2f));
metadata.mergeTree(standardFormat, newTree);
Directory ifd = metadata.getIFD();
assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
assertEquals(new Rational(3, 2), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
assertEquals(new Rational(1), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
assertEquals(TIFFBaseline.RESOLUTION_UNIT_NONE, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
}
@Test
public void testMergeTreeUnsupportedFormat() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = "com_foo_bar_tiff_42";
assertThrows(IllegalArgumentException.class, () -> metadata.mergeTree(nativeFormat, new IIOMetadataNode(nativeFormat)));
}
@Test
public void testMergeTreeFormatMisMatch() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
assertThrows(IIOInvalidTreeException.class, () -> metadata.mergeTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42")));
}
@Test
public void testMergeTreeInvalid() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
assertThrows(IIOInvalidTreeException.class, () ->metadata.mergeTree(nativeFormat, new IIOMetadataNode(nativeFormat))); // Requires at least one child node
}
// TODO: Test that failed merge leaves metadata unchanged
@Test
public void testSetFromTreeEmpty() throws IOException {
// Read from file, set empty to see that all is cleared
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
IIOMetadataNode root = new IIOMetadataNode(nativeFormat);
root.appendChild(new IIOMetadataNode("TIFFIFD"));
metadata.setFromTree(nativeFormat, root);
Directory ifd = metadata.getIFD();
assertNotNull(ifd);
assertEquals(0, ifd.size());
Node tree = metadata.getAsTree(nativeFormat);
assertNotNull(tree);
assertNotNull(tree.getFirstChild());
assertEquals(1, tree.getChildNodes().getLength());
}
@Test
public void testSetFromTree() throws IOException {
String softwareString = "12M UberTIFF 1.0";
TIFFImageMetadata metadata = new TIFFImageMetadata(Collections.<Entry>emptySet());
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
IIOMetadataNode root = new IIOMetadataNode(nativeFormat);
IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD");
root.appendChild(ifdNode);
createTIFFFieldNode(ifdNode, TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, softwareString);
metadata.setFromTree(nativeFormat, root);
Directory ifd = metadata.getIFD();
assertNotNull(ifd);
assertEquals(1, ifd.size());
assertNotNull(ifd.getEntryById(TIFF.TAG_SOFTWARE));
assertEquals(softwareString, ifd.getEntryById(TIFF.TAG_SOFTWARE).getValue());
Node tree = metadata.getAsTree(nativeFormat);
assertNotNull(tree);
assertNotNull(tree.getFirstChild());
assertEquals(1, tree.getChildNodes().getLength());
}
@Test
public void testSetFromTreeStandardFormat() throws IOException {
String softwareString = "12M UberTIFF 1.0";
String copyrightString = "Copyright (C) TwelveMonkeys, 2015";
TIFFImageMetadata metadata = new TIFFImageMetadata(Collections.<Entry>emptySet());
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
IIOMetadataNode root = new IIOMetadataNode(standardFormat);
IIOMetadataNode textNode = new IIOMetadataNode("Text");
root.appendChild(textNode);
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
textNode.appendChild(textEntry);
textEntry.setAttribute("keyword", "SOFTWARE"); // Spelling should not matter
textEntry.setAttribute("value", softwareString);
textEntry = new IIOMetadataNode("TextEntry");
textNode.appendChild(textEntry);
textEntry.setAttribute("keyword", "copyright"); // Spelling should not matter
textEntry.setAttribute("value", copyrightString);
metadata.setFromTree(standardFormat, root);
Directory ifd = metadata.getIFD();
assertNotNull(ifd);
assertEquals(2, ifd.size());
assertNotNull(ifd.getEntryById(TIFF.TAG_SOFTWARE));
assertEquals(softwareString, ifd.getEntryById(TIFF.TAG_SOFTWARE).getValue());
assertNotNull(ifd.getEntryById(TIFF.TAG_COPYRIGHT));
assertEquals(copyrightString, ifd.getEntryById(TIFF.TAG_COPYRIGHT).getValue());
}
@Test
public void testSetFromTreeUnsupportedFormat() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = "com_foo_bar_tiff_42";
assertThrows(IllegalArgumentException.class, () -> metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat)));
}
@Test
public void testSetFromTreeFormatMisMatch() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
assertThrows(IIOInvalidTreeException.class, () -> metadata.setFromTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42")));
}
@Test
public void testSetFromTreeInvalid() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
assertThrows(IIOInvalidTreeException.class, () -> metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat))); // Requires at least one child nod
}
@Test
public void testStandardChromaSamplesPerPixel() {
Set<Entry> entries = new HashSet<>();
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
entries.add(new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 4));
entries.add(new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, new int[] {8, 8, 8})); // This is incorrect, just making sure the correct value is selected
IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
assertNotNull(chromaNode);
IIOMetadataNode numChannels = (IIOMetadataNode) chromaNode.getElementsByTagName("NumChannels").item(0);
assertEquals("4", numChannels.getAttribute("value"));
}
@Test
public void testStandardChromaSamplesPerPixelFallbackBitsPerSample() {
Set<Entry> entries = new HashSet<>();
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
entries.add(new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, new int[] {8, 8, 8}));
IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
assertNotNull(chromaNode);
IIOMetadataNode numChannels = (IIOMetadataNode) chromaNode.getElementsByTagName("NumChannels").item(0);
assertEquals("3", numChannels.getAttribute("value"));
}
@Test
public void testStandardChromaSamplesPerPixelFallbackDefault() {
Set<Entry> entries = new HashSet<>();
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO));
IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
assertNotNull(chromaNode);
IIOMetadataNode numChannels = (IIOMetadataNode) chromaNode.getElementsByTagName("NumChannels").item(0);
assertEquals("1", numChannels.getAttribute("value"));
}
@Test
public void testStandardDataBitsPerSampleFallbackDefault() {
Set<Entry> entries = new HashSet<>();
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO));
IIOMetadataNode dataNode = new TIFFImageMetadata(entries).getStandardDataNode();
assertNotNull(dataNode);
IIOMetadataNode numChannels = (IIOMetadataNode) dataNode.getElementsByTagName("BitsPerSample").item(0);
assertEquals("1", numChannels.getAttribute("value"));
}
@Test
public void testStandardNodeSamplesPerPixelFallbackDefault() {
Set<Entry> entries = new HashSet<>();
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
// Just to make sure we haven't accidentally missed something
IIOMetadataNode standardTree = (IIOMetadataNode) new TIFFImageMetadata(entries).getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
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) {
String tagNumber = String.valueOf(tag);
String typeName = StringUtil.capitalize(TIFF.TYPE_NAMES[type].toLowerCase());
boolean foundTag = false;
for (int i = 0; i < fields.getLength(); i++) {
Element field = (Element) fields.item(i);
if (tagNumber.equals(field.getAttribute("number"))) {
assertFalse(foundTag, "Duplicate tag " + tagNumber + " found");
assertEquals(1, field.getChildNodes().getLength());
Node containerNode = field.getFirstChild();
assertEquals("TIFF" + typeName + "s", containerNode.getNodeName());
NodeList valueNodes = containerNode.getChildNodes();
assertEquals(expectedValue.length, valueNodes.getLength(), "Unexpected number of values for tag " + tagNumber);
for (int j = 0; j < expectedValue.length; j++) {
Element valueNode = (Element) valueNodes.item(j);
assertEquals("TIFF" + typeName, valueNode.getNodeName());
assertEquals(expectedValue[j], valueNode.getAttribute("value"), "Unexpected tag " + tagNumber + " value");
}
foundTag = true;
}
}
assertTrue(foundTag, "No tag " + tagNumber + " found");
}
static void createTIFFFieldNode(final IIOMetadataNode parentIFDNode, int tag, short type, Object value) {
IIOMetadataNode fieldNode = new IIOMetadataNode("TIFFField");
parentIFDNode.appendChild(fieldNode);
fieldNode.setAttribute("number", String.valueOf(tag));
switch (type) {
case TIFF.TYPE_ASCII:
createTIFFFieldContainerNode(fieldNode, "Ascii", value);
break;
case TIFF.TYPE_BYTE:
createTIFFFieldContainerNode(fieldNode, "Byte", value);
break;
case TIFF.TYPE_SHORT:
createTIFFFieldContainerNode(fieldNode, "Short", value);
break;
case TIFF.TYPE_RATIONAL:
createTIFFFieldContainerNode(fieldNode, "Rational", value);
break;
default:
throw new IllegalArgumentException("Unsupported type: " + type);
}
}
static void createTIFFFieldContainerNode(final IIOMetadataNode fieldNode, final String type, final Object value) {
IIOMetadataNode containerNode = new IIOMetadataNode("TIFF" + type + "s");
fieldNode.appendChild(containerNode);
IIOMetadataNode valueNode = new IIOMetadataNode("TIFF" + type);
valueNode.setAttribute("value", String.valueOf(value));
containerNode.appendChild(valueNode);
}
private void assertNodeNotEquals(final String message, final Node expected, final Node actual) {
// Lame, lazy implementation...
try {
assertNodeEquals(message, expected, actual);
}
catch (AssertionError ignore) {
return;
}
fail(message);
}
private void assertNodeEquals(final String message, final Node expected, final Node actual) {
assertEquals(expected.getClass(), actual.getClass(), message + " class differs");
assertEquals(expected.getNodeValue(), actual.getNodeValue(), message);
if (expected instanceof IIOMetadataNode) {
IIOMetadataNode expectedIIO = (IIOMetadataNode) expected;
IIOMetadataNode actualIIO = (IIOMetadataNode) actual;
assertEquals(expectedIIO.getUserObject(), actualIIO.getUserObject(), message);
}
NodeList expectedChildNodes = expected.getChildNodes();
NodeList actualChildNodes = actual.getChildNodes();
assertEquals(expectedChildNodes.getLength(), actualChildNodes.getLength(),
message + " child length differs: " + toString(expectedChildNodes) + " != " + toString(actualChildNodes));
for (int i = 0; i < expectedChildNodes.getLength(); i++) {
Node expectedChild = expectedChildNodes.item(i);
Node actualChild = actualChildNodes.item(i);
assertEquals(expectedChild.getLocalName(), actualChild.getLocalName(), message + " node name differs");
assertNodeEquals(message + "/" + expectedChild.getLocalName(), expectedChild, actualChild);
}
}
private String toString(final NodeList list) {
if (list.getLength() == 0) {
return "[]";
}
StringBuilder builder = new StringBuilder("[");
for (int i = 0; i < list.getLength(); i++) {
if (i > 0) {
builder.append(", ");
}
Node node = list.item(i);
builder.append(node.getLocalName());
}
builder.append("]");
return builder.toString();
}
}