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: */
045: package com.sun.media.imageio.plugins.tiff;
046:
047: import java.util.ArrayList;
048: import java.util.Arrays;
049: import java.util.Iterator;
050: import java.util.List;
051: import java.util.Map;
052: import java.util.NoSuchElementException;
053: import java.util.Set;
054: import java.util.TreeMap;
055: import javax.imageio.metadata.IIOInvalidTreeException;
056: import javax.imageio.metadata.IIOMetadata;
057: import javax.imageio.metadata.IIOMetadataFormatImpl;
058: import org.w3c.dom.Node;
059: import com.sun.media.imageioimpl.plugins.tiff.TIFFIFD;
060: import com.sun.media.imageioimpl.plugins.tiff.TIFFImageMetadata;
061:
062: /**
063: * A convenience class for simplifying interaction with TIFF native
064: * image metadata. A TIFF image metadata tree represents an Image File
065: * Directory (IFD) from a TIFF 6.0 stream. An IFD consists of a number of
066: * IFD Entries each of which associates an identifying tag number with
067: * a compatible value. A <code>TIFFDirectory</code> instance corresponds
068: * to an IFD and contains a set of {@link TIFFField}s each of which
069: * corresponds to an IFD Entry in the IFD.
070: *
071: * <p>When reading, a <code>TIFFDirectory</code> may be created by passing
072: * the value returned by {@link javax.imageio.ImageReader#getImageMetadata
073: * ImageReader.getImageMetadata()} to {@link #createFromMetadata
074: * createFromMetadata()}. The {@link TIFFField}s in the directory may then
075: * be obtained using the accessor methods provided in this class.</p>
076: *
077: * <p>When writing, an {@link IIOMetadata} object for use by one of the
078: * <core>write()</code> methods of {@link javax.imageio.ImageWriter} may be
079: * created from a <code>TIFFDirectory</code> by {@link #getAsMetadata()}.
080: * The <code>TIFFDirectory</code> itself may be created by construction or
081: * from the <code>IIOMetadata</code> object returned by
082: * {@link javax.imageio.ImageWriter#getDefaultImageMetadata
083: * ImageWriter.getDefaultImageMetadata()}. The <code>TIFFField</code>s in the
084: * directory may be set using the mutator methods provided in this class.</p>
085: *
086: * <p>A <code>TIFFDirectory</code> is aware of the tag numbers in the
087: * group of {@link TIFFTagSet}s associated with it. When
088: * a <code>TIFFDirectory</code> is created from a native image metadata
089: * object, these tag sets are derived from the <tt>tagSets</tt> attribute
090: * of the <tt>TIFFIFD</tt> node.</p>
091: *
092: * <p>A <code>TIFFDirectory</code> might also have a parent {@link TIFFTag}.
093: * This will occur if the directory represents an IFD other than the root
094: * IFD of the image. The parent tag is the tag of the IFD Entry which is a
095: * pointer to the IFD represented by this <code>TIFFDirectory</code>. The
096: * {@link TIFFTag#isIFDPointer} method of this parent <code>TIFFTag</code>
097: * must return <code>true</code>. When a <code>TIFFDirectory</code> is
098: * created from a native image metadata object, the parent tag set is set
099: * from the <tt>parentTagName</tt> attribute of the corresponding
100: * <tt>TIFFIFD</tt> node. Note that a <code>TIFFDirectory</code> instance
101: * which has a non-<code>null</code> parent tag will be contained in the
102: * data field of a <code>TIFFField</code> instance which has a tag field
103: * equal to the contained directory's parent tag.</p>
104: *
105: * <p>As an example consider an EXIF image. The <code>TIFFDirectory</code>
106: * instance corresponding to the EXIF IFD in the EXIF stream would have parent
107: * tag {@link EXIFParentTIFFTagSet#TAG_EXIF_IFD_POINTER TAG_EXIF_IFD_POINTER}
108: * and would include {@link EXIFTIFFTagSet} in its group of known tag sets.
109: * The <code>TIFFDirectory</code> corresponding to this EXIF IFD will be
110: * contained in the data field of a <code>TIFFField</code> which will in turn
111: * be contained in the <code>TIFFDirectory</code> corresponding to the primary
112: * IFD of the EXIF image which will itself have a <code>null</code>-valued
113: * parent tag.</p>
114: *
115: * <p><b>Note that this implementation is not synchronized.</b> If multiple
116: * threads use a <code>TIFFDirectory</code> instance concurrently, and at
117: * least one of the threads modifies the directory, for example, by adding
118: * or removing <code>TIFFField</code>s or <code>TIFFTagSet</code>s, it
119: * <i>must</i> be synchronized externally.</p>
120: *
121: * @see IIOMetadata
122: * @see TIFFField
123: * @see TIFFTag
124: * @see TIFFTagSet
125: *
126: * @since 1.1-beta
127: */
128: // XXX doc: not thread safe
129: public class TIFFDirectory implements Cloneable {
130:
131: /** The largest low-valued tag number in the TIFF 6.0 specification. */
132: private static final int MAX_LOW_FIELD_TAG_NUM = 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 createFromMetadata(
177: IIOMetadata tiffImageMetadata)
178: throws IIOInvalidTreeException {
179:
180: if (tiffImageMetadata == null) {
181: throw new IllegalArgumentException(
182: "tiffImageMetadata == null");
183: }
184:
185: TIFFImageMetadata tim;
186: if (tiffImageMetadata instanceof TIFFImageMetadata) {
187: tim = (TIFFImageMetadata) tiffImageMetadata;
188: } else {
189: // Create a native metadata object.
190: ArrayList l = new ArrayList(1);
191: l.add(BaselineTIFFTagSet.getInstance());
192: tim = new TIFFImageMetadata(l);
193:
194: // Determine the format name to use.
195: String formatName = null;
196: if (TIFFImageMetadata.nativeMetadataFormatName
197: .equals(tiffImageMetadata
198: .getNativeMetadataFormatName())) {
199: formatName = TIFFImageMetadata.nativeMetadataFormatName;
200: } else {
201: String[] extraNames = tiffImageMetadata
202: .getExtraMetadataFormatNames();
203: if (extraNames != null) {
204: for (int i = 0; i < extraNames.length; i++) {
205: if (TIFFImageMetadata.nativeMetadataFormatName
206: .equals(extraNames[i])) {
207: formatName = extraNames[i];
208: break;
209: }
210: }
211: }
212:
213: if (formatName == null) {
214: if (tiffImageMetadata
215: .isStandardMetadataFormatSupported()) {
216: formatName = IIOMetadataFormatImpl.standardMetadataFormatName;
217: } else {
218: throw new IllegalArgumentException(
219: "Parameter does not support required metadata format!");
220: }
221: }
222: }
223:
224: // Set the native metadata object from the tree.
225: tim.setFromTree(formatName, tiffImageMetadata
226: .getAsTree(formatName));
227: }
228:
229: return tim.getRootIFD();
230: }
231:
232: /**
233: * Converts a <code>TIFFDirectory</code> to a <code>TIFFIFD</code>.
234: */
235: private static TIFFIFD getDirectoryAsIFD(TIFFDirectory dir) {
236: if (dir instanceof TIFFIFD) {
237: return (TIFFIFD) dir;
238: }
239:
240: TIFFIFD ifd = new TIFFIFD(Arrays.asList(dir.getTagSets()), dir
241: .getParentTag());
242: TIFFField[] fields = dir.getTIFFFields();
243: int numFields = fields.length;
244: for (int i = 0; i < numFields; i++) {
245: TIFFField f = fields[i];
246: TIFFTag tag = f.getTag();
247: if (tag.isIFDPointer()) {
248: TIFFDirectory subIFD = getDirectoryAsIFD((TIFFDirectory) f
249: .getData());
250: f = new TIFFField(tag, f.getType(), f.getCount(),
251: subIFD);
252: }
253: ifd.addTIFFField(f);
254: }
255:
256: return ifd;
257: }
258:
259: /**
260: * Constructs a <code>TIFFDirectory</code> which is aware of a given
261: * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag}
262: * may also be specified.
263: *
264: * @param tagSets The <code>TIFFTagSets</code> associated with this
265: * directory.
266: * @param parentTag The parent <code>TIFFTag</code> of this directory;
267: * may be <code>null</code>.
268: * @throws IllegalArgumentException if <code>tagSets</code> is
269: * <code>null</code>.
270: */
271: public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) {
272: if (tagSets == null) {
273: throw new IllegalArgumentException("tagSets == null!");
274: }
275: this .tagSets = new ArrayList(tagSets.length);
276: int numTagSets = tagSets.length;
277: for (int i = 0; i < numTagSets; i++) {
278: this .tagSets.add(tagSets[i]);
279: }
280: this .parentTag = parentTag;
281: }
282:
283: /**
284: * Returns the {@link TIFFTagSet}s of which this directory is aware.
285: *
286: * @return The <code>TIFFTagSet</code>s associated with this
287: * <code>TIFFDirectory</code>.
288: */
289: public TIFFTagSet[] getTagSets() {
290: return (TIFFTagSet[]) tagSets.toArray(new TIFFTagSet[tagSets
291: .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 && lowFields[tagNumber] != null)
373: || highFields.containsKey(new Integer(tagNumber));
374: }
375:
376: /**
377: * Adds a TIFF field to the directory.
378: *
379: * @param f The field to add.
380: * @throws IllegalArgumentException if <code>f</code> is <code>null</code>.
381: */
382: public void addTIFFField(TIFFField f) {
383: if (f == null) {
384: throw new IllegalArgumentException("f == null");
385: }
386: int tagNumber = f.getTagNumber();
387: if (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
388: if (lowFields[tagNumber] == null) {
389: numLowFields++;
390: }
391: lowFields[tagNumber] = f;
392: } else {
393: highFields.put(new Integer(tagNumber), f);
394: }
395: }
396:
397: /**
398: * Retrieves a TIFF field from the directory.
399: *
400: * @param tagNumber The tag number of the tag associated with the field.
401: * @return A <code>TIFFField</code> with the requested tag number of
402: * <code>null</code> if no such field is present.
403: */
404: public TIFFField getTIFFField(int tagNumber) {
405: TIFFField f;
406: if (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
407: f = lowFields[tagNumber];
408: } else {
409: f = (TIFFField) highFields.get(new Integer(tagNumber));
410: }
411: return f;
412: }
413:
414: /**
415: * Removes a TIFF field from the directory.
416: *
417: * @param tagNumber The tag number of the tag associated with the field.
418: */
419: public void removeTIFFField(int tagNumber) {
420: if (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
421: if (lowFields[tagNumber] != null) {
422: numLowFields--;
423: lowFields[tagNumber] = null;
424: }
425: } else {
426: highFields.remove(new Integer(tagNumber));
427: }
428: }
429:
430: /**
431: * Retrieves all TIFF fields from the directory.
432: *
433: * @return An array of all TIFF fields in order of numerically increasing
434: * tag number.
435: */
436: public TIFFField[] getTIFFFields() {
437: // Allocate return value.
438: TIFFField[] fields = new TIFFField[numLowFields
439: + 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)
447: break;
448: }
449: }
450:
451: // Copy any high-index fields.
452: if (!highFields.isEmpty()) {
453: Iterator keys = highFields.keySet().iterator();
454: while (keys.hasNext()) {
455: fields[nextIndex++] = (TIFFField) highFields.get(keys
456: .next());
457: }
458: }
459:
460: return fields;
461: }
462:
463: /**
464: * Removes all TIFF fields from the directory.
465: */
466: public void removeTIFFFields() {
467: Arrays.fill(lowFields, (Object) null);
468: numLowFields = 0;
469: highFields.clear();
470: }
471:
472: /**
473: * Converts the directory to a metadata object.
474: *
475: * @return A metadata instance initialized from the contents of this
476: * <code>TIFFDirectory</code>.
477: */
478: public IIOMetadata getAsMetadata() {
479: return new TIFFImageMetadata(getDirectoryAsIFD(this ));
480: }
481:
482: /**
483: * Clones the directory and all the fields contained therein.
484: *
485: * @return A clone of this <code>TIFFDirectory</code>.
486: */
487: public Object clone() {
488: TIFFDirectory dir = new TIFFDirectory(getTagSets(),
489: getParentTag());
490: TIFFField[] fields = getTIFFFields();
491: int numFields = fields.length;
492: for (int i = 0; i < numFields; i++) {
493: dir.addTIFFField(fields[i]);
494: }
495:
496: return dir;
497: }
498: }
|