blob: 7d4152c3db474938505e3530e1d794e169f0b4e5 [file] [log] [blame]
/*
* Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.imageio.plugins.tiff;
import java.util.StringTokenizer;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.sun.imageio.plugins.tiff.TIFFFieldNode;
import com.sun.imageio.plugins.tiff.TIFFIFD;
/**
* A class representing a field in a TIFF 6.0 Image File Directory.
*
* <p> A field in a TIFF Image File Directory (IFD) is defined as a
* tag number accompanied by a sequence of values of identical data type.
* TIFF 6.0 defines 12 data types; a 13th type {@code IFD} is
* defined in TIFF Tech Note 1 of TIFF Specification Supplement 1. These
* TIFF data types are referred to by Java constants and mapped internally
* onto Java language data types and type names as follows:
*
* <br>
* <br>
* <table border="1">
* <caption>TIFF Data Type to Java Data Type Mapping</caption>
*
* <tr>
* <th>
* <b>TIFF Data Type</b>
* </th>
* <th>
* <b>Java Constant</b>
* </th>
* <th>
* <b>Java Data Type</b>
* </th>
* <th>
* <b>Java Type Name</b>
* </th>
* </tr>
*
* <tr>
* <td>
* <tt>BYTE</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_BYTE}
* </td>
* <td>
* {@code byte}
* </td>
* <td>
* {@code "Byte"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>ASCII</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_ASCII}
* </td>
* <td>
* {@code String}
* </td>
* <td>
* {@code "Ascii"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>SHORT</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_SHORT}
* </td>
* <td>
* {@code char}
* </td>
* <td>
* {@code "Short"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>LONG</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_LONG}
* </td>
* <td>
* {@code long}
* </td>
* <td>
* {@code "Long"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>RATIONAL</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_RATIONAL}
* </td>
* <td>
* {@code long[2]} {numerator, denominator}
* </td>
* <td>
* {@code "Rational"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>SBYTE</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_SBYTE}
* </td>
* <td>
* {@code byte}
* </td>
* <td>
* {@code "SByte"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>UNDEFINED</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_UNDEFINED}
* </td>
* <td>
* {@code byte}
* </td>
* <td>
* {@code "Undefined"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>SSHORT</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_SSHORT}
* </td>
* <td>
* {@code short}
* </td>
* <td>
* {@code "SShort"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>SLONG</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_SLONG}
* </td>
* <td>
* {@code int}
* </td>
* <td>
* {@code "SLong"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>SRATIONAL</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_SRATIONAL}
* </td>
* <td>
* {@code int[2]} {numerator, denominator}
* </td>
* <td>
* {@code "SRational"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>FLOAT</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_FLOAT}
* </td>
* <td>
* {@code float}
* </td>
* <td>
* {@code "Float"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>DOUBLE</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_DOUBLE}
* </td>
* <td>
* {@code double}
* </td>
* <td>
* {@code "Double"}
* </td>
* </tr>
*
* <tr>
* <td>
* <tt>IFD</tt>
* </td>
* <td>
* {@link TIFFTag#TIFF_IFD_POINTER}
* </td>
* <td>
* {@code long}
* </td>
* <td>
* {@code "IFDPointer"}
* </td>
* </tr>
*
* </table>
*
* @since 9
* @see TIFFDirectory
* @see TIFFTag
*/
public final class TIFFField implements Cloneable {
private static final long MAX_UINT32 = 0xffffffffL;
private static final String[] TYPE_NAMES = {
null,
"Byte", "Ascii", "Short", "Long", "Rational",
"SByte", "Undefined", "SShort", "SLong", "SRational",
"Float", "Double", "IFDPointer"
};
private static final boolean[] IS_INTEGRAL = {
false,
true, false, true, true, false,
true, true, true, true, false,
false, false, false
};
/** The tag. */
private TIFFTag tag;
/** The tag number. */
private int tagNumber;
/** The tag type. */
private int type;
/** The number of data items present in the field. */
private int count;
/** The field data. */
private Object data;
/** The IFD contents if available. This will usually be a TIFFIFD. */
private TIFFDirectory dir;
/** The default constructor. */
private TIFFField() {}
private static String getAttribute(Node node, String attrName) {
NamedNodeMap attrs = node.getAttributes();
return attrs.getNamedItem(attrName).getNodeValue();
}
private static void initData(Node node,
int[] otype, int[] ocount, Object[] odata) {
int type;
int count;
Object data = null;
String typeName = node.getNodeName();
typeName = typeName.substring(4);
typeName = typeName.substring(0, typeName.length() - 1);
type = TIFFField.getTypeByName(typeName);
if (type == -1) {
throw new IllegalArgumentException("typeName = " + typeName);
}
Node child = node.getFirstChild();
count = 0;
while (child != null) {
String childTypeName = child.getNodeName().substring(4);
if (!typeName.equals(childTypeName)) {
// warning
}
++count;
child = child.getNextSibling();
}
if (count > 0) {
data = createArrayForType(type, count);
child = node.getFirstChild();
int idx = 0;
while (child != null) {
String value = getAttribute(child, "value");
String numerator, denominator;
int slashPos;
switch (type) {
case TIFFTag.TIFF_ASCII:
((String[])data)[idx] = value;
break;
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_SBYTE:
((byte[])data)[idx] =
(byte)Integer.parseInt(value);
break;
case TIFFTag.TIFF_SHORT:
((char[])data)[idx] =
(char)Integer.parseInt(value);
break;
case TIFFTag.TIFF_SSHORT:
((short[])data)[idx] =
(short)Integer.parseInt(value);
break;
case TIFFTag.TIFF_SLONG:
((int[])data)[idx] =
Integer.parseInt(value);
break;
case TIFFTag.TIFF_LONG:
case TIFFTag.TIFF_IFD_POINTER:
((long[])data)[idx] =
Long.parseLong(value);
break;
case TIFFTag.TIFF_FLOAT:
((float[])data)[idx] =
Float.parseFloat(value);
break;
case TIFFTag.TIFF_DOUBLE:
((double[])data)[idx] =
Double.parseDouble(value);
break;
case TIFFTag.TIFF_SRATIONAL:
slashPos = value.indexOf("/");
numerator = value.substring(0, slashPos);
denominator = value.substring(slashPos + 1);
((int[][])data)[idx] = new int[2];
((int[][])data)[idx][0] =
Integer.parseInt(numerator);
((int[][])data)[idx][1] =
Integer.parseInt(denominator);
break;
case TIFFTag.TIFF_RATIONAL:
slashPos = value.indexOf("/");
numerator = value.substring(0, slashPos);
denominator = value.substring(slashPos + 1);
((long[][])data)[idx] = new long[2];
((long[][])data)[idx][0] =
Long.parseLong(numerator);
((long[][])data)[idx][1] =
Long.parseLong(denominator);
break;
default:
// error
}
idx++;
child = child.getNextSibling();
}
}
otype[0] = type;
ocount[0] = count;
odata[0] = data;
}
/**
* Creates a {@code TIFFField} from a TIFF native image
* metadata node. If the value of the {@code "number"} attribute
* of the node is not found in {@code tagSet} then a new
* {@code TIFFTag} with name {@code TIFFTag.UNKNOWN_TAG_NAME}
* will be created and assigned to the field.
*
* @param tagSet The {@code TIFFTagSet} to which the
* {@code TIFFTag} of the field belongs.
* @param node A native TIFF image metadata {@code TIFFField} node.
* @throws IllegalArgumentException If the {@code Node} parameter content
* does not adhere to the {@code TIFFField} element structure defined by
* the <a href="../../metadata/doc-files/tiff_metadata.html#ImageMetadata">
* TIFF native image metadata format specification</a>, or if the
* combination of node attributes and data is not legal per the
* {@link #TIFFField(TIFFTag,int,int,Object)} constructor specification.
* Note that a cause might be set on such an exception.
* @return A new {@code TIFFField}.
*/
public static TIFFField createFromMetadataNode(TIFFTagSet tagSet,
Node node) {
if (node == null) {
// This method is specified to throw only IllegalArgumentExceptions
// so we create an IAE with a NullPointerException as its cause.
throw new IllegalArgumentException(new NullPointerException
("node == null!"));
}
String name = node.getNodeName();
if (!name.equals("TIFFField")) {
throw new IllegalArgumentException("!name.equals(\"TIFFField\")");
}
int tagNumber = Integer.parseInt(getAttribute(node, "number"));
TIFFTag tag = null;
if (tagSet != null) {
tag = tagSet.getTag(tagNumber);
}
int type = TIFFTag.TIFF_UNDEFINED;
int count = 0;
Object data = null;
Node child = node.getFirstChild();
if (child != null) {
String typeName = child.getNodeName();
if (typeName.equals("TIFFUndefined")) {
String values = getAttribute(child, "value");
StringTokenizer st = new StringTokenizer(values, ",");
count = st.countTokens();
byte[] bdata = new byte[count];
for (int i = 0; i < count; i++) {
bdata[i] = (byte)Integer.parseInt(st.nextToken());
}
type = TIFFTag.TIFF_UNDEFINED;
data = bdata;
} else {
int[] otype = new int[1];
int[] ocount = new int[1];
Object[] odata = new Object[1];
initData(node.getFirstChild(), otype, ocount, odata);
type = otype[0];
count = ocount[0];
data = odata[0];
}
} else if (tag != null) {
int t = TIFFTag.MAX_DATATYPE;
while(t >= TIFFTag.MIN_DATATYPE && !tag.isDataTypeOK(t)) {
t--;
}
type = t;
}
if (tag == null) {
tag = new TIFFTag(TIFFTag.UNKNOWN_TAG_NAME, tagNumber, 1 << type);
}
TIFFField field;
try {
field = new TIFFField(tag, type, count, data);
} catch (NullPointerException npe) {
// This method is specified to throw only IllegalArgumentExceptions
// so we catch the NullPointerException and set it as the cause of
// the IAE which is thrown.
throw new IllegalArgumentException(npe);
}
return field;
}
/**
* Constructs a {@code TIFFField} with arbitrary data. The
* {@code type} parameter must be a value for which
* {@link TIFFTag#isDataTypeOK tag.isDataTypeOK()}
* returns {@code true}. The {@code data} parameter must
* be an array of a Java type appropriate for the type of the TIFF
* field.
*
* <p>Note that the value (data) of the {@code TIFFField}
* will always be the actual field value regardless of the number of
* bytes required for that value. This is the case despite the fact
* that the TIFF <i>IFD Entry</i> corresponding to the field may
* actually contain the offset to the value of the field rather than
* the value itself (the latter occurring if and only if the
* value fits into 4 bytes). In other words, the value of the
* field will already have been read from the TIFF stream. (An exception
* to this case may occur when the field represents the contents of a
* non-baseline IFD. In that case the data will be a {@code long[]}
* containing the offset to the IFD and the {@code TIFFDirectory}
* returned by {@link #getDirectory()} will be its contents.)
*
* @param tag The tag to associated with this field.
* @param type One of the {@code TIFFTag.TIFF_*} constants
* indicating the data type of the field as written to the TIFF stream.
* @param count The number of data values.
* @param data The actual data content of the field.
*
* @throws NullPointerException if {@code tag == null}.
* @throws IllegalArgumentException if {@code type} is not
* one of the {@code TIFFTag.TIFF_*} data type constants.
* @throws IllegalArgumentException if {@code type} is an unacceptable
* data type for the supplied {@code TIFFTag}.
* @throws IllegalArgumentException if {@code count < 0}.
* @throws IllegalArgumentException if {@code count < 1}
* and {@code type} is {@code TIFF_RATIONAL} or
* {@code TIFF_SRATIONAL}.
* @throws IllegalArgumentException if {@code count != 1}
* and {@code type} is {@code TIFF_IFD_POINTER}.
* @throws NullPointerException if {@code data == null}.
* @throws IllegalArgumentException if {@code data} is an instance of
* a class incompatible with the specified type.
* @throws IllegalArgumentException if the size of the data array is wrong.
* @throws IllegalArgumentException if the type of the data array is
* {@code TIFF_LONG}, {@code TIFF_RATIONAL}, or {@code TIFF_IFD_POINTER}
* and any of the elements is negative or greater than {@code 0xffffffff}.
*/
public TIFFField(TIFFTag tag, int type, int count, Object data) {
if(tag == null) {
throw new NullPointerException("tag == null!");
} else if(type < TIFFTag.MIN_DATATYPE || type > TIFFTag.MAX_DATATYPE) {
throw new IllegalArgumentException("Unknown data type "+type);
} else if(!tag.isDataTypeOK(type)) {
throw new IllegalArgumentException("Illegal data type " + type
+ " for " + tag.getName() + " tag");
} else if(count < 0) {
throw new IllegalArgumentException("count < 0!");
} else if((type == TIFFTag.TIFF_RATIONAL
|| type == TIFFTag.TIFF_SRATIONAL)
&& count < 1) {
throw new IllegalArgumentException
("Type is TIFF_RATIONAL or TIFF_SRATIONAL and count < 1");
} else if (type == TIFFTag.TIFF_IFD_POINTER && count != 1) {
throw new IllegalArgumentException
("Type is TIFF_IFD_POINTER and count != 1");
} else if(data == null) {
throw new NullPointerException("data == null!");
}
boolean isDataArrayCorrect = false;
switch (type) {
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_SBYTE:
case TIFFTag.TIFF_UNDEFINED:
isDataArrayCorrect = data instanceof byte[]
&& ((byte[])data).length == count;
break;
case TIFFTag.TIFF_ASCII:
isDataArrayCorrect = data instanceof String[]
&& ((String[])data).length == count;
break;
case TIFFTag.TIFF_SHORT:
isDataArrayCorrect = data instanceof char[]
&& ((char[])data).length == count;
break;
case TIFFTag.TIFF_LONG:
isDataArrayCorrect = data instanceof long[]
&& ((long[])data).length == count;
if (isDataArrayCorrect) {
for (long datum : (long[])data) {
if (datum < 0) {
throw new IllegalArgumentException
("Negative value supplied for TIFF_LONG");
}
if (datum > MAX_UINT32) {
throw new IllegalArgumentException
("Too large value supplied for TIFF_LONG");
}
}
}
break;
case TIFFTag.TIFF_IFD_POINTER:
isDataArrayCorrect = data instanceof long[]
&& ((long[])data).length == 1;
if (((long[])data)[0] < 0) {
throw new IllegalArgumentException
("Negative value supplied for TIFF_IFD_POINTER");
}
if (((long[])data)[0] > MAX_UINT32) {
throw new IllegalArgumentException
("Too large value supplied for TIFF_IFD_POINTER");
}
break;
case TIFFTag.TIFF_RATIONAL:
isDataArrayCorrect = data instanceof long[][]
&& ((long[][])data).length == count;
if (isDataArrayCorrect) {
for (long[] datum : (long[][])data) {
if (datum.length != 2) {
isDataArrayCorrect = false;
break;
}
if (datum[0] < 0 || datum[1] < 0) {
throw new IllegalArgumentException
("Negative value supplied for TIFF_RATIONAL");
}
if (datum[0] > MAX_UINT32 || datum[1] > MAX_UINT32) {
throw new IllegalArgumentException
("Too large value supplied for TIFF_RATIONAL");
}
}
}
break;
case TIFFTag.TIFF_SSHORT:
isDataArrayCorrect = data instanceof short[]
&& ((short[])data).length == count;
break;
case TIFFTag.TIFF_SLONG:
isDataArrayCorrect = data instanceof int[]
&& ((int[])data).length == count;
break;
case TIFFTag.TIFF_SRATIONAL:
isDataArrayCorrect = data instanceof int[][]
&& ((int[][])data).length == count;
if (isDataArrayCorrect) {
for (int[] datum : (int[][])data) {
if (datum.length != 2) {
isDataArrayCorrect = false;
break;
}
}
}
break;
case TIFFTag.TIFF_FLOAT:
isDataArrayCorrect = data instanceof float[]
&& ((float[])data).length == count;
break;
case TIFFTag.TIFF_DOUBLE:
isDataArrayCorrect = data instanceof double[]
&& ((double[])data).length == count;
break;
default:
throw new IllegalArgumentException("Unknown data type "+type);
}
if (!isDataArrayCorrect) {
throw new IllegalArgumentException
("Illegal class or length for data array");
}
this.tag = tag;
this.tagNumber = tag.getNumber();
this.type = type;
this.count = count;
this.data = data;
}
/**
* Constructs a data array using {@link #createArrayForType
* createArrayForType()} and invokes
* {@link #TIFFField(TIFFTag,int,int,Object)} with the supplied
* parameters and the created array.
*
* @param tag The tag to associated with this field.
* @param type One of the {@code TIFFTag.TIFF_*} constants
* indicating the data type of the field as written to the TIFF stream.
* @param count The number of data values.
* @throws NullPointerException if {@code tag == null}.
* @throws IllegalArgumentException if {@code type} is not
* one of the {@code TIFFTag.TIFF_*} data type constants.
* @throws IllegalArgumentException if {@code type} is an unacceptable
* data type for the supplied {@code TIFFTag}.
* @throws IllegalArgumentException if {@code count < 0}.
* @see #TIFFField(TIFFTag,int,int,Object)
* @throws IllegalArgumentException if {@code count < 1}
* and {@code type} is {@code TIFF_RATIONAL} or
* {@code TIFF_SRATIONAL}.
* @throws IllegalArgumentException if {@code count != 1}
* and {@code type} is {@code TIFF_IFD_POINTER}.
*/
public TIFFField(TIFFTag tag, int type, int count) {
this(tag, type, count, createArrayForType(type, count));
}
/**
* Constructs a {@code TIFFField} with a single non-negative integral
* value. The field will have type {@link TIFFTag#TIFF_SHORT TIFF_SHORT}
* if {@code value} is in {@code [0,0xffff]}, and type
* {@link TIFFTag#TIFF_LONG TIFF_LONG} if {@code value} is in
* {@code [0x10000,0xffffffff]}. The count of the field will be unity.
*
* @param tag The tag to associate with this field.
* @param value The value to associate with this field.
* @throws NullPointerException if {@code tag == null}.
* @throws IllegalArgumentException if {@code value} is not in
* {@code [0,0xffffffff]}.
* @throws IllegalArgumentException if {@code value} is in
* {@code [0,0xffff]} and {@code TIFF_SHORT} is an unacceptable type
* for the {@code TIFFTag}, or if {@code value} is in
* {@code [0x10000,0xffffffff]} and {@code TIFF_LONG} is an unacceptable
* type for the {@code TIFFTag}.
*/
public TIFFField(TIFFTag tag, long value) {
if(tag == null) {
throw new NullPointerException("tag == null!");
}
if (value < 0) {
throw new IllegalArgumentException("value < 0!");
}
if (value > MAX_UINT32) {
throw new IllegalArgumentException("value > 0xffffffff!");
}
this.tag = tag;
this.tagNumber = tag.getNumber();
this.count = 1;
if (value < 65536) {
if (!tag.isDataTypeOK(TIFFTag.TIFF_SHORT)) {
throw new IllegalArgumentException("Illegal data type "
+ getTypeName(TIFFTag.TIFF_SHORT) + " for tag "
+ "\"" + tag.getName() + "\"");
}
this.type = TIFFTag.TIFF_SHORT;
char[] cdata = new char[1];
cdata[0] = (char)value;
this.data = cdata;
} else {
if (!tag.isDataTypeOK(TIFFTag.TIFF_LONG)) {
throw new IllegalArgumentException("Illegal data type "
+ getTypeName(TIFFTag.TIFF_LONG) + " for tag "
+ "\"" + tag.getName() + "\"");
}
this.type = TIFFTag.TIFF_LONG;
long[] ldata = new long[1];
ldata[0] = value;
this.data = ldata;
}
}
/**
* Constructs a {@code TIFFField} with an IFD offset and contents.
* The offset will be stored as the data of this field as
* {@code long[] {offset}}. The directory will not be cloned. The count
* of the field will be unity.
*
* @param tag The tag to associated with this field.
* @param type One of the constants {@code TIFFTag.TIFF_LONG} or
* {@code TIFFTag.TIFF_IFD_POINTER}.
* @param offset The IFD offset.
* @param dir The directory.
*
* @throws NullPointerException if {@code tag == null}.
* @throws IllegalArgumentException if {@code type} is an unacceptable
* data type for the supplied {@code TIFFTag}.
* @throws IllegalArgumentException if {@code type} is neither
* {@code TIFFTag.TIFF_LONG} nor {@code TIFFTag.TIFF_IFD_POINTER}.
* @throws IllegalArgumentException if {@code offset <= 0}.
* @throws NullPointerException if {@code dir == null}.
*
* @see #TIFFField(TIFFTag,int,int,Object)
*/
public TIFFField(TIFFTag tag, int type, long offset, TIFFDirectory dir) {
if (tag == null) {
throw new NullPointerException("tag == null!");
} else if (type < TIFFTag.MIN_DATATYPE || type > TIFFTag.MAX_DATATYPE) {
throw new IllegalArgumentException("Unknown data type "+type);
} else if (!tag.isDataTypeOK(type)) {
throw new IllegalArgumentException("Illegal data type " + type
+ " for " + tag.getName() + " tag");
} else if (type != TIFFTag.TIFF_LONG
&& type != TIFFTag.TIFF_IFD_POINTER) {
throw new IllegalArgumentException("type " + type
+ " is neither TIFFTag.TIFF_LONG nor TIFFTag.TIFF_IFD_POINTER");
} else if (offset <= 0) {
throw new IllegalArgumentException("offset " + offset
+ " is non-positive");
} else if (dir == null) {
throw new NullPointerException("dir == null");
}
this.tag = tag;
this.tagNumber = tag.getNumber();
this.type = type;
this.count = 1;
this.data = new long[] {offset};
this.dir = dir;
}
/**
* Retrieves the tag associated with this field.
*
* @return The associated {@code TIFFTag}.
*/
public TIFFTag getTag() {
return tag;
}
/**
* Retrieves the tag number in the range {@code [0,65535]}.
*
* @return The tag number.
*/
public int getTagNumber() {
return tagNumber;
}
/**
* Returns the type of the data stored in the field. For a TIFF 6.0
* stream, the value will equal one of the {@code TIFFTag.TIFF_*}
* constants. For future revisions of TIFF, higher values are possible.
*
* @return The data type of the field value.
*/
public int getType() {
return type;
}
/**
* Returns the name of the supplied data type constant.
*
* @param dataType One of the {@code TIFFTag.TIFF_*} constants
* indicating the data type of the field as written to the TIFF stream.
* @return The type name corresponding to the supplied type constant.
* @throws IllegalArgumentException if {@code dataType} is not
* one of the {@code TIFFTag.TIFF_*} data type constants.
*/
public static String getTypeName(int dataType) {
if (dataType < TIFFTag.MIN_DATATYPE ||
dataType > TIFFTag.MAX_DATATYPE) {
throw new IllegalArgumentException("Unknown data type "+dataType);
}
return TYPE_NAMES[dataType];
}
/**
* Returns the data type constant corresponding to the supplied data
* type name. If the name is unknown {@code -1} will be returned.
*
* @param typeName The type name.
* @return One of the {@code TIFFTag.TIFF_*} constants or
* {@code -1} if the name is not recognized.
*/
public static int getTypeByName(String typeName) {
for (int i = TIFFTag.MIN_DATATYPE; i <= TIFFTag.MAX_DATATYPE; i++) {
if (typeName.equals(TYPE_NAMES[i])) {
return i;
}
}
return -1;
}
/**
* Creates an array appropriate for the indicated data type.
*
* @param dataType One of the {@code TIFFTag.TIFF_*} data type
* constants.
* @param count The number of values in the array.
* @return An array appropriate for the specified data type.
*
* @throws IllegalArgumentException if {@code dataType} is not
* one of the {@code TIFFTag.TIFF_*} data type constants.
* @throws IllegalArgumentException if {@code count < 0}.
* @throws IllegalArgumentException if {@code count < 1}
* and {@code type} is {@code TIFF_RATIONAL} or
* {@code TIFF_SRATIONAL}.
* @throws IllegalArgumentException if {@code count != 1}
* and {@code type} is {@code TIFF_IFD_POINTER}.
*/
public static Object createArrayForType(int dataType, int count) {
if(count < 0) {
throw new IllegalArgumentException("count < 0!");
} else if ((dataType == TIFFTag.TIFF_RATIONAL
|| dataType == TIFFTag.TIFF_SRATIONAL)
&& count < 1) {
throw new IllegalArgumentException
("Type is TIFF_RATIONAL or TIFF_SRATIONAL and count < 1");
} else if (dataType == TIFFTag.TIFF_IFD_POINTER && count != 1) {
throw new IllegalArgumentException
("Type is TIFF_IFD_POINTER and count != 1");
}
switch (dataType) {
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_SBYTE:
case TIFFTag.TIFF_UNDEFINED:
return new byte[count];
case TIFFTag.TIFF_ASCII:
return new String[count];
case TIFFTag.TIFF_SHORT:
return new char[count];
case TIFFTag.TIFF_LONG:
case TIFFTag.TIFF_IFD_POINTER:
return new long[count];
case TIFFTag.TIFF_RATIONAL:
return new long[count][2];
case TIFFTag.TIFF_SSHORT:
return new short[count];
case TIFFTag.TIFF_SLONG:
return new int[count];
case TIFFTag.TIFF_SRATIONAL:
return new int[count][2];
case TIFFTag.TIFF_FLOAT:
return new float[count];
case TIFFTag.TIFF_DOUBLE:
return new double[count];
default:
throw new IllegalArgumentException("Unknown data type "+dataType);
}
}
/**
* Returns the {@code TIFFField} as a node named either
* <tt>"TIFFField"</tt> or <tt>"TIFFIFD"</tt> as described in the
* TIFF native image metadata specification. The node will be named
* <tt>"TIFFIFD"</tt> if and only if {@link #hasDirectory()} returns
* {@code true} and the field's type is either {@link TIFFTag#TIFF_LONG}
* or {@link TIFFTag#TIFF_IFD_POINTER}.
*
* @return a {@code Node} named <tt>"TIFFField"</tt> or
* <tt>"TIFFIFD"</tt>.
*/
public Node getAsNativeNode() {
return new TIFFFieldNode(this);
}
/**
* Indicates whether the value associated with the field is of
* integral data type.
*
* @return Whether the field type is integral.
*/
public boolean isIntegral() {
return IS_INTEGRAL[type];
}
/**
* Returns the number of data items present in the field. For
* {@code TIFFTag.TIFF_ASCII} fields, the value returned is the
* number of {@code String}s, not the total length of the
* data as in the file representation.
*
* @return The number of data items present in the field.
*/
public int getCount() {
return count;
}
/**
* Returns a reference to the data object associated with the field.
*
* @return The data object of the field.
*/
public Object getData() {
return data;
}
/**
* Returns the data as an uninterpreted array of
* {@code byte}s. The type of the field must be one of
* {@code TIFFTag.TIFF_BYTE}, {@code TIFF_SBYTE}, or
* {@code TIFF_UNDEFINED}.
*
* <p> For data in {@code TIFFTag.TIFF_BYTE} format, the application
* must take care when promoting the data to longer integral types
* to avoid sign extension.
*
* @throws ClassCastException if the field is not of type
* {@code TIFF_BYTE}, {@code TIFF_SBYTE}, or
* {@code TIFF_UNDEFINED}.
* @return The data as an uninterpreted array of bytes.
*/
public byte[] getAsBytes() {
return (byte[])data;
}
/**
* Returns {@code TIFFTag.TIFF_SHORT} data as an array of
* {@code char}s (unsigned 16-bit integers).
*
* @throws ClassCastException if the field is not of type
* {@code TIFF_SHORT}.
* @return The data as an array of {@code char}s.
*/
public char[] getAsChars() {
return (char[])data;
}
/**
* Returns {@code TIFFTag.TIFF_SSHORT} data as an array of
* {@code short}s (signed 16-bit integers).
*
* @throws ClassCastException if the field is not of type
* {@code TIFF_SSHORT}.
* @return The data as an array of {@code short}s.
*/
public short[] getAsShorts() {
return (short[])data;
}
/**
* Returns {@code TIFFTag.TIFF_SLONG} data as an array of
* {@code int}s (signed 32-bit integers).
*
* @throws ClassCastException if the field is not of type
* {@code TIFF_SHORT}, {@code TIFF_SSHORT}, or
* {@code TIFF_SLONG}.
* @return The data as an array of {@code int}s.
*/
public int[] getAsInts() {
if (data instanceof int[]) {
return (int[])data;
} else if (data instanceof char[]){
char[] cdata = (char[])data;
int[] idata = new int[cdata.length];
for (int i = 0; i < cdata.length; i++) {
idata[i] = cdata[i] & 0xffff;
}
return idata;
} else if (data instanceof short[]){
short[] sdata = (short[])data;
int[] idata = new int[sdata.length];
for (int i = 0; i < sdata.length; i++) {
idata[i] = (int)sdata[i];
}
return idata;
} else {
throw new ClassCastException("Data not char[], short[], or int[]!");
}
}
/**
* Returns {@code TIFFTag.TIFF_LONG} or
* {@code TIFF_IFD_POINTER} data as an array of
* {@code long}s (signed 64-bit integers).
*
* @throws ClassCastException if the field is not of type
* {@code TIFF_LONG} or {@code TIFF_IFD_POINTER}.
* @return The data as an array of {@code long}s.
*/
public long[] getAsLongs() {
return (long[])data;
}
/**
* Returns {@code TIFFTag.TIFF_FLOAT} data as an array of
* {@code float}s (32-bit floating-point values).
*
* @throws ClassCastException if the field is not of type
* {@code TIFF_FLOAT}.
* @return The data as an array of {@code float}s.
*/
public float[] getAsFloats() {
return (float[])data;
}
/**
* Returns {@code TIFFTag.TIFF_DOUBLE} data as an array of
* {@code double}s (64-bit floating-point values).
*
* @throws ClassCastException if the field is not of type
* {@code TIFF_DOUBLE}.
* @return The data as an array of {@code double}s.
*/
public double[] getAsDoubles() {
return (double[])data;
}
/**
* Returns {@code TIFFTag.TIFF_SRATIONAL} data as an array of
* 2-element arrays of {@code int}s.
*
* @throws ClassCastException if the field is not of type
* {@code TIFF_SRATIONAL}.
* @return The data as an array of signed rationals.
*/
public int[][] getAsSRationals() {
return (int[][])data;
}
/**
* Returns {@code TIFFTag.TIFF_RATIONAL} data as an array of
* 2-element arrays of {@code long}s.
*
* @throws ClassCastException if the field is not of type
* {@code TIFF_RATIONAL}.
* @return The data as an array of unsigned rationals.
*/
public long[][] getAsRationals() {
return (long[][])data;
}
/**
* Returns data in any format as an {@code int}.
*
* <p> {@code TIFFTag.TIFF_BYTE} values are treated as unsigned; that
* is, no sign extension will take place and the returned value
* will be in the range [0, 255]. {@code TIFF_SBYTE} data
* will be returned in the range [-128, 127].
*
* <p> A {@code TIFF_UNDEFINED} value is treated as though
* it were a {@code TIFF_BYTE}.
*
* <p> Data in {@code TIFF_SLONG}, {@code TIFF_LONG},
* {@code TIFF_FLOAT}, {@code TIFF_DOUBLE} or
* {@code TIFF_IFD_POINTER} format are simply cast to
* {@code int} and may suffer from truncation.
*
* <p> Data in {@code TIFF_SRATIONAL} or
* {@code TIFF_RATIONAL} format are evaluated by dividing the
* numerator into the denominator using double-precision
* arithmetic and then casting to {@code int}. Loss of
* precision and truncation may occur.
*
* <p> Data in {@code TIFF_ASCII} format will be parsed as by
* the {@code Double.parseDouble} method, with the result
* case to {@code int}.
*
* @param index The index of the data.
* @return The data at the given index as an {@code int}.
*/
public int getAsInt(int index) {
switch (type) {
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_UNDEFINED:
return ((byte[])data)[index] & 0xff;
case TIFFTag.TIFF_SBYTE:
return ((byte[])data)[index];
case TIFFTag.TIFF_SHORT:
return ((char[])data)[index] & 0xffff;
case TIFFTag.TIFF_SSHORT:
return ((short[])data)[index];
case TIFFTag.TIFF_SLONG:
return ((int[])data)[index];
case TIFFTag.TIFF_LONG:
case TIFFTag.TIFF_IFD_POINTER:
return (int)((long[])data)[index];
case TIFFTag.TIFF_FLOAT:
return (int)((float[])data)[index];
case TIFFTag.TIFF_DOUBLE:
return (int)((double[])data)[index];
case TIFFTag.TIFF_SRATIONAL:
int[] ivalue = getAsSRational(index);
return (int)((double)ivalue[0]/ivalue[1]);
case TIFFTag.TIFF_RATIONAL:
long[] lvalue = getAsRational(index);
return (int)((double)lvalue[0]/lvalue[1]);
case TIFFTag.TIFF_ASCII:
String s = ((String[])data)[index];
return (int)Double.parseDouble(s);
default:
throw new ClassCastException(); // should never happen
}
}
/**
* Returns data in any format as a {@code long}.
*
* <p> {@code TIFFTag.TIFF_BYTE} and {@code TIFF_UNDEFINED} data
* are treated as unsigned; that is, no sign extension will take
* place and the returned value will be in the range [0, 255].
* {@code TIFF_SBYTE} data will be returned in the range
* [-128, 127].
*
* <p> Data in {@code TIFF_FLOAT} and {@code TIFF_DOUBLE} are
* simply cast to {@code long} and may suffer from truncation.
*
* <p> Data in {@code TIFF_SRATIONAL} or
* {@code TIFF_RATIONAL} format are evaluated by dividing the
* numerator into the denominator using double-precision
* arithmetic and then casting to {@code long}. Loss of
* precision and truncation may occur.
*
* <p> Data in {@code TIFF_ASCII} format will be parsed as by
* the {@code Double.parseDouble} method, with the result
* cast to {@code long}.
*
* @param index The index of the data.
* @return The data at the given index as a {@code long}.
*/
public long getAsLong(int index) {
switch (type) {
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_UNDEFINED:
return ((byte[])data)[index] & 0xff;
case TIFFTag.TIFF_SBYTE:
return ((byte[])data)[index];
case TIFFTag.TIFF_SHORT:
return ((char[])data)[index] & 0xffff;
case TIFFTag.TIFF_SSHORT:
return ((short[])data)[index];
case TIFFTag.TIFF_SLONG:
return ((int[])data)[index];
case TIFFTag.TIFF_LONG:
case TIFFTag.TIFF_IFD_POINTER:
return ((long[])data)[index];
case TIFFTag.TIFF_FLOAT:
return (long)((float[])data)[index];
case TIFFTag.TIFF_DOUBLE:
return (long)((double[])data)[index];
case TIFFTag.TIFF_SRATIONAL:
int[] ivalue = getAsSRational(index);
return (long)((double)ivalue[0]/ivalue[1]);
case TIFFTag.TIFF_RATIONAL:
long[] lvalue = getAsRational(index);
return (long)((double)lvalue[0]/lvalue[1]);
case TIFFTag.TIFF_ASCII:
String s = ((String[])data)[index];
return (long)Double.parseDouble(s);
default:
throw new ClassCastException(); // should never happen
}
}
/**
* Returns data in any format as a {@code float}.
*
* <p> {@code TIFFTag.TIFF_BYTE} and {@code TIFF_UNDEFINED} data
* are treated as unsigned; that is, no sign extension will take
* place and the returned value will be in the range [0, 255].
* {@code TIFF_SBYTE} data will be returned in the range
* [-128, 127].
*
* <p> Data in {@code TIFF_SLONG}, {@code TIFF_LONG},
* {@code TIFF_DOUBLE}, or {@code TIFF_IFD_POINTER} format are
* simply cast to {@code float} and may suffer from
* truncation.
*
* <p> Data in {@code TIFF_SRATIONAL} or
* {@code TIFF_RATIONAL} format are evaluated by dividing the
* numerator into the denominator using double-precision
* arithmetic and then casting to {@code float}.
*
* <p> Data in {@code TIFF_ASCII} format will be parsed as by
* the {@code Double.parseDouble} method, with the result
* cast to {@code float}.
*
* @param index The index of the data.
* @return The data at the given index as a {@code float}.
*/
public float getAsFloat(int index) {
switch (type) {
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_UNDEFINED:
return ((byte[])data)[index] & 0xff;
case TIFFTag.TIFF_SBYTE:
return ((byte[])data)[index];
case TIFFTag.TIFF_SHORT:
return ((char[])data)[index] & 0xffff;
case TIFFTag.TIFF_SSHORT:
return ((short[])data)[index];
case TIFFTag.TIFF_SLONG:
return ((int[])data)[index];
case TIFFTag.TIFF_LONG:
case TIFFTag.TIFF_IFD_POINTER:
return ((long[])data)[index];
case TIFFTag.TIFF_FLOAT:
return ((float[])data)[index];
case TIFFTag.TIFF_DOUBLE:
return (float)((double[])data)[index];
case TIFFTag.TIFF_SRATIONAL:
int[] ivalue = getAsSRational(index);
return (float)((double)ivalue[0]/ivalue[1]);
case TIFFTag.TIFF_RATIONAL:
long[] lvalue = getAsRational(index);
return (float)((double)lvalue[0]/lvalue[1]);
case TIFFTag.TIFF_ASCII:
String s = ((String[])data)[index];
return (float)Double.parseDouble(s);
default:
throw new ClassCastException(); // should never happen
}
}
/**
* Returns data in any format as a {@code double}.
*
* <p> {@code TIFFTag.TIFF_BYTE} and {@code TIFF_UNDEFINED} data
* are treated as unsigned; that is, no sign extension will take
* place and the returned value will be in the range [0, 255].
* {@code TIFF_SBYTE} data will be returned in the range
* [-128, 127].
*
* <p> Data in {@code TIFF_SRATIONAL} or
* {@code TIFF_RATIONAL} format are evaluated by dividing the
* numerator into the denominator using double-precision
* arithmetic.
*
* <p> Data in {@code TIFF_ASCII} format will be parsed as by
* the {@code Double.parseDouble} method.
*
* @param index The index of the data.
* @return The data at the given index as a {@code double}.
*/
public double getAsDouble(int index) {
switch (type) {
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_UNDEFINED:
return ((byte[])data)[index] & 0xff;
case TIFFTag.TIFF_SBYTE:
return ((byte[])data)[index];
case TIFFTag.TIFF_SHORT:
return ((char[])data)[index] & 0xffff;
case TIFFTag.TIFF_SSHORT:
return ((short[])data)[index];
case TIFFTag.TIFF_SLONG:
return ((int[])data)[index];
case TIFFTag.TIFF_LONG:
case TIFFTag.TIFF_IFD_POINTER:
return ((long[])data)[index];
case TIFFTag.TIFF_FLOAT:
return ((float[])data)[index];
case TIFFTag.TIFF_DOUBLE:
return ((double[])data)[index];
case TIFFTag.TIFF_SRATIONAL:
int[] ivalue = getAsSRational(index);
return (double)ivalue[0]/ivalue[1];
case TIFFTag.TIFF_RATIONAL:
long[] lvalue = getAsRational(index);
return (double)lvalue[0]/lvalue[1];
case TIFFTag.TIFF_ASCII:
String s = ((String[])data)[index];
return Double.parseDouble(s);
default:
throw new ClassCastException(); // should never happen
}
}
/**
* Returns a {@code TIFFTag.TIFF_ASCII} value as a
* {@code String}.
*
* @throws ClassCastException if the field is not of type
* {@code TIFF_ASCII}.
*
* @param index The index of the data.
* @return The data at the given index as a {@code String}.
*/
public String getAsString(int index) {
return ((String[])data)[index];
}
/**
* Returns a {@code TIFFTag.TIFF_SRATIONAL} data item as a
* two-element array of {@code int}s.
*
* @param index The index of the data.
* @return The data at the given index as a signed rational.
* @throws ClassCastException if the field is not of type
* {@code TIFF_SRATIONAL}.
*/
public int[] getAsSRational(int index) {
return ((int[][])data)[index];
}
/**
* Returns a TIFFTag.TIFF_RATIONAL data item as a two-element array
* of ints.
*
* @param index The index of the data.
* @return The data at the given index as an unsigned rational.
* @throws ClassCastException if the field is not of type
* {@code TIFF_RATIONAL}.
*/
public long[] getAsRational(int index) {
return ((long[][])data)[index];
}
/**
* Returns a {@code String} containing a human-readable
* version of the data item. Data of type
* {@code TIFFTag.TIFF_RATIONAL} or {@code TIFF_SRATIONAL} are
* represented as a pair of integers separated by a
* {@code '/'} character. If the numerator of a
* {@code TIFFTag.TIFF_RATIONAL} or {@code TIFF_SRATIONAL} is an integral
* multiple of the denominator, then the value is represented as
* {@code "q/1"} where {@code q} is the quotient of the numerator and
* denominator.
*
* @param index The index of the data.
* @return The data at the given index as a {@code String}.
* @throws ClassCastException if the field is not of one of the
* legal field types.
*/
public String getValueAsString(int index) {
switch (type) {
case TIFFTag.TIFF_ASCII:
return ((String[])data)[index];
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_UNDEFINED:
return Integer.toString(((byte[])data)[index] & 0xff);
case TIFFTag.TIFF_SBYTE:
return Integer.toString(((byte[])data)[index]);
case TIFFTag.TIFF_SHORT:
return Integer.toString(((char[])data)[index] & 0xffff);
case TIFFTag.TIFF_SSHORT:
return Integer.toString(((short[])data)[index]);
case TIFFTag.TIFF_SLONG:
return Integer.toString(((int[])data)[index]);
case TIFFTag.TIFF_LONG:
case TIFFTag.TIFF_IFD_POINTER:
return Long.toString(((long[])data)[index]);
case TIFFTag.TIFF_FLOAT:
return Float.toString(((float[])data)[index]);
case TIFFTag.TIFF_DOUBLE:
return Double.toString(((double[])data)[index]);
case TIFFTag.TIFF_SRATIONAL:
int[] ivalue = getAsSRational(index);
String srationalString;
if(ivalue[1] != 0 && ivalue[0] % ivalue[1] == 0) {
// If the denominator is a non-zero integral divisor
// of the numerator then convert the fraction to be
// with respect to a unity denominator.
srationalString =
Integer.toString(ivalue[0] / ivalue[1]) + "/1";
} else {
// Use the values directly.
srationalString =
Integer.toString(ivalue[0]) +
"/" +
Integer.toString(ivalue[1]);
}
return srationalString;
case TIFFTag.TIFF_RATIONAL:
long[] lvalue = getAsRational(index);
String rationalString;
if(lvalue[1] != 0L && lvalue[0] % lvalue[1] == 0) {
// If the denominator is a non-zero integral divisor
// of the numerator then convert the fraction to be
// with respect to a unity denominator.
rationalString =
Long.toString(lvalue[0] / lvalue[1]) + "/1";
} else {
// Use the values directly.
rationalString =
Long.toString(lvalue[0]) +
"/" +
Long.toString(lvalue[1]);
}
return rationalString;
default:
throw new ClassCastException(); // should never happen
}
}
/**
* Returns whether the field has a {@code TIFFDirectory}.
*
* @return true if and only if getDirectory() returns non-null.
*/
public boolean hasDirectory() {
return getDirectory() != null;
}
/**
* Returns the associated {@code TIFFDirectory}, if available. If no
* directory is set, then {@code null} will be returned.
*
* @return the TIFFDirectory instance or null.
*/
public TIFFDirectory getDirectory() {
return dir;
}
/**
* Clones the field and all the information contained therein.
*
* @return A clone of this {@code TIFFField}.
* @throws CloneNotSupportedException if the instance cannot be cloned.
*/
@Override
public TIFFField clone() throws CloneNotSupportedException {
TIFFField field = (TIFFField)super.clone();
Object fieldData;
switch (type) {
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_UNDEFINED:
case TIFFTag.TIFF_SBYTE:
fieldData = ((byte[])data).clone();
break;
case TIFFTag.TIFF_SHORT:
fieldData = ((char[])data).clone();
break;
case TIFFTag.TIFF_SSHORT:
fieldData = ((short[])data).clone();
break;
case TIFFTag.TIFF_SLONG:
fieldData = ((int[])data).clone();
break;
case TIFFTag.TIFF_LONG:
case TIFFTag.TIFF_IFD_POINTER:
fieldData = ((long[])data).clone();
break;
case TIFFTag.TIFF_FLOAT:
fieldData = ((float[])data).clone();
break;
case TIFFTag.TIFF_DOUBLE:
fieldData = ((double[])data).clone();
break;
case TIFFTag.TIFF_SRATIONAL:
fieldData = ((int[][])data).clone();
break;
case TIFFTag.TIFF_RATIONAL:
fieldData = ((long[][])data).clone();
break;
case TIFFTag.TIFF_ASCII:
fieldData = ((String[])data).clone();
break;
default:
throw new ClassCastException(); // should never happen
}
field.tag = tag;
field.tagNumber = tagNumber;
field.type = type;
field.count = count;
field.data = fieldData;
field.dir = dir != null ? dir.clone() : null;
return field;
}
}