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

When reading, a 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.

* *

When writing, an {@link IIOMetadata} object for use by one of the * write() methods of {@link javax.imageio.ImageWriter} may be * created from a TIFFDirectory by {@link #getAsMetadata()}. * The TIFFDirectory itself may be created by construction or * from the IIOMetadata object returned by * {@link javax.imageio.ImageWriter#getDefaultImageMetadata * ImageWriter.getDefaultImageMetadata()}. The TIFFFields in the * directory may be set using the mutator methods provided in this class.

* *

A TIFFDirectory is aware of the tag numbers in the * group of {@link TIFFTagSet}s associated with it. When * a TIFFDirectory is created from a native image metadata * object, these tag sets are derived from the tagSets attribute * of the TIFFIFD node.

* *

A 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 TIFFDirectory. The * {@link TIFFTag#isIFDPointer} method of this parent TIFFTag * must return true. When a TIFFDirectory is * created from a native image metadata object, the parent tag set is set * from the parentTagName attribute of the corresponding * TIFFIFD node. Note that a TIFFDirectory instance * which has a non-null parent tag will be contained in the * data field of a TIFFField instance which has a tag field * equal to the contained directory's parent tag.

* *

As an example consider an Exif image. The 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 TIFFDirectory corresponding to this Exif IFD will be * contained in the data field of a TIFFField which will in turn * be contained in the TIFFDirectory corresponding to the primary * IFD of the Exif image which will itself have a null-valued * parent tag.

* *

Note that this implementation is not synchronized. If multiple * threads use a TIFFDirectory instance concurrently, and at * least one of the threads modifies the directory, for example, by adding * or removing TIFFFields or TIFFTagSets, it * must be synchronized externally.

* * @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 TIFFTagSets associated with this directory. */ private List tagSets; /** The parent 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 Integer tag numbers to TIFFFields * for fields which are not low tag numbered. */ private Map highFields = new TreeMap(); /** * Creates a 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 javax_imageio_tiff_image_1.0 or the Java * Image I/O standard metadata format javax_imageio_1.0. * * @param tiffImageMetadata A metadata object which supports a compatible * image metadata format. * * @return A TIFFDirectory populated from the contents of * the supplied metadata object. * * @throws NullPointerException if tiffImageMetadata * is null. * @throws IllegalArgumentException if 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 l = new ArrayList(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(); } /** * Converts a TIFFDirectory to a TIFFIFD. */ private static TIFFIFD getDirectoryAsIFD(TIFFDirectory dir) { if(dir instanceof TIFFIFD) { return (TIFFIFD)dir; } TIFFIFD ifd = new TIFFIFD(Arrays.asList(dir.getTagSets()), dir.getParentTag()); TIFFField[] fields = dir.getTIFFFields(); int numFields = fields.length; for(int i = 0; i < numFields; i++) { TIFFField f = fields[i]; TIFFTag tag = f.getTag(); if(tag.isIFDPointer()) { TIFFDirectory subIFD = getDirectoryAsIFD((TIFFDirectory)f.getData()); f = new TIFFField(tag, f.getType(), (long)f.getCount(), subIFD); } ifd.addTIFFField(f); } return ifd; } /** * Constructs a TIFFDirectory which is aware of a given * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag} * may also be specified. * * @param tagSets The TIFFTagSets associated with this * directory. * @param parentTag The parent TIFFTag of this directory; * may be null. * @throws NullPointerException if tagSets is * null. */ public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) { if(tagSets == null) { throw new NullPointerException("tagSets == null!"); } this.tagSets = new ArrayList(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 TIFFTagSets associated with this * 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 TIFFTagSet to add. * @throws NullPointerException if tagSet is * 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 TIFFTagSet to remove. * @throws NullPointerException if tagSet is * 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 null otherwise. * * @return The parent TIFFTag of this * TIFFDiectory or null. */ public TIFFTag getParentTag() { return parentTag; } /** * Returns the {@link TIFFTag} which has tag number equal to * tagNumber or 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 TIFFTag or 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 TIFFFields in this * 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 * tagNumber is present in this 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 f is 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 TIFFField with the requested tag number of * 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 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 * TIFFDirectory. */ public IIOMetadata getAsMetadata() { return new TIFFImageMetadata(getDirectoryAsIFD(this)); } /** * Clones the directory and all the fields contained therein. * * @return A clone of this TIFFDirectory. * @throws CloneNotSupportedException if the instance cannot be cloned. */ @Override public TIFFDirectory clone() throws CloneNotSupportedException { TIFFDirectory dir = (TIFFDirectory) super.clone(); dir.tagSets = new ArrayList(tagSets); dir.parentTag = getParentTag(); TIFFField[] fields = getTIFFFields(); for(TIFFField field : fields) { dir.addTIFFField(field.clone()); } return dir; } }