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.NoSuchElementException; 053import java.util.Set; 054import java.util.TreeMap; 055 056import javax.imageio.metadata.IIOInvalidTreeException; 057import javax.imageio.metadata.IIOMetadata; 058import javax.imageio.metadata.IIOMetadataFormatImpl; 059 060import org.w3c.dom.Node; 061 062import com.github.jaiimageio.impl.plugins.tiff.TIFFIFD; 063import com.github.jaiimageio.impl.plugins.tiff.TIFFImageMetadata; 064 065/** 066 * A convenience class for simplifying interaction with TIFF native 067 * image metadata. A TIFF image metadata tree represents an Image File 068 * Directory (IFD) from a TIFF 6.0 stream. An IFD consists of a number of 069 * IFD Entries each of which associates an identifying tag number with 070 * a compatible value. A <code>TIFFDirectory</code> instance corresponds 071 * to an IFD and contains a set of {@link TIFFField}s each of which 072 * corresponds to an IFD Entry in the IFD. 073 * 074 * <p>When reading, a <code>TIFFDirectory</code> may be created by passing 075 * the value returned by {@link javax.imageio.ImageReader#getImageMetadata 076 * ImageReader.getImageMetadata()} to {@link #createFromMetadata 077 * createFromMetadata()}. The {@link TIFFField}s in the directory may then 078 * be obtained using the accessor methods provided in this class.</p> 079 * 080 * <p>When writing, an {@link IIOMetadata} object for use by one of the 081 * <core>write()</code> methods of {@link javax.imageio.ImageWriter} may be 082 * created from a <code>TIFFDirectory</code> by {@link #getAsMetadata()}. 083 * The <code>TIFFDirectory</code> itself may be created by construction or 084 * from the <code>IIOMetadata</code> object returned by 085 * {@link javax.imageio.ImageWriter#getDefaultImageMetadata 086 * ImageWriter.getDefaultImageMetadata()}. The <code>TIFFField</code>s in the 087 * directory may be set using the mutator methods provided in this class.</p> 088 * 089 * <p>A <code>TIFFDirectory</code> is aware of the tag numbers in the 090 * group of {@link TIFFTagSet}s associated with it. When 091 * a <code>TIFFDirectory</code> is created from a native image metadata 092 * object, these tag sets are derived from the <tt>tagSets</tt> attribute 093 * of the <tt>TIFFIFD</tt> node.</p> 094 * 095 * <p>A <code>TIFFDirectory</code> might also have a parent {@link TIFFTag}. 096 * This will occur if the directory represents an IFD other than the root 097 * IFD of the image. The parent tag is the tag of the IFD Entry which is a 098 * pointer to the IFD represented by this <code>TIFFDirectory</code>. The 099 * {@link TIFFTag#isIFDPointer} method of this parent <code>TIFFTag</code> 100 * must return <code>true</code>. When a <code>TIFFDirectory</code> is 101 * created from a native image metadata object, the parent tag set is set 102 * from the <tt>parentTagName</tt> attribute of the corresponding 103 * <tt>TIFFIFD</tt> node. Note that a <code>TIFFDirectory</code> instance 104 * which has a non-<code>null</code> parent tag will be contained in the 105 * data field of a <code>TIFFField</code> instance which has a tag field 106 * equal to the contained directory's parent tag.</p> 107 * 108 * <p>As an example consider an EXIF image. The <code>TIFFDirectory</code> 109 * instance corresponding to the EXIF IFD in the EXIF stream would have parent 110 * tag {@link EXIFParentTIFFTagSet#TAG_EXIF_IFD_POINTER TAG_EXIF_IFD_POINTER} 111 * and would include {@link EXIFTIFFTagSet} in its group of known tag sets. 112 * The <code>TIFFDirectory</code> corresponding to this EXIF IFD will be 113 * contained in the data field of a <code>TIFFField</code> which will in turn 114 * be contained in the <code>TIFFDirectory</code> corresponding to the primary 115 * IFD of the EXIF image which will itself have a <code>null</code>-valued 116 * parent tag.</p> 117 * 118 * <p><b>Note that this implementation is not synchronized.</b> If multiple 119 * threads use a <code>TIFFDirectory</code> instance concurrently, and at 120 * least one of the threads modifies the directory, for example, by adding 121 * or removing <code>TIFFField</code>s or <code>TIFFTagSet</code>s, it 122 * <i>must</i> be synchronized externally.</p> 123 * 124 * @see IIOMetadata 125 * @see TIFFField 126 * @see TIFFTag 127 * @see TIFFTagSet 128 * 129 * @since 1.1-beta 130 */ 131// XXX doc: not thread safe 132public class TIFFDirectory implements Cloneable { 133 134 /** The largest low-valued tag number in the TIFF 6.0 specification. */ 135 private static final int MAX_LOW_FIELD_TAG_NUM = 136 BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE; 137 138 /** The <code>TIFFTagSets</code> associated with this directory. */ 139 private List tagSets; 140 141 /** The parent <code>TIFFTag</code> of this directory. */ 142 private TIFFTag parentTag; 143 144 /** 145 * The fields in this directory which have a low tag number. These are 146 * managed as an array for efficiency as they are the most common fields. 147 */ 148 private TIFFField[] lowFields = new TIFFField[MAX_LOW_FIELD_TAG_NUM + 1]; 149 150 /** The number of low tag numbered fields in the directory. */ 151 private int numLowFields = 0; 152 153 /** 154 * A mapping of <code>Integer</code> tag numbers to <code>TIFFField</code>s 155 * for fields which are not low tag numbered. 156 */ 157 private Map highFields = new TreeMap(); 158 159 /** 160 * Creates a <code>TIFFDirectory</code> instance from the contents of 161 * an image metadata object. The supplied object must support an image 162 * metadata format supported by the TIFF {@link javax.imageio.ImageWriter} 163 * plug-in. This will usually be either the TIFF native image metadata 164 * format <tt>com_sun_media_imageio_plugins_tiff_1.0</tt> or the Java 165 * Image I/O standard metadata format <tt>javax_imageio_1.0</tt>. 166 * 167 * @param tiffImageMetadata A metadata object which supports a compatible 168 * image metadata format. 169 * 170 * @return A <code>TIFFDirectory</code> populated from the contents of 171 * the supplied metadata object. 172 * 173 * @throws IllegalArgumentException if <code>tiffImageMetadata</code> 174 * is <code>null</code>. 175 * @throws IllegalArgumentException if <code>tiffImageMetadata</code> 176 * does not support a compatible image metadata format. 177 * @throws IIOInvalidTreeException if the supplied metadata object 178 * cannot be parsed. 179 */ 180 public static TIFFDirectory 181 createFromMetadata(IIOMetadata tiffImageMetadata) 182 throws IIOInvalidTreeException { 183 184 if(tiffImageMetadata == null) { 185 throw new IllegalArgumentException("tiffImageMetadata == null"); 186 } 187 188 TIFFImageMetadata tim; 189 if(tiffImageMetadata instanceof TIFFImageMetadata) { 190 tim = (TIFFImageMetadata)tiffImageMetadata; 191 } else { 192 // Create a native metadata object. 193 ArrayList l = new ArrayList(1); 194 l.add(BaselineTIFFTagSet.getInstance()); 195 tim = new TIFFImageMetadata(l); 196 197 // Determine the format name to use. 198 String formatName = null; 199 if(TIFFImageMetadata.nativeMetadataFormatName.equals 200 (tiffImageMetadata.getNativeMetadataFormatName())) { 201 formatName = TIFFImageMetadata.nativeMetadataFormatName; 202 } else { 203 String[] extraNames = 204 tiffImageMetadata.getExtraMetadataFormatNames(); 205 if(extraNames != null) { 206 for(int i = 0; i < extraNames.length; i++) { 207 if(TIFFImageMetadata.nativeMetadataFormatName.equals 208 (extraNames[i])) { 209 formatName = extraNames[i]; 210 break; 211 } 212 } 213 } 214 215 if(formatName == null) { 216 if(tiffImageMetadata.isStandardMetadataFormatSupported()) { 217 formatName = 218 IIOMetadataFormatImpl.standardMetadataFormatName; 219 } else { 220 throw new IllegalArgumentException 221 ("Parameter does not support required metadata format!"); 222 } 223 } 224 } 225 226 // Set the native metadata object from the tree. 227 tim.setFromTree(formatName, 228 tiffImageMetadata.getAsTree(formatName)); 229 } 230 231 return tim.getRootIFD(); 232 } 233 234 /** 235 * Converts a <code>TIFFDirectory</code> to a <code>TIFFIFD</code>. 236 */ 237 private static TIFFIFD getDirectoryAsIFD(TIFFDirectory dir) { 238 if(dir instanceof TIFFIFD) { 239 return (TIFFIFD)dir; 240 } 241 242 TIFFIFD ifd = new TIFFIFD(Arrays.asList(dir.getTagSets()), 243 dir.getParentTag()); 244 TIFFField[] fields = dir.getTIFFFields(); 245 int numFields = fields.length; 246 for(int i = 0; i < numFields; i++) { 247 TIFFField f = fields[i]; 248 TIFFTag tag = f.getTag(); 249 if(tag.isIFDPointer()) { 250 TIFFDirectory subIFD = 251 getDirectoryAsIFD((TIFFDirectory)f.getData()); 252 f = new TIFFField(tag, f.getType(), f.getCount(), subIFD); 253 } 254 ifd.addTIFFField(f); 255 } 256 257 return ifd; 258 } 259 260 /** 261 * Constructs a <code>TIFFDirectory</code> which is aware of a given 262 * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag} 263 * may also be specified. 264 * 265 * @param tagSets The <code>TIFFTagSets</code> associated with this 266 * directory. 267 * @param parentTag The parent <code>TIFFTag</code> of this directory; 268 * may be <code>null</code>. 269 * @throws IllegalArgumentException if <code>tagSets</code> is 270 * <code>null</code>. 271 */ 272 public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) { 273 if(tagSets == null) { 274 throw new IllegalArgumentException("tagSets == null!"); 275 } 276 this.tagSets = new ArrayList(tagSets.length); 277 int numTagSets = tagSets.length; 278 for(int i = 0; i < numTagSets; i++) { 279 this.tagSets.add(tagSets[i]); 280 } 281 this.parentTag = parentTag; 282 } 283 284 /** 285 * Returns the {@link TIFFTagSet}s of which this directory is aware. 286 * 287 * @return The <code>TIFFTagSet</code>s associated with this 288 * <code>TIFFDirectory</code>. 289 */ 290 public TIFFTagSet[] getTagSets() { 291 return (TIFFTagSet[])tagSets.toArray(new TIFFTagSet[tagSets.size()]); 292 } 293 294 /** 295 * Adds an element to the group of {@link TIFFTagSet}s of which this 296 * directory is aware. 297 * 298 * @param tagSet The <code>TIFFTagSet</code> to add. 299 * @throws IllegalArgumentException if <code>tagSet</code> is 300 * <code>null</code>. 301 */ 302 public void addTagSet(TIFFTagSet tagSet) { 303 if(tagSet == null) { 304 throw new IllegalArgumentException("tagSet == null"); 305 } 306 307 if(!tagSets.contains(tagSet)) { 308 tagSets.add(tagSet); 309 } 310 } 311 312 /** 313 * Removes an element from the group of {@link TIFFTagSet}s of which this 314 * directory is aware. 315 * 316 * @param tagSet The <code>TIFFTagSet</code> to remove. 317 * @throws IllegalArgumentException if <code>tagSet</code> is 318 * <code>null</code>. 319 */ 320 public void removeTagSet(TIFFTagSet tagSet) { 321 if(tagSet == null) { 322 throw new IllegalArgumentException("tagSet == null"); 323 } 324 325 if(tagSets.contains(tagSet)) { 326 tagSets.remove(tagSet); 327 } 328 } 329 330 /** 331 * Returns the parent {@link TIFFTag} of this directory if one 332 * has been defined or <code>null</code> otherwise. 333 * 334 * @return The parent <code>TIFFTag</code> of this 335 * <code>TIFFDiectory</code> or <code>null</code>. 336 */ 337 public TIFFTag getParentTag() { 338 return parentTag; 339 } 340 341 /** 342 * Returns the {@link TIFFTag} which has tag number equal to 343 * <code>tagNumber</code> or <code>null</code> if no such tag 344 * exists in the {@link TIFFTagSet}s associated with this 345 * directory. 346 * 347 * @param tagNumber The tag number of interest. 348 * @return The corresponding <code>TIFFTag</code> or <code>null</code>. 349 */ 350 public TIFFTag getTag(int tagNumber) { 351 return TIFFIFD.getTag(tagNumber, tagSets); 352 } 353 354 /** 355 * Returns the number of {@link TIFFField}s in this directory. 356 * 357 * @return The number of <code>TIFFField</code>s in this 358 * <code>TIFFDirectory</code>. 359 */ 360 public int getNumTIFFFields() { 361 return numLowFields + highFields.size(); 362 } 363 364 /** 365 * Determines whether a TIFF field with the given tag number is 366 * contained in this directory. 367 * 368 * @return Whether a {@link TIFFTag} with tag number equal to 369 * <code>tagNumber</code> is present in this <code>TIFFDirectory</code>. 370 */ 371 public boolean containsTIFFField(int tagNumber) { 372 return (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM && 373 lowFields[tagNumber] != null) || 374 highFields.containsKey(new Integer(tagNumber)); 375 } 376 377 /** 378 * Adds a TIFF field to the directory. 379 * 380 * @param f The field to add. 381 * @throws IllegalArgumentException if <code>f</code> is <code>null</code>. 382 */ 383 public void addTIFFField(TIFFField f) { 384 if(f == null) { 385 throw new IllegalArgumentException("f == null"); 386 } 387 int tagNumber = f.getTagNumber(); 388 if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) { 389 if(lowFields[tagNumber] == null) { 390 numLowFields++; 391 } 392 lowFields[tagNumber] = f; 393 } else { 394 highFields.put(new Integer(tagNumber), f); 395 } 396 } 397 398 /** 399 * Retrieves a TIFF field from the directory. 400 * 401 * @param tagNumber The tag number of the tag associated with the field. 402 * @return A <code>TIFFField</code> with the requested tag number of 403 * <code>null</code> if no such field is present. 404 */ 405 public TIFFField getTIFFField(int tagNumber) { 406 TIFFField f; 407 if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) { 408 f = lowFields[tagNumber]; 409 } else { 410 f = (TIFFField)highFields.get(new Integer(tagNumber)); 411 } 412 return f; 413 } 414 415 /** 416 * Removes a TIFF field from the directory. 417 * 418 * @param tagNumber The tag number of the tag associated with the field. 419 */ 420 public void removeTIFFField(int tagNumber) { 421 if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) { 422 if(lowFields[tagNumber] != null) { 423 numLowFields--; 424 lowFields[tagNumber] = null; 425 } 426 } else { 427 highFields.remove(new Integer(tagNumber)); 428 } 429 } 430 431 /** 432 * Retrieves all TIFF fields from the directory. 433 * 434 * @return An array of all TIFF fields in order of numerically increasing 435 * tag number. 436 */ 437 public TIFFField[] getTIFFFields() { 438 // Allocate return value. 439 TIFFField[] fields = new TIFFField[numLowFields + highFields.size()]; 440 441 // Copy any low-index fields. 442 int nextIndex = 0; 443 for(int i = 0; i <= MAX_LOW_FIELD_TAG_NUM; i++) { 444 if(lowFields[i] != null) { 445 fields[nextIndex++] = lowFields[i]; 446 if(nextIndex == numLowFields) break; 447 } 448 } 449 450 // Copy any high-index fields. 451 if(!highFields.isEmpty()) { 452 Iterator keys = highFields.keySet().iterator(); 453 while(keys.hasNext()) { 454 fields[nextIndex++] = (TIFFField)highFields.get(keys.next()); 455 } 456 } 457 458 return fields; 459 } 460 461 /** 462 * Removes all TIFF fields from the directory. 463 */ 464 public void removeTIFFFields() { 465 Arrays.fill(lowFields, (Object)null); 466 numLowFields = 0; 467 highFields.clear(); 468 } 469 470 /** 471 * Converts the directory to a metadata object. 472 * 473 * @return A metadata instance initialized from the contents of this 474 * <code>TIFFDirectory</code>. 475 */ 476 public IIOMetadata getAsMetadata() { 477 return new TIFFImageMetadata(getDirectoryAsIFD(this)); 478 } 479 480 /** 481 * Clones the directory and all the fields contained therein. 482 * 483 * @return A clone of this <code>TIFFDirectory</code>. 484 */ 485 public Object clone() { 486 TIFFDirectory dir = new TIFFDirectory(getTagSets(), getParentTag()); 487 TIFFField[] fields = getTIFFFields(); 488 int numFields = fields.length; 489 for(int i = 0; i < numFields; i++) { 490 dir.addTIFFField(fields[i]); 491 } 492 493 return dir; 494 } 495}