001/* 002 * $RCSfile: TIFFDirectory.java,v $ 003 * 004 * 005 * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without 008 * modification, are permitted provided that the following conditions 009 * are met: 010 * 011 * - Redistribution of source code must retain the above copyright 012 * notice, this list of conditions and the following disclaimer. 013 * 014 * - Redistribution in binary form must reproduce the above copyright 015 * notice, this list of conditions and the following disclaimer in 016 * the documentation and/or other materials provided with the 017 * distribution. 018 * 019 * Neither the name of Sun Microsystems, Inc. or the names of 020 * contributors may be used to endorse or promote products derived 021 * from this software without specific prior written permission. 022 * 023 * This software is provided "AS IS," without a warranty of any 024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY 027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS 030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, 032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND 033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR 034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE 035 * POSSIBILITY OF SUCH DAMAGES. 036 * 037 * You acknowledge that this software is not designed or intended for 038 * use in the design, construction, operation or maintenance of any 039 * nuclear facility. 040 * 041 * $Revision: 1.4 $ 042 * $Date: 2006/08/25 00:16:49 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.plugins.tiff; 046 047import java.util.ArrayList; 048import java.util.Arrays; 049import java.util.Iterator; 050import java.util.List; 051import java.util.Map; 052import java.util.TreeMap; 053 054import javax.imageio.metadata.IIOInvalidTreeException; 055import javax.imageio.metadata.IIOMetadata; 056import javax.imageio.metadata.IIOMetadataFormatImpl; 057 058import com.github.jaiimageio.impl.plugins.tiff.TIFFIFD; 059import com.github.jaiimageio.impl.plugins.tiff.TIFFImageMetadata; 060 061/** 062 * A convenience class for simplifying interaction with TIFF native 063 * image metadata. A TIFF image metadata tree represents an Image File 064 * Directory (IFD) from a TIFF 6.0 stream. An IFD consists of a number of 065 * IFD Entries each of which associates an identifying tag number with 066 * a compatible value. A <code>TIFFDirectory</code> instance corresponds 067 * to an IFD and contains a set of {@link TIFFField}s each of which 068 * corresponds to an IFD Entry in the IFD. 069 * 070 * <p>When reading, a <code>TIFFDirectory</code> may be created by passing 071 * the value returned by {@link javax.imageio.ImageReader#getImageMetadata 072 * ImageReader.getImageMetadata()} to {@link #createFromMetadata 073 * createFromMetadata()}. The {@link TIFFField}s in the directory may then 074 * be obtained using the accessor methods provided in this class.</p> 075 * 076 * <p>When writing, an {@link IIOMetadata} object for use by one of the 077 * <core>write()</code> methods of {@link javax.imageio.ImageWriter} may be 078 * created from a <code>TIFFDirectory</code> by {@link #getAsMetadata()}. 079 * The <code>TIFFDirectory</code> itself may be created by construction or 080 * from the <code>IIOMetadata</code> object returned by 081 * {@link javax.imageio.ImageWriter#getDefaultImageMetadata 082 * ImageWriter.getDefaultImageMetadata()}. The <code>TIFFField</code>s in the 083 * directory may be set using the mutator methods provided in this class.</p> 084 * 085 * <p>A <code>TIFFDirectory</code> is aware of the tag numbers in the 086 * group of {@link TIFFTagSet}s associated with it. When 087 * a <code>TIFFDirectory</code> is created from a native image metadata 088 * object, these tag sets are derived from the <tt>tagSets</tt> attribute 089 * of the <tt>TIFFIFD</tt> node.</p> 090 * 091 * <p>A <code>TIFFDirectory</code> might also have a parent {@link TIFFTag}. 092 * This will occur if the directory represents an IFD other than the root 093 * IFD of the image. The parent tag is the tag of the IFD Entry which is a 094 * pointer to the IFD represented by this <code>TIFFDirectory</code>. The 095 * {@link TIFFTag#isIFDPointer} method of this parent <code>TIFFTag</code> 096 * must return <code>true</code>. When a <code>TIFFDirectory</code> is 097 * created from a native image metadata object, the parent tag set is set 098 * from the <tt>parentTagName</tt> attribute of the corresponding 099 * <tt>TIFFIFD</tt> node. Note that a <code>TIFFDirectory</code> instance 100 * which has a non-<code>null</code> parent tag will be contained in the 101 * data field of a <code>TIFFField</code> instance which has a tag field 102 * equal to the contained directory's parent tag.</p> 103 * 104 * <p>As an example consider an EXIF image. The <code>TIFFDirectory</code> 105 * instance corresponding to the EXIF IFD in the EXIF stream would have parent 106 * tag {@link EXIFParentTIFFTagSet#TAG_EXIF_IFD_POINTER TAG_EXIF_IFD_POINTER} 107 * and would include {@link EXIFTIFFTagSet} in its group of known tag sets. 108 * The <code>TIFFDirectory</code> corresponding to this EXIF IFD will be 109 * contained in the data field of a <code>TIFFField</code> which will in turn 110 * be contained in the <code>TIFFDirectory</code> corresponding to the primary 111 * IFD of the EXIF image which will itself have a <code>null</code>-valued 112 * parent tag.</p> 113 * 114 * <p><b>Note that this implementation is not synchronized.</b> If multiple 115 * threads use a <code>TIFFDirectory</code> instance concurrently, and at 116 * least one of the threads modifies the directory, for example, by adding 117 * or removing <code>TIFFField</code>s or <code>TIFFTagSet</code>s, it 118 * <i>must</i> be synchronized externally.</p> 119 * 120 * @see IIOMetadata 121 * @see TIFFField 122 * @see TIFFTag 123 * @see TIFFTagSet 124 * 125 * @since 1.1-beta 126 */ 127// XXX doc: not thread safe 128public class TIFFDirectory implements Cloneable { 129 130 /** The largest low-valued tag number in the TIFF 6.0 specification. */ 131 private static final int MAX_LOW_FIELD_TAG_NUM = 132 BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE; 133 134 /** The <code>TIFFTagSets</code> associated with this directory. */ 135 private List tagSets; 136 137 /** The parent <code>TIFFTag</code> of this directory. */ 138 private TIFFTag parentTag; 139 140 /** 141 * The fields in this directory which have a low tag number. These are 142 * managed as an array for efficiency as they are the most common fields. 143 */ 144 private TIFFField[] lowFields = new TIFFField[MAX_LOW_FIELD_TAG_NUM + 1]; 145 146 /** The number of low tag numbered fields in the directory. */ 147 private int numLowFields = 0; 148 149 /** 150 * A mapping of <code>Integer</code> tag numbers to <code>TIFFField</code>s 151 * for fields which are not low tag numbered. 152 */ 153 private Map highFields = new TreeMap(); 154 155 /** 156 * Creates a <code>TIFFDirectory</code> instance from the contents of 157 * an image metadata object. The supplied object must support an image 158 * metadata format supported by the TIFF {@link javax.imageio.ImageWriter} 159 * plug-in. This will usually be either the TIFF native image metadata 160 * format <tt>com_sun_media_imageio_plugins_tiff_1.0</tt> or the Java 161 * Image I/O standard metadata format <tt>javax_imageio_1.0</tt>. 162 * 163 * @param tiffImageMetadata A metadata object which supports a compatible 164 * image metadata format. 165 * 166 * @return A <code>TIFFDirectory</code> populated from the contents of 167 * the supplied metadata object. 168 * 169 * @throws IllegalArgumentException if <code>tiffImageMetadata</code> 170 * is <code>null</code>. 171 * @throws IllegalArgumentException if <code>tiffImageMetadata</code> 172 * does not support a compatible image metadata format. 173 * @throws IIOInvalidTreeException if the supplied metadata object 174 * cannot be parsed. 175 */ 176 public static TIFFDirectory 177 createFromMetadata(IIOMetadata tiffImageMetadata) 178 throws IIOInvalidTreeException { 179 180 if(tiffImageMetadata == null) { 181 throw new IllegalArgumentException("tiffImageMetadata == null"); 182 } 183 184 TIFFImageMetadata tim; 185 if(tiffImageMetadata instanceof TIFFImageMetadata) { 186 tim = (TIFFImageMetadata)tiffImageMetadata; 187 } else { 188 // Create a native metadata object. 189 ArrayList l = new ArrayList(1); 190 l.add(BaselineTIFFTagSet.getInstance()); 191 tim = new TIFFImageMetadata(l); 192 193 // Determine the format name to use. 194 String formatName = null; 195 if(TIFFImageMetadata.nativeMetadataFormatName.equals 196 (tiffImageMetadata.getNativeMetadataFormatName())) { 197 formatName = TIFFImageMetadata.nativeMetadataFormatName; 198 } else { 199 String[] extraNames = 200 tiffImageMetadata.getExtraMetadataFormatNames(); 201 if(extraNames != null) { 202 for(int i = 0; i < extraNames.length; i++) { 203 if(TIFFImageMetadata.nativeMetadataFormatName.equals 204 (extraNames[i])) { 205 formatName = extraNames[i]; 206 break; 207 } 208 } 209 } 210 211 if(formatName == null) { 212 if(tiffImageMetadata.isStandardMetadataFormatSupported()) { 213 formatName = 214 IIOMetadataFormatImpl.standardMetadataFormatName; 215 } else { 216 throw new IllegalArgumentException 217 ("Parameter does not support required metadata format!"); 218 } 219 } 220 } 221 222 // Set the native metadata object from the tree. 223 tim.setFromTree(formatName, 224 tiffImageMetadata.getAsTree(formatName)); 225 } 226 227 return tim.getRootIFD(); 228 } 229 230 /** 231 * Converts a <code>TIFFDirectory</code> to a <code>TIFFIFD</code>. 232 */ 233 private static TIFFIFD getDirectoryAsIFD(TIFFDirectory dir) { 234 if(dir instanceof TIFFIFD) { 235 return (TIFFIFD)dir; 236 } 237 238 TIFFIFD ifd = new TIFFIFD(Arrays.asList(dir.getTagSets()), 239 dir.getParentTag()); 240 TIFFField[] fields = dir.getTIFFFields(); 241 int numFields = fields.length; 242 for(int i = 0; i < numFields; i++) { 243 TIFFField f = fields[i]; 244 TIFFTag tag = f.getTag(); 245 if(tag.isIFDPointer()) { 246 TIFFDirectory subIFD = 247 getDirectoryAsIFD((TIFFDirectory)f.getData()); 248 f = new TIFFField(tag, f.getType(), f.getCount(), subIFD); 249 } 250 ifd.addTIFFField(f); 251 } 252 253 return ifd; 254 } 255 256 /** 257 * Constructs a <code>TIFFDirectory</code> which is aware of a given 258 * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag} 259 * may also be specified. 260 * 261 * @param tagSets The <code>TIFFTagSets</code> associated with this 262 * directory. 263 * @param parentTag The parent <code>TIFFTag</code> of this directory; 264 * may be <code>null</code>. 265 * @throws IllegalArgumentException if <code>tagSets</code> is 266 * <code>null</code>. 267 */ 268 public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) { 269 if(tagSets == null) { 270 throw new IllegalArgumentException("tagSets == null!"); 271 } 272 this.tagSets = new ArrayList(tagSets.length); 273 int numTagSets = tagSets.length; 274 for(int i = 0; i < numTagSets; i++) { 275 this.tagSets.add(tagSets[i]); 276 } 277 this.parentTag = parentTag; 278 } 279 280 /** 281 * Returns the {@link TIFFTagSet}s of which this directory is aware. 282 * 283 * @return The <code>TIFFTagSet</code>s associated with this 284 * <code>TIFFDirectory</code>. 285 */ 286 public TIFFTagSet[] getTagSets() { 287 return (TIFFTagSet[])tagSets.toArray(new TIFFTagSet[tagSets.size()]); 288 } 289 290 /** 291 * Adds an element to the group of {@link TIFFTagSet}s of which this 292 * directory is aware. 293 * 294 * @param tagSet The <code>TIFFTagSet</code> to add. 295 * @throws IllegalArgumentException if <code>tagSet</code> is 296 * <code>null</code>. 297 */ 298 public void addTagSet(TIFFTagSet tagSet) { 299 if(tagSet == null) { 300 throw new IllegalArgumentException("tagSet == null"); 301 } 302 303 if(!tagSets.contains(tagSet)) { 304 tagSets.add(tagSet); 305 } 306 } 307 308 /** 309 * Removes an element from the group of {@link TIFFTagSet}s of which this 310 * directory is aware. 311 * 312 * @param tagSet The <code>TIFFTagSet</code> to remove. 313 * @throws IllegalArgumentException if <code>tagSet</code> is 314 * <code>null</code>. 315 */ 316 public void removeTagSet(TIFFTagSet tagSet) { 317 if(tagSet == null) { 318 throw new IllegalArgumentException("tagSet == null"); 319 } 320 321 if(tagSets.contains(tagSet)) { 322 tagSets.remove(tagSet); 323 } 324 } 325 326 /** 327 * Returns the parent {@link TIFFTag} of this directory if one 328 * has been defined or <code>null</code> otherwise. 329 * 330 * @return The parent <code>TIFFTag</code> of this 331 * <code>TIFFDiectory</code> or <code>null</code>. 332 */ 333 public TIFFTag getParentTag() { 334 return parentTag; 335 } 336 337 /** 338 * Returns the {@link TIFFTag} which has tag number equal to 339 * <code>tagNumber</code> or <code>null</code> if no such tag 340 * exists in the {@link TIFFTagSet}s associated with this 341 * directory. 342 * 343 * @param tagNumber The tag number of interest. 344 * @return The corresponding <code>TIFFTag</code> or <code>null</code>. 345 */ 346 public TIFFTag getTag(int tagNumber) { 347 return TIFFIFD.getTag(tagNumber, tagSets); 348 } 349 350 /** 351 * Returns the number of {@link TIFFField}s in this directory. 352 * 353 * @return The number of <code>TIFFField</code>s in this 354 * <code>TIFFDirectory</code>. 355 */ 356 public int getNumTIFFFields() { 357 return numLowFields + highFields.size(); 358 } 359 360 /** 361 * Determines whether a TIFF field with the given tag number is 362 * contained in this directory. 363 * 364 * @return Whether a {@link TIFFTag} with tag number equal to 365 * <code>tagNumber</code> is present in this <code>TIFFDirectory</code>. 366 */ 367 public boolean containsTIFFField(int tagNumber) { 368 return (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM && 369 lowFields[tagNumber] != null) || 370 highFields.containsKey(new Integer(tagNumber)); 371 } 372 373 /** 374 * Adds a TIFF field to the directory. 375 * 376 * @param f The field to add. 377 * @throws IllegalArgumentException if <code>f</code> is <code>null</code>. 378 */ 379 public void addTIFFField(TIFFField f) { 380 if(f == null) { 381 throw new IllegalArgumentException("f == null"); 382 } 383 int tagNumber = f.getTagNumber(); 384 if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) { 385 if(lowFields[tagNumber] == null) { 386 numLowFields++; 387 } 388 lowFields[tagNumber] = f; 389 } else { 390 highFields.put(new Integer(tagNumber), f); 391 } 392 } 393 394 /** 395 * Retrieves a TIFF field from the directory. 396 * 397 * @param tagNumber The tag number of the tag associated with the field. 398 * @return A <code>TIFFField</code> with the requested tag number of 399 * <code>null</code> if no such field is present. 400 */ 401 public TIFFField getTIFFField(int tagNumber) { 402 TIFFField f; 403 if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) { 404 f = lowFields[tagNumber]; 405 } else { 406 f = (TIFFField)highFields.get(new Integer(tagNumber)); 407 } 408 return f; 409 } 410 411 /** 412 * Removes a TIFF field from the directory. 413 * 414 * @param tagNumber The tag number of the tag associated with the field. 415 */ 416 public void removeTIFFField(int tagNumber) { 417 if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) { 418 if(lowFields[tagNumber] != null) { 419 numLowFields--; 420 lowFields[tagNumber] = null; 421 } 422 } else { 423 highFields.remove(new Integer(tagNumber)); 424 } 425 } 426 427 /** 428 * Retrieves all TIFF fields from the directory. 429 * 430 * @return An array of all TIFF fields in order of numerically increasing 431 * tag number. 432 */ 433 public TIFFField[] getTIFFFields() { 434 // Allocate return value. 435 TIFFField[] fields = new TIFFField[numLowFields + highFields.size()]; 436 437 // Copy any low-index fields. 438 int nextIndex = 0; 439 for(int i = 0; i <= MAX_LOW_FIELD_TAG_NUM; i++) { 440 if(lowFields[i] != null) { 441 fields[nextIndex++] = lowFields[i]; 442 if(nextIndex == numLowFields) break; 443 } 444 } 445 446 // Copy any high-index fields. 447 if(!highFields.isEmpty()) { 448 Iterator keys = highFields.keySet().iterator(); 449 while(keys.hasNext()) { 450 fields[nextIndex++] = (TIFFField)highFields.get(keys.next()); 451 } 452 } 453 454 return fields; 455 } 456 457 /** 458 * Removes all TIFF fields from the directory. 459 */ 460 public void removeTIFFFields() { 461 Arrays.fill(lowFields, (Object)null); 462 numLowFields = 0; 463 highFields.clear(); 464 } 465 466 /** 467 * Converts the directory to a metadata object. 468 * 469 * @return A metadata instance initialized from the contents of this 470 * <code>TIFFDirectory</code>. 471 */ 472 public IIOMetadata getAsMetadata() { 473 return new TIFFImageMetadata(getDirectoryAsIFD(this)); 474 } 475 476 /** 477 * Clones the directory and all the fields contained therein. 478 * 479 * @return A clone of this <code>TIFFDirectory</code>. 480 */ 481 public Object clone() { 482 TIFFDirectory dir = new TIFFDirectory(getTagSets(), getParentTag()); 483 TIFFField[] fields = getTIFFFields(); 484 int numFields = fields.length; 485 for(int i = 0; i < numFields; i++) { 486 dir.addTIFFField(fields[i]); 487 } 488 489 return dir; 490 } 491}