mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 03:55:28 -04:00
TMI-139: Support for writing TIFF files with custom resolution value.
This commit is contained in:
parent
517fc770bd
commit
c913ef445b
@ -98,15 +98,6 @@ public abstract class AbstractMetadata extends IIOMetadata implements Cloneable
|
|||||||
if (!root.getNodeName().equals(formatName)) {
|
if (!root.getNodeName().equals(formatName)) {
|
||||||
throw new IIOInvalidTreeException("Root must be " + formatName, root);
|
throw new IIOInvalidTreeException("Root must be " + formatName, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Merge both native and standard!
|
|
||||||
Node node = root.getFirstChild();
|
|
||||||
while (node != null) {
|
|
||||||
// TODO: Merge values from node into this
|
|
||||||
|
|
||||||
// Move to the next sibling
|
|
||||||
node = node.getNextSibling();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -63,6 +63,7 @@ public abstract class ImageWriterAbstractTestCase {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||||
|
ImageIO.setUseCache(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract ImageWriter createImageWriter();
|
protected abstract ImageWriter createImageWriter();
|
||||||
@ -120,23 +121,20 @@ public abstract class ImageWriterAbstractTestCase {
|
|||||||
|
|
||||||
for (RenderedImage testData : getTestData()) {
|
for (RenderedImage testData : getTestData()) {
|
||||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
ImageOutputStream stream = ImageIO.createImageOutputStream(buffer);
|
|
||||||
writer.setOutput(stream);
|
|
||||||
|
|
||||||
try {
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
writer.write(drawSomething((BufferedImage) testData));
|
writer.write(drawSomething((BufferedImage) testData));
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
fail(e.getMessage());
|
fail(e.getMessage());
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
stream.close(); // Force data to be written
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue("No image data written", buffer.size() > 0);
|
assertTrue("No image data written", buffer.size() > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Test
|
@Test
|
||||||
public void testWriteNull() throws IOException {
|
public void testWriteNull() throws IOException {
|
||||||
ImageWriter writer = createImageWriter();
|
ImageWriter writer = createImageWriter();
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
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 javax.imageio.metadata.IIOMetadataNode;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.Calendar;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TIFFImageMetadata.
|
* TIFFImageMetadata.
|
||||||
@ -23,16 +29,19 @@ import java.util.Calendar;
|
|||||||
*/
|
*/
|
||||||
final class TIFFImageMetadata extends AbstractMetadata {
|
final class TIFFImageMetadata extends AbstractMetadata {
|
||||||
|
|
||||||
private final Directory ifd;
|
static final int RATIONAL_SCALE_FACTOR = 100000;
|
||||||
|
|
||||||
|
private final Directory original;
|
||||||
|
private Directory ifd;
|
||||||
|
|
||||||
TIFFImageMetadata(final Directory ifd) {
|
TIFFImageMetadata(final Directory ifd) {
|
||||||
super(true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, TIFFMedataFormat.class.getName(), null, null);
|
super(true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, TIFFMedataFormat.class.getName(), null, null);
|
||||||
this.ifd = Validate.notNull(ifd, "IFD");
|
this.ifd = Validate.notNull(ifd, "IFD");
|
||||||
|
this.original = ifd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
TIFFImageMetadata(final Collection<Entry> entries) {
|
||||||
public boolean isReadOnly() {
|
this(new TIFFIFD(entries));
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IIOMetadataNode getNativeTree() {
|
protected IIOMetadataNode getNativeTree() {
|
||||||
@ -99,7 +108,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
|
IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
|
||||||
valueNode.appendChild(elementNode);
|
valueNode.appendChild(elementNode);
|
||||||
|
|
||||||
setValue(value, unsigned, elementNode);
|
setTIFFNativeValue(value, unsigned, elementNode);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
@ -107,7 +116,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
|
IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
|
||||||
valueNode.appendChild(elementNode);
|
valueNode.appendChild(elementNode);
|
||||||
|
|
||||||
setValue(val, unsigned, elementNode);
|
setTIFFNativeValue(val, unsigned, elementNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +128,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
return ifdNode;
|
return ifdNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setValue(final Object value, final boolean unsigned, final IIOMetadataNode elementNode) {
|
private void setTIFFNativeValue(final Object value, final boolean unsigned, final IIOMetadataNode elementNode) {
|
||||||
if (unsigned && value instanceof Byte) {
|
if (unsigned && value instanceof Byte) {
|
||||||
elementNode.setAttribute("value", String.valueOf((Byte) value & 0xFF));
|
elementNode.setAttribute("value", String.valueOf((Byte) value & 0xFF));
|
||||||
}
|
}
|
||||||
@ -289,12 +298,12 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
// Handle ColorSpaceType (RGB/CMYK/YCbCr etc)...
|
// Handle ColorSpaceType (RGB/CMYK/YCbCr etc)...
|
||||||
Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
||||||
int photometricValue = ((Number) photometricTag.getValue()).intValue(); // No default for this tag!
|
int photometricValue = getValueAsInt(photometricTag); // No default for this tag!
|
||||||
|
|
||||||
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
||||||
Entry bitsPerSampleTag = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE);
|
Entry bitsPerSampleTag = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE);
|
||||||
int numChannelsValue = samplesPerPixelTag != null
|
int numChannelsValue = samplesPerPixelTag != null
|
||||||
? ((Number) samplesPerPixelTag.getValue()).intValue()
|
? getValueAsInt(samplesPerPixelTag)
|
||||||
: bitsPerSampleTag.valueCount();
|
: bitsPerSampleTag.valueCount();
|
||||||
|
|
||||||
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
|
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
|
||||||
@ -393,7 +402,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION);
|
Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION);
|
||||||
int compressionValue = compressionTag == null
|
int compressionValue = compressionTag == null
|
||||||
? TIFFBaseline.COMPRESSION_NONE
|
? TIFFBaseline.COMPRESSION_NONE
|
||||||
: ((Number) compressionTag.getValue()).intValue();
|
: 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) {
|
||||||
@ -502,7 +511,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
Entry planarConfigurationTag = ifd.getEntryById(TIFF.TAG_PLANAR_CONFIGURATION);
|
Entry planarConfigurationTag = ifd.getEntryById(TIFF.TAG_PLANAR_CONFIGURATION);
|
||||||
int planarConfigurationValue = planarConfigurationTag == null
|
int planarConfigurationValue = planarConfigurationTag == null
|
||||||
? TIFFBaseline.PLANARCONFIG_CHUNKY
|
? TIFFBaseline.PLANARCONFIG_CHUNKY
|
||||||
: ((Number) planarConfigurationTag.getValue()).intValue();
|
: getValueAsInt(planarConfigurationTag);
|
||||||
|
|
||||||
switch (planarConfigurationValue) {
|
switch (planarConfigurationValue) {
|
||||||
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
||||||
@ -519,14 +528,16 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
Entry photometricInterpretationTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
Entry photometricInterpretationTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
||||||
int photometricInterpretationValue = photometricInterpretationTag == null
|
int photometricInterpretationValue = photometricInterpretationTag == null
|
||||||
? TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO
|
? TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO
|
||||||
: ((Number) photometricInterpretationTag.getValue()).intValue();
|
: getValueAsInt(photometricInterpretationTag);
|
||||||
|
|
||||||
Entry samleFormatTag = ifd.getEntryById(TIFF.TAG_SAMPLE_FORMAT);
|
Entry samleFormatTag = ifd.getEntryById(TIFF.TAG_SAMPLE_FORMAT);
|
||||||
|
// TODO: Fix for sampleformat 1 1 1 (as int[]) ??!?!?
|
||||||
int sampleFormatValue = samleFormatTag == null
|
int sampleFormatValue = samleFormatTag == null
|
||||||
? TIFFBaseline.SAMPLEFORMAT_UINT
|
? TIFFBaseline.SAMPLEFORMAT_UINT
|
||||||
: ((Number) samleFormatTag.getValue()).intValue();
|
: getValueAsInt(samleFormatTag);
|
||||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||||
node.appendChild(sampleFormat);
|
node.appendChild(sampleFormat);
|
||||||
|
|
||||||
switch (sampleFormatValue) {
|
switch (sampleFormatValue) {
|
||||||
case TIFFBaseline.SAMPLEFORMAT_UINT:
|
case TIFFBaseline.SAMPLEFORMAT_UINT:
|
||||||
if (photometricInterpretationValue == TIFFBaseline.PHOTOMETRIC_PALETTE) {
|
if (photometricInterpretationValue == TIFFBaseline.PHOTOMETRIC_PALETTE) {
|
||||||
@ -562,13 +573,13 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
||||||
int numChannelsValue = samplesPerPixelTag != null
|
int numChannelsValue = samplesPerPixelTag != null
|
||||||
? ((Number) samplesPerPixelTag.getValue()).intValue()
|
? getValueAsInt(samplesPerPixelTag)
|
||||||
: bitsPerSampleTag.valueCount();
|
: bitsPerSampleTag.valueCount();
|
||||||
|
|
||||||
// SampleMSB
|
// SampleMSB
|
||||||
Entry fillOrderTag = ifd.getEntryById(TIFF.TAG_FILL_ORDER);
|
Entry fillOrderTag = ifd.getEntryById(TIFF.TAG_FILL_ORDER);
|
||||||
int fillOrder = fillOrderTag != null
|
int fillOrder = fillOrderTag != null
|
||||||
? ((Number) fillOrderTag.getValue()).intValue()
|
? getValueAsInt(fillOrderTag)
|
||||||
: TIFFBaseline.FILL_LEFT_TO_RIGHT;
|
: TIFFBaseline.FILL_LEFT_TO_RIGHT;
|
||||||
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
||||||
node.appendChild(sampleMSB);
|
node.appendChild(sampleMSB);
|
||||||
@ -588,6 +599,22 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getValueAsInt(final Entry entry) {
|
||||||
|
Object value = entry.getValue();
|
||||||
|
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).intValue();
|
||||||
|
}
|
||||||
|
else if (value instanceof short[]) {
|
||||||
|
return ((short[]) value)[0];
|
||||||
|
}
|
||||||
|
else if (value instanceof int[]) {
|
||||||
|
return ((int[]) value)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unsupported type: " + entry);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Candidate superclass method!
|
// TODO: Candidate superclass method!
|
||||||
private String createListValue(final int itemCount, final String... values) {
|
private String createListValue(final int itemCount, final String... values) {
|
||||||
StringBuilder buffer = new StringBuilder();
|
StringBuilder buffer = new StringBuilder();
|
||||||
@ -620,7 +647,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
// ImageOrientation
|
// ImageOrientation
|
||||||
Entry orientationTag = ifd.getEntryById(TIFF.TAG_ORIENTATION);
|
Entry orientationTag = ifd.getEntryById(TIFF.TAG_ORIENTATION);
|
||||||
if (orientationTag != null) {
|
if (orientationTag != null) {
|
||||||
int orientationValue = ((Number) orientationTag.getValue()).intValue();
|
int orientationValue = getValueAsInt(orientationTag);
|
||||||
|
|
||||||
String value = null;
|
String value = null;
|
||||||
switch (orientationValue) {
|
switch (orientationValue) {
|
||||||
@ -659,7 +686,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Entry resUnitTag = ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
Entry resUnitTag = ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
int resUnitValue = resUnitTag == null ? TIFFBaseline.RESOLUTION_UNIT_DPI : ((Number) resUnitTag.getValue()).intValue();
|
int resUnitValue = resUnitTag == null ? TIFFBaseline.RESOLUTION_UNIT_DPI : getValueAsInt(resUnitTag);
|
||||||
if (resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER || resUnitValue == TIFFBaseline.RESOLUTION_UNIT_DPI) {
|
if (resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER || resUnitValue == TIFFBaseline.RESOLUTION_UNIT_DPI) {
|
||||||
// 10 mm in 1 cm or 25.4 mm in 1 inch
|
// 10 mm in 1 cm or 25.4 mm in 1 inch
|
||||||
double scale = resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER ? 10 : 25.4;
|
double scale = resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER ? 10 : 25.4;
|
||||||
@ -703,7 +730,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
if (extraSamplesTag != null) {
|
if (extraSamplesTag != null) {
|
||||||
int extraSamplesValue = (extraSamplesTag.getValue() instanceof Number)
|
int extraSamplesValue = (extraSamplesTag.getValue() instanceof Number)
|
||||||
? ((Number) extraSamplesTag.getValue()).intValue()
|
? getValueAsInt(extraSamplesTag)
|
||||||
: ((Number) Array.get(extraSamplesTag.getValue(), 0)).intValue();
|
: ((Number) Array.get(extraSamplesTag.getValue(), 0)).intValue();
|
||||||
|
|
||||||
// Other values exists, these are not alpha
|
// Other values exists, these are not alpha
|
||||||
@ -739,7 +766,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
if (subFileTypeTag != null) {
|
if (subFileTypeTag != null) {
|
||||||
// NOTE: The JAI metadata is somewhat broken here, as these are bit flags, not values...
|
// NOTE: The JAI metadata is somewhat broken here, as these are bit flags, not values...
|
||||||
String value = null;
|
String value = null;
|
||||||
int subFileTypeValue = ((Number) subFileTypeTag.getValue()).intValue();
|
int subFileTypeValue = getValueAsInt(subFileTypeTag);
|
||||||
if ((subFileTypeValue & TIFFBaseline.FILETYPE_MASK) != 0) {
|
if ((subFileTypeValue & TIFFBaseline.FILETYPE_MASK) != 0) {
|
||||||
value = "TransparencyMask";
|
value = "TransparencyMask";
|
||||||
}
|
}
|
||||||
@ -795,6 +822,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
addTextEntryIfPresent(text, TIFF.TAG_IMAGE_DESCRIPTION);
|
addTextEntryIfPresent(text, TIFF.TAG_IMAGE_DESCRIPTION);
|
||||||
addTextEntryIfPresent(text, TIFF.TAG_MAKE);
|
addTextEntryIfPresent(text, TIFF.TAG_MAKE);
|
||||||
addTextEntryIfPresent(text, TIFF.TAG_MODEL);
|
addTextEntryIfPresent(text, TIFF.TAG_MODEL);
|
||||||
|
addTextEntryIfPresent(text, TIFF.TAG_PAGE_NAME);
|
||||||
addTextEntryIfPresent(text, TIFF.TAG_SOFTWARE);
|
addTextEntryIfPresent(text, TIFF.TAG_SOFTWARE);
|
||||||
addTextEntryIfPresent(text, TIFF.TAG_ARTIST);
|
addTextEntryIfPresent(text, TIFF.TAG_ARTIST);
|
||||||
addTextEntryIfPresent(text, TIFF.TAG_HOST_COMPUTER);
|
addTextEntryIfPresent(text, TIFF.TAG_HOST_COMPUTER);
|
||||||
@ -821,4 +849,435 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
// See http://stackoverflow.com/questions/30910719/javax-imageio-1-0-standard-plug-in-neutral-metadata-format-tiling-information
|
// See http://stackoverflow.com/questions/30910719/javax-imageio-1-0-standard-plug-in-neutral-metadata-format-tiling-information
|
||||||
return super.getStandardTileNode();
|
return super.getStandardTileNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mutation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFromTree(final String formatName, final Node root) throws IIOInvalidTreeException {
|
||||||
|
// Standard validation
|
||||||
|
super.mergeTree(formatName, root);
|
||||||
|
|
||||||
|
// Set by "merging" with empty map
|
||||||
|
LinkedHashMap<Integer, Entry> entries = new LinkedHashMap<>();
|
||||||
|
mergeEntries(formatName, root, entries);
|
||||||
|
|
||||||
|
// TODO: Consistency validation?
|
||||||
|
|
||||||
|
// Finally create a new IFD from merged values
|
||||||
|
ifd = new TIFFIFD(entries.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mergeTree(final String formatName, final Node root) throws IIOInvalidTreeException {
|
||||||
|
// Standard validation
|
||||||
|
super.mergeTree(formatName, root);
|
||||||
|
|
||||||
|
// Clone entries (shallow clone, as entries themselves are immutable)
|
||||||
|
LinkedHashMap<Integer, Entry> entries = new LinkedHashMap<>(ifd.size() + 10);
|
||||||
|
|
||||||
|
for (Entry entry : ifd) {
|
||||||
|
entries.put((Integer) entry.getIdentifier(), entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeEntries(formatName, root, entries);
|
||||||
|
|
||||||
|
// TODO: Consistency validation?
|
||||||
|
|
||||||
|
// Finally create a new IFD from merged values
|
||||||
|
ifd = new TIFFIFD(entries.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeEntries(final String formatName, final Node root, final Map<Integer, Entry> entries) throws IIOInvalidTreeException {
|
||||||
|
// Merge from both native and standard trees
|
||||||
|
if (getNativeMetadataFormatName().equals(formatName)) {
|
||||||
|
mergeNativeTree(root, entries);
|
||||||
|
}
|
||||||
|
else if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
|
||||||
|
mergeStandardTree(root, entries);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Should already be checked for
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeStandardTree(final Node root, final Map<Integer, Entry> entries) throws IIOInvalidTreeException {
|
||||||
|
NodeList nodes = root.getChildNodes();
|
||||||
|
|
||||||
|
// Merge selected values from standard tree
|
||||||
|
for (int i = 0; i < nodes.getLength(); i++) {
|
||||||
|
Node node = nodes.item(i);
|
||||||
|
|
||||||
|
if ("Dimension".equals(node.getNodeName())) {
|
||||||
|
mergeFromStandardDimensionNode(node, entries);
|
||||||
|
}
|
||||||
|
else if ("Document".equals(node.getNodeName())) {
|
||||||
|
mergeFromStandardDocumentNode(node, entries);
|
||||||
|
}
|
||||||
|
else if ("Text".equals(node.getNodeName())) {
|
||||||
|
mergeFromStandardTextNode(node, entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeFromStandardDimensionNode(final Node dimensionNode, final Map<Integer, Entry> entries) {
|
||||||
|
// Dimension: xRes/yRes
|
||||||
|
// - If set, set res unit to pixels per cm as this better reflects values?
|
||||||
|
// - Or, convert to DPI, if we already had values in DPI??
|
||||||
|
// Also, if we have only aspect, set these values, and use unknown as unit?
|
||||||
|
// TODO: ImageOrientation => Orientation
|
||||||
|
NodeList children = dimensionNode.getChildNodes();
|
||||||
|
|
||||||
|
Float aspect = null;
|
||||||
|
Float xRes = null;
|
||||||
|
Float yRes = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < children.getLength(); i++) {
|
||||||
|
Node child = children.item(i);
|
||||||
|
String nodeName = child.getNodeName();
|
||||||
|
|
||||||
|
if ("PixelAspectRatio".equals(nodeName)) {
|
||||||
|
aspect = Float.parseFloat(getAttribute(child, "value"));
|
||||||
|
}
|
||||||
|
else if ("HorizontalPixelSize".equals(nodeName)) {
|
||||||
|
xRes = Float.parseFloat(getAttribute(child, "value"));
|
||||||
|
}
|
||||||
|
else if ("VerticalPixelSize".equals(nodeName)) {
|
||||||
|
yRes = Float.parseFloat(getAttribute(child, "value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have one size compute the other
|
||||||
|
if (xRes == null && yRes != null) {
|
||||||
|
xRes = yRes * (aspect != null ? aspect : 1f);
|
||||||
|
}
|
||||||
|
else if (yRes == null && xRes != null) {
|
||||||
|
yRes = xRes / (aspect != null ? aspect : 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have resolution
|
||||||
|
if (xRes != null && yRes != null) {
|
||||||
|
// If old unit was DPI, convert values and keep DPI, otherwise use PPCM
|
||||||
|
Entry resUnitEntry = entries.get(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
|
int resUnitValue = resUnitEntry != null && resUnitEntry.getValue() != null
|
||||||
|
&& ((Number) resUnitEntry.getValue()).intValue() == TIFFBaseline.RESOLUTION_UNIT_DPI
|
||||||
|
? TIFFBaseline.RESOLUTION_UNIT_DPI
|
||||||
|
: TIFFBaseline.RESOLUTION_UNIT_CENTIMETER;
|
||||||
|
|
||||||
|
// Units from standard format are pixels per mm, convert to cm or inches
|
||||||
|
float scale = resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER ? 10 : 25.4f;
|
||||||
|
|
||||||
|
int x = Math.round(xRes * scale * RATIONAL_SCALE_FACTOR);
|
||||||
|
int y = Math.round(yRes * scale * RATIONAL_SCALE_FACTOR);
|
||||||
|
|
||||||
|
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(x, RATIONAL_SCALE_FACTOR)));
|
||||||
|
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(y, RATIONAL_SCALE_FACTOR)));
|
||||||
|
entries.put(TIFF.TAG_RESOLUTION_UNIT,
|
||||||
|
new TIFFImageWriter.TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, resUnitValue));
|
||||||
|
}
|
||||||
|
else if (aspect != null) {
|
||||||
|
if (aspect >= 1) {
|
||||||
|
int v = Math.round(aspect * RATIONAL_SCALE_FACTOR);
|
||||||
|
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
|
||||||
|
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(1)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int v = Math.round(RATIONAL_SCALE_FACTOR / aspect);
|
||||||
|
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(1)));
|
||||||
|
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.put(TIFF.TAG_RESOLUTION_UNIT,
|
||||||
|
new TIFFImageWriter.TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, TIFFBaseline.RESOLUTION_UNIT_NONE));
|
||||||
|
}
|
||||||
|
// Else give up...
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeFromStandardDocumentNode(final Node documentNode, final Map<Integer, Entry> entries) {
|
||||||
|
// Document: SubfileType, CreationDate
|
||||||
|
NodeList children = documentNode.getChildNodes();
|
||||||
|
|
||||||
|
for (int i = 0; i < children.getLength(); i++) {
|
||||||
|
Node child = children.item(i);
|
||||||
|
String nodeName = child.getNodeName();
|
||||||
|
|
||||||
|
if ("SubimageInterpretation".equals(nodeName)) {
|
||||||
|
// TODO: SubFileType
|
||||||
|
}
|
||||||
|
else if ("ImageCreationTime".equals(nodeName)) {
|
||||||
|
// TODO: CreationDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeFromStandardTextNode(final Node textNode, final Map<Integer, Entry> entries) throws IIOInvalidTreeException {
|
||||||
|
NodeList textEntries = textNode.getChildNodes();
|
||||||
|
|
||||||
|
for (int i = 0; i < textEntries.getLength(); i++) {
|
||||||
|
Node textEntry = textEntries.item(i);
|
||||||
|
|
||||||
|
if (!"TextEntry".equals(textEntry.getNodeName())) {
|
||||||
|
throw new IIOInvalidTreeException("Text node should only contain TextEntry nodes", textNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
String keyword = getAttribute(textEntry, "keyword");
|
||||||
|
String value = getAttribute(textEntry, "value");
|
||||||
|
|
||||||
|
// DocumentName, ImageDescription, Make, Model, PageName,
|
||||||
|
// Software, Artist, HostComputer, InkNames, Copyright
|
||||||
|
if (value != null && !value.isEmpty() && keyword != null) {
|
||||||
|
// We do all comparisons in lower case, for compatibility
|
||||||
|
keyword = keyword.toLowerCase();
|
||||||
|
|
||||||
|
TIFFImageWriter.TIFFEntry entry;
|
||||||
|
|
||||||
|
if ("documentname".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_DOCUMENT_NAME, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("imagedescription".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_IMAGE_DESCRIPTION, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("make".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_MAKE, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("model".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_MODEL, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("pagename".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_PAGE_NAME, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("software".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("artist".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("hostcomputer".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_HOST_COMPUTER, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("inknames".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_INK_NAMES, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("copyright".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_COPYRIGHT, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.put((Integer) entry.getIdentifier(), entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeNativeTree(final Node root, final Map<Integer, Entry> entries) throws IIOInvalidTreeException {
|
||||||
|
Directory ifd = toIFD(root.getFirstChild());
|
||||||
|
|
||||||
|
// Merge (overwrite) entries with entries from IFD
|
||||||
|
for (Entry entry : ifd) {
|
||||||
|
entries.put((Integer) entry.getIdentifier(), entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Directory toIFD(final Node ifdNode) throws IIOInvalidTreeException {
|
||||||
|
if (ifdNode == null || !ifdNode.getNodeName().equals("TIFFIFD")) {
|
||||||
|
throw new IIOInvalidTreeException("Expected \"TIFFIFD\" node", ifdNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Entry> entries = new ArrayList<>();
|
||||||
|
NodeList nodes = ifdNode.getChildNodes();
|
||||||
|
|
||||||
|
for (int i = 0; i < nodes.getLength(); i++) {
|
||||||
|
entries.add(toEntry(nodes.item(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TIFFIFD(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entry toEntry(final Node node) throws IIOInvalidTreeException {
|
||||||
|
String name = node.getNodeName();
|
||||||
|
|
||||||
|
if (name.equals("TIFFIFD")) {
|
||||||
|
int tag = Integer.parseInt(getAttribute(node, "parentTagNumber"));
|
||||||
|
Directory subIFD = toIFD(node);
|
||||||
|
|
||||||
|
return new TIFFImageWriter.TIFFEntry(tag, TIFF.TYPE_IFD, subIFD);
|
||||||
|
}
|
||||||
|
else if (name.equals("TIFFField")) {
|
||||||
|
int tag = Integer.parseInt(getAttribute(node, "number"));
|
||||||
|
short type = getTIFFType(node);
|
||||||
|
Object value = getValue(node, type);
|
||||||
|
|
||||||
|
return value != null ? new TIFFImageWriter.TIFFEntry(tag, type, value) : null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IIOInvalidTreeException("Expected \"TIFFIFD\" or \"TIFFField\" node: " + name, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private short getTIFFType(final Node node) throws IIOInvalidTreeException {
|
||||||
|
Node containerNode = node.getFirstChild();
|
||||||
|
if (containerNode == null) {
|
||||||
|
throw new IIOInvalidTreeException("Missing value wrapper node", node);
|
||||||
|
}
|
||||||
|
|
||||||
|
String nodeName = containerNode.getNodeName();
|
||||||
|
if (!nodeName.startsWith("TIFF")) {
|
||||||
|
throw new IIOInvalidTreeException("Unexpected value wrapper node, expected type", containerNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
String typeName = nodeName.substring(4);
|
||||||
|
|
||||||
|
if (typeName.equals("Undefined")) {
|
||||||
|
return TIFF.TYPE_UNDEFINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
typeName = typeName.substring(0, typeName.length() - 1).toUpperCase();
|
||||||
|
|
||||||
|
for (int i = 1; i < TIFF.TYPE_NAMES.length; i++) {
|
||||||
|
if (typeName.equals(TIFF.TYPE_NAMES[i])) {
|
||||||
|
return (short) i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOInvalidTreeException("Unknown TIFF type: " + typeName, containerNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getValue(final Node node, final short type) throws IIOInvalidTreeException {
|
||||||
|
Node child = node.getFirstChild();
|
||||||
|
|
||||||
|
if (child != null) {
|
||||||
|
String typeName = child.getNodeName();
|
||||||
|
|
||||||
|
if (type == TIFF.TYPE_UNDEFINED) {
|
||||||
|
String values = getAttribute(child, "value");
|
||||||
|
String[] vals = values.split(",\\s?");
|
||||||
|
|
||||||
|
byte[] bytes = new byte[vals.length];
|
||||||
|
for (int i = 0; i < vals.length; i++) {
|
||||||
|
bytes[i] = Byte.parseByte(vals[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
NodeList valueNodes = child.getChildNodes();
|
||||||
|
|
||||||
|
// Create array for each type
|
||||||
|
int count = valueNodes.getLength();
|
||||||
|
Object value = createArrayForType(type, count);
|
||||||
|
|
||||||
|
// Parse each value
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
Node valueNode = valueNodes.item(i);
|
||||||
|
|
||||||
|
if (!typeName.startsWith(valueNode.getNodeName())) {
|
||||||
|
throw new IIOInvalidTreeException("Value node does not match container node", child);
|
||||||
|
}
|
||||||
|
|
||||||
|
String stringValue = getAttribute(valueNode, "value");
|
||||||
|
|
||||||
|
// NOTE: The reason for parsing "wider" type, is to allow for unsigned values
|
||||||
|
switch (type) {
|
||||||
|
case TIFF.TYPE_BYTE:
|
||||||
|
case TIFF.TYPE_SBYTE:
|
||||||
|
((byte[]) value)[i] = (byte) Short.parseShort(stringValue);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_ASCII:
|
||||||
|
((String[]) value)[i] = stringValue;
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_SHORT:
|
||||||
|
case TIFF.TYPE_SSHORT:
|
||||||
|
((short[]) value)[i] = (short) Integer.parseInt(stringValue);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_LONG:
|
||||||
|
case TIFF.TYPE_SLONG:
|
||||||
|
((int[]) value)[i] = (int) Long.parseLong(stringValue);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_RATIONAL:
|
||||||
|
case TIFF.TYPE_SRATIONAL:
|
||||||
|
String[] numDenom = stringValue.split("/");
|
||||||
|
((Rational[]) value)[i] = numDenom.length > 1
|
||||||
|
? new Rational(Long.parseLong(numDenom[0]), Long.parseLong(numDenom[1]))
|
||||||
|
: new Rational(Long.parseLong(numDenom[0]));
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_FLOAT:
|
||||||
|
((float[]) value)[i] = Float.parseFloat(stringValue);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_DOUBLE:
|
||||||
|
((double[]) value)[i] = Double.parseDouble(stringValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unsupported TIFF type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize value
|
||||||
|
if (count == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (count == 1) {
|
||||||
|
return Array.get(value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOInvalidTreeException("Empty TIFField node", node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object createArrayForType(final short type, final int length) {
|
||||||
|
switch (type) {
|
||||||
|
case TIFF.TYPE_ASCII:
|
||||||
|
return new String[length];
|
||||||
|
case TIFF.TYPE_BYTE:
|
||||||
|
case TIFF.TYPE_SBYTE:
|
||||||
|
case TIFF.TYPE_UNDEFINED: // Not used here, but for completeness
|
||||||
|
return new byte[length];
|
||||||
|
case TIFF.TYPE_SHORT:
|
||||||
|
case TIFF.TYPE_SSHORT:
|
||||||
|
return new short[length];
|
||||||
|
case TIFF.TYPE_LONG:
|
||||||
|
case TIFF.TYPE_SLONG:
|
||||||
|
return new int[length];
|
||||||
|
case TIFF.TYPE_IFD:
|
||||||
|
return new long[length];
|
||||||
|
case TIFF.TYPE_RATIONAL:
|
||||||
|
case TIFF.TYPE_SRATIONAL:
|
||||||
|
return new Rational[length];
|
||||||
|
case TIFF.TYPE_FLOAT:
|
||||||
|
return new float[length];
|
||||||
|
case TIFF.TYPE_DOUBLE:
|
||||||
|
return new double[length];
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unsupported TIFF type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAttribute(final Node node, final String attribute) {
|
||||||
|
return node instanceof Element ? ((Element) node).getAttribute(attribute) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
|
|
||||||
|
ifd = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory getIFD() {
|
||||||
|
return ifd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Replace with IFD class when moved to new package and made public!
|
||||||
|
private final static class TIFFIFD extends AbstractDirectory {
|
||||||
|
public TIFFIFD(final Collection<Entry> entries) {
|
||||||
|
super(entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,10 @@ import com.twelvemonkeys.imageio.metadata.Entry;
|
|||||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
@ -64,7 +67,9 @@ import java.awt.image.*;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.zip.Inflater;
|
import java.util.zip.Inflater;
|
||||||
import java.util.zip.InflaterInputStream;
|
import java.util.zip.InflaterInputStream;
|
||||||
@ -82,15 +87,16 @@ import java.util.zip.InflaterInputStream;
|
|||||||
* In addition, it supports many common TIFF extensions such as:
|
* In addition, it supports many common TIFF extensions such as:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Tiling</li>
|
* <li>Tiling</li>
|
||||||
|
* <li>Class F (Facsimile), CCITT T.4 and T.6 compression (types 3 and 4), 1 bit per sample</li>
|
||||||
* <li>LZW Compression (type 5)</li>
|
* <li>LZW Compression (type 5)</li>
|
||||||
* <li>"Old-style" JPEG Compression (type 6), as a best effort, as the spec is not well-defined</li>
|
* <li>"Old-style" JPEG Compression (type 6), as a best effort, as the spec is not well-defined</li>
|
||||||
* <li>JPEG Compression (type 7)</li>
|
* <li>JPEG Compression (type 7)</li>
|
||||||
* <li>ZLib (aka Adobe-style Deflate) Compression (type 8)</li>
|
* <li>ZLib (aka Adobe-style Deflate) Compression (type 8)</li>
|
||||||
* <li>Deflate Compression (type 32946)</li>
|
* <li>Deflate Compression (type 32946)</li>
|
||||||
* <li>Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate and PackBits compression</li>
|
* <li>Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate and PackBits compression</li>
|
||||||
* <li>Alpha channel (ExtraSamples type 1/Associated Alpha)</li>
|
* <li>Alpha channel (ExtraSamples types 1/Associated Alpha and 2/Unassociated Alpha)</li>
|
||||||
* <li>CMYK data (PhotometricInterpretation type 5/Separated)</li>
|
* <li>Class S, CMYK data (PhotometricInterpretation type 5/Separated)</li>
|
||||||
* <li>YCbCr data (PhotometricInterpretation type 6/YCbCr) for JPEG</li>
|
* <li>Class Y, YCbCr data (PhotometricInterpretation type 6/YCbCr for both JPEG and other compressions</li>
|
||||||
* <li>Planar data (PlanarConfiguration type 2/Planar)</li>
|
* <li>Planar data (PlanarConfiguration type 2/Planar)</li>
|
||||||
* <li>ICC profiles (ICCProfile)</li>
|
* <li>ICC profiles (ICCProfile)</li>
|
||||||
* <li>BitsPerSample values up to 16 for most PhotometricInterpretations</li>
|
* <li>BitsPerSample values up to 16 for most PhotometricInterpretations</li>
|
||||||
@ -119,7 +125,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// TODO: Implement readAsRenderedImage to allow tiled RenderedImage?
|
// TODO: Implement readAsRenderedImage to allow tiled RenderedImage?
|
||||||
// For some layouts, we could do reads super-fast with a memory mapped buffer.
|
// For some layouts, we could do reads super-fast with a memory mapped buffer.
|
||||||
// TODO: Implement readAsRaster directly
|
// TODO: Implement readAsRaster directly
|
||||||
// TODO: IIOMetadata (stay close to Sun's TIFF metadata)
|
|
||||||
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
|
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
|
||||||
|
|
||||||
// TODOs Extension support
|
// TODOs Extension support
|
||||||
@ -136,6 +141,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// Support Compression 2 (CCITT Modified Huffman RLE) for bi-level images
|
// Support Compression 2 (CCITT Modified Huffman RLE) for bi-level images
|
||||||
// Source region
|
// Source region
|
||||||
// Subsampling
|
// Subsampling
|
||||||
|
// IIOMetadata (stay close to Sun's TIFF metadata)
|
||||||
|
|
||||||
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
|
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
|
||||||
|
|
||||||
@ -167,6 +173,73 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
for (int i = 0; i < IFDs.directoryCount(); i++) {
|
for (int i = 0; i < IFDs.directoryCount(); i++) {
|
||||||
System.err.printf("IFD %d: %s\n", i, IFDs.getDirectory(i));
|
System.err.printf("IFD %d: %s\n", i, IFDs.getDirectory(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Entry tiffXMP = IFDs.getEntryById(TIFF.TAG_XMP);
|
||||||
|
if (tiffXMP != null) {
|
||||||
|
byte[] value = (byte[]) tiffXMP.getValue();
|
||||||
|
|
||||||
|
// The XMPReader doesn't like null-termination...
|
||||||
|
int len = value.length;
|
||||||
|
for (int i = len - 1; i > 0; i--) {
|
||||||
|
if (value[i] == 0) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory xmp = new XMPReader().read(new ByteArrayImageInputStream(value, 0, len));
|
||||||
|
System.err.println("-----------------------------------------------------------------------------");
|
||||||
|
System.err.println("xmp: " + xmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry tiffIPTC = IFDs.getEntryById(TIFF.TAG_IPTC);
|
||||||
|
if (tiffIPTC != null) {
|
||||||
|
Object value = tiffIPTC.getValue();
|
||||||
|
if (value instanceof short[]) {
|
||||||
|
System.err.println("short[]: " + value);
|
||||||
|
}
|
||||||
|
if (value instanceof long[]) {
|
||||||
|
// As seen in a Magick produced image...
|
||||||
|
System.err.println("long[]: " + value);
|
||||||
|
long[] longs = (long[]) value;
|
||||||
|
value = new byte[longs.length * 8];
|
||||||
|
ByteBuffer.wrap((byte[]) value).asLongBuffer().put(longs);
|
||||||
|
}
|
||||||
|
if (value instanceof float[]) {
|
||||||
|
System.err.println("float[]: " + value);
|
||||||
|
}
|
||||||
|
if (value instanceof double[]) {
|
||||||
|
System.err.println("double[]: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory iptc = new IPTCReader().read(new ByteArrayImageInputStream((byte[]) value));
|
||||||
|
System.err.println("-----------------------------------------------------------------------------");
|
||||||
|
System.err.println("iptc: " + iptc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry tiffPSD = IFDs.getEntryById(TIFF.TAG_PHOTOSHOP);
|
||||||
|
if (tiffPSD != null) {
|
||||||
|
Directory psd = new PSDReader().read(new ByteArrayImageInputStream((byte[]) tiffPSD.getValue()));
|
||||||
|
System.err.println("-----------------------------------------------------------------------------");
|
||||||
|
System.err.println("psd: " + psd);
|
||||||
|
}
|
||||||
|
Entry tiffPSD2 = IFDs.getEntryById(TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA);
|
||||||
|
if (tiffPSD2 != null) {
|
||||||
|
byte[] value = (byte[]) tiffPSD2.getValue();
|
||||||
|
String foo = "Adobe Photoshop Document Data Block";
|
||||||
|
|
||||||
|
if (Arrays.equals(foo.getBytes(StandardCharsets.US_ASCII), Arrays.copyOf(value, foo.length()))) {
|
||||||
|
System.err.println("foo: " + foo);
|
||||||
|
// int offset = foo.length() + 1;
|
||||||
|
// ImageInputStream input = new ByteArrayImageInputStream(value, offset, value.length - offset);
|
||||||
|
// input.setByteOrder(ByteOrder.LITTLE_ENDIAN); // TODO: WHY???!
|
||||||
|
// Directory psd2 = new PSDReader().read(input);
|
||||||
|
// System.err.println("-----------------------------------------------------------------------------");
|
||||||
|
// System.err.println("psd2: " + psd2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -414,11 +487,17 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
if (bitsPerSample == 16) {
|
if (bitsPerSample == 16) {
|
||||||
return DataBuffer.TYPE_SHORT;
|
return DataBuffer.TYPE_SHORT;
|
||||||
}
|
}
|
||||||
throw new IIOException("Unsupported BitPerSample for SampleFormat 2/Signed Integer (expected 16): " + bitsPerSample);
|
throw new IIOException("Unsupported BitsPerSample for SampleFormat 2/Signed Integer (expected 16): " + bitsPerSample);
|
||||||
case TIFFExtension.SAMPLEFORMAT_FP:
|
case TIFFExtension.SAMPLEFORMAT_FP:
|
||||||
throw new IIOException("Unsupported TIFF SampleFormat: (3/Floating point)");
|
throw new IIOException("Unsupported TIFF SampleFormat: 3 (Floating point)");
|
||||||
case TIFFExtension.SAMPLEFORMAT_UNDEFINED:
|
case TIFFExtension.SAMPLEFORMAT_UNDEFINED:
|
||||||
throw new IIOException("Unsupported TIFF SampleFormat (4/Undefined)");
|
// Spec says:
|
||||||
|
// A field value of “undefined” is a statement by the writer that it did not know how
|
||||||
|
// to interpret the data samples; for example, if it were copying an existing image. A
|
||||||
|
// reader would typically treat an image with “undefined” data as if the field were
|
||||||
|
// not present (i.e. as unsigned integer data).
|
||||||
|
// TODO: We should probably issue a warning instead, and assume SAMPLEFORMAT_UINT
|
||||||
|
throw new IIOException("Unsupported TIFF SampleFormat: 4 (Undefined)");
|
||||||
default:
|
default:
|
||||||
throw new IIOException("Unknown TIFF SampleFormat (expected 1, 2, 3 or 4): " + sampleFormat);
|
throw new IIOException("Unknown TIFF SampleFormat (expected 1, 2, 3 or 4): " + sampleFormat);
|
||||||
}
|
}
|
||||||
@ -888,7 +967,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
imageInput.seek(realJPEGOffset);
|
imageInput.seek(realJPEGOffset);
|
||||||
|
|
||||||
stream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Short.MAX_VALUE);
|
stream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Integer.MAX_VALUE);
|
||||||
jpegReader.setInput(stream);
|
jpegReader.setInput(stream);
|
||||||
|
|
||||||
// Read data
|
// Read data
|
||||||
@ -1500,27 +1579,30 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// param.setSourceSubsampling(sub, sub, 0, 0);
|
// param.setSourceSubsampling(sub, sub, 0, 0);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
try {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
// int width = reader.getWidth(imageNo);
|
// int width = reader.getWidth(imageNo);
|
||||||
// int height = reader.getHeight(imageNo);
|
// int height = reader.getHeight(imageNo);
|
||||||
// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
|
// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
|
||||||
// param.setSourceRegion(new Rectangle(100, 300, 400, 400));
|
// param.setSourceRegion(new Rectangle(100, 300, 400, 400));
|
||||||
|
// param.setSourceRegion(new Rectangle(3, 3, 9, 9));
|
||||||
// param.setDestinationOffset(new Point(50, 150));
|
// param.setDestinationOffset(new Point(50, 150));
|
||||||
// param.setSourceSubsampling(2, 2, 0, 0);
|
// param.setSourceSubsampling(2, 2, 0, 0);
|
||||||
BufferedImage image = reader.read(imageNo, param);
|
BufferedImage image = reader.read(imageNo, param);
|
||||||
System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
|
System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
|
|
||||||
IIOMetadata metadata = reader.getImageMetadata(imageNo);
|
IIOMetadata metadata = reader.getImageMetadata(imageNo);
|
||||||
if (metadata != null) {
|
if (metadata != null) {
|
||||||
if (metadata.getNativeMetadataFormatName() != null) {
|
if (metadata.getNativeMetadataFormatName() != null) {
|
||||||
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(metadata.getNativeMetadataFormatName()), false);
|
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(metadata.getNativeMetadataFormatName()), false);
|
||||||
|
}
|
||||||
|
/*else*/
|
||||||
|
if (metadata.isStandardMetadataFormatSupported()) {
|
||||||
|
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*else*/ if (metadata.isStandardMetadataFormatSupported()) {
|
|
||||||
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.err.println("image: " + image);
|
System.err.println("image: " + image);
|
||||||
|
|
||||||
// File tempFile = File.createTempFile("lzw-", ".bin");
|
// File tempFile = File.createTempFile("lzw-", ".bin");
|
||||||
// byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
|
// byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
|
||||||
@ -1536,7 +1618,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
//
|
//
|
||||||
// System.err.println("tempFile: " + tempFile.getAbsolutePath());
|
// System.err.println("tempFile: " + tempFile.getAbsolutePath());
|
||||||
|
|
||||||
// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||||
//
|
//
|
||||||
// int maxW = 800;
|
// int maxW = 800;
|
||||||
// int maxH = 800;
|
// int maxH = 800;
|
||||||
@ -1553,30 +1635,35 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// // System.err.println("Scale time: " + (System.currentTimeMillis() - start) + " ms");
|
// // System.err.println("Scale time: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (image.getType() == BufferedImage.TYPE_CUSTOM) {
|
if (image.getType() == BufferedImage.TYPE_CUSTOM) {
|
||||||
start = System.currentTimeMillis();
|
start = System.currentTimeMillis();
|
||||||
image = new ColorConvertOp(null).filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB));
|
image = new ColorConvertOp(null).filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB));
|
||||||
System.err.println("Conversion time: " + (System.currentTimeMillis() - start) + " ms");
|
System.err.println("Conversion time: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
showIt(image, String.format("Image: %s [%d x %d]", file.getName(), reader.getWidth(imageNo), reader.getHeight(imageNo)));
|
showIt(image, String.format("Image: %s [%d x %d]", file.getName(), reader.getWidth(imageNo), reader.getHeight(imageNo)));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int numThumbnails = reader.getNumThumbnails(0);
|
int numThumbnails = reader.getNumThumbnails(0);
|
||||||
for (int thumbnailNo = 0; thumbnailNo < numThumbnails; thumbnailNo++) {
|
for (int thumbnailNo = 0; thumbnailNo < numThumbnails; thumbnailNo++) {
|
||||||
BufferedImage thumbnail = reader.readThumbnail(imageNo, thumbnailNo);
|
BufferedImage thumbnail = reader.readThumbnail(imageNo, thumbnailNo);
|
||||||
// System.err.println("thumbnail: " + thumbnail);
|
// System.err.println("thumbnail: " + thumbnail);
|
||||||
showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
|
showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IIOException e) {
|
||||||
|
System.err.println("Could not read thumbnails: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IIOException e) {
|
catch (Throwable t) {
|
||||||
System.err.println("Could not read thumbnails: " + e.getMessage());
|
System.err.println(file + " image " + imageNo + " can't be read:");
|
||||||
e.printStackTrace();
|
t.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
catch (Throwable t) {
|
||||||
System.err.println(file);
|
System.err.println(file + " can't be read:");
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
|||||||
import com.twelvemonkeys.image.ImageUtil;
|
import com.twelvemonkeys.image.ImageUtil;
|
||||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
@ -39,9 +40,12 @@ import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
|
|||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
import com.twelvemonkeys.io.enc.EncoderStream;
|
import com.twelvemonkeys.io.enc.EncoderStream;
|
||||||
import com.twelvemonkeys.io.enc.PackBitsEncoder;
|
import com.twelvemonkeys.io.enc.PackBitsEncoder;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.*;
|
||||||
|
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
import javax.imageio.spi.ImageWriterSpi;
|
import javax.imageio.spi.ImageWriterSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
@ -50,10 +54,9 @@ import java.awt.color.ColorSpace;
|
|||||||
import java.awt.color.ICC_ColorSpace;
|
import java.awt.color.ICC_ColorSpace;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.zip.Deflater;
|
import java.util.zip.Deflater;
|
||||||
import java.util.zip.DeflaterOutputStream;
|
import java.util.zip.DeflaterOutputStream;
|
||||||
|
|
||||||
@ -65,13 +68,16 @@ import java.util.zip.DeflaterOutputStream;
|
|||||||
* @version $Id: TIFFImageWriter.java,v 1.0 18.09.13 12:46 haraldk Exp$
|
* @version $Id: TIFFImageWriter.java,v 1.0 18.09.13 12:46 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public final class TIFFImageWriter extends ImageWriterBase {
|
public final class TIFFImageWriter extends ImageWriterBase {
|
||||||
|
// Short term
|
||||||
|
// TODO: Support more of the ImageIO metadata (ie. compression from metadata, etc)
|
||||||
|
|
||||||
// Long term
|
// Long term
|
||||||
// TODO: Support tiling
|
// TODO: Support tiling
|
||||||
// TODO: Support thumbnails
|
// TODO: Support thumbnails
|
||||||
// TODO: Support ImageIO metadata
|
|
||||||
// TODO: Support CCITT Modified Huffman compression (2)
|
// TODO: Support CCITT Modified Huffman compression (2)
|
||||||
// TODO: Full "Baseline TIFF" support (pending CCITT compression 2)
|
// TODO: Full "Baseline TIFF" support (pending CCITT compression 2)
|
||||||
// TODO: CCITT compressions T.4 and T.6
|
// TODO: CCITT compressions T.4 and T.6
|
||||||
|
// TODO: Support JPEG compression of CMYK data (pending JPEGImageWriter CMYK write support)
|
||||||
// ----
|
// ----
|
||||||
// TODO: Support storing multiple images in one stream (multi-page TIFF)
|
// TODO: Support storing multiple images in one stream (multi-page TIFF)
|
||||||
// TODO: Support use-case: Transcode multi-layer PSD to multi-page TIFF with metadata
|
// TODO: Support use-case: Transcode multi-layer PSD to multi-page TIFF with metadata
|
||||||
@ -91,6 +97,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// Support LZW compression (5)
|
// Support LZW compression (5)
|
||||||
// Support JPEG compression (7) - might need extra input to allow multiple images with single DQT
|
// Support JPEG compression (7) - might need extra input to allow multiple images with single DQT
|
||||||
// Use sensible defaults for compression based on input? None is sensible... :-)
|
// Use sensible defaults for compression based on input? None is sensible... :-)
|
||||||
|
// Support resolution, resolution unit and software tags from ImageIO metadata
|
||||||
|
|
||||||
public static final Rational STANDARD_DPI = new Rational(72);
|
public static final Rational STANDARD_DPI = new Rational(72);
|
||||||
|
|
||||||
@ -98,14 +105,81 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOutput(final Object output) {
|
||||||
|
super.setOutput(output);
|
||||||
|
|
||||||
|
// TODO: Allow appending/partly overwrite of existing file...
|
||||||
|
}
|
||||||
|
|
||||||
static final class TIFFEntry extends AbstractEntry {
|
static final class TIFFEntry extends AbstractEntry {
|
||||||
TIFFEntry(Object identifier, Object value) {
|
// TODO: Expose a merge of this and the EXIFEntry class...
|
||||||
|
private final short type;
|
||||||
|
|
||||||
|
private static short guessType(final Object val) {
|
||||||
|
// TODO: This code is duplicated in EXIFWriter.getType, needs refactor!
|
||||||
|
Object value = Validate.notNull(val);
|
||||||
|
|
||||||
|
boolean array = value.getClass().isArray();
|
||||||
|
if (array) {
|
||||||
|
value = Array.get(value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This "narrowing" is to keep data consistent between read/write.
|
||||||
|
// TODO: Check for negative values and use signed types?
|
||||||
|
if (value instanceof Byte) {
|
||||||
|
return TIFF.TYPE_BYTE;
|
||||||
|
}
|
||||||
|
if (value instanceof Short) {
|
||||||
|
if (!array && (Short) value < Byte.MAX_VALUE) {
|
||||||
|
return TIFF.TYPE_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TIFF.TYPE_SHORT;
|
||||||
|
}
|
||||||
|
if (value instanceof Integer) {
|
||||||
|
if (!array && (Integer) value < Short.MAX_VALUE) {
|
||||||
|
return TIFF.TYPE_SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TIFF.TYPE_LONG;
|
||||||
|
}
|
||||||
|
if (value instanceof Long) {
|
||||||
|
if (!array && (Long) value < Integer.MAX_VALUE) {
|
||||||
|
return TIFF.TYPE_LONG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof Rational) {
|
||||||
|
return TIFF.TYPE_RATIONAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof String) {
|
||||||
|
return TIFF.TYPE_ASCII;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: More types
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException(String.format("Method guessType not implemented for value of type %s", value.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TIFFEntry(final int identifier, final Object value) {
|
||||||
|
this(identifier, guessType(value), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TIFFEntry(int identifier, short type, Object value) {
|
||||||
super(identifier, value);
|
super(identifier, value);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeName() {
|
||||||
|
return TIFF.TYPE_NAMES[type];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
|
public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||||
// TODO: Validate input
|
// TODO: Validate input
|
||||||
|
|
||||||
assertOutput();
|
assertOutput();
|
||||||
@ -120,6 +194,14 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
ColorModel colorModel = renderedImage.getColorModel();
|
ColorModel colorModel = renderedImage.getColorModel();
|
||||||
int numComponents = colorModel.getNumComponents();
|
int numComponents = colorModel.getNumComponents();
|
||||||
|
|
||||||
|
TIFFImageMetadata metadata;
|
||||||
|
if (image.getMetadata() != null) {
|
||||||
|
metadata = convertImageMetadata(image.getMetadata(), ImageTypeSpecifier.createFromRenderedImage(renderedImage), param);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
metadata = initMeta(null, ImageTypeSpecifier.createFromRenderedImage(renderedImage), param);
|
||||||
|
}
|
||||||
|
|
||||||
SampleModel sampleModel = renderedImage.getSampleModel();
|
SampleModel sampleModel = renderedImage.getSampleModel();
|
||||||
|
|
||||||
int[] bandOffsets;
|
int[] bandOffsets;
|
||||||
@ -140,7 +222,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
throw new IllegalArgumentException("Unknown bit/bandOffsets for sample model: " + sampleModel);
|
throw new IllegalArgumentException("Unknown bit/bandOffsets for sample model: " + sampleModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Entry> entries = new ArrayList<>();
|
Set<Entry> entries = new LinkedHashSet<>();
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, renderedImage.getWidth()));
|
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, renderedImage.getWidth()));
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_HEIGHT, renderedImage.getHeight()));
|
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_HEIGHT, renderedImage.getHeight()));
|
||||||
// entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, 1)); // (optional)
|
// entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, 1)); // (optional)
|
||||||
@ -157,10 +239,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write compression field from param or metadata
|
// Write compression field from param or metadata
|
||||||
|
// TODO: Support COPY_FROM_METADATA
|
||||||
int compression = TIFFImageWriteParam.getCompressionType(param);
|
int compression = TIFFImageWriteParam.getCompressionType(param);
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_COMPRESSION, compression));
|
entries.add(new TIFFEntry(TIFF.TAG_COMPRESSION, compression));
|
||||||
|
|
||||||
// TODO: Let param/metadata control predictor
|
// TODO: Let param/metadata control predictor
|
||||||
|
// TODO: Depending on param.getCompressionMode(): DISABLED/EXPLICIT/COPY_FROM_METADATA/DEFAULT
|
||||||
switch (compression) {
|
switch (compression) {
|
||||||
case TIFFExtension.COMPRESSION_ZLIB:
|
case TIFFExtension.COMPRESSION_ZLIB:
|
||||||
case TIFFExtension.COMPRESSION_DEFLATE:
|
case TIFFExtension.COMPRESSION_DEFLATE:
|
||||||
@ -169,7 +253,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We might want to support CMYK in JPEG as well...
|
// TODO: We might want to support CMYK in JPEG as well... Pending JPEG CMYK write support.
|
||||||
int photometric = compression == TIFFExtension.COMPRESSION_JPEG ?
|
int photometric = compression == TIFFExtension.COMPRESSION_JPEG ?
|
||||||
TIFFExtension.PHOTOMETRIC_YCBCR :
|
TIFFExtension.PHOTOMETRIC_YCBCR :
|
||||||
getPhotometricInterpretation(colorModel);
|
getPhotometricInterpretation(colorModel);
|
||||||
@ -189,15 +273,24 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default sample format SAMPLEFORMAT_UINT need not be written
|
||||||
if (sampleModel.getDataType() == DataBuffer.TYPE_SHORT /* TODO: if (isSigned(sampleModel.getDataType) or getSampleFormat(sampleModel) != 0 */) {
|
if (sampleModel.getDataType() == DataBuffer.TYPE_SHORT /* TODO: if (isSigned(sampleModel.getDataType) or getSampleFormat(sampleModel) != 0 */) {
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_SAMPLE_FORMAT, TIFFExtension.SAMPLEFORMAT_INT));
|
entries.add(new TIFFEntry(TIFF.TAG_SAMPLE_FORMAT, TIFFExtension.SAMPLEFORMAT_INT));
|
||||||
}
|
}
|
||||||
|
// TODO: Float values!
|
||||||
|
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO TIFF writer")); // TODO: Get from metadata (optional) + fill in version number
|
// Get Software from metadata, or use default
|
||||||
|
Entry software = metadata.getIFD().getEntryById(TIFF.TAG_SOFTWARE);
|
||||||
|
entries.add(software != null ? software : new TIFFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO TIFF writer " + originatingProvider.getVersion()));
|
||||||
|
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_X_RESOLUTION, STANDARD_DPI));
|
// Get X/YResolution and ResolutionUnit from metadata if set, otherwise use defaults
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_Y_RESOLUTION, STANDARD_DPI));
|
// TODO: Add logic here OR in metadata merging, to make sure these 3 values are consistent.
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFFBaseline.RESOLUTION_UNIT_DPI));
|
Entry xRes = metadata.getIFD().getEntryById(TIFF.TAG_X_RESOLUTION);
|
||||||
|
entries.add(xRes != null ? xRes : new TIFFEntry(TIFF.TAG_X_RESOLUTION, STANDARD_DPI));
|
||||||
|
Entry yRes = metadata.getIFD().getEntryById(TIFF.TAG_Y_RESOLUTION);
|
||||||
|
entries.add(yRes != null ? yRes : new TIFFEntry(TIFF.TAG_Y_RESOLUTION, STANDARD_DPI));
|
||||||
|
Entry resUnit = metadata.getIFD().getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
|
entries.add(resUnit != null ? resUnit : new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFFBaseline.RESOLUTION_UNIT_DPI));
|
||||||
|
|
||||||
// TODO: RowsPerStrip - can be entire image (or even 2^32 -1), but it's recommended to write "about 8K bytes" per strip
|
// TODO: RowsPerStrip - can be entire image (or even 2^32 -1), but it's recommended to write "about 8K bytes" per strip
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, Integer.MAX_VALUE)); // TODO: Allowed but not recommended
|
entries.add(new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, Integer.MAX_VALUE)); // TODO: Allowed but not recommended
|
||||||
@ -208,7 +301,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
TIFFEntry dummyStripOffsets = new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, -1);
|
TIFFEntry dummyStripOffsets = new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, -1);
|
||||||
entries.add(dummyStripOffsets); // Updated later
|
entries.add(dummyStripOffsets); // Updated later
|
||||||
|
|
||||||
// TODO: If tiled, write tile indexes etc, or always do that?
|
// TODO: If tiled, write tile indexes etc
|
||||||
|
// Depending on param.getTilingMode
|
||||||
|
|
||||||
EXIFWriter exifWriter = new EXIFWriter();
|
EXIFWriter exifWriter = new EXIFWriter();
|
||||||
|
|
||||||
@ -233,6 +327,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// TODO: Create compressor stream per Tile/Strip
|
// TODO: Create compressor stream per Tile/Strip
|
||||||
if (compression == TIFFExtension.COMPRESSION_JPEG) {
|
if (compression == TIFFExtension.COMPRESSION_JPEG) {
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("JPEG");
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("JPEG");
|
||||||
|
|
||||||
if (!writers.hasNext()) {
|
if (!writers.hasNext()) {
|
||||||
// This can only happen if someone deliberately uninstalled it
|
// This can only happen if someone deliberately uninstalled it
|
||||||
throw new IIOException("No JPEG ImageWriter found!");
|
throw new IIOException("No JPEG ImageWriter found!");
|
||||||
@ -607,13 +702,75 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// Metadata
|
// Metadata
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
|
public TIFFImageMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||||
return null;
|
return initMeta(null, imageType, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
|
public TIFFImageMetadata convertImageMetadata(final IIOMetadata inData,
|
||||||
return null;
|
final ImageTypeSpecifier imageType,
|
||||||
|
final ImageWriteParam param) {
|
||||||
|
Validate.notNull(inData, "inData");
|
||||||
|
Validate.notNull(imageType, "imageType");
|
||||||
|
|
||||||
|
Directory ifd;
|
||||||
|
|
||||||
|
if (inData instanceof TIFFImageMetadata) {
|
||||||
|
ifd = ((TIFFImageMetadata) inData).getIFD();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TIFFImageMetadata outData = new TIFFImageMetadata(Collections.<Entry>emptySet());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Arrays.asList(inData.getMetadataFormatNames()).contains(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME)) {
|
||||||
|
outData.setFromTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, inData.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME));
|
||||||
|
}
|
||||||
|
else if (inData.isStandardMetadataFormatSupported()) {
|
||||||
|
outData.setFromTree(IIOMetadataFormatImpl.standardMetadataFormatName, inData.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Unknown format, we can't convert it
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IIOInvalidTreeException e) {
|
||||||
|
// TODO: How to issue warning when warning requires imageIndex??? Use -1?
|
||||||
|
}
|
||||||
|
|
||||||
|
ifd = outData.getIFD();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite in values with values from imageType and param as needed
|
||||||
|
return initMeta(ifd, imageType, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TIFFImageMetadata initMeta(final Directory ifd, final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||||
|
Validate.notNull(imageType, "imageType");
|
||||||
|
|
||||||
|
Map<Integer, Entry> entries = new LinkedHashMap<>(ifd != null ? ifd.size() + 10 : 20);
|
||||||
|
|
||||||
|
if (ifd != null) {
|
||||||
|
for (Entry entry : ifd) {
|
||||||
|
entries.put((Integer) entry.getIdentifier(), entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Set values from imageType
|
||||||
|
entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, getPhotometricInterpretation(imageType.getColorModel())));
|
||||||
|
|
||||||
|
// TODO: Set values from param if != null + combined values...
|
||||||
|
|
||||||
|
return new TIFFImageMetadata(entries.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getDefaultStreamMetadata(final ImageWriteParam param) {
|
||||||
|
return super.getDefaultStreamMetadata(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata convertStreamMetadata(final IIOMetadata inData, final ImageWriteParam param) {
|
||||||
|
return super.convertStreamMetadata(inData, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Param
|
// Param
|
||||||
@ -762,5 +919,4 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
|
|
||||||
TIFFImageReader.showIt(read, output.getName());
|
TIFFImageReader.showIt(read, output.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,599 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
|
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||||
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
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 java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 EXIFReader().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("value"));
|
||||||
|
|
||||||
|
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 testMetadataNativeFormat() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/quad-lzw.tif");
|
||||||
|
Node root = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
|
|
||||||
|
// Root: "com_sun_media_imageio_plugins_tiff_image_1.0"
|
||||||
|
assertNotNull(root);
|
||||||
|
assertEquals(TIFFMedataFormat.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");
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_Y_POSITION, TIFF.TYPE_RATIONAL, "0");
|
||||||
|
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(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
|
assertNotNull(nativeTree);
|
||||||
|
|
||||||
|
Node nativeTree2 = metadata.getAsTree(TIFFMedataFormat.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 = TIFFMedataFormat.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");
|
||||||
|
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));
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
// 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(expected = IllegalArgumentException.class)
|
||||||
|
public void testMergeTreeUnsupportedFormat() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = "com_foo_bar_tiff_42";
|
||||||
|
metadata.mergeTree(nativeFormat, new IIOMetadataNode(nativeFormat));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IIOInvalidTreeException.class)
|
||||||
|
public void testMergeTreeFormatMisMatch() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
metadata.mergeTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IIOInvalidTreeException.class)
|
||||||
|
public void testMergeTreeInvalid() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
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 = TIFFMedataFormat.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 = TIFFMedataFormat.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(expected = IllegalArgumentException.class)
|
||||||
|
public void testSetFromTreeUnsupportedFormat() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = "com_foo_bar_tiff_42";
|
||||||
|
metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IIOInvalidTreeException.class)
|
||||||
|
public void testSetFromTreeFormatMisMatch() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
metadata.setFromTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IIOInvalidTreeException.class)
|
||||||
|
public void testSetFromTreeInvalid() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat)); // Requires at least one child node
|
||||||
|
}
|
||||||
|
|
||||||
|
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("Duplicate tag " + tagNumber + " found", foundTag);
|
||||||
|
|
||||||
|
assertEquals(1, field.getChildNodes().getLength());
|
||||||
|
Node containerNode = field.getFirstChild();
|
||||||
|
assertEquals("TIFF" + typeName + "s", containerNode.getNodeName());
|
||||||
|
|
||||||
|
NodeList valueNodes = containerNode.getChildNodes();
|
||||||
|
assertEquals("Unexpected number of values for tag " + tagNumber, expectedValue.length, valueNodes.getLength());
|
||||||
|
|
||||||
|
for (int j = 0; j < expectedValue.length; j++) {
|
||||||
|
Element valueNode = (Element) valueNodes.item(j);
|
||||||
|
assertEquals("TIFF" + typeName, valueNode.getNodeName());
|
||||||
|
assertEquals("Unexpected tag " + tagNumber + " value", expectedValue[j], valueNode.getAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
foundTag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("No tag " + tagNumber + " found", foundTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Test that failed set leaves metadata unchanged
|
||||||
|
|
||||||
|
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(message + " class differs", expected.getClass(), actual.getClass());
|
||||||
|
assertEquals(message, expected.getNodeValue(), actual.getNodeValue());
|
||||||
|
|
||||||
|
if (expected instanceof IIOMetadataNode) {
|
||||||
|
IIOMetadataNode expectedIIO = (IIOMetadataNode) expected;
|
||||||
|
IIOMetadataNode actualIIO = (IIOMetadataNode) actual;
|
||||||
|
|
||||||
|
assertEquals(message, expectedIIO.getUserObject(), actualIIO.getUserObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeList expectedChildNodes = expected.getChildNodes();
|
||||||
|
NodeList actualChildNodes = actual.getChildNodes();
|
||||||
|
|
||||||
|
assertEquals(message + " child length differs: " + toString(expectedChildNodes) + " != " + toString(actualChildNodes),
|
||||||
|
expectedChildNodes.getLength(), actualChildNodes.getLength());
|
||||||
|
|
||||||
|
for (int i = 0; i < expectedChildNodes.getLength(); i++) {
|
||||||
|
Node expectedChild = expectedChildNodes.item(i);
|
||||||
|
Node actualChild = actualChildNodes.item(i);
|
||||||
|
|
||||||
|
assertEquals(message + " node name differs", expectedChild.getLocalName(), actualChild.getLocalName());
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -28,15 +28,33 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.imageio.IIOImage;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.ImageWriter;
|
import javax.imageio.ImageWriter;
|
||||||
import java.awt.*;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataTest.createTIFFFieldNode;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TIFFImageWriterTest
|
* TIFFImageWriterTest
|
||||||
*
|
*
|
||||||
@ -55,19 +73,208 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<? extends RenderedImage> getTestData() {
|
protected List<? extends RenderedImage> getTestData() {
|
||||||
BufferedImage image = new BufferedImage(300, 200, BufferedImage.TYPE_INT_ARGB);
|
return Arrays.asList(
|
||||||
Graphics2D graphics = image.createGraphics();
|
new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB),
|
||||||
try {
|
new BufferedImage(300, 200, BufferedImage.TYPE_INT_ARGB),
|
||||||
graphics.setColor(Color.RED);
|
new BufferedImage(300, 200, BufferedImage.TYPE_3BYTE_BGR),
|
||||||
graphics.fillRect(0, 0, 100, 200);
|
new BufferedImage(300, 200, BufferedImage.TYPE_4BYTE_ABGR),
|
||||||
graphics.setColor(Color.BLUE);
|
new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_GRAY),
|
||||||
graphics.fillRect(100, 0, 100, 200);
|
new BufferedImage(300, 200, BufferedImage.TYPE_USHORT_GRAY),
|
||||||
graphics.clearRect(200, 0, 100, 200);
|
// new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_BINARY), // TODO!
|
||||||
|
new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_INDEXED)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Test write bilevel stays bilevel
|
||||||
|
// TODO: Test write indexed stays indexed
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteWithCustomResolutionNative() throws IOException {
|
||||||
|
// Issue 139 Writing TIFF files with custom resolution value
|
||||||
|
Rational resolutionValue = new Rational(1200);
|
||||||
|
int resolutionUnitValue = TIFFBaseline.RESOLUTION_UNIT_CENTIMETER;
|
||||||
|
|
||||||
|
RenderedImage image = getTestData(0);
|
||||||
|
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
|
||||||
|
|
||||||
|
IIOMetadataNode customMeta = new IIOMetadataNode(nativeFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode ifd = new IIOMetadataNode("TIFFIFD");
|
||||||
|
customMeta.appendChild(ifd);
|
||||||
|
|
||||||
|
createTIFFFieldNode(ifd, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, resolutionUnitValue);
|
||||||
|
createTIFFFieldNode(ifd, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, resolutionValue);
|
||||||
|
createTIFFFieldNode(ifd, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, resolutionValue);
|
||||||
|
|
||||||
|
metadata.mergeTree(nativeFormat, customMeta);
|
||||||
|
|
||||||
|
writer.write(null, new IIOImage(image, null, metadata), null);
|
||||||
}
|
}
|
||||||
finally {
|
catch (IOException e) {
|
||||||
graphics.dispose();
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Arrays.asList(image);
|
assertTrue("No image data written", buffer.size() > 0);
|
||||||
|
|
||||||
|
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||||
|
|
||||||
|
Entry resolutionUnit = ifds.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
|
assertNotNull(resolutionUnit);
|
||||||
|
assertEquals(resolutionUnitValue, ((Number) resolutionUnit.getValue()).intValue());
|
||||||
|
|
||||||
|
Entry xResolution = ifds.getEntryById(TIFF.TAG_X_RESOLUTION);
|
||||||
|
assertNotNull(xResolution);
|
||||||
|
assertEquals(resolutionValue, xResolution.getValue());
|
||||||
|
|
||||||
|
Entry yResolution = ifds.getEntryById(TIFF.TAG_Y_RESOLUTION);
|
||||||
|
assertNotNull(yResolution);
|
||||||
|
assertEquals(resolutionValue, yResolution.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteWithCustomSoftwareNative() throws IOException {
|
||||||
|
String softwareString = "12M TIFF Test 1.0 (build $foo$)";
|
||||||
|
|
||||||
|
RenderedImage image = getTestData(0);
|
||||||
|
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
|
||||||
|
|
||||||
|
IIOMetadataNode customMeta = new IIOMetadataNode(nativeFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode ifd = new IIOMetadataNode("TIFFIFD");
|
||||||
|
customMeta.appendChild(ifd);
|
||||||
|
|
||||||
|
createTIFFFieldNode(ifd, TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, softwareString);
|
||||||
|
|
||||||
|
metadata.mergeTree(nativeFormat, customMeta);
|
||||||
|
|
||||||
|
writer.write(null, new IIOImage(image, null, metadata), null);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("No image data written", buffer.size() > 0);
|
||||||
|
|
||||||
|
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||||
|
Entry software = ifds.getEntryById(TIFF.TAG_SOFTWARE);
|
||||||
|
assertNotNull(software);
|
||||||
|
assertEquals(softwareString, software.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteWithCustomResolutionStandard() throws IOException {
|
||||||
|
// Issue 139 Writing TIFF files with custom resolution value
|
||||||
|
double resolutionValue = 300 / 25.4; // 300 dpi, 1 inch = 2.54 cm or 25.4 mm
|
||||||
|
int resolutionUnitValue = TIFFBaseline.RESOLUTION_UNIT_CENTIMETER;
|
||||||
|
Rational expectedResolutionValue = new Rational(Math.round(resolutionValue * 10 * TIFFImageMetadata.RATIONAL_SCALE_FACTOR), TIFFImageMetadata.RATIONAL_SCALE_FACTOR);
|
||||||
|
|
||||||
|
RenderedImage image = getTestData(0);
|
||||||
|
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||||
|
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
|
||||||
|
|
||||||
|
IIOMetadataNode customMeta = new IIOMetadataNode(standardFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||||
|
customMeta.appendChild(dimension);
|
||||||
|
|
||||||
|
IIOMetadataNode xSize = new IIOMetadataNode("HorizontalPixelSize");
|
||||||
|
dimension.appendChild(xSize);
|
||||||
|
xSize.setAttribute("value", String.valueOf(resolutionValue));
|
||||||
|
|
||||||
|
IIOMetadataNode ySize = new IIOMetadataNode("VerticalPixelSize");
|
||||||
|
dimension.appendChild(ySize);
|
||||||
|
ySize.setAttribute("value", String.valueOf(resolutionValue));
|
||||||
|
|
||||||
|
metadata.mergeTree(standardFormat, customMeta);
|
||||||
|
|
||||||
|
writer.write(null, new IIOImage(image, null, metadata), null);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("No image data written", buffer.size() > 0);
|
||||||
|
|
||||||
|
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||||
|
|
||||||
|
Entry resolutionUnit = ifds.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
|
assertNotNull(resolutionUnit);
|
||||||
|
assertEquals(resolutionUnitValue, ((Number) resolutionUnit.getValue()).intValue());
|
||||||
|
|
||||||
|
Entry xResolution = ifds.getEntryById(TIFF.TAG_X_RESOLUTION);
|
||||||
|
assertNotNull(xResolution);
|
||||||
|
assertEquals(expectedResolutionValue, xResolution.getValue());
|
||||||
|
|
||||||
|
Entry yResolution = ifds.getEntryById(TIFF.TAG_Y_RESOLUTION);
|
||||||
|
assertNotNull(yResolution);
|
||||||
|
assertEquals(expectedResolutionValue, yResolution.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteWithCustomSoftwareStandard() throws IOException {
|
||||||
|
String softwareString = "12M TIFF Test 1.0 (build $foo$)";
|
||||||
|
|
||||||
|
RenderedImage image = getTestData(0);
|
||||||
|
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||||
|
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
|
||||||
|
|
||||||
|
IIOMetadataNode customMeta = new IIOMetadataNode(standardFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode dimension = new IIOMetadataNode("Text");
|
||||||
|
customMeta.appendChild(dimension);
|
||||||
|
|
||||||
|
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||||
|
dimension.appendChild(textEntry);
|
||||||
|
textEntry.setAttribute("keyword", "Software");
|
||||||
|
textEntry.setAttribute("value", softwareString);
|
||||||
|
|
||||||
|
metadata.mergeTree(standardFormat, customMeta);
|
||||||
|
|
||||||
|
writer.write(null, new IIOImage(image, null, metadata), null);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("No image data written", buffer.size() > 0);
|
||||||
|
|
||||||
|
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||||
|
Entry software = ifds.getEntryById(TIFF.TAG_SOFTWARE);
|
||||||
|
assertNotNull(software);
|
||||||
|
assertEquals(softwareString, software.getValueAsString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user