blob: 8b522da3ed6a1d578bd48e12439336a9a617b40e [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.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import com.sun.imageio.plugins.tiff.TIFFIFD;
import com.sun.imageio.plugins.tiff.TIFFImageMetadata;
/**
* A convenience class for simplifying interaction with TIFF native
* image metadata. A TIFF image metadata tree represents an Image File
* Directory (IFD) from a TIFF 6.0 stream. An IFD consists of a number of
* IFD Entries each of which associates an identifying tag number with
* a compatible value. A {@code TIFFDirectory} instance corresponds
* to an IFD and contains a set of {@link TIFFField}s each of which
* corresponds to an IFD Entry in the IFD.
*
* <p>When reading, a {@code TIFFDirectory} may be created by passing
* the value returned by {@link javax.imageio.ImageReader#getImageMetadata
* ImageReader.getImageMetadata()} to {@link #createFromMetadata
* createFromMetadata()}. The {@link TIFFField}s in the directory may then
* be obtained using the accessor methods provided in this class.</p>
*
* <p>When writing, an {@link IIOMetadata} object for use by one of the
* {@code write()} methods of {@link javax.imageio.ImageWriter} may be
* created from a {@code TIFFDirectory} by {@link #getAsMetadata()}.
* The {@code TIFFDirectory} itself may be created by construction or
* from the {@code IIOMetadata} object returned by
* {@link javax.imageio.ImageWriter#getDefaultImageMetadata
* ImageWriter.getDefaultImageMetadata()}. The {@code TIFFField}s in the
* directory may be set using the mutator methods provided in this class.</p>
*
* <p>A {@code TIFFDirectory} is aware of the tag numbers in the
* group of {@link TIFFTagSet}s associated with it. When
* a {@code TIFFDirectory} is created from a native image metadata
* object, these tag sets are derived from the {@code tagSets} attribute
* of the {@code TIFFIFD} node.</p>
*
* <p>A {@code TIFFDirectory} might also have a parent {@link TIFFTag}.
* This will occur if the directory represents an IFD other than the root
* IFD of the image. The parent tag is the tag of the IFD Entry which is a
* pointer to the IFD represented by this {@code TIFFDirectory}. The
* {@link TIFFTag#isIFDPointer} method of this parent {@code TIFFTag}
* must return {@code true}. When a {@code TIFFDirectory} is
* created from a native image metadata object, the parent tag set is set
* from the {@code parentTagName} attribute of the corresponding
* {@code TIFFIFD} node. Note that a {@code TIFFDirectory} instance
* which has a non-{@code null} parent tag will be contained in the
* data field of a {@code TIFFField} instance which has a tag field
* equal to the contained directory's parent tag.</p>
*
* <p>As an example consider an Exif image. The {@code TIFFDirectory}
* instance corresponding to the Exif IFD in the Exif stream would have parent
* tag {@link ExifParentTIFFTagSet#TAG_EXIF_IFD_POINTER TAG_EXIF_IFD_POINTER}
* and would include {@link ExifTIFFTagSet} in its group of known tag sets.
* The {@code TIFFDirectory} corresponding to this Exif IFD will be
* contained in the data field of a {@code TIFFField} which will in turn
* be contained in the {@code TIFFDirectory} corresponding to the primary
* IFD of the Exif image which will itself have a {@code null}-valued
* parent tag.</p>
*
* <p><b>Note that this implementation is not synchronized. </b>If multiple
* threads use a {@code TIFFDirectory} instance concurrently, and at
* least one of the threads modifies the directory, for example, by adding
* or removing {@code TIFFField}s or {@code TIFFTagSet}s, it
* <i>must</i> be synchronized externally.</p>
*
* @since 9
* @see IIOMetadata
* @see TIFFField
* @see TIFFTag
* @see TIFFTagSet
*/
public class TIFFDirectory implements Cloneable {
/** The largest low-valued tag number in the TIFF 6.0 specification. */
private static final int MAX_LOW_FIELD_TAG_NUM =
BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE;
/** The {@code TIFFTagSets} associated with this directory. */
private List<TIFFTagSet> tagSets;
/** The parent {@code TIFFTag} of this directory. */
private TIFFTag parentTag;
/**
* The fields in this directory which have a low tag number. These are
* managed as an array for efficiency as they are the most common fields.
*/
private TIFFField[] lowFields = new TIFFField[MAX_LOW_FIELD_TAG_NUM + 1];
/** The number of low tag numbered fields in the directory. */
private int numLowFields = 0;
/**
* A mapping of {@code Integer} tag numbers to {@code TIFFField}s
* for fields which are not low tag numbered.
*/
private Map<Integer,TIFFField> highFields = new TreeMap<Integer,TIFFField>();
/**
* Creates a {@code TIFFDirectory} instance from the contents of
* an image metadata object. The supplied object must support an image
* metadata format supported by the TIFF {@link javax.imageio.ImageWriter}
* plug-in. This will usually be either the TIFF native image metadata
* format {@code javax_imageio_tiff_image_1.0} or the Java
* Image I/O standard metadata format {@code javax_imageio_1.0}.
*
* @param tiffImageMetadata A metadata object which supports a compatible
* image metadata format.
*
* @return A {@code TIFFDirectory} populated from the contents of
* the supplied metadata object.
*
* @throws NullPointerException if {@code tiffImageMetadata}
* is {@code null}.
* @throws IllegalArgumentException if {@code tiffImageMetadata}
* does not support a compatible image metadata format.
* @throws IIOInvalidTreeException if the supplied metadata object
* cannot be parsed.
*/
public static TIFFDirectory
createFromMetadata(IIOMetadata tiffImageMetadata)
throws IIOInvalidTreeException {
if(tiffImageMetadata == null) {
throw new NullPointerException("tiffImageMetadata == null");
}
TIFFImageMetadata tim;
if(tiffImageMetadata instanceof TIFFImageMetadata) {
tim = (TIFFImageMetadata)tiffImageMetadata;
} else {
// Create a native metadata object.
ArrayList<TIFFTagSet> l = new ArrayList<TIFFTagSet>(1);
l.add(BaselineTIFFTagSet.getInstance());
tim = new TIFFImageMetadata(l);
// Determine the format name to use.
String formatName = null;
if(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME.equals
(tiffImageMetadata.getNativeMetadataFormatName())) {
formatName = TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME;
} else {
String[] extraNames =
tiffImageMetadata.getExtraMetadataFormatNames();
if(extraNames != null) {
for(int i = 0; i < extraNames.length; i++) {
if(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME.equals
(extraNames[i])) {
formatName = extraNames[i];
break;
}
}
}
if(formatName == null) {
if(tiffImageMetadata.isStandardMetadataFormatSupported()) {
formatName =
IIOMetadataFormatImpl.standardMetadataFormatName;
} else {
throw new IllegalArgumentException
("Parameter does not support required metadata format!");
}
}
}
// Set the native metadata object from the tree.
tim.setFromTree(formatName,
tiffImageMetadata.getAsTree(formatName));
}
return tim.getRootIFD();
}
/**
* Constructs a {@code TIFFDirectory} which is aware of a given
* group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag}
* may also be specified.
*
* @param tagSets The {@code TIFFTagSets} associated with this
* directory.
* @param parentTag The parent {@code TIFFTag} of this directory;
* may be {@code null}.
* @throws NullPointerException if {@code tagSets} is
* {@code null}.
*/
public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) {
if(tagSets == null) {
throw new NullPointerException("tagSets == null!");
}
this.tagSets = new ArrayList<TIFFTagSet>(tagSets.length);
int numTagSets = tagSets.length;
for(int i = 0; i < numTagSets; i++) {
this.tagSets.add(tagSets[i]);
}
this.parentTag = parentTag;
}
/**
* Returns the {@link TIFFTagSet}s of which this directory is aware.
*
* @return The {@code TIFFTagSet}s associated with this
* {@code TIFFDirectory}.
*/
public TIFFTagSet[] getTagSets() {
return tagSets.toArray(new TIFFTagSet[tagSets.size()]);
}
/**
* Adds an element to the group of {@link TIFFTagSet}s of which this
* directory is aware.
*
* @param tagSet The {@code TIFFTagSet} to add.
* @throws NullPointerException if {@code tagSet} is
* {@code null}.
*/
public void addTagSet(TIFFTagSet tagSet) {
if(tagSet == null) {
throw new NullPointerException("tagSet == null");
}
if(!tagSets.contains(tagSet)) {
tagSets.add(tagSet);
}
}
/**
* Removes an element from the group of {@link TIFFTagSet}s of which this
* directory is aware.
*
* @param tagSet The {@code TIFFTagSet} to remove.
* @throws NullPointerException if {@code tagSet} is
* {@code null}.
*/
public void removeTagSet(TIFFTagSet tagSet) {
if(tagSet == null) {
throw new NullPointerException("tagSet == null");
}
if(tagSets.contains(tagSet)) {
tagSets.remove(tagSet);
}
}
/**
* Returns the parent {@link TIFFTag} of this directory if one
* has been defined or {@code null} otherwise.
*
* @return The parent {@code TIFFTag} of this
* {@code TIFFDiectory} or {@code null}.
*/
public TIFFTag getParentTag() {
return parentTag;
}
/**
* Returns the {@link TIFFTag} which has tag number equal to
* {@code tagNumber} or {@code null} if no such tag
* exists in the {@link TIFFTagSet}s associated with this
* directory.
*
* @param tagNumber The tag number of interest.
* @return The corresponding {@code TIFFTag} or {@code null}.
*/
public TIFFTag getTag(int tagNumber) {
return TIFFIFD.getTag(tagNumber, tagSets);
}
/**
* Returns the number of {@link TIFFField}s in this directory.
*
* @return The number of {@code TIFFField}s in this
* {@code TIFFDirectory}.
*/
public int getNumTIFFFields() {
return numLowFields + highFields.size();
}
/**
* Determines whether a TIFF field with the given tag number is
* contained in this directory.
*
* @param tagNumber The tag number.
* @return Whether a {@link TIFFTag} with tag number equal to
* {@code tagNumber} is present in this {@code TIFFDirectory}.
*/
public boolean containsTIFFField(int tagNumber) {
return (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM &&
lowFields[tagNumber] != null) ||
highFields.containsKey(Integer.valueOf(tagNumber));
}
/**
* Adds a TIFF field to the directory.
*
* @param f The field to add.
* @throws NullPointerException if {@code f} is {@code null}.
*/
public void addTIFFField(TIFFField f) {
if(f == null) {
throw new NullPointerException("f == null");
}
int tagNumber = f.getTagNumber();
if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
if(lowFields[tagNumber] == null) {
numLowFields++;
}
lowFields[tagNumber] = f;
} else {
highFields.put(Integer.valueOf(tagNumber), f);
}
}
/**
* Retrieves a TIFF field from the directory.
*
* @param tagNumber The tag number of the tag associated with the field.
* @return A {@code TIFFField} with the requested tag number of
* {@code null} if no such field is present.
*/
public TIFFField getTIFFField(int tagNumber) {
TIFFField f;
if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
f = lowFields[tagNumber];
} else {
f = highFields.get(Integer.valueOf(tagNumber));
}
return f;
}
/**
* Removes a TIFF field from the directory.
*
* @param tagNumber The tag number of the tag associated with the field.
*/
public void removeTIFFField(int tagNumber) {
if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
if(lowFields[tagNumber] != null) {
numLowFields--;
lowFields[tagNumber] = null;
}
} else {
highFields.remove(Integer.valueOf(tagNumber));
}
}
/**
* Retrieves all TIFF fields from the directory.
*
* @return An array of all TIFF fields in order of numerically increasing
* tag number.
*/
public TIFFField[] getTIFFFields() {
// Allocate return value.
TIFFField[] fields = new TIFFField[numLowFields + highFields.size()];
// Copy any low-index fields.
int nextIndex = 0;
for(int i = 0; i <= MAX_LOW_FIELD_TAG_NUM; i++) {
if(lowFields[i] != null) {
fields[nextIndex++] = lowFields[i];
if(nextIndex == numLowFields) break;
}
}
// Copy any high-index fields.
if(!highFields.isEmpty()) {
Iterator<Integer> keys = highFields.keySet().iterator();
while(keys.hasNext()) {
fields[nextIndex++] = highFields.get(keys.next());
}
}
return fields;
}
/**
* Removes all TIFF fields from the directory.
*/
public void removeTIFFFields() {
Arrays.fill(lowFields, (Object)null);
numLowFields = 0;
highFields.clear();
}
/**
* Converts the directory to a metadata object.
*
* @return A metadata instance initialized from the contents of this
* {@code TIFFDirectory}.
*/
public IIOMetadata getAsMetadata() {
return new TIFFImageMetadata(TIFFIFD.getDirectoryAsIFD(this));
}
/**
* Clones the directory and all the fields contained therein.
*
* @return A clone of this {@code TIFFDirectory}.
* @throws CloneNotSupportedException if the instance cannot be cloned.
*/
@Override
public TIFFDirectory clone() throws CloneNotSupportedException {
TIFFDirectory dir = (TIFFDirectory) super.clone();
dir.tagSets = new ArrayList<TIFFTagSet>(tagSets);
dir.parentTag = getParentTag();
TIFFField[] fields = getTIFFFields();
for(TIFFField field : fields) {
dir.addTIFFField(field.clone());
}
return dir;
}
}