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.TIFF_DIRECTORY_HEADER_LENGTH + entries.size() * TiffConstants.TIFF_ENTRY_LENGTH + TiffConstants.TIFF_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 TiffDirectory.description(type);
127     }
128 
129     public void dump() {
130         for (final TiffField entry : entries) {
131             entry.dump();
132         }
133     }
134 
135     public TiffField findField(final TagInfo tag) throws ImagingException {
136         final boolean failIfMissing = false;
137         return findField(tag, failIfMissing);
138     }
139 
140     public TiffField findField(final TagInfo tag, final boolean failIfMissing) throws ImagingException {
141         for (final TiffField field : entries) {
142             if (field.getTag() == tag.tag) {
143                 return field;
144             }
145         }
146 
147         if (failIfMissing) {
148             throw new ImagingException("Missing expected field: " + tag.getDescription());
149         }
150 
151         return null;
152     }
153 
154     /**
155      * Gets the byte order used by the source file for storing this directory and its content.
156      *
157      * @return A valid byte order instance.
158      */
159     public ByteOrder getByteOrder() {
160         return headerByteOrder;
161     }
162 
163     public List<TiffField> getDirectoryEntries() {
164         return new ArrayList<>(entries);
165     }
166 
167     @Override
168     public String getElementDescription() {
169         long entryOffset = offset + TiffConstants.TIFF_DIRECTORY_HEADER_LENGTH;
170 
171         final StringBuilder result = new StringBuilder();
172         for (final TiffField entry : entries) {
173             result.append(String.format("\t[%d]: %s (%d, 0x%x), %s, %d: %s%n", entryOffset, entry.getTagInfo().name, entry.getTag(), entry.getTag(),
174                     entry.getFieldType().getName(), entry.getBytesLength(), entry.getValueDescription()));
175 
176             entryOffset += TiffConstants.TIFF_ENTRY_LENGTH;
177         }
178         return result.toString();
179     }
180 
181     public Object getFieldValue(final TagInfo tag) throws ImagingException {
182         final TiffField field = findField(tag);
183         if (field == null) {
184             return null;
185         }
186         return field.getValue();
187     }
188 
189     public String[] getFieldValue(final TagInfoAscii tag, final boolean mustExist) throws ImagingException {
190         final TiffField field = findField(tag);
191         if (field == null) {
192             if (mustExist) {
193                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
194             }
195             return null;
196         }
197         if (!tag.dataTypes.contains(field.getFieldType())) {
198             if (mustExist) {
199                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
200             }
201             return null;
202         }
203         final byte[] bytes = field.getByteArrayValue();
204         return tag.getValue(field.getByteOrder(), bytes);
205     }
206 
207     public byte getFieldValue(final TagInfoByte tag) throws ImagingException {
208         final TiffField field = findField(tag);
209         if (field == null) {
210             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
211         }
212         if (!tag.dataTypes.contains(field.getFieldType())) {
213             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
214         }
215         if (field.getCount() != 1) {
216             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
217         }
218         return field.getByteArrayValue()[0];
219     }
220 
221     public byte[] getFieldValue(final TagInfoBytes tag, final boolean mustExist) throws ImagingException {
222         final TiffField field = findField(tag);
223         if (field == null) {
224             if (mustExist) {
225                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
226             }
227             return null;
228         }
229         if (!tag.dataTypes.contains(field.getFieldType())) {
230             if (mustExist) {
231                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
232             }
233             return null;
234         }
235         return field.getByteArrayValue();
236     }
237 
238     public double getFieldValue(final TagInfoDouble tag) throws ImagingException {
239         final TiffField field = findField(tag);
240         if (field == null) {
241             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
242         }
243         if (!tag.dataTypes.contains(field.getFieldType())) {
244             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
245         }
246         if (field.getCount() != 1) {
247             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
248         }
249         final byte[] bytes = field.getByteArrayValue();
250         return tag.getValue(field.getByteOrder(), bytes);
251     }
252 
253     public double[] getFieldValue(final TagInfoDoubles tag, final boolean mustExist) throws ImagingException {
254         final TiffField field = findField(tag);
255         if (field == null) {
256             if (mustExist) {
257                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
258             }
259             return null;
260         }
261         if (!tag.dataTypes.contains(field.getFieldType())) {
262             if (mustExist) {
263                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
264             }
265             return null;
266         }
267         final byte[] bytes = field.getByteArrayValue();
268         return tag.getValue(field.getByteOrder(), bytes);
269     }
270 
271     public float getFieldValue(final TagInfoFloat tag) throws ImagingException {
272         final TiffField field = findField(tag);
273         if (field == null) {
274             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
275         }
276         if (!tag.dataTypes.contains(field.getFieldType())) {
277             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
278         }
279         if (field.getCount() != 1) {
280             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
281         }
282         final byte[] bytes = field.getByteArrayValue();
283         return tag.getValue(field.getByteOrder(), bytes);
284     }
285 
286     public float[] getFieldValue(final TagInfoFloats tag, final boolean mustExist) throws ImagingException {
287         final TiffField field = findField(tag);
288         if (field == null) {
289             if (mustExist) {
290                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
291             }
292             return null;
293         }
294         if (!tag.dataTypes.contains(field.getFieldType())) {
295             if (mustExist) {
296                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
297             }
298             return null;
299         }
300         final byte[] bytes = field.getByteArrayValue();
301         return tag.getValue(field.getByteOrder(), bytes);
302     }
303 
304     public String getFieldValue(final TagInfoGpsText tag, final boolean mustExist) throws ImagingException {
305         final TiffField field = findField(tag);
306         if (field == null) {
307             if (mustExist) {
308                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
309             }
310             return null;
311         }
312         return tag.getValue(field);
313     }
314 
315     public int getFieldValue(final TagInfoLong tag) throws ImagingException {
316         final TiffField field = findField(tag);
317         if (field == null) {
318             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
319         }
320         if (!tag.dataTypes.contains(field.getFieldType())) {
321             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
322         }
323         if (field.getCount() != 1) {
324             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
325         }
326         final byte[] bytes = field.getByteArrayValue();
327         return tag.getValue(field.getByteOrder(), bytes);
328     }
329 
330     public int[] getFieldValue(final TagInfoLongs tag, final boolean mustExist) throws ImagingException {
331         final TiffField field = findField(tag);
332         if (field == null) {
333             if (mustExist) {
334                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
335             }
336             return null;
337         }
338         if (!tag.dataTypes.contains(field.getFieldType())) {
339             if (mustExist) {
340                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
341             }
342             return null;
343         }
344         final byte[] bytes = field.getByteArrayValue();
345         return tag.getValue(field.getByteOrder(), bytes);
346     }
347 
348     public RationalNumber getFieldValue(final TagInfoRational tag) throws ImagingException {
349         final TiffField field = findField(tag);
350         if (field == null) {
351             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
352         }
353         if (!tag.dataTypes.contains(field.getFieldType())) {
354             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
355         }
356         if (field.getCount() != 1) {
357             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
358         }
359         final byte[] bytes = field.getByteArrayValue();
360         return tag.getValue(field.getByteOrder(), bytes);
361     }
362 
363     public RationalNumber[] getFieldValue(final TagInfoRationals tag, final boolean mustExist) throws ImagingException {
364         final TiffField field = findField(tag);
365         if (field == null) {
366             if (mustExist) {
367                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
368             }
369             return null;
370         }
371         if (!tag.dataTypes.contains(field.getFieldType())) {
372             if (mustExist) {
373                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
374             }
375             return null;
376         }
377         final byte[] bytes = field.getByteArrayValue();
378         return tag.getValue(field.getByteOrder(), bytes);
379     }
380 
381     public byte getFieldValue(final TagInfoSByte tag) throws ImagingException {
382         final TiffField field = findField(tag);
383         if (field == null) {
384             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
385         }
386         if (!tag.dataTypes.contains(field.getFieldType())) {
387             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
388         }
389         if (field.getCount() != 1) {
390             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
391         }
392         return field.getByteArrayValue()[0];
393     }
394 
395     public byte[] getFieldValue(final TagInfoSBytes tag, final boolean mustExist) throws ImagingException {
396         final TiffField field = findField(tag);
397         if (field == null) {
398             if (mustExist) {
399                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
400             }
401             return null;
402         }
403         if (!tag.dataTypes.contains(field.getFieldType())) {
404             if (mustExist) {
405                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
406             }
407             return null;
408         }
409         return field.getByteArrayValue();
410     }
411 
412     public short getFieldValue(final TagInfoShort tag) throws ImagingException {
413         final TiffField field = findField(tag);
414         if (field == null) {
415             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
416         }
417         if (!tag.dataTypes.contains(field.getFieldType())) {
418             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
419         }
420         if (field.getCount() != 1) {
421             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
422         }
423         final byte[] bytes = field.getByteArrayValue();
424         return tag.getValue(field.getByteOrder(), bytes);
425     }
426 
427     public int[] getFieldValue(final TagInfoShortOrLong tag, final boolean mustExist) throws ImagingException {
428         final TiffField field = findField(tag);
429         if (field == null) {
430             if (mustExist) {
431                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
432             }
433             return null;
434         }
435         if (!tag.dataTypes.contains(field.getFieldType())) {
436             if (mustExist) {
437                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
438             }
439             return null;
440         }
441         final byte[] bytes = field.getByteArrayValue();
442         if (field.getFieldType() == AbstractFieldType.SHORT) {
443             return ByteConversions.toUInt16s(bytes, field.getByteOrder());
444         }
445         return ByteConversions.toInts(bytes, field.getByteOrder());
446     }
447 
448     public short[] getFieldValue(final TagInfoShorts tag, final boolean mustExist) throws ImagingException {
449         final TiffField field = findField(tag);
450         if (field == null) {
451             if (mustExist) {
452                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
453             }
454             return null;
455         }
456         if (!tag.dataTypes.contains(field.getFieldType())) {
457             if (mustExist) {
458                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
459             }
460             return null;
461         }
462         final byte[] bytes = field.getByteArrayValue();
463         return tag.getValue(field.getByteOrder(), bytes);
464     }
465 
466     public int getFieldValue(final TagInfoSLong tag) throws ImagingException {
467         final TiffField field = findField(tag);
468         if (field == null) {
469             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
470         }
471         if (!tag.dataTypes.contains(field.getFieldType())) {
472             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
473         }
474         if (field.getCount() != 1) {
475             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
476         }
477         final byte[] bytes = field.getByteArrayValue();
478         return tag.getValue(field.getByteOrder(), bytes);
479     }
480 
481     public int[] getFieldValue(final TagInfoSLongs tag, final boolean mustExist) throws ImagingException {
482         final TiffField field = findField(tag);
483         if (field == null) {
484             if (mustExist) {
485                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
486             }
487             return null;
488         }
489         if (!tag.dataTypes.contains(field.getFieldType())) {
490             if (mustExist) {
491                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
492             }
493             return null;
494         }
495         final byte[] bytes = field.getByteArrayValue();
496         return tag.getValue(field.getByteOrder(), bytes);
497     }
498 
499     public RationalNumber getFieldValue(final TagInfoSRational tag) throws ImagingException {
500         final TiffField field = findField(tag);
501         if (field == null) {
502             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
503         }
504         if (!tag.dataTypes.contains(field.getFieldType())) {
505             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
506         }
507         if (field.getCount() != 1) {
508             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
509         }
510         final byte[] bytes = field.getByteArrayValue();
511         return tag.getValue(field.getByteOrder(), bytes);
512     }
513 
514     public RationalNumber[] getFieldValue(final TagInfoSRationals tag, final boolean mustExist) throws ImagingException {
515         final TiffField field = findField(tag);
516         if (field == null) {
517             if (mustExist) {
518                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
519             }
520             return null;
521         }
522         if (!tag.dataTypes.contains(field.getFieldType())) {
523             if (mustExist) {
524                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
525             }
526             return null;
527         }
528         final byte[] bytes = field.getByteArrayValue();
529         return tag.getValue(field.getByteOrder(), bytes);
530     }
531 
532     public short getFieldValue(final TagInfoSShort tag) throws ImagingException {
533         final TiffField field = findField(tag);
534         if (field == null) {
535             throw new ImagingException("Required field \"" + tag.name + "\" is missing");
536         }
537         if (!tag.dataTypes.contains(field.getFieldType())) {
538             throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
539         }
540         if (field.getCount() != 1) {
541             throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
542         }
543         final byte[] bytes = field.getByteArrayValue();
544         return tag.getValue(field.getByteOrder(), bytes);
545     }
546 
547     public short[] getFieldValue(final TagInfoSShorts tag, final boolean mustExist) throws ImagingException {
548         final TiffField field = findField(tag);
549         if (field == null) {
550             if (mustExist) {
551                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
552             }
553             return null;
554         }
555         if (!tag.dataTypes.contains(field.getFieldType())) {
556             if (mustExist) {
557                 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName());
558             }
559             return null;
560         }
561         final byte[] bytes = field.getByteArrayValue();
562         return tag.getValue(field.getByteOrder(), bytes);
563     }
564 
565     public String getFieldValue(final TagInfoXpString tag, final boolean mustExist) throws ImagingException {
566         final TiffField field = findField(tag);
567         if (field == null) {
568             if (mustExist) {
569                 throw new ImagingException("Required field \"" + tag.name + "\" is missing");
570             }
571             return null;
572         }
573         return tag.getValue(field);
574     }
575 
576     public JpegImageData getJpegImageData() {
577         return jpegImageData;
578     }
579 
580     public ImageDataElement getJpegRawImageDataElement() throws ImagingException {
581         final TiffField jpegInterchangeFormat = findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT);
582         final TiffField jpegInterchangeFormatLength = findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
583 
584         if (jpegInterchangeFormat != null && jpegInterchangeFormatLength != null) {
585             final int offSet = jpegInterchangeFormat.getIntArrayValue()[0];
586             final int byteCount = jpegInterchangeFormatLength.getIntArrayValue()[0];
587 
588             return new ImageDataElement(offSet, byteCount);
589         }
590         throw new ImagingException("Couldn't find image data.");
591     }
592 
593     public long getNextDirectoryOffset() {
594         return nextDirectoryOffset;
595     }
596 
597     /**
598      * Reads the numerical data stored in this TIFF directory, if available. Note that this method is defined only for TIFF directories that contain
599      * floating-point data or two-byte signed integer data.
600      * <p>
601      * 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.
602      * 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
603      * and floating-point raster data.
604      * <p>
605      * 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
606      * code illustrates the approach:
607      *
608      * <pre>
609      * int x; // coordinate (column) of corner of sub-image
610      * int y; // coordinate (row) of corner of sub-image
611      * int width; // width of sub-image
612      * int height; // height of sub-image
613      *
614      * Map&lt;String, Object&gt; params = new HashMap&lt;&gt;();
615      * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_X, x);
616      * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_Y, y);
617      * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_WIDTH, width);
618      * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_HEIGHT, height);
619      * TiffRasterData raster = directory.readFloatingPointRasterData(params);
620      * </pre>
621      *
622      * @param params an optional parameter map instance
623      * @return a valid instance
624      * @throws ImagingException in the event of incompatible or malformed data
625      * @throws IOException      in the event of an I/O error
626      */
627     public TiffRasterData getRasterData(final TiffImagingParameters params) throws ImagingException, IOException {
628 
629         final TiffImageParser parser = new TiffImageParser();
630         return parser.getRasterData(this, headerByteOrder, params);
631     }
632 
633     private List<ImageDataElement> getRawImageDataElements(final TiffField offsetsField, final TiffField byteCountsField) throws ImagingException {
634         final long[] offsets = offsetsField.getLongArrayValue();
635         final int[] byteCounts = byteCountsField.getIntArrayValue();
636 
637         if (offsets.length != byteCounts.length) {
638             throw new ImagingException("offsets.length(" + offsets.length + ") != byteCounts.length(" + byteCounts.length + ")");
639         }
640 
641         final List<ImageDataElement> result = Allocator.arrayList(offsets.length);
642         for (int i = 0; i < offsets.length; i++) {
643             result.add(new ImageDataElement(offsets[i], byteCounts[i]));
644         }
645         return result;
646     }
647 
648     public String getSingleFieldValue(final TagInfoAscii tag) throws ImagingException {
649         final String[] result = getFieldValue(tag, true);
650         if (result.length != 1) {
651             throw new ImagingException("Field \"" + tag.name + "\" has incorrect length " + result.length);
652         }
653         return result[0];
654     }
655 
656     public int getSingleFieldValue(final TagInfoShortOrLong tag) throws ImagingException {
657         final int[] result = getFieldValue(tag, true);
658         if (result.length != 1) {
659             throw new ImagingException("Field \"" + tag.name + "\" has incorrect length " + result.length);
660         }
661         return result[0];
662     }
663 
664     /**
665      * Gets the image associated with the directory, if any. Note that not all directories contain images.
666      *
667      * @return if successful, a valid BufferedImage instance.
668      * @throws ImagingException in the event of an invalid or incompatible data format.
669      * @throws IOException      in the event of an I/O error.
670      */
671     public BufferedImage getTiffImage() throws ImagingException, IOException {
672         if (null == abstractTiffImageData) {
673             return null;
674         }
675 
676         return new TiffImageParser().getBufferedImage(this, headerByteOrder, null);
677     }
678 
679     /**
680      * Gets the image associated with the directory, if any. Note that not all directories contain images.
681      * <p>
682      * 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
683      * simpler version of getTiffImage that does not require the byte-order argument.
684      *
685      * @param byteOrder byte-order obtained from the containing TIFF file
686      * @return if successful, a valid BufferedImage instance.
687      * @throws ImagingException in the event of an invalid or incompatible data format.
688      * @throws IOException      in the event of an I/O error.
689      */
690     public BufferedImage getTiffImage(final ByteOrder byteOrder) throws ImagingException, IOException {
691         return getTiffImage(byteOrder, new TiffImagingParameters());
692     }
693 
694     /**
695      * Gets the image associated with the directory, if any. Note that not all directories contain images.
696      * <p>
697      * 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
698      * simpler version of getTiffImage that does not require the byte-order argument.
699      *
700      * @param byteOrder byte-order obtained from the containing TIFF file
701      * @param params    an object containing optional parameters to be applied to the read operation.
702      * @return if successful, a valid BufferedImage instance.
703      * @throws ImagingException in the event of an invalid or incompatible data format.
704      * @throws IOException      in the event of an I/O error.
705      */
706     public BufferedImage getTiffImage(final ByteOrder byteOrder, final TiffImagingParameters params) throws ImagingException, IOException {
707         if (null == abstractTiffImageData) {
708             return null;
709         }
710 
711         return new TiffImageParser().getBufferedImage(this, byteOrder, params);
712     }
713 
714     /**
715      * Gets the image associated with the directory, if any. Note that not all directories contain images.
716      * <p>
717      * 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
718      * sub-image) or applying a custom photometric interpreter.
719      *
720      * @param params an object containing optional parameters to be applied to the read operation.
721      * @return if successful, a valid BufferedImage instance.
722      * @throws ImagingException in the event of an invalid or incompatible data format.
723      * @throws IOException      in the event of an I/O error.
724      */
725     public BufferedImage getTiffImage(final TiffImagingParameters params) throws ImagingException, IOException {
726         if (null == abstractTiffImageData) {
727             return null;
728         }
729 
730         return new TiffImageParser().getBufferedImage(this, headerByteOrder, params);
731     }
732 
733     public AbstractTiffImageData getTiffImageData() {
734         return abstractTiffImageData;
735     }
736 
737     public List<ImageDataElement> getTiffRawImageDataElements() throws ImagingException {
738         final TiffField tileOffsets = findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS);
739         final TiffField tileByteCounts = findField(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS);
740         final TiffField stripOffsets = findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS);
741         final TiffField stripByteCounts = findField(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS);
742 
743         if (tileOffsets != null && tileByteCounts != null) {
744             return getRawImageDataElements(tileOffsets, tileByteCounts);
745         }
746         if (stripOffsets != null && stripByteCounts != null) {
747             return getRawImageDataElements(stripOffsets, stripByteCounts);
748         }
749         throw new ImagingException("Couldn't find image data.");
750     }
751 
752     public boolean hasJpegImageData() throws ImagingException {
753         return null != findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT);
754     }
755 
756     /**
757      * Indicates whether the directory definition specifies a float-point data format.
758      *
759      * @return {@code true} if the directory contains floating point data; otherwise, {@code false}
760      *
761      * @throws ImagingException in the event of an invalid or malformed specification.
762      */
763     public boolean hasTiffFloatingPointRasterData() throws ImagingException {
764         if (!this.hasTiffImageData()) {
765             return false;
766         }
767         final short[] s = getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false);
768         return s != null && s.length > 0 && s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT;
769 
770     }
771 
772     public boolean hasTiffImageData() throws ImagingException {
773         if (null != findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS)) {
774             return true;
775         }
776 
777         return null != findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS);
778     }
779 
780     /**
781      * Indicates whether the content associated with the directory is given in a supported numerical-data format. If this method returns {@code true}, the
782      * Imaging API will be able to extract a TiffRasterData instance from the associated TIFF file using this directory.
783      *
784      * @return {@code true} if the directory contains a supported raster data format; otherwise, {@code false}.
785      * @throws ImagingException in the event of an invalid or malformed specification.
786      */
787     public boolean hasTiffRasterData() throws ImagingException {
788         if (!this.hasTiffImageData()) {
789             return false;
790         }
791         final short[] s = getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false);
792         return s != null && s.length > 0 && (s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT
793                 || s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER);
794     }
795 
796     public boolean imageDataInStrips() throws ImagingException {
797         final TiffField tileOffsets = findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS);
798         final TiffField tileByteCounts = findField(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS);
799         final TiffField stripOffsets = findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS);
800         final TiffField stripByteCounts = findField(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS);
801 
802         if (tileOffsets != null && tileByteCounts != null) {
803             return false;
804         }
805         if (stripOffsets != null && stripByteCounts != null) {
806             return true;
807         }
808         throw new ImagingException("Couldn't find image data.");
809     }
810 
811     @Override
812     public Iterator<TiffField> iterator() {
813         return entries.iterator();
814     }
815 
816     public void setJpegImageData(final JpegImageData value) {
817         this.jpegImageData = value;
818     }
819 
820     public void setTiffImageData(final AbstractTiffImageData rawImageData) {
821         this.abstractTiffImageData = rawImageData;
822     }
823 
824     public int size() {
825         return entries.size();
826     }
827 }