diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java index 9abbff29..1988d376 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java @@ -28,17 +28,16 @@ package com.twelvemonkeys.imageio.plugins.bmp; +import com.twelvemonkeys.imageio.AbstractMetadata; import com.twelvemonkeys.lang.Validate; import org.w3c.dom.Node; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; /** * BMPMetadata. */ -final class BMPMetadata extends IIOMetadata { +final class BMPMetadata extends AbstractMetadata { /** We return metadata in the exact same form as the JRE built-in, to be compatible with the BMPImageWriter. */ public static final String nativeMetadataFormatName = "javax_imageio_bmp_1.0"; @@ -46,46 +45,13 @@ final class BMPMetadata extends IIOMetadata { private final int[] colorMap; BMPMetadata(final DIBHeader header, final int[] colorMap) { + super(true, nativeMetadataFormatName, "com.sun.imageio.plugins.bmp.BMPMetadataFormat", null, null); this.header = Validate.notNull(header, "header"); this.colorMap = colorMap == null || colorMap.length == 0 ? null : colorMap; - - standardFormatSupported = true; - } - - @Override public boolean isReadOnly() { - return true; - } - - @Override public Node getAsTree(final String formatName) { - if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) { - return getStandardTree(); - } - else if (nativeMetadataFormatName.equals(formatName)) { - return getNativeTree(); - } - else { - throw new IllegalArgumentException("Unsupported metadata format: " + formatName); - } - } - - @Override public void mergeTree(final String formatName, final Node root) { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - @Override public void reset() { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } } @Override - public String getNativeMetadataFormatName() { - return nativeMetadataFormatName; - } - - private Node getNativeTree() { + protected Node getNativeTree() { IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); addChildNode(root, "BMPVersion", header.getBMPVersion()); @@ -170,7 +136,8 @@ final class BMPMetadata extends IIOMetadata { return child; } - @Override protected IIOMetadataNode getStandardChromaNode() { + @Override + protected IIOMetadataNode getStandardChromaNode() { // NOTE: BMP files may contain a color map, even if true color... // Not sure if this is a good idea to expose to the meta data, // as it might be unexpected... Then again... @@ -197,7 +164,8 @@ final class BMPMetadata extends IIOMetadata { return null; } - @Override protected IIOMetadataNode getStandardCompressionNode() { + @Override + protected IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression = new IIOMetadataNode("Compression"); IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null); compressionTypeName.setAttribute("value", "NONE"); @@ -229,7 +197,8 @@ final class BMPMetadata extends IIOMetadata { // } } - @Override protected IIOMetadataNode getStandardDataNode() { + @Override + protected IIOMetadataNode getStandardDataNode() { IIOMetadataNode node = new IIOMetadataNode("Data"); // IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); @@ -294,7 +263,8 @@ final class BMPMetadata extends IIOMetadata { return buffer.toString(); } - @Override protected IIOMetadataNode getStandardDimensionNode() { + @Override + protected IIOMetadataNode getStandardDimensionNode() { if (header.xPixelsPerMeter > 0 || header.yPixelsPerMeter > 0) { IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); @@ -302,16 +272,16 @@ final class BMPMetadata extends IIOMetadata { addChildNode(dimension, "HorizontalPhysicalPixelSpacing", null); addChildNode(dimension, "VerticalPhysicalPixelSpacing", null); - // IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); - // - // if (header.topDown) { - // imageOrientation.setAttribute("value", "FlipH"); - // } - // else { - // imageOrientation.setAttribute("value", "Normal"); - // } - // - // dimension.appendChild(imageOrientation); +// IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); +// +// if (header.topDown) { +// imageOrientation.setAttribute("value", "FlipH"); +// } +// else { +// imageOrientation.setAttribute("value", "Normal"); +// } +// +// dimension.appendChild(imageOrientation); return dimension; } @@ -325,7 +295,8 @@ final class BMPMetadata extends IIOMetadata { // No tiling - @Override protected IIOMetadataNode getStandardTransparencyNode() { + @Override + protected IIOMetadataNode getStandardTransparencyNode() { return null; // IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/AbstractMetadata.java similarity index 66% rename from imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java rename to imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/AbstractMetadata.java index 091246c7..202a8357 100644 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/AbstractMetadata.java @@ -26,7 +26,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package com.twelvemonkeys.imageio.plugins.psd; +package com.twelvemonkeys.imageio; import org.w3c.dom.Node; @@ -42,13 +42,15 @@ import java.util.Arrays; * @author last modified by $Author: haraldk$ * @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$ */ -abstract class AbstractMetadata extends IIOMetadata implements Cloneable { - // TODO: Move to core... +public abstract class AbstractMetadata extends IIOMetadata implements Cloneable { + protected AbstractMetadata(final boolean standardFormatSupported, + final String nativeFormatName, final String nativeFormatClassName, + final String[] extraFormatNames, final String[] extraFormatClassNames) { + super(standardFormatSupported, nativeFormatName, nativeFormatClassName, extraFormatNames, extraFormatClassNames); + } - protected AbstractMetadata(final boolean pStandardFormatSupported, - final String pNativeFormatName, final String pNativeFormatClassName, - final String[] pExtraFormatNames, final String[] pExtraFormatClassNames) { - super(pStandardFormatSupported, pNativeFormatName, pNativeFormatClassName, pExtraFormatNames, pExtraFormatClassNames); + protected AbstractMetadata() { + super(true, null, null, null, null); } /** @@ -63,31 +65,42 @@ abstract class AbstractMetadata extends IIOMetadata implements Cloneable { } @Override - public Node getAsTree(final String pFormatName) { - validateFormatName(pFormatName); + public Node getAsTree(final String formatName) { + validateFormatName(formatName); - if (pFormatName.equals(nativeMetadataFormatName)) { + if (formatName.equals(nativeMetadataFormatName)) { return getNativeTree(); } - else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { + else if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { return getStandardTree(); } - // TODO: What about extra formats?? - throw new AssertionError("Unreachable"); + // Subclasses that supports extra formats need to check for these formats themselves... + return null; + } + + /** + * Default implementation that throws {@code UnsupportedOperationException}. + * Subclasses that supports formats other than standard metadata should override this method. + * + * @throws UnsupportedOperationException + */ + protected Node getNativeTree() { + throw new UnsupportedOperationException("getNativeTree"); } @Override - public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException { + public void mergeTree(final String formatName, final Node root) throws IIOInvalidTreeException { assertMutable(); - validateFormatName(pFormatName); + validateFormatName(formatName); - if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) { - throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot); + if (!root.getNodeName().equals(formatName)) { + throw new IIOInvalidTreeException("Root must be " + formatName, root); } - Node node = pRoot.getFirstChild(); + // TODO: Merge both native and standard! + Node node = root.getFirstChild(); while (node != null) { // TODO: Merge values from node into this @@ -112,21 +125,19 @@ abstract class AbstractMetadata extends IIOMetadata implements Cloneable { } } - protected abstract Node getNativeTree(); - - protected final void validateFormatName(final String pFormatName) { + protected final void validateFormatName(final String formatName) { String[] metadataFormatNames = getMetadataFormatNames(); if (metadataFormatNames != null) { for (String metadataFormatName : metadataFormatNames) { - if (metadataFormatName.equals(pFormatName)) { + if (metadataFormatName.equals(formatName)) { return; // Found, we're ok! } } } throw new IllegalArgumentException( - String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames)) + String.format("Bad format name: \"%s\". Expected one of %s", formatName, Arrays.toString(metadataFormatNames)) ); } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java index ce7c6074..be03f90c 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java @@ -64,4 +64,8 @@ public interface Entry { // For arrays only int valueCount(); + + // TODO: getValueAsInt, UnsignedInt, Short, UnsignedShort, Byte, UnsignedByte etc + // TODO: getValueAsIntArray, ShortArray, ByteArray, StringArray etc (also for non-arrays, to return a single element array) + } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java index dfe40b72..3fcfadf8 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java @@ -43,7 +43,7 @@ final class EXIFEntry extends AbstractEntry { EXIFEntry(final int identifier, final Object value, final short type) { super(identifier, value); - if (type < 1 || type > TIFF.TYPE_NAMES.length) { + if (type < 1 || type >= TIFF.TYPE_NAMES.length) { throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", type)); } @@ -86,8 +86,16 @@ final class EXIFEntry extends AbstractEntry { return "Compression"; case TIFF.TAG_PHOTOMETRIC_INTERPRETATION: return "PhotometricInterpretation"; + case TIFF.TAG_FILL_ORDER: + return "FillOrder"; + case TIFF.TAG_DOCUMENT_NAME: + return "DocumentName"; case TIFF.TAG_IMAGE_DESCRIPTION: return "ImageDescription"; + case TIFF.TAG_MAKE: + return "Make"; + case TIFF.TAG_MODEL: + return "Model"; case TIFF.TAG_STRIP_OFFSETS: return "StripOffsets"; case TIFF.TAG_ORIENTATION: @@ -106,14 +114,8 @@ final class EXIFEntry extends AbstractEntry { return "PlanarConfiguration"; case TIFF.TAG_RESOLUTION_UNIT: return "ResolutionUnit"; - case TIFF.TAG_JPEG_INTERCHANGE_FORMAT: - return "JPEGInterchangeFormat"; - case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: - return "JPEGInterchangeFormatLength"; - case TIFF.TAG_MAKE: - return "Make"; - case TIFF.TAG_MODEL: - return "Model"; + case TIFF.TAG_PAGE_NUMBER: + return "PageNumber"; case TIFF.TAG_SOFTWARE: return "Software"; case TIFF.TAG_DATE_TIME: @@ -140,10 +142,20 @@ final class EXIFEntry extends AbstractEntry { return "YCbCrPositioning"; case TIFF.TAG_COLOR_MAP: return "ColorMap"; + case TIFF.TAG_INK_SET: + return "InkSet"; + case TIFF.TAG_INK_NAMES: + return "InkNames"; case TIFF.TAG_EXTRA_SAMPLES: return "ExtraSamples"; case TIFF.TAG_SAMPLE_FORMAT: return "SampleFormat"; + case TIFF.TAG_JPEG_TABLES: + return "JPEGTables"; + case TIFF.TAG_JPEG_INTERCHANGE_FORMAT: + return "JPEGInterchangeFormat"; + case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: + return "JPEGInterchangeFormatLength"; case TIFF.TAG_SUB_IFD: return "SubIFD"; @@ -261,6 +273,6 @@ final class EXIFEntry extends AbstractEntry { @Override public String getTypeName() { - return TIFF.TYPE_NAMES[type - 1]; + return TIFF.TYPE_NAMES[type]; } } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java index ac13be42..40ce112f 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java @@ -446,8 +446,8 @@ public final class EXIFReader extends MetadataReader { } static int getValueLength(final int pType, final int pCount) { - if (pType > 0 && pType <= TIFF.TYPE_LENGTHS.length) { - return TIFF.TYPE_LENGTHS[pType - 1] * pCount; + if (pType > 0 && pType < TIFF.TYPE_LENGTHS.length) { + return TIFF.TYPE_LENGTHS[pType] * pCount; } return -1; diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java index 558a8d63..66892cb1 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java @@ -94,11 +94,11 @@ public final class EXIFWriter extends MetadataWriter { stream.writeShort(42); } - public long writeIFD(final Collection entries, ImageOutputStream stream) throws IOException { + public long writeIFD(final Collection entries, final ImageOutputStream stream) throws IOException { return writeIFD(new IFD(entries), stream, false); } - private long writeIFD(final Directory original, ImageOutputStream stream, boolean isSubIFD) throws IOException { + private long writeIFD(final Directory original, final ImageOutputStream stream, final boolean isSubIFD) throws IOException { // TIFF spec says tags should be in increasing order, enforce that when writing Directory ordered = ensureOrderedDirectory(original); @@ -183,7 +183,7 @@ public final class EXIFWriter extends MetadataWriter { private Directory ensureOrderedDirectory(final Directory directory) { if (!isSorted(directory)) { - List entries = new ArrayList(directory.size()); + List entries = new ArrayList<>(directory.size()); for (Entry entry : directory) { entries.add(entry); @@ -217,7 +217,7 @@ public final class EXIFWriter extends MetadataWriter { return true; } - private long writeValue(Entry entry, long dataOffset, ImageOutputStream stream) throws IOException { + private long writeValue(final Entry entry, final long dataOffset, final ImageOutputStream stream) throws IOException { short type = getType(entry); int valueLength = EXIFReader.getValueLength(type, getCount(entry)); @@ -238,14 +238,15 @@ public final class EXIFWriter extends MetadataWriter { } } - private int getCount(Entry entry) { + private int getCount(final Entry entry) { Object value = entry.getValue(); return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount(); } - private void writeValueInline(Object value, short type, ImageOutputStream stream) throws IOException { + private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException { if (value.getClass().isArray()) { switch (type) { + case TIFF.TYPE_UNDEFINED: case TIFF.TYPE_BYTE: stream.write((byte[]) value); break; @@ -293,7 +294,7 @@ public final class EXIFWriter extends MetadataWriter { } } else { - throw new IllegalArgumentException("Unsupported type for TIFF SHORT: " + value.getClass()); + throw new IllegalArgumentException("Unsupported type for TIFF LONG: " + value.getClass()); } stream.writeInts(ints, 0, ints.length); @@ -318,6 +319,7 @@ public final class EXIFWriter extends MetadataWriter { // } else { switch (type) { + case TIFF.TYPE_UNDEFINED: case TIFF.TYPE_BYTE: stream.writeByte((Integer) value); break; @@ -345,7 +347,7 @@ public final class EXIFWriter extends MetadataWriter { } } - private void writeValueAt(long dataOffset, Object value, short type, ImageOutputStream stream) throws IOException { + private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException { stream.writeInt(assertIntegerOffset(dataOffset)); long position = stream.getStreamPosition(); stream.seek(dataOffset); @@ -353,7 +355,7 @@ public final class EXIFWriter extends MetadataWriter { stream.seek(position); } - private short getType(Entry entry) { + private short getType(final Entry entry) { if (entry instanceof EXIFEntry) { EXIFEntry exifEntry = (EXIFEntry) entry; return exifEntry.getType(); diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java index daea665f..e7215b1f 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java @@ -88,6 +88,7 @@ public interface TIFF { Should probably all map to Java long (and fail if high bit is set for the unsigned types???) */ String[] TYPE_NAMES = { + null, "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", "IFD", @@ -95,6 +96,7 @@ public interface TIFF { "LONG8", "SLONG8", "IFD8" }; int[] TYPE_LENGTHS = { + -1, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4, @@ -124,6 +126,8 @@ public interface TIFF { int TAG_YCBCR_POSITIONING = 531; int TAG_X_RESOLUTION = 282; int TAG_Y_RESOLUTION = 283; + int TAG_X_POSITION = 286; + int TAG_Y_POSITION = 287; int TAG_RESOLUTION_UNIT = 296; /// B. Tags relating to recording offset @@ -131,6 +135,7 @@ public interface TIFF { int TAG_STRIP_OFFSETS = 273; int TAG_ROWS_PER_STRIP = 278; int TAG_STRIP_BYTE_COUNTS = 279; + int TAG_FREE_OFFSETS = 288; // "Not recommended for general interchange." // "Old-style" JPEG (still used as EXIF thumbnail) int TAG_JPEG_INTERCHANGE_FORMAT = 513; int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514; @@ -153,6 +158,7 @@ public interface TIFF { /// D. Other tags int TAG_DATE_TIME = 306; + int TAG_DOCUMENT_NAME = 269; int TAG_IMAGE_DESCRIPTION = 270; int TAG_MAKE = 271; int TAG_MODEL = 272; diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java index 642ecf7f..28abce4f 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java @@ -71,6 +71,7 @@ public final class XMPReader extends MetadataReader { // TODO: Consider parsing using SAX? // TODO: Determine encoding and parse using a Reader... // TODO: Refactor scanner to return inputstream? + // TODO: Be smarter about ASCII-NULL termination/padding (the SAXParser aka Xerces DOMParser doesn't like it)... DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input))); diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java index 25b376a7..2c6d1ca6 100755 --- a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java +++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java @@ -28,52 +28,22 @@ package com.twelvemonkeys.imageio.plugins.pcx; -import org.w3c.dom.Node; +import com.twelvemonkeys.imageio.AbstractMetadata; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; import java.awt.image.IndexColorModel; -final class PCXMetadata extends IIOMetadata { - // TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core) - +final class PCXMetadata extends AbstractMetadata { private final PCXHeader header; private final IndexColorModel vgaPalette; PCXMetadata(final PCXHeader header, final IndexColorModel vgaPalette) { this.header = header; this.vgaPalette = vgaPalette; - - standardFormatSupported = true; } - @Override public boolean isReadOnly() { - return true; - } - - @Override public Node getAsTree(final String formatName) { - if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) { - return getStandardTree(); - } - else { - throw new IllegalArgumentException("Unsupported metadata format: " + formatName); - } - } - - @Override public void mergeTree(final String formatName, final Node root) { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - @Override public void reset() { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - @Override protected IIOMetadataNode getStandardChromaNode() { + @Override + protected IIOMetadataNode getStandardChromaNode() { IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IndexColorModel palette = null; @@ -141,7 +111,8 @@ final class PCXMetadata extends IIOMetadata { // No compression - @Override protected IIOMetadataNode getStandardCompressionNode() { + @Override + protected IIOMetadataNode getStandardCompressionNode() { if (header.getCompression() != PCX.COMPRESSION_NONE) { IIOMetadataNode node = new IIOMetadataNode("Compression"); @@ -159,7 +130,8 @@ final class PCXMetadata extends IIOMetadata { return null; } - @Override protected IIOMetadataNode getStandardDataNode() { + @Override + protected IIOMetadataNode getStandardDataNode() { IIOMetadataNode node = new IIOMetadataNode("Data"); // Planar configuration only makes sense for multi-channel images @@ -202,7 +174,8 @@ final class PCXMetadata extends IIOMetadata { return buffer.toString(); } - @Override protected IIOMetadataNode getStandardDimensionNode() { + @Override + protected IIOMetadataNode getStandardDimensionNode() { IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); @@ -218,7 +191,8 @@ final class PCXMetadata extends IIOMetadata { // No tiling - @Override protected IIOMetadataNode getStandardTransparencyNode() { + @Override + protected IIOMetadataNode getStandardTransparencyNode() { // NOTE: There doesn't seem to be any god way to determine transparency, other than by convention // 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...) diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java index 267439b5..58578603 100755 --- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java +++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java @@ -28,51 +28,22 @@ package com.twelvemonkeys.imageio.plugins.pnm; -import org.w3c.dom.Node; +import com.twelvemonkeys.imageio.AbstractMetadata; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; import java.awt.*; import java.awt.image.DataBuffer; import java.nio.ByteOrder; -final class PNMMetadata extends IIOMetadata { - // TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core) - +final class PNMMetadata extends AbstractMetadata { private final PNMHeader header; PNMMetadata(final PNMHeader header) { this.header = header; - standardFormatSupported = true; } - @Override public boolean isReadOnly() { - return true; - } - - @Override public Node getAsTree(final String formatName) { - if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) { - return getStandardTree(); - } - else { - throw new IllegalArgumentException("Unsupported metadata format: " + formatName); - } - } - - @Override public void mergeTree(final String formatName, final Node root) { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - @Override public void reset() { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - @Override protected IIOMetadataNode getStandardChromaNode() { + @Override + protected IIOMetadataNode getStandardChromaNode() { IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); @@ -105,7 +76,9 @@ final class PNMMetadata extends IIOMetadata { // TODO: Might make sense to set gamma? IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); - blackIsZero.setAttribute("value", header.getTupleType() == TupleType.BLACKANDWHITE_WHITE_IS_ZERO ? "FALSE" : "TRUE"); + blackIsZero.setAttribute("value", header.getTupleType() == TupleType.BLACKANDWHITE_WHITE_IS_ZERO + ? "FALSE" + : "TRUE"); chroma.appendChild(blackIsZero); return chroma; @@ -113,11 +86,14 @@ final class PNMMetadata extends IIOMetadata { // No compression - @Override protected IIOMetadataNode getStandardDataNode() { + @Override + protected IIOMetadataNode getStandardDataNode() { IIOMetadataNode node = new IIOMetadataNode("Data"); IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); - sampleFormat.setAttribute("value", header.getTransferType() == DataBuffer.TYPE_FLOAT ? "Real" : "UnsignedIntegral"); + sampleFormat.setAttribute("value", header.getTransferType() == DataBuffer.TYPE_FLOAT + ? "Real" + : "UnsignedIntegral"); node.appendChild(sampleFormat); IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); @@ -128,7 +104,9 @@ final class PNMMetadata extends IIOMetadata { significantBitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(computeSignificantBits()))); node.appendChild(significantBitsPerSample); - String msb = header.getByteOrder() == ByteOrder.BIG_ENDIAN ? "0" : Integer.toString(header.getBitsPerSample() - 1); + String msb = header.getByteOrder() == ByteOrder.BIG_ENDIAN + ? "0" + : Integer.toString(header.getBitsPerSample() - 1); IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB"); sampleMSB.setAttribute("value", createListValue(header.getSamplesPerPixel(), msb)); @@ -166,7 +144,8 @@ final class PNMMetadata extends IIOMetadata { return buffer.toString(); } - @Override protected IIOMetadataNode getStandardDimensionNode() { + @Override + protected IIOMetadataNode getStandardDimensionNode() { IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); @@ -178,7 +157,8 @@ final class PNMMetadata extends IIOMetadata { // No document node - @Override protected IIOMetadataNode getStandardTextNode() { + @Override + protected IIOMetadataNode getStandardTextNode() { if (!header.getComments().isEmpty()) { IIOMetadataNode text = new IIOMetadataNode("Text"); @@ -197,7 +177,8 @@ final class PNMMetadata extends IIOMetadata { // No tiling - @Override protected IIOMetadataNode getStandardTransparencyNode() { + @Override + protected IIOMetadataNode getStandardTransparencyNode() { IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java index 19bd5d7f..cb9c83f5 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java @@ -28,6 +28,7 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.imageio.AbstractMetadata; import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.imageio.metadata.exif.TIFF; @@ -571,9 +572,8 @@ public final class PSDMetadata extends AbstractMetadata { compressionNode.appendChild(compressionTypeName); if (compression != PSD.COMPRESSION_NONE) { - IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); - lossless.setAttribute("value", "true"); - compressionNode.appendChild(lossless); + compressionNode.appendChild(new IIOMetadataNode("Lossless")); + // "value" defaults to TRUE, all PSD compressions are lossless } return compressionNode; @@ -755,7 +755,7 @@ public final class PSDMetadata extends AbstractMetadata { } private void appendTextEntriesFlat(final IIOMetadataNode node, final Directory directory, final FilterIterator.Filter filter) { - FilterIterator entries = new FilterIterator(directory.iterator(), filter); + FilterIterator entries = new FilterIterator<>(directory.iterator(), filter); while (entries.hasNext()) { Entry entry = entries.next(); @@ -807,7 +807,7 @@ public final class PSDMetadata extends AbstractMetadata { @SuppressWarnings({"unchecked"}) Iterator iterator = (Iterator) imageResources.iterator(); - return new FilterIterator(iterator, new FilterIterator.Filter() { + return new FilterIterator<>(iterator, new FilterIterator.Filter() { public boolean accept(final T pElement) { return resourceType.isInstance(pElement); } @@ -817,7 +817,7 @@ public final class PSDMetadata extends AbstractMetadata { Iterator getResources(final int... resourceTypes) { Iterator iterator = imageResources.iterator(); - return new FilterIterator(iterator, new FilterIterator.Filter() { + return new FilterIterator<>(iterator, new FilterIterator.Filter() { public boolean accept(final PSDImageResource pResource) { for (int type : resourceTypes) { if (type == pResource.id) { diff --git a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIMetadata.java b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIMetadata.java index 16e8b8b6..5e4614fd 100755 --- a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIMetadata.java +++ b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIMetadata.java @@ -28,48 +28,19 @@ package com.twelvemonkeys.imageio.plugins.sgi; -import org.w3c.dom.Node; +import com.twelvemonkeys.imageio.AbstractMetadata; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; -final class SGIMetadata extends IIOMetadata { - // TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core) - +final class SGIMetadata extends AbstractMetadata { private final SGIHeader header; SGIMetadata(final SGIHeader header) { this.header = header; - standardFormatSupported = true; } - @Override public boolean isReadOnly() { - return true; - } - - @Override public Node getAsTree(final String formatName) { - if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) { - return getStandardTree(); - } - else { - throw new IllegalArgumentException("Unsupported metadata format: " + formatName); - } - } - - @Override public void mergeTree(final String formatName, final Node root) { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - @Override public void reset() { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - @Override protected IIOMetadataNode getStandardChromaNode() { + @Override + protected IIOMetadataNode getStandardChromaNode() { IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); // NOTE: There doesn't seem to be any god way to determine color space, other than by convention @@ -117,12 +88,15 @@ final class SGIMetadata extends IIOMetadata { // No compression - @Override protected IIOMetadataNode getStandardCompressionNode() { + @Override + protected IIOMetadataNode getStandardCompressionNode() { if (header.getCompression() != SGI.COMPRESSION_NONE) { IIOMetadataNode node = new IIOMetadataNode("Compression"); IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); - compressionTypeName.setAttribute("value", header.getCompression() == SGI.COMPRESSION_RLE ? "RLE" : "Uknown"); + compressionTypeName.setAttribute("value", header.getCompression() == SGI.COMPRESSION_RLE + ? "RLE" + : "Uknown"); node.appendChild(compressionTypeName); IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); @@ -135,7 +109,8 @@ final class SGIMetadata extends IIOMetadata { return null; } - @Override protected IIOMetadataNode getStandardDataNode() { + @Override + protected IIOMetadataNode getStandardDataNode() { IIOMetadataNode node = new IIOMetadataNode("Data"); IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); @@ -183,7 +158,8 @@ final class SGIMetadata extends IIOMetadata { return buffer.toString(); } - @Override protected IIOMetadataNode getStandardDimensionNode() { + @Override + protected IIOMetadataNode getStandardDimensionNode() { IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); @@ -195,7 +171,8 @@ final class SGIMetadata extends IIOMetadata { // No document node - @Override protected IIOMetadataNode getStandardTextNode() { + @Override + protected IIOMetadataNode getStandardTextNode() { if (!header.getName().isEmpty()) { IIOMetadataNode text = new IIOMetadataNode("Text"); @@ -212,14 +189,17 @@ final class SGIMetadata extends IIOMetadata { // No tiling - @Override protected IIOMetadataNode getStandardTransparencyNode() { + @Override + protected IIOMetadataNode getStandardTransparencyNode() { // NOTE: There doesn't seem to be any god way to determine transparency, other than by convention // 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...) IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); - alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3 ? "none" : "nonpremultiplied"); + alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3 + ? "none" + : "nonpremultiplied"); transparency.appendChild(alpha); return transparency; diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadata.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadata.java index d78cf8f3..f2d8f138 100755 --- a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadata.java +++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadata.java @@ -28,50 +28,20 @@ package com.twelvemonkeys.imageio.plugins.tga; -import org.w3c.dom.Node; +import com.twelvemonkeys.imageio.AbstractMetadata; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; import java.awt.image.IndexColorModel; -final class TGAMetadata extends IIOMetadata { - // TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core) - +final class TGAMetadata extends AbstractMetadata { private final TGAHeader header; TGAMetadata(final TGAHeader header) { this.header = header; - standardFormatSupported = true; } - @Override public boolean isReadOnly() { - return true; - } - - @Override public Node getAsTree(final String formatName) { - if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) { - return getStandardTree(); - } - else { - throw new IllegalArgumentException("Unsupported metadata format: " + formatName); - } - } - - @Override public void mergeTree(final String formatName, final Node root) { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - @Override public void reset() { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - - @Override protected IIOMetadataNode getStandardChromaNode() { + @Override + protected IIOMetadataNode getStandardChromaNode() { IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); @@ -137,7 +107,8 @@ final class TGAMetadata extends IIOMetadata { return chroma; } - @Override protected IIOMetadataNode getStandardCompressionNode() { + @Override + protected IIOMetadataNode getStandardCompressionNode() { switch (header.getImageType()) { case TGA.IMAGETYPE_COLORMAPPED_RLE: case TGA.IMAGETYPE_TRUECOLOR_RLE: @@ -147,7 +118,7 @@ final class TGAMetadata extends IIOMetadata { IIOMetadataNode node = new IIOMetadataNode("Compression"); IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE - ? "Uknown" : "RLE"; + ? "Uknown" : "RLE"; compressionTypeName.setAttribute("value", value); node.appendChild(compressionTypeName); @@ -162,7 +133,8 @@ final class TGAMetadata extends IIOMetadata { } } - @Override protected IIOMetadataNode getStandardDataNode() { + @Override + protected IIOMetadataNode getStandardDataNode() { IIOMetadataNode node = new IIOMetadataNode("Data"); IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); @@ -221,7 +193,8 @@ final class TGAMetadata extends IIOMetadata { return buffer.toString(); } - @Override protected IIOMetadataNode getStandardDimensionNode() { + @Override + protected IIOMetadataNode getStandardDimensionNode() { IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); @@ -248,7 +221,8 @@ final class TGAMetadata extends IIOMetadata { // No document node - @Override protected IIOMetadataNode getStandardTextNode() { + @Override + protected IIOMetadataNode getStandardTextNode() { // TODO: Extra "developer area" and other stuff might go here... if (header.getIdentification() != null && !header.getIdentification().isEmpty()) { IIOMetadataNode text = new IIOMetadataNode("Text"); @@ -266,7 +240,8 @@ final class TGAMetadata extends IIOMetadata { // No tiling - @Override protected IIOMetadataNode getStandardTransparencyNode() { + @Override + protected IIOMetadataNode getStandardTransparencyNode() { IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java index 2fbd54e5..6d9a8cee 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java @@ -59,4 +59,13 @@ interface TIFFBaseline { int RESOLUTION_UNIT_NONE = 1; int RESOLUTION_UNIT_DPI = 2; // Default int RESOLUTION_UNIT_CENTIMETER = 3; + + int FILL_LEFT_TO_RIGHT = 1; // Default + + // NOTE: These are bit flags that can be ORed together! + int FILETYPE_REDUCEDIMAGE = 1; + int FILETYPE_PAGE = 2; + int FILETYPE_MASK = 4; + + int ORIENTATION_TOPLEFT = 1; } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java index de714606..994ffd4f 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java @@ -83,4 +83,13 @@ interface TIFFExtension { * description of the inks to be used. */ int INKSET_NOT_CMYK = 2; + + int ORIENTATION_TOPRIGHT = 2; + int ORIENTATION_BOTRIGHT = 3; + int ORIENTATION_BOTLEFT = 4; + int ORIENTATION_LEFTTOP = 5; + int ORIENTATION_RIGHTTOP = 6; + int ORIENTATION_RIGHTBOT = 7; + int ORIENTATION_LEFTBOT = 8; + } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadata.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadata.java new file mode 100644 index 00000000..5b6e44c2 --- /dev/null +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadata.java @@ -0,0 +1,824 @@ +package com.twelvemonkeys.imageio.plugins.tiff; + +import com.twelvemonkeys.imageio.AbstractMetadata; +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.Entry; +import com.twelvemonkeys.imageio.metadata.exif.TIFF; +import com.twelvemonkeys.lang.Validate; + +import javax.imageio.metadata.IIOMetadataNode; +import java.lang.reflect.Array; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; + +/** + * TIFFImageMetadata. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: TIFFImageMetadata.java,v 1.0 17/04/15 harald.kuhr Exp$ + */ +final class TIFFImageMetadata extends AbstractMetadata { + + private final Directory ifd; + + TIFFImageMetadata(final Directory ifd) { + super(true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, TIFFMedataFormat.class.getName(), null, null); + this.ifd = Validate.notNull(ifd, "IFD"); + } + + @Override + public boolean isReadOnly() { + return false; + } + + protected IIOMetadataNode getNativeTree() { + IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); + root.appendChild(asTree(ifd)); + + return root; + } + + private IIOMetadataNode asTree(final Directory ifd) { + IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD"); + + for (Entry tag : ifd) { + IIOMetadataNode tagNode; + Object value = tag.getValue(); + + if (value instanceof Directory) { + // TODO: Don't expand non-TIFF IFDs... + tagNode = asTree((Directory) value); + tagNode.setAttribute("parentTagNumber", String.valueOf(tag.getIdentifier())); + String fieldName = tag.getFieldName(); + if (fieldName != null) { + tagNode.setAttribute("parentTagName", fieldName); + } + + // TODO: tagSets is REQUIRED! + } + else { + tagNode = new IIOMetadataNode("TIFFField"); + tagNode.setAttribute("number", String.valueOf(tag.getIdentifier())); + + String fieldName = tag.getFieldName(); + if (fieldName != null) { + tagNode.setAttribute("name", fieldName); + } + + int count = tag.valueCount(); + + if (TIFF.TYPE_NAMES[TIFF.TYPE_UNDEFINED].equals(tag.getTypeName())) { + // Why does "undefined" need special handling?! It's just a byte array.. :-P + // Or maybe rather, why isn't all types implemented like this..? + // TODO: Consider handling IPTC, Photoshop/Adobe, XMP and ICC Profile as Undefined always + // (even if older software wrote as Byte), as it's more compact? + IIOMetadataNode valueNode = new IIOMetadataNode("TIFFUndefined"); + tagNode.appendChild(valueNode); + + if (count == 1) { + valueNode.setAttribute("value", String.valueOf(value)); + } + else { + valueNode.setAttribute("value", Arrays.toString((byte[]) value).replaceAll("\\[?\\]?", "")); + } + } + else { + String arrayTypeName = getMetadataArrayType(tag); + IIOMetadataNode valueNode = new IIOMetadataNode(arrayTypeName); + tagNode.appendChild(valueNode); + + boolean unsigned = !isSignedType(tag); + String typeName = getMetadataType(tag); + + // NOTE: ASCII/Strings have count 1, always. This seems consistent with the JAI ImageIO version. + if (count == 1) { + IIOMetadataNode elementNode = new IIOMetadataNode(typeName); + valueNode.appendChild(elementNode); + + setValue(value, unsigned, elementNode); + } + else { + for (int i = 0; i < count; i++) { + Object val = Array.get(value, i); + IIOMetadataNode elementNode = new IIOMetadataNode(typeName); + valueNode.appendChild(elementNode); + + setValue(val, unsigned, elementNode); + } + } + } + } + + ifdNode.appendChild(tagNode); + } + + return ifdNode; + } + + private void setValue(final Object value, final boolean unsigned, final IIOMetadataNode elementNode) { + if (unsigned && value instanceof Byte) { + elementNode.setAttribute("value", String.valueOf((Byte) value & 0xFF)); + } + else if (unsigned && value instanceof Short) { + elementNode.setAttribute("value", String.valueOf((Short) value & 0xFFFF)); + } + else if (unsigned && value instanceof Integer) { + elementNode.setAttribute("value", String.valueOf((Integer) value & 0xFFFFFFFFl)); + } + else { + elementNode.setAttribute("value", String.valueOf(value)); + } + } + + private boolean isSignedType(final Entry tag) { + String typeName = tag.getTypeName(); + + // Stupid special cases implementation, until we can access the type id... + if ("SBYTE".equals(typeName)) { + return true; + } + if ("SSHORT".equals(typeName)) { + return true; + } + if ("SLONG".equals(typeName)) { + return true; + } + if ("SRATIONAL".equals(typeName)) { + return true; + } + if ("FLOAT".equals(typeName)) { + return true; + } + if ("DOUBLE".equals(typeName)) { + return true; + } + if ("SLONG8".equals(typeName)) { + return true; + } + // IFD8 not used + + return false; + } + + private String getMetadataArrayType(final Entry tag) { + String typeName = tag.getTypeName(); + + // Stupid special cases implementation, until we can access the type id... + if ("BYTE".equals(typeName)) { + return "TIFFBytes"; + } + if ("ASCII".equals(typeName)) { + return "TIFFAsciis"; + } + if ("SHORT".equals(typeName)) { + return "TIFFShorts"; + } + if ("LONG".equals(typeName)) { + return "TIFFLongs"; + } + if ("RATIONAL".equals(typeName)) { + return "TIFFRationals"; + } + // UNDEFINED not used... + if ("SBYTE".equals(typeName)) { + return "TIFFSBytes"; + } + if ("SSHORT".equals(typeName)) { + return "TIFFSShorts"; + } + if ("SLONG".equals(typeName)) { + return "TIFFSLongs"; + } + if ("SRATIONAL".equals(typeName)) { + return "TIFFSRationals"; + } + if ("FLOAT".equals(typeName)) { + return "TIFFFloats"; + } + if ("DOUBLE".equals(typeName)) { + return "TIFFDoubles"; + } + // IFD not used + if ("LONG8".equals(typeName)) { + return "TIFFLong8s"; + } + if ("SLONG8".equals(typeName)) { + return "TIFFSLong8s"; + } + // IFD8 not used + + throw new IllegalArgumentException(typeName); + } + + private String getMetadataType(final Entry tag) { + String typeName = tag.getTypeName(); + + // Stupid special cases implementation, until we can access the type id... + if ("BYTE".equals(typeName)) { + return "TIFFByte"; + } + if ("ASCII".equals(typeName)) { + return "TIFFAscii"; + } + if ("SHORT".equals(typeName)) { + return "TIFFShort"; + } + if ("LONG".equals(typeName)) { + return "TIFFLong"; + } + if ("RATIONAL".equals(typeName)) { + return "TIFFRational"; + } + // UNDEFINED not used... + if ("SBYTE".equals(typeName)) { + return "TIFFSByte"; + } + if ("SSHORT".equals(typeName)) { + return "TIFFSShort"; + } + if ("SLONG".equals(typeName)) { + return "TIFFSLong"; + } + if ("SRATIONAL".equals(typeName)) { + return "TIFFSRational"; + } + if ("FLOAT".equals(typeName)) { + return "TIFFFloat"; + } + if ("DOUBLE".equals(typeName)) { + return "TIFFDouble"; + } + // IFD not used + if ("LONG8".equals(typeName)) { + return "TIFFLong8"; + } + if ("SLONG8".equals(typeName)) { + return "TIFFSLong8"; + } + // IFD8 not used + + throw new IllegalArgumentException(typeName); + } + + // TODO: Candidate superclass method! + private IIOMetadataNode addChildNode(final IIOMetadataNode parent, + final String name, + final Object object) { + IIOMetadataNode child = new IIOMetadataNode(name); + + if (object != null) { + child.setUserObject(object); // TODO: Should we always store user object?!?! + child.setNodeValue(object.toString()); // TODO: Fix this line + } + + parent.appendChild(child); + + return child; + } + + /// Standard metadata + // See: http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html + + @Override + protected IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); + + // Handle ColorSpaceType (RGB/CMYK/YCbCr etc)... + Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION); + int photometricValue = ((Number) photometricTag.getValue()).intValue(); // No default for this tag! + + Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL); + Entry bitsPerSampleTag = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE); + int numChannelsValue = samplesPerPixelTag != null + ? ((Number) samplesPerPixelTag.getValue()).intValue() + : bitsPerSampleTag.valueCount(); + + IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType"); + chroma.appendChild(colorSpaceType); + switch (photometricValue) { + case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO: + case TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO: + case TIFFBaseline.PHOTOMETRIC_MASK: // It's really a transparency mask/alpha channel, but... + colorSpaceType.setAttribute("value", "GRAY"); + break; + case TIFFBaseline.PHOTOMETRIC_RGB: + case TIFFBaseline.PHOTOMETRIC_PALETTE: + colorSpaceType.setAttribute("value", "RGB"); + break; + case TIFFExtension.PHOTOMETRIC_YCBCR: + colorSpaceType.setAttribute("value", "YCbCr"); + break; + case TIFFExtension.PHOTOMETRIC_CIELAB: + case TIFFExtension.PHOTOMETRIC_ICCLAB: + case TIFFExtension.PHOTOMETRIC_ITULAB: + colorSpaceType.setAttribute("value", "Lab"); + break; + case TIFFExtension.PHOTOMETRIC_SEPARATED: + // TODO: May be CMYK, or something else... Consult InkSet and NumberOfInks! + if (numChannelsValue == 3) { + colorSpaceType.setAttribute("value", "CMY"); + } + else { + colorSpaceType.setAttribute("value", "CMYK"); + } + break; + case TIFFCustom.PHOTOMETRIC_LOGL: // ..? + case TIFFCustom.PHOTOMETRIC_LOGLUV: + colorSpaceType.setAttribute("value", "Luv"); + break; + case TIFFCustom.PHOTOMETRIC_CFA: + case TIFFCustom.PHOTOMETRIC_LINEAR_RAW: // ...or is this RGB? + colorSpaceType.setAttribute("value", "3CLR"); + break; + default: + colorSpaceType.setAttribute("value", Integer.toHexString(numChannelsValue) + "CLR"); + break; + } + + // NumChannels + IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); + chroma.appendChild(numChannels); + if (photometricValue == TIFFBaseline.PHOTOMETRIC_PALETTE) { + numChannels.setAttribute("value", "3"); + } + else { + numChannels.setAttribute("value", Integer.toString(numChannelsValue)); + } + + // BlackIsZero (defaults to TRUE) + IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); + chroma.appendChild(blackIsZero); + switch (photometricValue) { + case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO: + blackIsZero.setAttribute("value", "FALSE"); + break; + default: + break; + } + + Entry colorMapTag = ifd.getEntryById(TIFF.TAG_COLOR_MAP); + + if (colorMapTag != null) { + int[] colorMapValues = (int[]) colorMapTag.getValue(); + + IIOMetadataNode palette = new IIOMetadataNode("Palette"); + chroma.appendChild(palette); + + int count = colorMapValues.length / 3; + for (int i = 0; i < count; i++) { + IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry"); + paletteEntry.setAttribute("index", Integer.toString(i)); + + // TODO: See TIFFImageReader createIndexColorModel, to detect 8 bit colorMap + paletteEntry.setAttribute("red", Integer.toString((colorMapValues[i] >> 8) & 0xff)); + paletteEntry.setAttribute("green", Integer.toString((colorMapValues[i + count] >> 8) & 0xff)); + paletteEntry.setAttribute("blue", Integer.toString((colorMapValues[i + count * 2] >> 8) & 0xff)); + + palette.appendChild(paletteEntry); + } + } + + return chroma; + } + + @Override + protected IIOMetadataNode getStandardCompressionNode() { + IIOMetadataNode compression = new IIOMetadataNode("Compression"); + IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null); + + Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION); + int compressionValue = compressionTag == null + ? TIFFBaseline.COMPRESSION_NONE + : ((Number) compressionTag.getValue()).intValue(); + + // Naming is identical to JAI ImageIO metadata as far as possible + switch (compressionValue) { + case TIFFBaseline.COMPRESSION_NONE: + compressionTypeName.setAttribute("value", "None"); + break; + case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: + compressionTypeName.setAttribute("value", "CCITT RLE"); + break; + case TIFFExtension.COMPRESSION_CCITT_T4: + compressionTypeName.setAttribute("value", "CCITT T4"); + break; + case TIFFExtension.COMPRESSION_CCITT_T6: + compressionTypeName.setAttribute("value", "CCITT T6"); + break; + case TIFFExtension.COMPRESSION_LZW: + compressionTypeName.setAttribute("value", "LZW"); + break; + case TIFFExtension.COMPRESSION_OLD_JPEG: + compressionTypeName.setAttribute("value", "Old JPEG"); + break; + case TIFFExtension.COMPRESSION_JPEG: + compressionTypeName.setAttribute("value", "JPEG"); + break; + case TIFFExtension.COMPRESSION_ZLIB: + compressionTypeName.setAttribute("value", "ZLib"); + break; + case TIFFExtension.COMPRESSION_DEFLATE: + compressionTypeName.setAttribute("value", "Deflate"); + break; + case TIFFBaseline.COMPRESSION_PACKBITS: + compressionTypeName.setAttribute("value", "PackBits"); + break; + case TIFFCustom.COMPRESSION_CCITTRLEW: + compressionTypeName.setAttribute("value", "CCITT RLEW"); + break; + case TIFFCustom.COMPRESSION_DCS: + compressionTypeName.setAttribute("value", "DCS"); + break; + case TIFFCustom.COMPRESSION_IT8BL: + compressionTypeName.setAttribute("value", "IT8BL"); + break; + case TIFFCustom.COMPRESSION_IT8CTPAD: + compressionTypeName.setAttribute("value", "IT8CTPAD"); + break; + case TIFFCustom.COMPRESSION_IT8LW: + compressionTypeName.setAttribute("value", "IT8LW"); + break; + case TIFFCustom.COMPRESSION_IT8MP: + compressionTypeName.setAttribute("value", "IT8MP"); + break; + case TIFFCustom.COMPRESSION_JBIG: + compressionTypeName.setAttribute("value", "JBIG"); + break; + case TIFFCustom.COMPRESSION_JPEG2000: + compressionTypeName.setAttribute("value", "JPEG 2000"); + break; + case TIFFCustom.COMPRESSION_NEXT: + compressionTypeName.setAttribute("value", "NEXT"); + break; + case TIFFCustom.COMPRESSION_PIXARFILM: + compressionTypeName.setAttribute("value", "Pixar Film"); + break; + case TIFFCustom.COMPRESSION_PIXARLOG: + compressionTypeName.setAttribute("value", "Pixar Log"); + break; + case TIFFCustom.COMPRESSION_SGILOG: + compressionTypeName.setAttribute("value", "SGI Log"); + break; + case TIFFCustom.COMPRESSION_SGILOG24: + compressionTypeName.setAttribute("value", "SGI Log24"); + break; + case TIFFCustom.COMPRESSION_THUNDERSCAN: + compressionTypeName.setAttribute("value", "ThunderScan"); + break; + default: + compressionTypeName.setAttribute("value", "Unknown " + compressionValue); + break; + } + + if (compressionValue != TIFFBaseline.COMPRESSION_NONE) { + // Lossless (defaults to TRUE) + IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); + compression.appendChild(lossless); + + switch (compressionValue) { + case TIFFExtension.COMPRESSION_OLD_JPEG: + case TIFFExtension.COMPRESSION_JPEG: + case TIFFCustom.COMPRESSION_JBIG: + case TIFFCustom.COMPRESSION_JPEG2000: + lossless.setAttribute("value", "FALSE"); + break; + default: + break; + } + } + + return compression; + } + + @Override + protected IIOMetadataNode getStandardDataNode() { + IIOMetadataNode node = new IIOMetadataNode("Data"); + + IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); + Entry planarConfigurationTag = ifd.getEntryById(TIFF.TAG_PLANAR_CONFIGURATION); + int planarConfigurationValue = planarConfigurationTag == null + ? TIFFBaseline.PLANARCONFIG_CHUNKY + : ((Number) planarConfigurationTag.getValue()).intValue(); + + switch (planarConfigurationValue) { + case TIFFBaseline.PLANARCONFIG_CHUNKY: + planarConfiguration.setAttribute("value", "PixelInterleaved"); + break; + case TIFFExtension.PLANARCONFIG_PLANAR: + planarConfiguration.setAttribute("value", "PlaneInterleaved"); + break; + default: + planarConfiguration.setAttribute("value", "Unknown " + planarConfigurationValue); + } + node.appendChild(planarConfiguration); + + Entry photometricInterpretationTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION); + int photometricInterpretationValue = photometricInterpretationTag == null + ? TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO + : ((Number) photometricInterpretationTag.getValue()).intValue(); + + Entry samleFormatTag = ifd.getEntryById(TIFF.TAG_SAMPLE_FORMAT); + int sampleFormatValue = samleFormatTag == null + ? TIFFBaseline.SAMPLEFORMAT_UINT + : ((Number) samleFormatTag.getValue()).intValue(); + IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); + node.appendChild(sampleFormat); + switch (sampleFormatValue) { + case TIFFBaseline.SAMPLEFORMAT_UINT: + if (photometricInterpretationValue == TIFFBaseline.PHOTOMETRIC_PALETTE) { + sampleFormat.setAttribute("value", "Index"); + } + else { + sampleFormat.setAttribute("value", "UnsignedIntegral"); + } + break; + case TIFFExtension.SAMPLEFORMAT_INT: + sampleFormat.setAttribute("value", "SignedIntegral"); + break; + case TIFFExtension.SAMPLEFORMAT_FP: + sampleFormat.setAttribute("value", "Real"); + break; + default: + sampleFormat.setAttribute("value", "Unknown " + sampleFormatValue); + break; + } + + // TODO: See TIFFImageReader.getBitsPerSample + fix the metadata to have getAsXxxArray methods. + // BitsPerSample (not required field for Class B/Bilevel, defaults to 1) + Entry bitsPerSampleTag = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE); + String bitsPerSampleValue = bitsPerSampleTag == null && + (photometricInterpretationValue == TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO || + photometricInterpretationValue == TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO) + ? "1" + : bitsPerSampleTag.getValueAsString().replaceAll("\\[?\\]?,?", ""); + + IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); + node.appendChild(bitsPerSample); + bitsPerSample.setAttribute("value", bitsPerSampleValue); + + Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL); + int numChannelsValue = samplesPerPixelTag != null + ? ((Number) samplesPerPixelTag.getValue()).intValue() + : bitsPerSampleTag.valueCount(); + + // SampleMSB + Entry fillOrderTag = ifd.getEntryById(TIFF.TAG_FILL_ORDER); + int fillOrder = fillOrderTag != null + ? ((Number) fillOrderTag.getValue()).intValue() + : TIFFBaseline.FILL_LEFT_TO_RIGHT; + IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB"); + node.appendChild(sampleMSB); + if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) { + sampleMSB.setAttribute("value", createListValue(numChannelsValue, "0")); + } + else { + if ("1".equals(bitsPerSampleValue)) { + sampleMSB.setAttribute("value", createListValue(numChannelsValue, "7")); + } + else { + // TODO: FixMe for bitsPerSample > 8 + sampleMSB.setAttribute("value", createListValue(numChannelsValue, "7")); + } + } + + return node; + } + + // TODO: Candidate superclass method! + private String createListValue(final int itemCount, final String... values) { + StringBuilder buffer = new StringBuilder(); + + for (int i = 0; i < itemCount; i++) { + if (buffer.length() > 0) { + buffer.append(' '); + } + + buffer.append(values[i % values.length]); + } + + return buffer.toString(); + } + + @Override + protected IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); + + // PixelAspectRatio + Entry xResTag = ifd.getEntryById(TIFF.TAG_X_RESOLUTION); + Entry yResTag = ifd.getEntryById(TIFF.TAG_Y_RESOLUTION); + double xSizeValue = 1 / (xResTag == null ? 72.0 : ((Number) xResTag.getValue()).doubleValue()); + double ySizeValue = 1 / (xResTag == null ? 72.0 : ((Number) yResTag.getValue()).doubleValue()); + + IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio"); + dimension.appendChild(pixelAspectRatio); + pixelAspectRatio.setAttribute("value", String.valueOf(xSizeValue / ySizeValue)); + + // ImageOrientation + Entry orientationTag = ifd.getEntryById(TIFF.TAG_ORIENTATION); + if (orientationTag != null) { + int orientationValue = ((Number) orientationTag.getValue()).intValue(); + + String value = null; + switch (orientationValue) { + case TIFFBaseline.ORIENTATION_TOPLEFT: + value = "Normal"; + break; + case TIFFExtension.ORIENTATION_TOPRIGHT: + value = "FlipH"; + break; + case TIFFExtension.ORIENTATION_BOTRIGHT: + value = "Rotate180"; + break; + case TIFFExtension.ORIENTATION_BOTLEFT: + value = "FlipV"; + break; + case TIFFExtension.ORIENTATION_LEFTTOP: + value = "FlipHRotate90"; + break; + case TIFFExtension.ORIENTATION_RIGHTTOP: + value = "Rotate270"; + break; + case TIFFExtension.ORIENTATION_RIGHTBOT: + value = "FlipVRotate90"; + break; + case TIFFExtension.ORIENTATION_LEFTBOT: + value = "Rotate90"; + break; + } + + if (value != null) { + IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); + dimension.appendChild(imageOrientation); + imageOrientation.setAttribute("value", value); + } + + } + + Entry resUnitTag = ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT); + int resUnitValue = resUnitTag == null ? TIFFBaseline.RESOLUTION_UNIT_DPI : ((Number) resUnitTag.getValue()).intValue(); + if (resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER || resUnitValue == TIFFBaseline.RESOLUTION_UNIT_DPI) { + // 10 mm in 1 cm or 25.4 mm in 1 inch + double scale = resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER ? 10 : 25.4; + + // HorizontalPixelSize + // VerticalPixelSize + IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize"); + dimension.appendChild(horizontalPixelSize); + horizontalPixelSize.setAttribute("value", String.valueOf(xSizeValue * scale)); + + IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize"); + dimension.appendChild(verticalPixelSize); + verticalPixelSize.setAttribute("value", String.valueOf(ySizeValue * scale)); + + // HorizontalPosition + // VerticalPosition + Entry xPosTag = ifd.getEntryById(TIFF.TAG_X_POSITION); + Entry yPosTag = ifd.getEntryById(TIFF.TAG_Y_POSITION); + + if (xPosTag != null && yPosTag != null) { + double xPosValue = ((Number) xPosTag.getValue()).doubleValue(); + double yPosValue = ((Number) yPosTag.getValue()).doubleValue(); + + IIOMetadataNode horizontalPosition = new IIOMetadataNode("HorizontalPosition"); + dimension.appendChild(horizontalPosition); + horizontalPosition.setAttribute("value", String.valueOf(xPosValue * scale)); + + IIOMetadataNode verticalPosition = new IIOMetadataNode("VerticalPosition"); + dimension.appendChild(verticalPosition); + verticalPosition.setAttribute("value", String.valueOf(yPosValue * scale)); + } + } + + return dimension; + } + + @Override + protected IIOMetadataNode getStandardTransparencyNode() { + // Consult ExtraSamples + Entry extraSamplesTag = ifd.getEntryById(TIFF.TAG_EXTRA_SAMPLES); + + if (extraSamplesTag != null) { + int extraSamplesValue = (extraSamplesTag.getValue() instanceof Number) + ? ((Number) extraSamplesTag.getValue()).intValue() + : ((Number) Array.get(extraSamplesTag.getValue(), 0)).intValue(); + + // Other values exists, these are not alpha + if (extraSamplesValue == TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA || extraSamplesValue == TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA) { + IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); + IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); + transparency.appendChild(alpha); + + alpha.setAttribute("value", extraSamplesValue == TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA + ? "premultiplied" + : "nonpremultiplied"); + + return transparency; + } + } + + return null; + + } + + @Override + protected IIOMetadataNode getStandardDocumentNode() { + IIOMetadataNode document = new IIOMetadataNode("Document"); + + // FormatVersion, hardcoded to 6.0 (the current TIFF specification version), + // as there's no format information in the TIFF structure. + IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion"); + document.appendChild(formatVersion); + formatVersion.setAttribute("value", "6.0"); + + // SubImageInterpretation from SubImageInterpretation (if applicable) + Entry subFileTypeTag = ifd.getEntryById(TIFF.TAG_SUBFILE_TYPE); + if (subFileTypeTag != null) { + // NOTE: The JAI metadata is somewhat broken here, as these are bit flags, not values... + String value = null; + int subFileTypeValue = ((Number) subFileTypeTag.getValue()).intValue(); + if ((subFileTypeValue & TIFFBaseline.FILETYPE_MASK) != 0) { + value = "TransparencyMask"; + } + else if ((subFileTypeValue & TIFFBaseline.FILETYPE_REDUCEDIMAGE) != 0) { + value = "ReducedResolution"; + } + else if ((subFileTypeValue & TIFFBaseline.FILETYPE_PAGE) != 0) { + value = "SinglePage"; + } + + // If no flag is set, we don't know... + if (value != null) { + IIOMetadataNode subImageInterpretation = new IIOMetadataNode("SubImageInterpretation"); + document.appendChild(subImageInterpretation); + subImageInterpretation.setAttribute("value", value); + } + } + + // ImageCreationTime from DateTime + Entry dateTimeTag = ifd.getEntryById(TIFF.TAG_DATE_TIME); + if (dateTimeTag != null) { + DateFormat format = new SimpleDateFormat("yyyy:MM:dd hh:mm:ss"); + + try { + IIOMetadataNode imageCreationTime = new IIOMetadataNode("ImageCreationTime"); + document.appendChild(imageCreationTime); + + Calendar date = Calendar.getInstance(); + date.setTime(format.parse(dateTimeTag.getValueAsString())); + + imageCreationTime.setAttribute("year", String.valueOf(date.get(Calendar.YEAR))); + imageCreationTime.setAttribute("month", String.valueOf(date.get(Calendar.MONTH) + 1)); + imageCreationTime.setAttribute("day", String.valueOf(date.get(Calendar.DAY_OF_MONTH))); + imageCreationTime.setAttribute("hour", String.valueOf(date.get(Calendar.HOUR_OF_DAY))); + imageCreationTime.setAttribute("minute", String.valueOf(date.get(Calendar.MINUTE))); + imageCreationTime.setAttribute("second", String.valueOf(date.get(Calendar.SECOND))); + } + catch (ParseException ignore) { + // Bad format... + } + } + + return document; + } + + @Override + protected IIOMetadataNode getStandardTextNode() { + IIOMetadataNode text = new IIOMetadataNode("Text"); + + // DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright: + // /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value. + addTextEntryIfPresent(text, TIFF.TAG_DOCUMENT_NAME); + addTextEntryIfPresent(text, TIFF.TAG_IMAGE_DESCRIPTION); + addTextEntryIfPresent(text, TIFF.TAG_MAKE); + addTextEntryIfPresent(text, TIFF.TAG_MODEL); + addTextEntryIfPresent(text, TIFF.TAG_SOFTWARE); + addTextEntryIfPresent(text, TIFF.TAG_ARTIST); + addTextEntryIfPresent(text, TIFF.TAG_HOST_COMPUTER); + addTextEntryIfPresent(text, TIFF.TAG_INK_NAMES); + addTextEntryIfPresent(text, TIFF.TAG_COPYRIGHT); + + return text.hasChildNodes() ? text : null; + } + + private void addTextEntryIfPresent(final IIOMetadataNode text, final int tag) { + Entry entry = ifd.getEntryById(tag); + if (entry != null) { + IIOMetadataNode node = new IIOMetadataNode("TextEntry"); + text.appendChild(node); + node.setAttribute("keyword", entry.getFieldName()); + node.setAttribute("value", entry.getValueAsString()); + } + } + + @Override + protected IIOMetadataNode getStandardTileNode() { + // TODO! Woot?! This node is not documented in the DTD (although the page mentions a "tile" node)..? + // See http://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html + // See http://stackoverflow.com/questions/30910719/javax-imageio-1-0-standard-plug-in-neutral-metadata-format-tiling-information + return super.getStandardTileNode(); + } +} diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 46769901..cd9a0564 100755 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -243,6 +243,7 @@ public class TIFFImageReader extends ImageReaderBase { int bitsPerSample = getBitsPerSample(); int dataType = getDataType(sampleFormat, bitsPerSample); + // TODO: Validate CS using ColorSpaces.validateProfile // Read embedded cs ICC_Profile profile = getICCProfile(); ColorSpace cs; @@ -503,7 +504,7 @@ public class TIFFImageReader extends ImageReaderBase { readIFD(imageIndex); ImageTypeSpecifier rawType = getRawImageType(imageIndex); - Set specs = new LinkedHashSet(5); + Set specs = new LinkedHashSet<>(5); // TODO: Based on raw type, we can probably convert to most RGB types at least, maybe gray etc // TODO: Planar to chunky by default @@ -1354,6 +1355,7 @@ public class TIFFImageReader extends ImageReaderBase { private long[] getValueAsLongArray(final int tag, final String tagName, boolean required) throws IIOException { Entry entry = currentIFD.getEntryById(tag); + if (entry == null) { if (required) { throw new IIOException("Missing TIFF tag " + tagName); @@ -1413,6 +1415,21 @@ public class TIFFImageReader extends ImageReaderBase { // TODO: Thumbnail support + /// Metadata + + @Override + public IIOMetadata getImageMetadata(int imageIndex) throws IOException { + readIFD(imageIndex); + + return new TIFFImageMetadata(currentIFD); + } + + @Override + public IIOMetadata getStreamMetadata() throws IOException { + // TODO: + return super.getStreamMetadata(); + } + public static void main(final String[] args) throws IOException { ImageIO.setUseCache(false); @@ -1500,7 +1517,7 @@ public class TIFFImageReader extends ImageReaderBase { if (metadata.getNativeMetadataFormatName() != null) { new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(metadata.getNativeMetadataFormatName()), false); } - else if (metadata.isStandardMetadataFormatSupported()) { + /*else*/ if (metadata.isStandardMetadataFormatSupported()) { new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false); } } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java index 02f470d1..c57ea06a 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java @@ -50,8 +50,8 @@ final class TIFFProviderInfo extends ReaderWriterProviderInfo { new String[] {"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi"}, "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter", new String[] {"com.twelvemkonkeys.imageio.plugins.tif.TIFFImageWriterSpi"}, - false, null, null, null, null, - true, null, null, null, null + false, TIFFMedataFormat.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadata", null, null, + true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, "TODO", null, null ); } }