View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.tiff;
18  
19  import java.awt.image.BufferedImage;
20  import java.io.IOException;
21  import java.nio.ByteOrder;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import org.apache.commons.imaging.ImagingException;
28  import org.apache.commons.imaging.common.Allocator;
29  import org.apache.commons.imaging.common.ByteConversions;
30  import org.apache.commons.imaging.common.RationalNumber;
31  import org.apache.commons.imaging.formats.tiff.constants.TiffConstants;
32  import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants;
33  import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
34  import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType;
35  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
36  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAscii;
37  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoByte;
38  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoBytes;
39  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDouble;
40  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDoubles;
41  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloat;
42  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloats;
43  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoGpsText;
44  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLong;
45  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLongs;
46  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRational;
47  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRationals;
48  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSByte;
49  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSBytes;
50  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLong;
51  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLongs;
52  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRational;
53  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRationals;
54  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShort;
55  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShorts;
56  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShort;
57  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShortOrLong;
58  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShorts;
59  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoXpString;
60  
61  /**
62   * Provides methods and elements for accessing an Image File Directory (IFD) from a TIFF file. In the TIFF specification, the IFD is the main container for
63   * individual images or sets of metadata. While not all Directories contain images, images are always stored in a Directory.
64   */
65  public class TiffDirectory extends AbstractTiffElement implements Iterable<TiffField> {
66  
67      public static final class ImageDataElement extends AbstractTiffElement {
68          public ImageDataElement(final long offset, final int length) {
69              super(offset, length);
70          }
71  
72          @Override
73          public String getElementDescription() {
74              return "ImageDataElement";
75          }
76      }
77  
78      public static String description(final int type) {
79          switch (type) {
80          case TiffDirectoryConstants.DIRECTORY_TYPE_UNKNOWN:
81              return "Unknown";
82          case TiffDirectoryConstants.DIRECTORY_TYPE_ROOT:
83              return "Root";
84          case TiffDirectoryConstants.DIRECTORY_TYPE_SUB:
85              return "Sub";
86          case TiffDirectoryConstants.DIRECTORY_TYPE_THUMBNAIL:
87              return "Thumbnail";
88          case TiffDirectoryConstants.DIRECTORY_TYPE_EXIF:
89              return "Exif";
90          case TiffDirectoryConstants.DIRECTORY_TYPE_GPS:
91              return "Gps";
92          case TiffDirectoryConstants.DIRECTORY_TYPE_INTEROPERABILITY:
93              return "Interoperability";
94          default:
95              return "Bad Type";
96          }
97      }
98  
99      private final List<TiffField> entries;
100 
101     /**
102      * Preserves the byte order derived from the TIFF file header. Some of the legacy methods in this class require byte order as an argument, though that use
103      * could be phased out eventually.
104      */
105     private final ByteOrder headerByteOrder;
106 
107     private JpegImageData jpegImageData;
108 
109     private final long nextDirectoryOffset;
110 
111     private AbstractTiffImageData abstractTiffImageData;
112 
113     public final int type;
114 
115     public TiffDirectory(final int type, final List<TiffField> entries, final long offset, final long nextDirectoryOffset, final ByteOrder byteOrder) {
116         super(offset,
117                 TiffConstants.DIRECTORY_HEADER_LENGTH + entries.size() * TiffConstants.ENTRY_LENGTH + TiffConstants.DIRECTORY_FOOTER_LENGTH);
118 
119         this.type = type;
120         this.entries = Collections.unmodifiableList(entries);
121         this.nextDirectoryOffset = nextDirectoryOffset;
122         this.headerByteOrder = byteOrder;
123     }
124 
125     public String description() {
126         return description(type);
127     }
128 
129     public void dump() {
130         entries.forEach(TiffField::dump);
131     }
132 
133     public TiffField findField(final TagInfo tag) throws ImagingException {
134         final boolean failIfMissing = false;
135         return findField(tag, failIfMissing);
136     }
137 
138     public TiffField findField(final TagInfo tag, final boolean failIfMissing) throws ImagingException {
139         for (final TiffField field : entries) {
140             if (field.getTag() == tag.tag) {
141                 return field;
142             }
143         }
144 
145         if (failIfMissing) {
146             throw new ImagingException("Missing expected field: " + tag.getDescription());
147         }
148 
149         return null;
150     }
151 
152     /**
153      * Gets the byte order used by the source file for storing this directory and its content.
154      *
155      * @return A valid byte order instance.
156      */
157     public ByteOrder getByteOrder() {
158         return headerByteOrder;
159     }
160 
161     public List<TiffField> getDirectoryEntries() {
162         return new ArrayList<>(entries);
163     }
164 
165     @Override
166     public String getElementDescription() {
167         long entryOffset = offset + TiffConstants.DIRECTORY_HEADER_LENGTH;
168 
169         final StringBuilder result = new StringBuilder();
170         for (final TiffField entry : entries) {
171             result.append(String.format("\t[%d]: %s (%d, 0x%x), %s, %d: %s%n", entryOffset, entry.getTagInfo().name, entry.getTag(), entry.getTag(),
172                     entry.getFieldType().getName(), entry.getBytesLength(), entry.getValueDescription()));
173 
174             entryOffset += TiffConstants.ENTRY_LENGTH;
175         }
176         return result.toString();
177     }
178 
179     public Object getFieldValue(final TagInfo tag) throws ImagingException {
180         final TiffField field = findField(tag);
181         if (field == null) {
182             return null;
183         }
184         return field.getValue();
185     }
186 
187     public String[] getFieldValue(final TagInfoAscii tag, final boolean mustExist) throws ImagingException {
188         final TiffField field = findField(tag);
189         if (field == null) {
190             if (mustExist) {
191                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
192             }
193             return null;
194         }
195         if (!tag.dataTypes.contains(field.getFieldType())) {
196             if (mustExist) {
197                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
198             }
199             return null;
200         }
201         final byte[] bytes = field.getByteArrayValue();
202         return tag.getValue(field.getByteOrder(), bytes);
203     }
204 
205     public byte getFieldValue(final TagInfoByte tag) throws ImagingException {
206         final TiffField field = findField(tag);
207         if (field == null) {
208             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
209         }
210         if (!tag.dataTypes.contains(field.getFieldType())) {
211             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
212         }
213         if (field.getCount() != 1) {
214             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
215         }
216         return field.getByteArrayValue()[0];
217     }
218 
219     public byte[] getFieldValue(final TagInfoBytes tag, final boolean mustExist) throws ImagingException {
220         final TiffField field = findField(tag);
221         if (field == null) {
222             if (mustExist) {
223                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
224             }
225             return null;
226         }
227         if (!tag.dataTypes.contains(field.getFieldType())) {
228             if (mustExist) {
229                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
230             }
231             return null;
232         }
233         return field.getByteArrayValue();
234     }
235 
236     public double getFieldValue(final TagInfoDouble tag) throws ImagingException {
237         final TiffField field = findField(tag);
238         if (field == null) {
239             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
240         }
241         if (!tag.dataTypes.contains(field.getFieldType())) {
242             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
243         }
244         if (field.getCount() != 1) {
245             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
246         }
247         final byte[] bytes = field.getByteArrayValue();
248         return tag.getValue(field.getByteOrder(), bytes);
249     }
250 
251     public double[] getFieldValue(final TagInfoDoubles tag, final boolean mustExist) throws ImagingException {
252         final TiffField field = findField(tag);
253         if (field == null) {
254             if (mustExist) {
255                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
256             }
257             return null;
258         }
259         if (!tag.dataTypes.contains(field.getFieldType())) {
260             if (mustExist) {
261                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
262             }
263             return null;
264         }
265         final byte[] bytes = field.getByteArrayValue();
266         return tag.getValue(field.getByteOrder(), bytes);
267     }
268 
269     public float getFieldValue(final TagInfoFloat tag) throws ImagingException {
270         final TiffField field = findField(tag);
271         if (field == null) {
272             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
273         }
274         if (!tag.dataTypes.contains(field.getFieldType())) {
275             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
276         }
277         if (field.getCount() != 1) {
278             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
279         }
280         final byte[] bytes = field.getByteArrayValue();
281         return tag.getValue(field.getByteOrder(), bytes);
282     }
283 
284     public float[] getFieldValue(final TagInfoFloats tag, final boolean mustExist) throws ImagingException {
285         final TiffField field = findField(tag);
286         if (field == null) {
287             if (mustExist) {
288                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
289             }
290             return null;
291         }
292         if (!tag.dataTypes.contains(field.getFieldType())) {
293             if (mustExist) {
294                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
295             }
296             return null;
297         }
298         final byte[] bytes = field.getByteArrayValue();
299         return tag.getValue(field.getByteOrder(), bytes);
300     }
301 
302     public String getFieldValue(final TagInfoGpsText tag, final boolean mustExist) throws ImagingException {
303         final TiffField field = findField(tag);
304         if (field == null) {
305             if (mustExist) {
306                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
307             }
308             return null;
309         }
310         return tag.getValue(field);
311     }
312 
313     public int getFieldValue(final TagInfoLong tag) throws ImagingException {
314         final TiffField field = findField(tag);
315         if (field == null) {
316             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
317         }
318         if (!tag.dataTypes.contains(field.getFieldType())) {
319             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
320         }
321         if (field.getCount() != 1) {
322             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
323         }
324         final byte[] bytes = field.getByteArrayValue();
325         return tag.getValue(field.getByteOrder(), bytes);
326     }
327 
328     public int[] getFieldValue(final TagInfoLongs tag, final boolean mustExist) throws ImagingException {
329         final TiffField field = findField(tag);
330         if (field == null) {
331             if (mustExist) {
332                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
333             }
334             return null;
335         }
336         if (!tag.dataTypes.contains(field.getFieldType())) {
337             if (mustExist) {
338                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
339             }
340             return null;
341         }
342         final byte[] bytes = field.getByteArrayValue();
343         return tag.getValue(field.getByteOrder(), bytes);
344     }
345 
346     public RationalNumber getFieldValue(final TagInfoRational tag) throws ImagingException {
347         final TiffField field = findField(tag);
348         if (field == null) {
349             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
350         }
351         if (!tag.dataTypes.contains(field.getFieldType())) {
352             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
353         }
354         if (field.getCount() != 1) {
355             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
356         }
357         final byte[] bytes = field.getByteArrayValue();
358         return tag.getValue(field.getByteOrder(), bytes);
359     }
360 
361     public RationalNumber[] getFieldValue(final TagInfoRationals tag, final boolean mustExist) throws ImagingException {
362         final TiffField field = findField(tag);
363         if (field == null) {
364             if (mustExist) {
365                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
366             }
367             return null;
368         }
369         if (!tag.dataTypes.contains(field.getFieldType())) {
370             if (mustExist) {
371                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
372             }
373             return null;
374         }
375         final byte[] bytes = field.getByteArrayValue();
376         return tag.getValue(field.getByteOrder(), bytes);
377     }
378 
379     public byte getFieldValue(final TagInfoSByte tag) throws ImagingException {
380         final TiffField field = findField(tag);
381         if (field == null) {
382             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
383         }
384         if (!tag.dataTypes.contains(field.getFieldType())) {
385             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
386         }
387         if (field.getCount() != 1) {
388             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
389         }
390         return field.getByteArrayValue()[0];
391     }
392 
393     public byte[] getFieldValue(final TagInfoSBytes tag, final boolean mustExist) throws ImagingException {
394         final TiffField field = findField(tag);
395         if (field == null) {
396             if (mustExist) {
397                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
398             }
399             return null;
400         }
401         if (!tag.dataTypes.contains(field.getFieldType())) {
402             if (mustExist) {
403                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
404             }
405             return null;
406         }
407         return field.getByteArrayValue();
408     }
409 
410     public short getFieldValue(final TagInfoShort tag) throws ImagingException {
411         final TiffField field = findField(tag);
412         if (field == null) {
413             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
414         }
415         if (!tag.dataTypes.contains(field.getFieldType())) {
416             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
417         }
418         if (field.getCount() != 1) {
419             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
420         }
421         final byte[] bytes = field.getByteArrayValue();
422         return tag.getValue(field.getByteOrder(), bytes);
423     }
424 
425     public int[] getFieldValue(final TagInfoShortOrLong tag, final boolean mustExist) throws ImagingException {
426         final TiffField field = findField(tag);
427         if (field == null) {
428             if (mustExist) {
429                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
430             }
431             return null;
432         }
433         if (!tag.dataTypes.contains(field.getFieldType())) {
434             if (mustExist) {
435                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
436             }
437             return null;
438         }
439         final byte[] bytes = field.getByteArrayValue();
440         if (field.getFieldType() == AbstractFieldType.SHORT) {
441             return ByteConversions.toUInt16s(bytes, field.getByteOrder());
442         }
443         return ByteConversions.toInts(bytes, field.getByteOrder());
444     }
445 
446     public short[] getFieldValue(final TagInfoShorts tag, final boolean mustExist) throws ImagingException {
447         final TiffField field = findField(tag);
448         if (field == null) {
449             if (mustExist) {
450                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
451             }
452             return null;
453         }
454         if (!tag.dataTypes.contains(field.getFieldType())) {
455             if (mustExist) {
456                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
457             }
458             return null;
459         }
460         final byte[] bytes = field.getByteArrayValue();
461         return tag.getValue(field.getByteOrder(), bytes);
462     }
463 
464     public int getFieldValue(final TagInfoSLong tag) throws ImagingException {
465         final TiffField field = findField(tag);
466         if (field == null) {
467             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
468         }
469         if (!tag.dataTypes.contains(field.getFieldType())) {
470             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
471         }
472         if (field.getCount() != 1) {
473             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
474         }
475         final byte[] bytes = field.getByteArrayValue();
476         return tag.getValue(field.getByteOrder(), bytes);
477     }
478 
479     public int[] getFieldValue(final TagInfoSLongs tag, final boolean mustExist) throws ImagingException {
480         final TiffField field = findField(tag);
481         if (field == null) {
482             if (mustExist) {
483                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
484             }
485             return null;
486         }
487         if (!tag.dataTypes.contains(field.getFieldType())) {
488             if (mustExist) {
489                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
490             }
491             return null;
492         }
493         final byte[] bytes = field.getByteArrayValue();
494         return tag.getValue(field.getByteOrder(), bytes);
495     }
496 
497     public RationalNumber getFieldValue(final TagInfoSRational tag) throws ImagingException {
498         final TiffField field = findField(tag);
499         if (field == null) {
500             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
501         }
502         if (!tag.dataTypes.contains(field.getFieldType())) {
503             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
504         }
505         if (field.getCount() != 1) {
506             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
507         }
508         final byte[] bytes = field.getByteArrayValue();
509         return tag.getValue(field.getByteOrder(), bytes);
510     }
511 
512     public RationalNumber[] getFieldValue(final TagInfoSRationals tag, final boolean mustExist) throws ImagingException {
513         final TiffField field = findField(tag);
514         if (field == null) {
515             if (mustExist) {
516                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
517             }
518             return null;
519         }
520         if (!tag.dataTypes.contains(field.getFieldType())) {
521             if (mustExist) {
522                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
523             }
524             return null;
525         }
526         final byte[] bytes = field.getByteArrayValue();
527         return tag.getValue(field.getByteOrder(), bytes);
528     }
529 
530     public short getFieldValue(final TagInfoSShort tag) throws ImagingException {
531         final TiffField field = findField(tag);
532         if (field == null) {
533             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
534         }
535         if (!tag.dataTypes.contains(field.getFieldType())) {
536             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
537         }
538         if (field.getCount() != 1) {
539             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
540         }
541         final byte[] bytes = field.getByteArrayValue();
542         return tag.getValue(field.getByteOrder(), bytes);
543     }
544 
545     public short[] getFieldValue(final TagInfoSShorts tag, final boolean mustExist) throws ImagingException {
546         final TiffField field = findField(tag);
547         if (field == null) {
548             if (mustExist) {
549                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
550             }
551             return null;
552         }
553         if (!tag.dataTypes.contains(field.getFieldType())) {
554             if (mustExist) {
555                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
556             }
557             return null;
558         }
559         final byte[] bytes = field.getByteArrayValue();
560         return tag.getValue(field.getByteOrder(), bytes);
561     }
562 
563     public String getFieldValue(final TagInfoXpString tag, final boolean mustExist) throws ImagingException {
564         final TiffField field = findField(tag);
565         if (field == null) {
566             if (mustExist) {
567                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
568             }
569             return null;
570         }
571         return tag.getValue(field);
572     }
573 
574     public JpegImageData getJpegImageData() {
575         return jpegImageData;
576     }
577 
578     public ImageDataElement getJpegRawImageDataElement() throws ImagingException {
579         final TiffField jpegInterchangeFormat = findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT);
580         final TiffField jpegInterchangeFormatLength = findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
581 
582         if (jpegInterchangeFormat != null && jpegInterchangeFormatLength != null) {
583             final int offSet = jpegInterchangeFormat.getIntArrayValue()[0];
584             final int byteCount = jpegInterchangeFormatLength.getIntArrayValue()[0];
585 
586             return new ImageDataElement(offSet, byteCount);
587         }
588         throw new ImagingException("Couldn't find image data.");
589     }
590 
591     public long getNextDirectoryOffset() {
592         return nextDirectoryOffset;
593     }
594 
595     /**
596      * Reads the numerical data stored in this TIFF directory, if available. Note that this method is defined only for TIFF directories that contain
597      * floating-point data or two-byte signed integer data.
598      * <p>
599      * TIFF directories that provide numerical data do not directly specify images, though it is possible to interpret the data as an image using this library.
600      * TIFF files may contain multiple directories which are allowed to have different formats. Thus it is possible for a TIFF file to contain a mix of image
601      * and floating-point raster data.
602      * <p>
603      * If desired, sub-image data can be read from the file by using a Java Map instance to specify the subsection of the image that is required. The following
604      * code illustrates the approach:
605      *
606      * <pre>
607      * int x; // coordinate (column) of corner of sub-image
608      * int y; // coordinate (row) of corner of sub-image
609      * int width; // width of sub-image
610      * int height; // height of sub-image
611      *
612      * Map&lt;String, Object&gt; params = new HashMap&lt;&gt;();
613      * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_X, x);
614      * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_Y, y);
615      * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_WIDTH, width);
616      * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_HEIGHT, height);
617      * TiffRasterData raster = directory.readFloatingPointRasterData(params);
618      * </pre>
619      *
620      * @param params an optional parameter map instance
621      * @return a valid instance
622      * @throws ImagingException in the event of incompatible or malformed data
623      * @throws IOException      in the event of an I/O error
624      */
625     public AbstractTiffRasterData getRasterData(final TiffImagingParameters params) throws ImagingException, IOException {
626 
627         final TiffImageParser parser = new TiffImageParser();
628         return parser.getRasterData(this, headerByteOrder, params);
629     }
630 
631     private List<ImageDataElement> getRawImageDataElements(final TiffField offsetsField, final TiffField byteCountsField) throws ImagingException {
632         final long[] offsets = offsetsField.getLongArrayValue();
633         final int[] byteCounts = byteCountsField.getIntArrayValue();
634 
635         if (offsets.length != byteCounts.length) {
636             throw new ImagingException("offsets.length(" + offsets.length + ") != byteCounts.length(" + byteCounts.length + ")");
637         }
638 
639         final List<ImageDataElement> result = Allocator.arrayList(offsets.length);
640         for (int i = 0; i < offsets.length; i++) {
641             result.add(new ImageDataElement(offsets[i], byteCounts[i]));
642         }
643         return result;
644     }
645 
646     public String getSingleFieldValue(final TagInfoAscii tag) throws ImagingException {
647         final String[] result = getFieldValue(tag, true);
648         if (result.length != 1) {
649             throw new ImagingException("Field \"" + tag.name + "\" has incorrect length " + result.length);
650         }
651         return result[0];
652     }
653 
654     public int getSingleFieldValue(final TagInfoShortOrLong tag) throws ImagingException {
655         final int[] result = getFieldValue(tag, true);
656         if (result.length != 1) {
657             throw new ImagingException("Field \"" + tag.name + "\" has incorrect length " + result.length);
658         }
659         return result[0];
660     }
661 
662     /**
663      * Gets the image associated with the directory, if any. Note that not all directories contain images.
664      *
665      * @return if successful, a valid BufferedImage instance.
666      * @throws ImagingException in the event of an invalid or incompatible data format.
667      * @throws IOException      in the event of an I/O error.
668      */
669     public BufferedImage getTiffImage() throws ImagingException, IOException {
670         if (null == abstractTiffImageData) {
671             return null;
672         }
673 
674         return new TiffImageParser().getBufferedImage(this, headerByteOrder, null);
675     }
676 
677     /**
678      * Gets the image associated with the directory, if any. Note that not all directories contain images.
679      * <p>
680      * This method comes from an older version of this class in which byte order was required from an external source. Developers are encouraged to use the
681      * simpler version of getTiffImage that does not require the byte-order argument.
682      *
683      * @param byteOrder byte-order obtained from the containing TIFF file
684      * @return if successful, a valid BufferedImage instance.
685      * @throws ImagingException in the event of an invalid or incompatible data format.
686      * @throws IOException      in the event of an I/O error.
687      */
688     public BufferedImage getTiffImage(final ByteOrder byteOrder) throws ImagingException, IOException {
689         return getTiffImage(byteOrder, new TiffImagingParameters());
690     }
691 
692     /**
693      * Gets the image associated with the directory, if any. Note that not all directories contain images.
694      * <p>
695      * This method comes from an older version of this class in which byte order was required from an external source. Developers are encouraged to use the
696      * simpler version of getTiffImage that does not require the byte-order argument.
697      *
698      * @param byteOrder byte-order obtained from the containing TIFF file
699      * @param params    an object containing optional parameters to be applied to the read operation.
700      * @return if successful, a valid BufferedImage instance.
701      * @throws ImagingException in the event of an invalid or incompatible data format.
702      * @throws IOException      in the event of an I/O error.
703      */
704     public BufferedImage getTiffImage(final ByteOrder byteOrder, final TiffImagingParameters params) throws ImagingException, IOException {
705         if (null == abstractTiffImageData) {
706             return null;
707         }
708 
709         return new TiffImageParser().getBufferedImage(this, byteOrder, params);
710     }
711 
712     /**
713      * Gets the image associated with the directory, if any. Note that not all directories contain images.
714      * <p>
715      * The optional parameters object can be used to specify image access or rendering options such as reading only a part of the overall image (i.e. reading a
716      * sub-image) or applying a custom photometric interpreter.
717      *
718      * @param params an object containing optional parameters to be applied to the read operation.
719      * @return if successful, a valid BufferedImage instance.
720      * @throws ImagingException in the event of an invalid or incompatible data format.
721      * @throws IOException      in the event of an I/O error.
722      */
723     public BufferedImage getTiffImage(final TiffImagingParameters params) throws ImagingException, IOException {
724         if (null == abstractTiffImageData) {
725             return null;
726         }
727 
728         return new TiffImageParser().getBufferedImage(this, headerByteOrder, params);
729     }
730 
731     public AbstractTiffImageData getTiffImageData() {
732         return abstractTiffImageData;
733     }
734 
735     public List<ImageDataElement> getTiffRawImageDataElements() throws ImagingException {
736         final TiffField tileOffsets = findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS);
737         final TiffField tileByteCounts = findField(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS);
738         final TiffField stripOffsets = findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS);
739         final TiffField stripByteCounts = findField(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS);
740 
741         if (tileOffsets != null && tileByteCounts != null) {
742             return getRawImageDataElements(tileOffsets, tileByteCounts);
743         }
744         if (stripOffsets != null && stripByteCounts != null) {
745             return getRawImageDataElements(stripOffsets, stripByteCounts);
746         }
747         throw new ImagingException("Couldn't find image data.");
748     }
749 
750     public boolean hasJpegImageData() throws ImagingException {
751         return null != findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT);
752     }
753 
754     /**
755      * Indicates whether the directory definition specifies a float-point data format.
756      *
757      * @return {@code true} if the directory contains floating point data; otherwise, {@code false}
758      * @throws ImagingException in the event of an invalid or malformed specification.
759      */
760     public boolean hasTiffFloatingPointRasterData() throws ImagingException {
761         if (!hasTiffImageData()) {
762             return false;
763         }
764         final short[] s = getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false);
765         return s != null && s.length > 0 && s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT;
766 
767     }
768 
769     public boolean hasTiffImageData() throws ImagingException {
770         if (null != findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS)) {
771             return true;
772         }
773 
774         return null != findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS);
775     }
776 
777     /**
778      * Indicates whether the content associated with the directory is given in a supported numerical-data format. If this method returns {@code true}, the
779      * Imaging API will be able to extract a TiffRasterData instance from the associated TIFF file using this directory.
780      *
781      * @return {@code true} if the directory contains a supported raster data format; otherwise, {@code false}.
782      * @throws ImagingException in the event of an invalid or malformed specification.
783      */
784     public boolean hasTiffRasterData() throws ImagingException {
785         if (!hasTiffImageData()) {
786             return false;
787         }
788         final short[] s = getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false);
789         return s != null && s.length > 0 && (s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT
790                 || s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER);
791     }
792 
793     public boolean imageDataInStrips() throws ImagingException {
794         final TiffField tileOffsets = findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS);
795         final TiffField tileByteCounts = findField(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS);
796         final TiffField stripOffsets = findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS);
797         final TiffField stripByteCounts = findField(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS);
798 
799         if (tileOffsets != null && tileByteCounts != null) {
800             return false;
801         }
802         if (stripOffsets != null && stripByteCounts != null) {
803             return true;
804         }
805         throw new ImagingException("Couldn't find image data.");
806     }
807 
808     @Override
809     public Iterator<TiffField> iterator() {
810         return entries.iterator();
811     }
812 
813     public void setJpegImageData(final JpegImageData value) {
814         this.jpegImageData = value;
815     }
816 
817     public void setTiffImageData(final AbstractTiffImageData rawImageData) {
818         this.abstractTiffImageData = rawImageData;
819     }
820 
821     public int size() {
822         return entries.size();
823     }
824 }