jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFDirectory.java
changeset 34416 68c0d866db5d
child 35302 e4d2275861c3
equal deleted inserted replaced
34415:098d54b4051d 34416:68c0d866db5d
       
     1 /*
       
     2  * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package javax.imageio.plugins.tiff;
       
    26 
       
    27 import java.util.ArrayList;
       
    28 import java.util.Arrays;
       
    29 import java.util.Iterator;
       
    30 import java.util.List;
       
    31 import java.util.Map;
       
    32 import java.util.TreeMap;
       
    33 import javax.imageio.metadata.IIOInvalidTreeException;
       
    34 import javax.imageio.metadata.IIOMetadata;
       
    35 import javax.imageio.metadata.IIOMetadataFormatImpl;
       
    36 import com.sun.imageio.plugins.tiff.TIFFIFD;
       
    37 import com.sun.imageio.plugins.tiff.TIFFImageMetadata;
       
    38 
       
    39 /**
       
    40  * A convenience class for simplifying interaction with TIFF native
       
    41  * image metadata. A TIFF image metadata tree represents an Image File
       
    42  * Directory (IFD) from a TIFF 6.0 stream. An IFD consists of a number of
       
    43  * IFD Entries each of which associates an identifying tag number with
       
    44  * a compatible value. A <code>TIFFDirectory</code> instance corresponds
       
    45  * to an IFD and contains a set of {@link TIFFField}s each of which
       
    46  * corresponds to an IFD Entry in the IFD.
       
    47  *
       
    48  * <p>When reading, a <code>TIFFDirectory</code> may be created by passing
       
    49  * the value returned by {@link javax.imageio.ImageReader#getImageMetadata
       
    50  * ImageReader.getImageMetadata()} to {@link #createFromMetadata
       
    51  * createFromMetadata()}. The {@link TIFFField}s in the directory may then
       
    52  * be obtained using the accessor methods provided in this class.</p>
       
    53  *
       
    54  * <p>When writing, an {@link IIOMetadata} object for use by one of the
       
    55  * <code>write()</code> methods of {@link javax.imageio.ImageWriter} may be
       
    56  * created from a <code>TIFFDirectory</code> by {@link #getAsMetadata()}.
       
    57  * The <code>TIFFDirectory</code> itself may be created by construction or
       
    58  * from the <code>IIOMetadata</code> object returned by
       
    59  * {@link javax.imageio.ImageWriter#getDefaultImageMetadata
       
    60  * ImageWriter.getDefaultImageMetadata()}. The <code>TIFFField</code>s in the
       
    61  * directory may be set using the mutator methods provided in this class.</p>
       
    62  *
       
    63  * <p>A <code>TIFFDirectory</code> is aware of the tag numbers in the
       
    64  * group of {@link TIFFTagSet}s associated with it. When
       
    65  * a <code>TIFFDirectory</code> is created from a native image metadata
       
    66  * object, these tag sets are derived from the <tt>tagSets</tt> attribute
       
    67  * of the <tt>TIFFIFD</tt> node.</p>
       
    68  *
       
    69  * <p>A <code>TIFFDirectory</code> might also have a parent {@link TIFFTag}.
       
    70  * This will occur if the directory represents an IFD other than the root
       
    71  * IFD of the image. The parent tag is the tag of the IFD Entry which is a
       
    72  * pointer to the IFD represented by this <code>TIFFDirectory</code>. The
       
    73  * {@link TIFFTag#isIFDPointer} method of this parent <code>TIFFTag</code>
       
    74  * must return <code>true</code>.  When a <code>TIFFDirectory</code> is
       
    75  * created from a native image metadata object, the parent tag set is set
       
    76  * from the <tt>parentTagName</tt> attribute of the corresponding
       
    77  * <tt>TIFFIFD</tt> node. Note that a <code>TIFFDirectory</code> instance
       
    78  * which has a non-<code>null</code> parent tag will be contained in the
       
    79  * data field of a <code>TIFFField</code> instance which has a tag field
       
    80  * equal to the contained directory's parent tag.</p>
       
    81  *
       
    82  * <p>As an example consider an Exif image. The <code>TIFFDirectory</code>
       
    83  * instance corresponding to the Exif IFD in the Exif stream would have parent
       
    84  * tag {@link ExifParentTIFFTagSet#TAG_EXIF_IFD_POINTER TAG_EXIF_IFD_POINTER}
       
    85  * and would include {@link ExifTIFFTagSet} in its group of known tag sets.
       
    86  * The <code>TIFFDirectory</code> corresponding to this Exif IFD will be
       
    87  * contained in the data field of a <code>TIFFField</code> which will in turn
       
    88  * be contained in the <code>TIFFDirectory</code> corresponding to the primary
       
    89  * IFD of the Exif image which will itself have a <code>null</code>-valued
       
    90  * parent tag.</p>
       
    91  *
       
    92  * <p><b>Note that this implementation is not synchronized. </b>If multiple
       
    93  * threads use a <code>TIFFDirectory</code> instance concurrently, and at
       
    94  * least one of the threads modifies the directory, for example, by adding
       
    95  * or removing <code>TIFFField</code>s or <code>TIFFTagSet</code>s, it
       
    96  * <i>must</i> be synchronized externally.</p>
       
    97  *
       
    98  * @since 1.9
       
    99  * @see   IIOMetadata
       
   100  * @see   TIFFField
       
   101  * @see   TIFFTag
       
   102  * @see   TIFFTagSet
       
   103  */
       
   104 public class TIFFDirectory implements Cloneable {
       
   105 
       
   106     /** The largest low-valued tag number in the TIFF 6.0 specification. */
       
   107     private static final int MAX_LOW_FIELD_TAG_NUM =
       
   108         BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE;
       
   109 
       
   110     /** The <code>TIFFTagSets</code> associated with this directory. */
       
   111     private List<TIFFTagSet> tagSets;
       
   112 
       
   113     /** The parent <code>TIFFTag</code> of this directory. */
       
   114     private TIFFTag parentTag;
       
   115 
       
   116     /**
       
   117      * The fields in this directory which have a low tag number. These are
       
   118      * managed as an array for efficiency as they are the most common fields.
       
   119      */
       
   120     private TIFFField[] lowFields = new TIFFField[MAX_LOW_FIELD_TAG_NUM + 1];
       
   121 
       
   122     /** The number of low tag numbered fields in the directory. */
       
   123     private int numLowFields = 0;
       
   124 
       
   125     /**
       
   126      * A mapping of <code>Integer</code> tag numbers to <code>TIFFField</code>s
       
   127      * for fields which are not low tag numbered.
       
   128      */
       
   129     private Map<Integer,TIFFField> highFields = new TreeMap<Integer,TIFFField>();
       
   130 
       
   131     /**
       
   132      * Creates a <code>TIFFDirectory</code> instance from the contents of
       
   133      * an image metadata object. The supplied object must support an image
       
   134      * metadata format supported by the TIFF {@link javax.imageio.ImageWriter}
       
   135      * plug-in. This will usually be either the TIFF native image metadata
       
   136      * format <tt>javax_imageio_tiff_image_1.0</tt> or the Java
       
   137      * Image I/O standard metadata format <tt>javax_imageio_1.0</tt>.
       
   138      *
       
   139      * @param tiffImageMetadata A metadata object which supports a compatible
       
   140      * image metadata format.
       
   141      *
       
   142      * @return A <code>TIFFDirectory</code> populated from the contents of
       
   143      * the supplied metadata object.
       
   144      *
       
   145      * @throws NullPointerException if <code>tiffImageMetadata</code>
       
   146      * is <code>null</code>.
       
   147      * @throws IllegalArgumentException if <code>tiffImageMetadata</code>
       
   148      * does not support a compatible image metadata format.
       
   149      * @throws IIOInvalidTreeException if the supplied metadata object
       
   150      * cannot be parsed.
       
   151      */
       
   152     public static TIFFDirectory
       
   153         createFromMetadata(IIOMetadata tiffImageMetadata)
       
   154         throws IIOInvalidTreeException {
       
   155 
       
   156         if(tiffImageMetadata == null) {
       
   157             throw new NullPointerException("tiffImageMetadata == null");
       
   158         }
       
   159 
       
   160         TIFFImageMetadata tim;
       
   161         if(tiffImageMetadata instanceof TIFFImageMetadata) {
       
   162             tim = (TIFFImageMetadata)tiffImageMetadata;
       
   163         } else {
       
   164             // Create a native metadata object.
       
   165             ArrayList<TIFFTagSet> l = new ArrayList<TIFFTagSet>(1);
       
   166             l.add(BaselineTIFFTagSet.getInstance());
       
   167             tim = new TIFFImageMetadata(l);
       
   168 
       
   169             // Determine the format name to use.
       
   170             String formatName = null;
       
   171             if(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME.equals
       
   172                (tiffImageMetadata.getNativeMetadataFormatName())) {
       
   173                 formatName = TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME;
       
   174             } else {
       
   175                 String[] extraNames =
       
   176                     tiffImageMetadata.getExtraMetadataFormatNames();
       
   177                 if(extraNames != null) {
       
   178                     for(int i = 0; i < extraNames.length; i++) {
       
   179                         if(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME.equals
       
   180                            (extraNames[i])) {
       
   181                             formatName = extraNames[i];
       
   182                             break;
       
   183                         }
       
   184                     }
       
   185                 }
       
   186 
       
   187                 if(formatName == null) {
       
   188                     if(tiffImageMetadata.isStandardMetadataFormatSupported()) {
       
   189                         formatName =
       
   190                             IIOMetadataFormatImpl.standardMetadataFormatName;
       
   191                     } else {
       
   192                         throw new IllegalArgumentException
       
   193                             ("Parameter does not support required metadata format!");
       
   194                     }
       
   195                 }
       
   196             }
       
   197 
       
   198             // Set the native metadata object from the tree.
       
   199             tim.setFromTree(formatName,
       
   200                             tiffImageMetadata.getAsTree(formatName));
       
   201         }
       
   202 
       
   203         return tim.getRootIFD();
       
   204     }
       
   205 
       
   206     /**
       
   207      * Converts a <code>TIFFDirectory</code> to a <code>TIFFIFD</code>.
       
   208      */
       
   209     private static TIFFIFD getDirectoryAsIFD(TIFFDirectory dir) {
       
   210         if(dir instanceof TIFFIFD) {
       
   211             return (TIFFIFD)dir;
       
   212         }
       
   213 
       
   214         TIFFIFD ifd = new TIFFIFD(Arrays.asList(dir.getTagSets()),
       
   215                                   dir.getParentTag());
       
   216         TIFFField[] fields = dir.getTIFFFields();
       
   217         int numFields = fields.length;
       
   218         for(int i = 0; i < numFields; i++) {
       
   219             TIFFField f = fields[i];
       
   220             TIFFTag tag = f.getTag();
       
   221             if(tag.isIFDPointer()) {
       
   222                 TIFFDirectory subIFD =
       
   223                     getDirectoryAsIFD((TIFFDirectory)f.getData());
       
   224                 f = new TIFFField(tag, f.getType(), (long)f.getCount(), subIFD);
       
   225             }
       
   226             ifd.addTIFFField(f);
       
   227         }
       
   228 
       
   229         return ifd;
       
   230     }
       
   231 
       
   232     /**
       
   233      * Constructs a <code>TIFFDirectory</code> which is aware of a given
       
   234      * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag}
       
   235      * may also be specified.
       
   236      *
       
   237      * @param tagSets The <code>TIFFTagSets</code> associated with this
       
   238      * directory.
       
   239      * @param parentTag The parent <code>TIFFTag</code> of this directory;
       
   240      * may be <code>null</code>.
       
   241      * @throws NullPointerException if <code>tagSets</code> is
       
   242      * <code>null</code>.
       
   243      */
       
   244     public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) {
       
   245         if(tagSets == null) {
       
   246             throw new NullPointerException("tagSets == null!");
       
   247         }
       
   248         this.tagSets = new ArrayList<TIFFTagSet>(tagSets.length);
       
   249         int numTagSets = tagSets.length;
       
   250         for(int i = 0; i < numTagSets; i++) {
       
   251             this.tagSets.add(tagSets[i]);
       
   252         }
       
   253         this.parentTag = parentTag;
       
   254     }
       
   255 
       
   256     /**
       
   257      * Returns the {@link TIFFTagSet}s of which this directory is aware.
       
   258      *
       
   259      * @return The <code>TIFFTagSet</code>s associated with this
       
   260      * <code>TIFFDirectory</code>.
       
   261      */
       
   262     public TIFFTagSet[] getTagSets() {
       
   263         return tagSets.toArray(new TIFFTagSet[tagSets.size()]);
       
   264     }
       
   265 
       
   266     /**
       
   267      * Adds an element to the group of {@link TIFFTagSet}s of which this
       
   268      * directory is aware.
       
   269      *
       
   270      * @param tagSet The <code>TIFFTagSet</code> to add.
       
   271      * @throws NullPointerException if <code>tagSet</code> is
       
   272      * <code>null</code>.
       
   273      */
       
   274     public void addTagSet(TIFFTagSet tagSet) {
       
   275         if(tagSet == null) {
       
   276             throw new NullPointerException("tagSet == null");
       
   277         }
       
   278 
       
   279         if(!tagSets.contains(tagSet)) {
       
   280             tagSets.add(tagSet);
       
   281         }
       
   282     }
       
   283 
       
   284     /**
       
   285      * Removes an element from the group of {@link TIFFTagSet}s of which this
       
   286      * directory is aware.
       
   287      *
       
   288      * @param tagSet The <code>TIFFTagSet</code> to remove.
       
   289      * @throws NullPointerException if <code>tagSet</code> is
       
   290      * <code>null</code>.
       
   291      */
       
   292     public void removeTagSet(TIFFTagSet tagSet) {
       
   293         if(tagSet == null) {
       
   294             throw new NullPointerException("tagSet == null");
       
   295         }
       
   296 
       
   297         if(tagSets.contains(tagSet)) {
       
   298             tagSets.remove(tagSet);
       
   299         }
       
   300     }
       
   301 
       
   302     /**
       
   303      * Returns the parent {@link TIFFTag} of this directory if one
       
   304      * has been defined or <code>null</code> otherwise.
       
   305      *
       
   306      * @return The parent <code>TIFFTag</code> of this
       
   307      * <code>TIFFDiectory</code> or <code>null</code>.
       
   308      */
       
   309     public TIFFTag getParentTag() {
       
   310         return parentTag;
       
   311     }
       
   312 
       
   313     /**
       
   314      * Returns the {@link TIFFTag} which has tag number equal to
       
   315      * <code>tagNumber</code> or <code>null</code> if no such tag
       
   316      * exists in the {@link TIFFTagSet}s associated with this
       
   317      * directory.
       
   318      *
       
   319      * @param tagNumber The tag number of interest.
       
   320      * @return The corresponding <code>TIFFTag</code> or <code>null</code>.
       
   321      */
       
   322     public TIFFTag getTag(int tagNumber) {
       
   323         return TIFFIFD.getTag(tagNumber, tagSets);
       
   324     }
       
   325 
       
   326     /**
       
   327      * Returns the number of {@link TIFFField}s in this directory.
       
   328      *
       
   329      * @return The number of <code>TIFFField</code>s in this
       
   330      * <code>TIFFDirectory</code>.
       
   331      */
       
   332     public int getNumTIFFFields() {
       
   333         return numLowFields + highFields.size();
       
   334     }
       
   335 
       
   336     /**
       
   337      * Determines whether a TIFF field with the given tag number is
       
   338      * contained in this directory.
       
   339      *
       
   340      * @param tagNumber The tag number.
       
   341      * @return Whether a {@link TIFFTag} with tag number equal to
       
   342      * <code>tagNumber</code> is present in this <code>TIFFDirectory</code>.
       
   343      */
       
   344     public boolean containsTIFFField(int tagNumber) {
       
   345         return (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM &&
       
   346                 lowFields[tagNumber] != null) ||
       
   347             highFields.containsKey(Integer.valueOf(tagNumber));
       
   348     }
       
   349 
       
   350     /**
       
   351      * Adds a TIFF field to the directory.
       
   352      *
       
   353      * @param f The field to add.
       
   354      * @throws NullPointerException if <code>f</code> is <code>null</code>.
       
   355      */
       
   356     public void addTIFFField(TIFFField f) {
       
   357         if(f == null) {
       
   358             throw new NullPointerException("f == null");
       
   359         }
       
   360         int tagNumber = f.getTagNumber();
       
   361         if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
       
   362             if(lowFields[tagNumber] == null) {
       
   363                 numLowFields++;
       
   364             }
       
   365             lowFields[tagNumber] = f;
       
   366         } else {
       
   367             highFields.put(Integer.valueOf(tagNumber), f);
       
   368         }
       
   369     }
       
   370 
       
   371     /**
       
   372      * Retrieves a TIFF field from the directory.
       
   373      *
       
   374      * @param tagNumber The tag number of the tag associated with the field.
       
   375      * @return A <code>TIFFField</code> with the requested tag number of
       
   376      * <code>null</code> if no such field is present.
       
   377      */
       
   378     public TIFFField getTIFFField(int tagNumber) {
       
   379         TIFFField f;
       
   380         if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
       
   381             f = lowFields[tagNumber];
       
   382         } else {
       
   383             f = highFields.get(Integer.valueOf(tagNumber));
       
   384         }
       
   385         return f;
       
   386     }
       
   387 
       
   388     /**
       
   389      * Removes a TIFF field from the directory.
       
   390      *
       
   391      * @param tagNumber The tag number of the tag associated with the field.
       
   392      */
       
   393     public void removeTIFFField(int tagNumber) {
       
   394         if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
       
   395             if(lowFields[tagNumber] != null) {
       
   396                 numLowFields--;
       
   397                 lowFields[tagNumber] = null;
       
   398             }
       
   399         } else {
       
   400             highFields.remove(Integer.valueOf(tagNumber));
       
   401         }
       
   402     }
       
   403 
       
   404     /**
       
   405      * Retrieves all TIFF fields from the directory.
       
   406      *
       
   407      * @return An array of all TIFF fields in order of numerically increasing
       
   408      * tag number.
       
   409      */
       
   410     public TIFFField[] getTIFFFields() {
       
   411         // Allocate return value.
       
   412         TIFFField[] fields = new TIFFField[numLowFields + highFields.size()];
       
   413 
       
   414         // Copy any low-index fields.
       
   415         int nextIndex = 0;
       
   416         for(int i = 0; i <= MAX_LOW_FIELD_TAG_NUM; i++) {
       
   417             if(lowFields[i] != null) {
       
   418                 fields[nextIndex++] = lowFields[i];
       
   419                 if(nextIndex == numLowFields) break;
       
   420             }
       
   421         }
       
   422 
       
   423         // Copy any high-index fields.
       
   424         if(!highFields.isEmpty()) {
       
   425             Iterator<Integer> keys = highFields.keySet().iterator();
       
   426             while(keys.hasNext()) {
       
   427                 fields[nextIndex++] = highFields.get(keys.next());
       
   428             }
       
   429         }
       
   430 
       
   431         return fields;
       
   432     }
       
   433 
       
   434     /**
       
   435      * Removes all TIFF fields from the directory.
       
   436      */
       
   437     public void removeTIFFFields() {
       
   438         Arrays.fill(lowFields, (Object)null);
       
   439         numLowFields = 0;
       
   440         highFields.clear();
       
   441     }
       
   442 
       
   443     /**
       
   444      * Converts the directory to a metadata object.
       
   445      *
       
   446      * @return A metadata instance initialized from the contents of this
       
   447      * <code>TIFFDirectory</code>.
       
   448      */
       
   449     public IIOMetadata getAsMetadata() {
       
   450         return new TIFFImageMetadata(getDirectoryAsIFD(this));
       
   451     }
       
   452 
       
   453     /**
       
   454      * Clones the directory and all the fields contained therein.
       
   455      *
       
   456      * @return A clone of this <code>TIFFDirectory</code>.
       
   457      * @throws CloneNotSupportedException if the instance cannot be cloned.
       
   458      */
       
   459     @Override
       
   460     public TIFFDirectory clone() throws CloneNotSupportedException {
       
   461         TIFFDirectory dir = (TIFFDirectory) super.clone();
       
   462         dir.tagSets = new ArrayList<TIFFTagSet>(tagSets);
       
   463         dir.parentTag = getParentTag();
       
   464         TIFFField[] fields = getTIFFFields();
       
   465         for(TIFFField field : fields) {
       
   466             dir.addTIFFField(field.clone());
       
   467         }
       
   468 
       
   469         return dir;
       
   470     }
       
   471 }