TMI-139: Work in progress: TIFF image metadata.

This commit is contained in:
Harald Kuhr 2015-06-22 11:11:37 +02:00
parent 7e65164b87
commit f4cc310096
18 changed files with 1041 additions and 265 deletions

View File

@ -28,17 +28,16 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.AbstractMetadata;
import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.lang.Validate;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
/** /**
* BMPMetadata. * 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. */ /** 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"; public static final String nativeMetadataFormatName = "javax_imageio_bmp_1.0";
@ -46,46 +45,13 @@ final class BMPMetadata extends IIOMetadata {
private final int[] colorMap; private final int[] colorMap;
BMPMetadata(final DIBHeader header, 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.header = Validate.notNull(header, "header");
this.colorMap = colorMap == null || colorMap.length == 0 ? null : colorMap; 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 @Override
public String getNativeMetadataFormatName() { protected Node getNativeTree() {
return nativeMetadataFormatName;
}
private Node getNativeTree() {
IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
addChildNode(root, "BMPVersion", header.getBMPVersion()); addChildNode(root, "BMPVersion", header.getBMPVersion());
@ -170,7 +136,8 @@ final class BMPMetadata extends IIOMetadata {
return child; return child;
} }
@Override protected IIOMetadataNode getStandardChromaNode() { @Override
protected IIOMetadataNode getStandardChromaNode() {
// NOTE: BMP files may contain a color map, even if true color... // 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, // Not sure if this is a good idea to expose to the meta data,
// as it might be unexpected... Then again... // as it might be unexpected... Then again...
@ -197,7 +164,8 @@ final class BMPMetadata extends IIOMetadata {
return null; return null;
} }
@Override protected IIOMetadataNode getStandardCompressionNode() { @Override
protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode compression = new IIOMetadataNode("Compression"); IIOMetadataNode compression = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null); IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null);
compressionTypeName.setAttribute("value", "NONE"); 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 node = new IIOMetadataNode("Data");
// IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); // IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
@ -294,7 +263,8 @@ final class BMPMetadata extends IIOMetadata {
return buffer.toString(); return buffer.toString();
} }
@Override protected IIOMetadataNode getStandardDimensionNode() { @Override
protected IIOMetadataNode getStandardDimensionNode() {
if (header.xPixelsPerMeter > 0 || header.yPixelsPerMeter > 0) { if (header.xPixelsPerMeter > 0 || header.yPixelsPerMeter > 0) {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
@ -302,16 +272,16 @@ final class BMPMetadata extends IIOMetadata {
addChildNode(dimension, "HorizontalPhysicalPixelSpacing", null); addChildNode(dimension, "HorizontalPhysicalPixelSpacing", null);
addChildNode(dimension, "VerticalPhysicalPixelSpacing", null); addChildNode(dimension, "VerticalPhysicalPixelSpacing", null);
// IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); // IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
// //
// if (header.topDown) { // if (header.topDown) {
// imageOrientation.setAttribute("value", "FlipH"); // imageOrientation.setAttribute("value", "FlipH");
// } // }
// else { // else {
// imageOrientation.setAttribute("value", "Normal"); // imageOrientation.setAttribute("value", "Normal");
// } // }
// //
// dimension.appendChild(imageOrientation); // dimension.appendChild(imageOrientation);
return dimension; return dimension;
} }
@ -325,7 +295,8 @@ final class BMPMetadata extends IIOMetadata {
// No tiling // No tiling
@Override protected IIOMetadataNode getStandardTransparencyNode() { @Override
protected IIOMetadataNode getStandardTransparencyNode() {
return null; return null;
// IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); // IIOMetadataNode transparency = new IIOMetadataNode("Transparency");

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * 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; import org.w3c.dom.Node;
@ -42,13 +42,15 @@ import java.util.Arrays;
* @author last modified by $Author: haraldk$ * @author last modified by $Author: haraldk$
* @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$ * @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$
*/ */
abstract class AbstractMetadata extends IIOMetadata implements Cloneable { public abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
// TODO: Move to core... 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, protected AbstractMetadata() {
final String pNativeFormatName, final String pNativeFormatClassName, super(true, null, null, null, null);
final String[] pExtraFormatNames, final String[] pExtraFormatClassNames) {
super(pStandardFormatSupported, pNativeFormatName, pNativeFormatClassName, pExtraFormatNames, pExtraFormatClassNames);
} }
/** /**
@ -63,31 +65,42 @@ abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
} }
@Override @Override
public Node getAsTree(final String pFormatName) { public Node getAsTree(final String formatName) {
validateFormatName(pFormatName); validateFormatName(formatName);
if (pFormatName.equals(nativeMetadataFormatName)) { if (formatName.equals(nativeMetadataFormatName)) {
return getNativeTree(); return getNativeTree();
} }
else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { else if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
return getStandardTree(); return getStandardTree();
} }
// TODO: What about extra formats?? // Subclasses that supports extra formats need to check for these formats themselves...
throw new AssertionError("Unreachable"); 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 @Override
public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException { public void mergeTree(final String formatName, final Node root) throws IIOInvalidTreeException {
assertMutable(); assertMutable();
validateFormatName(pFormatName); validateFormatName(formatName);
if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) { if (!root.getNodeName().equals(formatName)) {
throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot); 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) { while (node != null) {
// TODO: Merge values from node into this // 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 formatName) {
protected final void validateFormatName(final String pFormatName) {
String[] metadataFormatNames = getMetadataFormatNames(); String[] metadataFormatNames = getMetadataFormatNames();
if (metadataFormatNames != null) { if (metadataFormatNames != null) {
for (String metadataFormatName : metadataFormatNames) { for (String metadataFormatName : metadataFormatNames) {
if (metadataFormatName.equals(pFormatName)) { if (metadataFormatName.equals(formatName)) {
return; // Found, we're ok! return; // Found, we're ok!
} }
} }
} }
throw new IllegalArgumentException( 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))
); );
} }

View File

@ -64,4 +64,8 @@ public interface Entry {
// For arrays only // For arrays only
int valueCount(); 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)
} }

View File

@ -43,7 +43,7 @@ final class EXIFEntry extends AbstractEntry {
EXIFEntry(final int identifier, final Object value, final short type) { EXIFEntry(final int identifier, final Object value, final short type) {
super(identifier, value); 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)); throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", type));
} }
@ -86,8 +86,16 @@ final class EXIFEntry extends AbstractEntry {
return "Compression"; return "Compression";
case TIFF.TAG_PHOTOMETRIC_INTERPRETATION: case TIFF.TAG_PHOTOMETRIC_INTERPRETATION:
return "PhotometricInterpretation"; return "PhotometricInterpretation";
case TIFF.TAG_FILL_ORDER:
return "FillOrder";
case TIFF.TAG_DOCUMENT_NAME:
return "DocumentName";
case TIFF.TAG_IMAGE_DESCRIPTION: case TIFF.TAG_IMAGE_DESCRIPTION:
return "ImageDescription"; return "ImageDescription";
case TIFF.TAG_MAKE:
return "Make";
case TIFF.TAG_MODEL:
return "Model";
case TIFF.TAG_STRIP_OFFSETS: case TIFF.TAG_STRIP_OFFSETS:
return "StripOffsets"; return "StripOffsets";
case TIFF.TAG_ORIENTATION: case TIFF.TAG_ORIENTATION:
@ -106,14 +114,8 @@ final class EXIFEntry extends AbstractEntry {
return "PlanarConfiguration"; return "PlanarConfiguration";
case TIFF.TAG_RESOLUTION_UNIT: case TIFF.TAG_RESOLUTION_UNIT:
return "ResolutionUnit"; return "ResolutionUnit";
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT: case TIFF.TAG_PAGE_NUMBER:
return "JPEGInterchangeFormat"; return "PageNumber";
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
return "JPEGInterchangeFormatLength";
case TIFF.TAG_MAKE:
return "Make";
case TIFF.TAG_MODEL:
return "Model";
case TIFF.TAG_SOFTWARE: case TIFF.TAG_SOFTWARE:
return "Software"; return "Software";
case TIFF.TAG_DATE_TIME: case TIFF.TAG_DATE_TIME:
@ -140,10 +142,20 @@ final class EXIFEntry extends AbstractEntry {
return "YCbCrPositioning"; return "YCbCrPositioning";
case TIFF.TAG_COLOR_MAP: case TIFF.TAG_COLOR_MAP:
return "ColorMap"; return "ColorMap";
case TIFF.TAG_INK_SET:
return "InkSet";
case TIFF.TAG_INK_NAMES:
return "InkNames";
case TIFF.TAG_EXTRA_SAMPLES: case TIFF.TAG_EXTRA_SAMPLES:
return "ExtraSamples"; return "ExtraSamples";
case TIFF.TAG_SAMPLE_FORMAT: case TIFF.TAG_SAMPLE_FORMAT:
return "SampleFormat"; 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: case TIFF.TAG_SUB_IFD:
return "SubIFD"; return "SubIFD";
@ -261,6 +273,6 @@ final class EXIFEntry extends AbstractEntry {
@Override @Override
public String getTypeName() { public String getTypeName() {
return TIFF.TYPE_NAMES[type - 1]; return TIFF.TYPE_NAMES[type];
} }
} }

View File

@ -446,8 +446,8 @@ public final class EXIFReader extends MetadataReader {
} }
static int getValueLength(final int pType, final int pCount) { static int getValueLength(final int pType, final int pCount) {
if (pType > 0 && pType <= TIFF.TYPE_LENGTHS.length) { if (pType > 0 && pType < TIFF.TYPE_LENGTHS.length) {
return TIFF.TYPE_LENGTHS[pType - 1] * pCount; return TIFF.TYPE_LENGTHS[pType] * pCount;
} }
return -1; return -1;

View File

@ -94,11 +94,11 @@ public final class EXIFWriter extends MetadataWriter {
stream.writeShort(42); stream.writeShort(42);
} }
public long writeIFD(final Collection<Entry> entries, ImageOutputStream stream) throws IOException { public long writeIFD(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
return writeIFD(new IFD(entries), stream, false); 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 // TIFF spec says tags should be in increasing order, enforce that when writing
Directory ordered = ensureOrderedDirectory(original); Directory ordered = ensureOrderedDirectory(original);
@ -183,7 +183,7 @@ public final class EXIFWriter extends MetadataWriter {
private Directory ensureOrderedDirectory(final Directory directory) { private Directory ensureOrderedDirectory(final Directory directory) {
if (!isSorted(directory)) { if (!isSorted(directory)) {
List<Entry> entries = new ArrayList<Entry>(directory.size()); List<Entry> entries = new ArrayList<>(directory.size());
for (Entry entry : directory) { for (Entry entry : directory) {
entries.add(entry); entries.add(entry);
@ -217,7 +217,7 @@ public final class EXIFWriter extends MetadataWriter {
return true; 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); short type = getType(entry);
int valueLength = EXIFReader.getValueLength(type, getCount(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(); Object value = entry.getValue();
return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount(); 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()) { if (value.getClass().isArray()) {
switch (type) { switch (type) {
case TIFF.TYPE_UNDEFINED:
case TIFF.TYPE_BYTE: case TIFF.TYPE_BYTE:
stream.write((byte[]) value); stream.write((byte[]) value);
break; break;
@ -293,7 +294,7 @@ public final class EXIFWriter extends MetadataWriter {
} }
} }
else { 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); stream.writeInts(ints, 0, ints.length);
@ -318,6 +319,7 @@ public final class EXIFWriter extends MetadataWriter {
// } // }
else { else {
switch (type) { switch (type) {
case TIFF.TYPE_UNDEFINED:
case TIFF.TYPE_BYTE: case TIFF.TYPE_BYTE:
stream.writeByte((Integer) value); stream.writeByte((Integer) value);
break; 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)); stream.writeInt(assertIntegerOffset(dataOffset));
long position = stream.getStreamPosition(); long position = stream.getStreamPosition();
stream.seek(dataOffset); stream.seek(dataOffset);
@ -353,7 +355,7 @@ public final class EXIFWriter extends MetadataWriter {
stream.seek(position); stream.seek(position);
} }
private short getType(Entry entry) { private short getType(final Entry entry) {
if (entry instanceof EXIFEntry) { if (entry instanceof EXIFEntry) {
EXIFEntry exifEntry = (EXIFEntry) entry; EXIFEntry exifEntry = (EXIFEntry) entry;
return exifEntry.getType(); return exifEntry.getType();

View File

@ -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???) Should probably all map to Java long (and fail if high bit is set for the unsigned types???)
*/ */
String[] TYPE_NAMES = { String[] TYPE_NAMES = {
null,
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
"IFD", "IFD",
@ -95,6 +96,7 @@ public interface TIFF {
"LONG8", "SLONG8", "IFD8" "LONG8", "SLONG8", "IFD8"
}; };
int[] TYPE_LENGTHS = { int[] TYPE_LENGTHS = {
-1,
1, 1, 2, 4, 8, 1, 1, 2, 4, 8,
1, 1, 2, 4, 8, 4, 8, 1, 1, 2, 4, 8, 4, 8,
4, 4,
@ -124,6 +126,8 @@ public interface TIFF {
int TAG_YCBCR_POSITIONING = 531; int TAG_YCBCR_POSITIONING = 531;
int TAG_X_RESOLUTION = 282; int TAG_X_RESOLUTION = 282;
int TAG_Y_RESOLUTION = 283; int TAG_Y_RESOLUTION = 283;
int TAG_X_POSITION = 286;
int TAG_Y_POSITION = 287;
int TAG_RESOLUTION_UNIT = 296; int TAG_RESOLUTION_UNIT = 296;
/// B. Tags relating to recording offset /// B. Tags relating to recording offset
@ -131,6 +135,7 @@ public interface TIFF {
int TAG_STRIP_OFFSETS = 273; int TAG_STRIP_OFFSETS = 273;
int TAG_ROWS_PER_STRIP = 278; int TAG_ROWS_PER_STRIP = 278;
int TAG_STRIP_BYTE_COUNTS = 279; int TAG_STRIP_BYTE_COUNTS = 279;
int TAG_FREE_OFFSETS = 288; // "Not recommended for general interchange."
// "Old-style" JPEG (still used as EXIF thumbnail) // "Old-style" JPEG (still used as EXIF thumbnail)
int TAG_JPEG_INTERCHANGE_FORMAT = 513; int TAG_JPEG_INTERCHANGE_FORMAT = 513;
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514; int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
@ -153,6 +158,7 @@ public interface TIFF {
/// D. Other tags /// D. Other tags
int TAG_DATE_TIME = 306; int TAG_DATE_TIME = 306;
int TAG_DOCUMENT_NAME = 269;
int TAG_IMAGE_DESCRIPTION = 270; int TAG_IMAGE_DESCRIPTION = 270;
int TAG_MAKE = 271; int TAG_MAKE = 271;
int TAG_MODEL = 272; int TAG_MODEL = 272;

View File

@ -71,6 +71,7 @@ public final class XMPReader extends MetadataReader {
// TODO: Consider parsing using SAX? // TODO: Consider parsing using SAX?
// TODO: Determine encoding and parse using a Reader... // TODO: Determine encoding and parse using a Reader...
// TODO: Refactor scanner to return inputstream? // 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(); DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input))); Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input)));

View File

@ -28,52 +28,22 @@
package com.twelvemonkeys.imageio.plugins.pcx; 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 javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.IndexColorModel; import java.awt.image.IndexColorModel;
final class PCXMetadata extends IIOMetadata { final class PCXMetadata extends AbstractMetadata {
// TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
private final PCXHeader header; private final PCXHeader header;
private final IndexColorModel vgaPalette; private final IndexColorModel vgaPalette;
PCXMetadata(final PCXHeader header, final IndexColorModel vgaPalette) { PCXMetadata(final PCXHeader header, final IndexColorModel vgaPalette) {
this.header = header; this.header = header;
this.vgaPalette = vgaPalette; this.vgaPalette = vgaPalette;
standardFormatSupported = true;
} }
@Override public boolean isReadOnly() { @Override
return true; protected IIOMetadataNode getStandardChromaNode() {
}
@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() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IndexColorModel palette = null; IndexColorModel palette = null;
@ -141,7 +111,8 @@ final class PCXMetadata extends IIOMetadata {
// No compression // No compression
@Override protected IIOMetadataNode getStandardCompressionNode() { @Override
protected IIOMetadataNode getStandardCompressionNode() {
if (header.getCompression() != PCX.COMPRESSION_NONE) { if (header.getCompression() != PCX.COMPRESSION_NONE) {
IIOMetadataNode node = new IIOMetadataNode("Compression"); IIOMetadataNode node = new IIOMetadataNode("Compression");
@ -159,7 +130,8 @@ final class PCXMetadata extends IIOMetadata {
return null; return null;
} }
@Override protected IIOMetadataNode getStandardDataNode() { @Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data"); IIOMetadataNode node = new IIOMetadataNode("Data");
// Planar configuration only makes sense for multi-channel images // Planar configuration only makes sense for multi-channel images
@ -202,7 +174,8 @@ final class PCXMetadata extends IIOMetadata {
return buffer.toString(); return buffer.toString();
} }
@Override protected IIOMetadataNode getStandardDimensionNode() { @Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
@ -218,7 +191,8 @@ final class PCXMetadata extends IIOMetadata {
// No tiling // 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 // 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...) // 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)

View File

@ -28,51 +28,22 @@
package com.twelvemonkeys.imageio.plugins.pnm; 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 javax.imageio.metadata.IIOMetadataNode;
import java.awt.*; import java.awt.*;
import java.awt.image.DataBuffer; import java.awt.image.DataBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
final class PNMMetadata extends IIOMetadata { final class PNMMetadata extends AbstractMetadata {
// TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
private final PNMHeader header; private final PNMHeader header;
PNMMetadata(final PNMHeader header) { PNMMetadata(final PNMHeader header) {
this.header = header; this.header = header;
standardFormatSupported = true;
} }
@Override public boolean isReadOnly() { @Override
return true; protected IIOMetadataNode getStandardChromaNode() {
}
@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() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
@ -105,7 +76,9 @@ final class PNMMetadata extends IIOMetadata {
// TODO: Might make sense to set gamma? // TODO: Might make sense to set gamma?
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); 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); chroma.appendChild(blackIsZero);
return chroma; return chroma;
@ -113,11 +86,14 @@ final class PNMMetadata extends IIOMetadata {
// No compression // No compression
@Override protected IIOMetadataNode getStandardDataNode() { @Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data"); IIOMetadataNode node = new IIOMetadataNode("Data");
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); 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); node.appendChild(sampleFormat);
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
@ -128,7 +104,9 @@ final class PNMMetadata extends IIOMetadata {
significantBitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(computeSignificantBits()))); significantBitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(computeSignificantBits())));
node.appendChild(significantBitsPerSample); 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"); IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
sampleMSB.setAttribute("value", createListValue(header.getSamplesPerPixel(), msb)); sampleMSB.setAttribute("value", createListValue(header.getSamplesPerPixel(), msb));
@ -166,7 +144,8 @@ final class PNMMetadata extends IIOMetadata {
return buffer.toString(); return buffer.toString();
} }
@Override protected IIOMetadataNode getStandardDimensionNode() { @Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
@ -178,7 +157,8 @@ final class PNMMetadata extends IIOMetadata {
// No document node // No document node
@Override protected IIOMetadataNode getStandardTextNode() { @Override
protected IIOMetadataNode getStandardTextNode() {
if (!header.getComments().isEmpty()) { if (!header.getComments().isEmpty()) {
IIOMetadataNode text = new IIOMetadataNode("Text"); IIOMetadataNode text = new IIOMetadataNode("Text");
@ -197,7 +177,8 @@ final class PNMMetadata extends IIOMetadata {
// No tiling // No tiling
@Override protected IIOMetadataNode getStandardTransparencyNode() { @Override
protected IIOMetadataNode getStandardTransparencyNode() {
IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); IIOMetadataNode alpha = new IIOMetadataNode("Alpha");

View File

@ -28,6 +28,7 @@
package com.twelvemonkeys.imageio.plugins.psd; package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.AbstractMetadata;
import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.exif.TIFF; import com.twelvemonkeys.imageio.metadata.exif.TIFF;
@ -571,9 +572,8 @@ public final class PSDMetadata extends AbstractMetadata {
compressionNode.appendChild(compressionTypeName); compressionNode.appendChild(compressionTypeName);
if (compression != PSD.COMPRESSION_NONE) { if (compression != PSD.COMPRESSION_NONE) {
IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); compressionNode.appendChild(new IIOMetadataNode("Lossless"));
lossless.setAttribute("value", "true"); // "value" defaults to TRUE, all PSD compressions are lossless
compressionNode.appendChild(lossless);
} }
return compressionNode; return compressionNode;
@ -755,7 +755,7 @@ public final class PSDMetadata extends AbstractMetadata {
} }
private void appendTextEntriesFlat(final IIOMetadataNode node, final Directory directory, final FilterIterator.Filter<Entry> filter) { private void appendTextEntriesFlat(final IIOMetadataNode node, final Directory directory, final FilterIterator.Filter<Entry> filter) {
FilterIterator<Entry> entries = new FilterIterator<Entry>(directory.iterator(), filter); FilterIterator<Entry> entries = new FilterIterator<>(directory.iterator(), filter);
while (entries.hasNext()) { while (entries.hasNext()) {
Entry entry = entries.next(); Entry entry = entries.next();
@ -807,7 +807,7 @@ public final class PSDMetadata extends AbstractMetadata {
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
Iterator<T> iterator = (Iterator<T>) imageResources.iterator(); Iterator<T> iterator = (Iterator<T>) imageResources.iterator();
return new FilterIterator<T>(iterator, new FilterIterator.Filter<T>() { return new FilterIterator<>(iterator, new FilterIterator.Filter<T>() {
public boolean accept(final T pElement) { public boolean accept(final T pElement) {
return resourceType.isInstance(pElement); return resourceType.isInstance(pElement);
} }
@ -817,7 +817,7 @@ public final class PSDMetadata extends AbstractMetadata {
Iterator<PSDImageResource> getResources(final int... resourceTypes) { Iterator<PSDImageResource> getResources(final int... resourceTypes) {
Iterator<PSDImageResource> iterator = imageResources.iterator(); Iterator<PSDImageResource> iterator = imageResources.iterator();
return new FilterIterator<PSDImageResource>(iterator, new FilterIterator.Filter<PSDImageResource>() { return new FilterIterator<>(iterator, new FilterIterator.Filter<PSDImageResource>() {
public boolean accept(final PSDImageResource pResource) { public boolean accept(final PSDImageResource pResource) {
for (int type : resourceTypes) { for (int type : resourceTypes) {
if (type == pResource.id) { if (type == pResource.id) {

View File

@ -28,48 +28,19 @@
package com.twelvemonkeys.imageio.plugins.sgi; 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; import javax.imageio.metadata.IIOMetadataNode;
final class SGIMetadata extends IIOMetadata { final class SGIMetadata extends AbstractMetadata {
// TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
private final SGIHeader header; private final SGIHeader header;
SGIMetadata(final SGIHeader header) { SGIMetadata(final SGIHeader header) {
this.header = header; this.header = header;
standardFormatSupported = true;
} }
@Override public boolean isReadOnly() { @Override
return true; protected IIOMetadataNode getStandardChromaNode() {
}
@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() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
// NOTE: There doesn't seem to be any god way to determine color space, other than by convention // 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 // No compression
@Override protected IIOMetadataNode getStandardCompressionNode() { @Override
protected IIOMetadataNode getStandardCompressionNode() {
if (header.getCompression() != SGI.COMPRESSION_NONE) { if (header.getCompression() != SGI.COMPRESSION_NONE) {
IIOMetadataNode node = new IIOMetadataNode("Compression"); IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); 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); node.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
@ -135,7 +109,8 @@ final class SGIMetadata extends IIOMetadata {
return null; return null;
} }
@Override protected IIOMetadataNode getStandardDataNode() { @Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data"); IIOMetadataNode node = new IIOMetadataNode("Data");
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
@ -183,7 +158,8 @@ final class SGIMetadata extends IIOMetadata {
return buffer.toString(); return buffer.toString();
} }
@Override protected IIOMetadataNode getStandardDimensionNode() { @Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
@ -195,7 +171,8 @@ final class SGIMetadata extends IIOMetadata {
// No document node // No document node
@Override protected IIOMetadataNode getStandardTextNode() { @Override
protected IIOMetadataNode getStandardTextNode() {
if (!header.getName().isEmpty()) { if (!header.getName().isEmpty()) {
IIOMetadataNode text = new IIOMetadataNode("Text"); IIOMetadataNode text = new IIOMetadataNode("Text");
@ -212,14 +189,17 @@ final class SGIMetadata extends IIOMetadata {
// No tiling // 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 // 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...) // 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); 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); transparency.appendChild(alpha);
return transparency; return transparency;

View File

@ -28,50 +28,20 @@
package com.twelvemonkeys.imageio.plugins.tga; 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 javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.IndexColorModel; import java.awt.image.IndexColorModel;
final class TGAMetadata extends IIOMetadata { final class TGAMetadata extends AbstractMetadata {
// TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
private final TGAHeader header; private final TGAHeader header;
TGAMetadata(final TGAHeader header) { TGAMetadata(final TGAHeader header) {
this.header = header; this.header = header;
standardFormatSupported = true;
} }
@Override public boolean isReadOnly() { @Override
return true; protected IIOMetadataNode getStandardChromaNode() {
}
@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() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
@ -137,7 +107,8 @@ final class TGAMetadata extends IIOMetadata {
return chroma; return chroma;
} }
@Override protected IIOMetadataNode getStandardCompressionNode() { @Override
protected IIOMetadataNode getStandardCompressionNode() {
switch (header.getImageType()) { switch (header.getImageType()) {
case TGA.IMAGETYPE_COLORMAPPED_RLE: case TGA.IMAGETYPE_COLORMAPPED_RLE:
case TGA.IMAGETYPE_TRUECOLOR_RLE: case TGA.IMAGETYPE_TRUECOLOR_RLE:
@ -147,7 +118,7 @@ final class TGAMetadata extends IIOMetadata {
IIOMetadataNode node = new IIOMetadataNode("Compression"); IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
? "Uknown" : "RLE"; ? "Uknown" : "RLE";
compressionTypeName.setAttribute("value", value); compressionTypeName.setAttribute("value", value);
node.appendChild(compressionTypeName); 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 node = new IIOMetadataNode("Data");
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
@ -221,7 +193,8 @@ final class TGAMetadata extends IIOMetadata {
return buffer.toString(); return buffer.toString();
} }
@Override protected IIOMetadataNode getStandardDimensionNode() { @Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
@ -248,7 +221,8 @@ final class TGAMetadata extends IIOMetadata {
// No document node // No document node
@Override protected IIOMetadataNode getStandardTextNode() { @Override
protected IIOMetadataNode getStandardTextNode() {
// TODO: Extra "developer area" and other stuff might go here... // TODO: Extra "developer area" and other stuff might go here...
if (header.getIdentification() != null && !header.getIdentification().isEmpty()) { if (header.getIdentification() != null && !header.getIdentification().isEmpty()) {
IIOMetadataNode text = new IIOMetadataNode("Text"); IIOMetadataNode text = new IIOMetadataNode("Text");
@ -266,7 +240,8 @@ final class TGAMetadata extends IIOMetadata {
// No tiling // No tiling
@Override protected IIOMetadataNode getStandardTransparencyNode() { @Override
protected IIOMetadataNode getStandardTransparencyNode() {
IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); IIOMetadataNode alpha = new IIOMetadataNode("Alpha");

View File

@ -59,4 +59,13 @@ interface TIFFBaseline {
int RESOLUTION_UNIT_NONE = 1; int RESOLUTION_UNIT_NONE = 1;
int RESOLUTION_UNIT_DPI = 2; // Default int RESOLUTION_UNIT_DPI = 2; // Default
int RESOLUTION_UNIT_CENTIMETER = 3; 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;
} }

View File

@ -83,4 +83,13 @@ interface TIFFExtension {
* description of the inks to be used. * description of the inks to be used.
*/ */
int INKSET_NOT_CMYK = 2; 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;
} }

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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();
}
}

View File

@ -243,6 +243,7 @@ public class TIFFImageReader extends ImageReaderBase {
int bitsPerSample = getBitsPerSample(); int bitsPerSample = getBitsPerSample();
int dataType = getDataType(sampleFormat, bitsPerSample); int dataType = getDataType(sampleFormat, bitsPerSample);
// TODO: Validate CS using ColorSpaces.validateProfile
// Read embedded cs // Read embedded cs
ICC_Profile profile = getICCProfile(); ICC_Profile profile = getICCProfile();
ColorSpace cs; ColorSpace cs;
@ -503,7 +504,7 @@ public class TIFFImageReader extends ImageReaderBase {
readIFD(imageIndex); readIFD(imageIndex);
ImageTypeSpecifier rawType = getRawImageType(imageIndex); ImageTypeSpecifier rawType = getRawImageType(imageIndex);
Set<ImageTypeSpecifier> specs = new LinkedHashSet<ImageTypeSpecifier>(5); Set<ImageTypeSpecifier> specs = new LinkedHashSet<>(5);
// TODO: Based on raw type, we can probably convert to most RGB types at least, maybe gray etc // TODO: Based on raw type, we can probably convert to most RGB types at least, maybe gray etc
// TODO: Planar to chunky by default // 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 { private long[] getValueAsLongArray(final int tag, final String tagName, boolean required) throws IIOException {
Entry entry = currentIFD.getEntryById(tag); Entry entry = currentIFD.getEntryById(tag);
if (entry == null) { if (entry == null) {
if (required) { if (required) {
throw new IIOException("Missing TIFF tag " + tagName); throw new IIOException("Missing TIFF tag " + tagName);
@ -1413,6 +1415,21 @@ public class TIFFImageReader extends ImageReaderBase {
// TODO: Thumbnail support // 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 { public static void main(final String[] args) throws IOException {
ImageIO.setUseCache(false); ImageIO.setUseCache(false);
@ -1500,7 +1517,7 @@ public class TIFFImageReader extends ImageReaderBase {
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()) { /*else*/ if (metadata.isStandardMetadataFormatSupported()) {
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false); new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
} }
} }

View File

@ -50,8 +50,8 @@ final class TIFFProviderInfo extends ReaderWriterProviderInfo {
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi"}, new String[] {"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi"},
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter", "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter",
new String[] {"com.twelvemkonkeys.imageio.plugins.tif.TIFFImageWriterSpi"}, new String[] {"com.twelvemkonkeys.imageio.plugins.tif.TIFFImageWriterSpi"},
false, null, null, null, null, false, TIFFMedataFormat.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadata", null, null,
true, null, null, null, null true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, "TODO", null, null
); );
} }
} }