001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.imaging.formats.tiff;
018
019import java.awt.image.BufferedImage;
020import java.io.IOException;
021import java.nio.ByteOrder;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.Iterator;
025import java.util.List;
026
027import org.apache.commons.imaging.ImagingException;
028import org.apache.commons.imaging.common.Allocator;
029import org.apache.commons.imaging.common.ByteConversions;
030import org.apache.commons.imaging.common.RationalNumber;
031import org.apache.commons.imaging.formats.tiff.constants.TiffConstants;
032import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants;
033import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
034import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType;
035import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
036import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAscii;
037import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoByte;
038import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoBytes;
039import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDouble;
040import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDoubles;
041import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloat;
042import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloats;
043import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoGpsText;
044import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLong;
045import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLongs;
046import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRational;
047import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRationals;
048import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSByte;
049import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSBytes;
050import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLong;
051import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLongs;
052import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRational;
053import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRationals;
054import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShort;
055import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShorts;
056import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShort;
057import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShortOrLong;
058import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShorts;
059import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoXpString;
060
061/**
062 * 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
063 * individual images or sets of metadata. While not all Directories contain images, images are always stored in a Directory.
064 */
065public class TiffDirectory extends AbstractTiffElement implements Iterable<TiffField> {
066
067    public static final class ImageDataElement extends AbstractTiffElement {
068        public ImageDataElement(final long offset, final int length) {
069            super(offset, length);
070        }
071
072        @Override
073        public String getElementDescription() {
074            return "ImageDataElement";
075        }
076    }
077
078    public static String description(final int type) {
079        switch (type) {
080        case TiffDirectoryConstants.DIRECTORY_TYPE_UNKNOWN:
081            return "Unknown";
082        case TiffDirectoryConstants.DIRECTORY_TYPE_ROOT:
083            return "Root";
084        case TiffDirectoryConstants.DIRECTORY_TYPE_SUB:
085            return "Sub";
086        case TiffDirectoryConstants.DIRECTORY_TYPE_THUMBNAIL:
087            return "Thumbnail";
088        case TiffDirectoryConstants.DIRECTORY_TYPE_EXIF:
089            return "Exif";
090        case TiffDirectoryConstants.DIRECTORY_TYPE_GPS:
091            return "Gps";
092        case TiffDirectoryConstants.DIRECTORY_TYPE_INTEROPERABILITY:
093            return "Interoperability";
094        default:
095            return "Bad Type";
096        }
097    }
098
099    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}