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