/* * Copyright (c) 2005, 2016, 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. * *

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: * *
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
TIFF Data Type to Java Data Type Mapping
* TIFF Data Type * * Java Constant * * Java Data Type * * Java Type Name *
* BYTE * * {@link TIFFTag#TIFF_BYTE} * * {@code byte} * * {@code "Byte"} *
* ASCII * * {@link TIFFTag#TIFF_ASCII} * * {@code String} * * {@code "Ascii"} *
* SHORT * * {@link TIFFTag#TIFF_SHORT} * * {@code char} * * {@code "Short"} *
* LONG * * {@link TIFFTag#TIFF_LONG} * * {@code long} * * {@code "Long"} *
* RATIONAL * * {@link TIFFTag#TIFF_RATIONAL} * * {@code long[2]} {numerator, denominator} * * {@code "Rational"} *
* SBYTE * * {@link TIFFTag#TIFF_SBYTE} * * {@code byte} * * {@code "SByte"} *
* UNDEFINED * * {@link TIFFTag#TIFF_UNDEFINED} * * {@code byte} * * {@code "Undefined"} *
* SSHORT * * {@link TIFFTag#TIFF_SSHORT} * * {@code short} * * {@code "SShort"} *
* SLONG * * {@link TIFFTag#TIFF_SLONG} * * {@code int} * * {@code "SLong"} *
* SRATIONAL * * {@link TIFFTag#TIFF_SRATIONAL} * * {@code int[2]} {numerator, denominator} * * {@code "SRational"} *
* FLOAT * * {@link TIFFTag#TIFF_FLOAT} * * {@code float} * * {@code "Float"} *
* DOUBLE * * {@link TIFFTag#TIFF_DOUBLE} * * {@code double} * * {@code "Double"} *
* IFD * * {@link TIFFTag#TIFF_IFD_POINTER} * * {@code long} * * {@code "IFDPointer"} *
* * @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 * TIFF native image metadata format specification, 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. * *

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 IFD Entry 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 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 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 * "TIFFField" or "TIFFIFD" as described in the * TIFF native image metadata specification. The node will be named * "TIFFIFD" 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 "TIFFField" or * "TIFFIFD". */ 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}. * *

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}. * *

{@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]. * *

A {@code TIFF_UNDEFINED} value is treated as though * it were a {@code TIFF_BYTE}. * *

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. * *

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. * *

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}. * *

{@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]. * *

Data in {@code TIFF_FLOAT} and {@code TIFF_DOUBLE} are * simply cast to {@code long} and may suffer from truncation. * *

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. * *

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}. * *

{@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]. * *

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. * *

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}. * *

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}. * *

{@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]. * *

Data in {@code TIFF_SRATIONAL} or * {@code TIFF_RATIONAL} format are evaluated by dividing the * numerator into the denominator using double-precision * arithmetic. * *

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; } }