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