1 /* 2 * Copyright (c) 2005, 2016, 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} 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} 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()} methods of {@link javax.imageio.ImageWriter} may be 56 * created from a {@code TIFFDirectory} by {@link #getAsMetadata()}. 57 * The {@code TIFFDirectory} itself may be created by construction or 58 * from the {@code IIOMetadata} object returned by 59 * {@link javax.imageio.ImageWriter#getDefaultImageMetadata 60 * ImageWriter.getDefaultImageMetadata()}. The {@code TIFFField}s in the 61 * directory may be set using the mutator methods provided in this class.</p> 62 * 63 * <p>A {@code TIFFDirectory} is aware of the tag numbers in the 64 * group of {@link TIFFTagSet}s associated with it. When 65 * a {@code TIFFDirectory} 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} 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}. The 73 * {@link TIFFTag#isIFDPointer} method of this parent {@code TIFFTag} 74 * must return {@code true}. When a {@code TIFFDirectory} 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} instance 78 * which has a non-{@code null} parent tag will be contained in the 79 * data field of a {@code TIFFField} 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} 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} corresponding to this Exif IFD will be 87 * contained in the data field of a {@code TIFFField} which will in turn 88 * be contained in the {@code TIFFDirectory} corresponding to the primary 89 * IFD of the Exif image which will itself have a {@code null}-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} instance concurrently, and at 94 * least one of the threads modifies the directory, for example, by adding 95 * or removing {@code TIFFField}s or {@code TIFFTagSet}s, it 96 * <i>must</i> be synchronized externally.</p> 97 * 98 * @since 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} associated with this directory. */ 111 private List<TIFFTagSet> tagSets; 112 113 /** The parent {@code TIFFTag} 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} tag numbers to {@code TIFFField}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} 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} populated from the contents of 143 * the supplied metadata object. 144 * 145 * @throws NullPointerException if {@code tiffImageMetadata} 146 * is {@code null}. 147 * @throws IllegalArgumentException if {@code tiffImageMetadata} 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 * Constructs a {@code TIFFDirectory} which is aware of a given 208 * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag} 209 * may also be specified. 210 * 211 * @param tagSets The {@code TIFFTagSets} associated with this 212 * directory. 213 * @param parentTag The parent {@code TIFFTag} of this directory; 214 * may be {@code null}. 215 * @throws NullPointerException if {@code tagSets} is 216 * {@code null}. 217 */ 218 public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) { 219 if(tagSets == null) { 220 throw new NullPointerException("tagSets == null!"); 221 } 222 this.tagSets = new ArrayList<TIFFTagSet>(tagSets.length); 223 int numTagSets = tagSets.length; 224 for(int i = 0; i < numTagSets; i++) { 225 this.tagSets.add(tagSets[i]); 226 } 227 this.parentTag = parentTag; 228 } 229 230 /** 231 * Returns the {@link TIFFTagSet}s of which this directory is aware. 232 * 233 * @return The {@code TIFFTagSet}s associated with this 234 * {@code TIFFDirectory}. 235 */ 236 public TIFFTagSet[] getTagSets() { 237 return tagSets.toArray(new TIFFTagSet[tagSets.size()]); 238 } 239 240 /** 241 * Adds an element to the group of {@link TIFFTagSet}s of which this 242 * directory is aware. 243 * 244 * @param tagSet The {@code TIFFTagSet} to add. 245 * @throws NullPointerException if {@code tagSet} is 246 * {@code null}. 247 */ 248 public void addTagSet(TIFFTagSet tagSet) { 249 if(tagSet == null) { 250 throw new NullPointerException("tagSet == null"); 251 } 252 253 if(!tagSets.contains(tagSet)) { 254 tagSets.add(tagSet); 255 } 256 } 257 258 /** 259 * Removes an element from the group of {@link TIFFTagSet}s of which this 260 * directory is aware. 261 * 262 * @param tagSet The {@code TIFFTagSet} to remove. 263 * @throws NullPointerException if {@code tagSet} is 264 * {@code null}. 265 */ 266 public void removeTagSet(TIFFTagSet tagSet) { 267 if(tagSet == null) { 268 throw new NullPointerException("tagSet == null"); 269 } 270 271 if(tagSets.contains(tagSet)) { 272 tagSets.remove(tagSet); 273 } 274 } 275 276 /** 277 * Returns the parent {@link TIFFTag} of this directory if one 278 * has been defined or {@code null} otherwise. 279 * 280 * @return The parent {@code TIFFTag} of this 281 * {@code TIFFDiectory} or {@code null}. 282 */ 283 public TIFFTag getParentTag() { 284 return parentTag; 285 } 286 287 /** 288 * Returns the {@link TIFFTag} which has tag number equal to 289 * {@code tagNumber} or {@code null} if no such tag 290 * exists in the {@link TIFFTagSet}s associated with this 291 * directory. 292 * 293 * @param tagNumber The tag number of interest. 294 * @return The corresponding {@code TIFFTag} or {@code null}. 295 */ 296 public TIFFTag getTag(int tagNumber) { 297 return TIFFIFD.getTag(tagNumber, tagSets); 298 } 299 300 /** 301 * Returns the number of {@link TIFFField}s in this directory. 302 * 303 * @return The number of {@code TIFFField}s in this 304 * {@code TIFFDirectory}. 305 */ 306 public int getNumTIFFFields() { 307 return numLowFields + highFields.size(); 308 } 309 310 /** 311 * Determines whether a TIFF field with the given tag number is 312 * contained in this directory. 313 * 314 * @param tagNumber The tag number. 315 * @return Whether a {@link TIFFTag} with tag number equal to 316 * {@code tagNumber} is present in this {@code TIFFDirectory}. 317 */ 318 public boolean containsTIFFField(int tagNumber) { 319 return (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM && 320 lowFields[tagNumber] != null) || 321 highFields.containsKey(Integer.valueOf(tagNumber)); 322 } 323 324 /** 325 * Adds a TIFF field to the directory. 326 * 327 * @param f The field to add. 328 * @throws NullPointerException if {@code f} is {@code null}. 329 */ 330 public void addTIFFField(TIFFField f) { 331 if(f == null) { 332 throw new NullPointerException("f == null"); 333 } 334 int tagNumber = f.getTagNumber(); 335 if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) { 336 if(lowFields[tagNumber] == null) { 337 numLowFields++; 338 } 339 lowFields[tagNumber] = f; 340 } else { 341 highFields.put(Integer.valueOf(tagNumber), f); 342 } 343 } 344 345 /** 346 * Retrieves a TIFF field from the directory. 347 * 348 * @param tagNumber The tag number of the tag associated with the field. 349 * @return A {@code TIFFField} with the requested tag number of 350 * {@code null} if no such field is present. 351 */ 352 public TIFFField getTIFFField(int tagNumber) { 353 TIFFField f; 354 if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) { 355 f = lowFields[tagNumber]; 356 } else { 357 f = highFields.get(Integer.valueOf(tagNumber)); 358 } 359 return f; 360 } 361 362 /** 363 * Removes a TIFF field from the directory. 364 * 365 * @param tagNumber The tag number of the tag associated with the field. 366 */ 367 public void removeTIFFField(int tagNumber) { 368 if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) { 369 if(lowFields[tagNumber] != null) { 370 numLowFields--; 371 lowFields[tagNumber] = null; 372 } 373 } else { 374 highFields.remove(Integer.valueOf(tagNumber)); 375 } 376 } 377 378 /** 379 * Retrieves all TIFF fields from the directory. 380 * 381 * @return An array of all TIFF fields in order of numerically increasing 382 * tag number. 383 */ 384 public TIFFField[] getTIFFFields() { 385 // Allocate return value. 386 TIFFField[] fields = new TIFFField[numLowFields + highFields.size()]; 387 388 // Copy any low-index fields. 389 int nextIndex = 0; 390 for(int i = 0; i <= MAX_LOW_FIELD_TAG_NUM; i++) { 391 if(lowFields[i] != null) { 392 fields[nextIndex++] = lowFields[i]; 393 if(nextIndex == numLowFields) break; 394 } 395 } 396 397 // Copy any high-index fields. 398 if(!highFields.isEmpty()) { 399 Iterator<Integer> keys = highFields.keySet().iterator(); 400 while(keys.hasNext()) { 401 fields[nextIndex++] = highFields.get(keys.next()); 402 } 403 } 404 405 return fields; 406 } 407 408 /** 409 * Removes all TIFF fields from the directory. 410 */ 411 public void removeTIFFFields() { 412 Arrays.fill(lowFields, (Object)null); 413 numLowFields = 0; 414 highFields.clear(); 415 } 416 417 /** 418 * Converts the directory to a metadata object. 419 * 420 * @return A metadata instance initialized from the contents of this 421 * {@code TIFFDirectory}. 422 */ 423 public IIOMetadata getAsMetadata() { 424 return new TIFFImageMetadata(TIFFIFD.getDirectoryAsIFD(this)); 425 } 426 427 /** 428 * Clones the directory and all the fields contained therein. 429 * 430 * @return A clone of this {@code TIFFDirectory}. 431 * @throws CloneNotSupportedException if the instance cannot be cloned. 432 */ 433 @Override 434 public TIFFDirectory clone() throws CloneNotSupportedException { 435 TIFFDirectory dir = (TIFFDirectory) super.clone(); 436 dir.tagSets = new ArrayList<TIFFTagSet>(tagSets); 437 dir.parentTag = getParentTag(); 438 TIFFField[] fields = getTIFFFields(); 439 for(TIFFField field : fields) { 440 dir.addTIFFField(field.clone()); 441 } 442 443 return dir; 444 } 445 }