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 1.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 }
--- EOF ---