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.List;
025
026import org.apache.commons.imaging.ImageReadException;
027import org.apache.commons.imaging.common.ByteConversions;
028import org.apache.commons.imaging.common.RationalNumber;
029import org.apache.commons.imaging.formats.tiff.constants.TiffConstants;
030import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants;
031import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
032import org.apache.commons.imaging.formats.tiff.fieldtypes.FieldType;
033import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
034import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAscii;
035import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoByte;
036import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoBytes;
037import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDouble;
038import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDoubles;
039import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloat;
040import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloats;
041import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoGpsText;
042import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLong;
043import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLongs;
044import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRational;
045import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRationals;
046import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSByte;
047import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSBytes;
048import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLong;
049import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLongs;
050import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRational;
051import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRationals;
052import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShort;
053import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShorts;
054import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShort;
055import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShortOrLong;
056import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShorts;
057import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoXpString;
058
059/**
060 * Provides methods and elements for accessing an Image File Directory (IFD)
061 * from a TIFF file. In the TIFF specification, the IFD is the main container
062 * for individual images or sets of metadata. While not all Directories contain
063 * images, images are always stored in a Directory.
064 */
065public class TiffDirectory extends TiffElement {
066    public final int type;
067    public final List<TiffField> entries;
068    public final long nextDirectoryOffset;
069    private TiffImageData tiffImageData;
070    private JpegImageData jpegImageData;
071
072    // Preservers the byte order derived from the TIFF file header.
073    // Some of the legacy methods in this class require byte order as an
074    // argument, though that use could be phased out eventually.
075    private final ByteOrder headerByteOrder;
076
077
078    public TiffDirectory(
079        final int type,
080        final List<TiffField> entries,
081        final long offset,
082        final long nextDirectoryOffset,
083        final ByteOrder byteOrder) {
084        super(offset, TiffConstants.TIFF_DIRECTORY_HEADER_LENGTH
085                + entries.size() * TiffConstants.TIFF_ENTRY_LENGTH
086                + TiffConstants.TIFF_DIRECTORY_FOOTER_LENGTH);
087
088        this.type = type;
089        this.entries = Collections.unmodifiableList(entries);
090        this.nextDirectoryOffset = nextDirectoryOffset;
091        this.headerByteOrder = byteOrder;
092    }
093
094    /**
095     * Gets the byte order used by the source file for storing this directory
096     * and its content.
097     *
098     * @return A valid byte order instance.
099     */
100    public ByteOrder getByteOrder() {
101        return headerByteOrder;
102    }
103
104    public String description() {
105        return TiffDirectory.description(type);
106    }
107
108    @Override
109    public String getElementDescription() {
110        long entryOffset = offset + TiffConstants.TIFF_DIRECTORY_HEADER_LENGTH;
111
112        final StringBuilder result = new StringBuilder();
113        for (final TiffField entry : entries) {
114            result.append(String.format("\t[%d]: %s (%d, 0x%x), %s, %d: %s%n",
115                    entryOffset, entry.getTagInfo().name,
116                    entry.getTag(), entry.getTag(),
117                    entry.getFieldType().getName(), entry.getBytesLength(),
118                    entry.getValueDescription()));
119
120            entryOffset += TiffConstants.TIFF_ENTRY_LENGTH;
121        }
122        return result.toString();
123    }
124
125    public static String description(final int type) {
126        switch (type) {
127        case TiffDirectoryConstants.DIRECTORY_TYPE_UNKNOWN:
128            return "Unknown";
129        case TiffDirectoryConstants.DIRECTORY_TYPE_ROOT:
130            return "Root";
131        case TiffDirectoryConstants.DIRECTORY_TYPE_SUB:
132            return "Sub";
133        case TiffDirectoryConstants.DIRECTORY_TYPE_THUMBNAIL:
134            return "Thumbnail";
135        case TiffDirectoryConstants.DIRECTORY_TYPE_EXIF:
136            return "Exif";
137        case TiffDirectoryConstants.DIRECTORY_TYPE_GPS:
138            return "Gps";
139        case TiffDirectoryConstants.DIRECTORY_TYPE_INTEROPERABILITY:
140            return "Interoperability";
141        default:
142            return "Bad Type";
143        }
144    }
145
146
147    public List<TiffField> getDirectoryEntries() {
148        return new ArrayList<>(entries);
149    }
150
151    public void dump() {
152        for (final TiffField entry : entries) {
153            entry.dump();
154        }
155
156    }
157
158    public boolean hasJpegImageData() throws ImageReadException {
159        return null != findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT);
160    }
161
162    public boolean hasTiffImageData() throws ImageReadException {
163        if (null != findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS)) {
164            return true;
165        }
166
167        return null != findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS);
168    }
169
170    /**
171     * Gets the image associated with the directory, if any. Note that not all
172     * directories contain images.
173     *
174     * @return if successful, a valid BufferedImage instance.
175     * @throws ImageReadException in the event of an invalid or incompatible
176     * data format.
177     * @throws IOException in the event of an I/O error.
178     */
179    public BufferedImage getTiffImage() throws ImageReadException, IOException {
180        if (null == tiffImageData) {
181            return null;
182        }
183
184        return new TiffImageParser().getBufferedImage(this, headerByteOrder, null);
185    }
186
187    /**
188     * Gets the image associated with the directory, if any. Note that not all
189     * directories contain images.
190     * <p>
191     * The optional parameters object can be used to specify image access or
192     * rendering options such as reading only a part of the overall image (i.e.
193     * reading a sub-image) or applying a custom photometric interpreter.
194     *
195     * @param params an object containing optional parameters to be applied to the
196     * read operation.
197     * @return if successful, a valid BufferedImage instance.
198     * @throws ImageReadException in the event of an invalid or incompatible
199     * data format.
200     * @throws IOException in the event of an I/O error.
201     */
202    public BufferedImage getTiffImage(final TiffImagingParameters params)
203        throws ImageReadException, IOException {
204        if (null == tiffImageData) {
205            return null;
206        }
207
208        return new TiffImageParser().getBufferedImage(this, headerByteOrder, params);
209    }
210
211    /**
212     * Gets the image associated with the directory, if any. Note that not all
213     * directories contain images.
214     * <p>
215     * This method comes from an older version of this class in which byte order
216     * was required from an external source. Developers are encouraged to use
217     * the simpler version of getTiffImage that does not require the byte-order
218     * argument.
219     *
220     * @param byteOrder byte-order obtained from the containing TIFF file
221     * @return if successful, a valid BufferedImage instance.
222     * @throws ImageReadException in the event of an invalid or incompatible
223     * data format.
224     * @throws IOException in the event of an I/O error.
225     */
226    public BufferedImage getTiffImage(final ByteOrder byteOrder) throws ImageReadException,
227            IOException {
228        return getTiffImage(byteOrder, new TiffImagingParameters());
229    }
230
231    /**
232     * Gets the image associated with the directory, if any. Note that not all
233     * directories contain images.
234     * <p>
235     * This method comes from an older version of this class in which byte order
236     * was required from an external source. Developers are encouraged to use
237     * the simpler version of getTiffImage that does not require the byte-order
238     * argument.
239     *
240     * @param byteOrder byte-order obtained from the containing TIFF file
241     * @param params an object containing optional parameters to be applied to the
242     * read operation.
243     * @return if successful, a valid BufferedImage instance.
244     * @throws ImageReadException in the event of an invalid or incompatible
245     * data format.
246     * @throws IOException in the event of an I/O error.
247     */
248    public BufferedImage getTiffImage(final ByteOrder byteOrder, final TiffImagingParameters params)
249            throws ImageReadException, IOException {
250        if (null == tiffImageData) {
251            return null;
252        }
253
254        return new TiffImageParser().getBufferedImage(this, byteOrder, params);
255    }
256
257
258
259    public TiffField findField(final TagInfo tag) throws ImageReadException {
260        final boolean failIfMissing = false;
261        return findField(tag, failIfMissing);
262    }
263
264    public TiffField findField(final TagInfo tag, final boolean failIfMissing)
265            throws ImageReadException {
266        if (entries == null) {
267            return null;
268        }
269
270        for (final TiffField field : entries) {
271            if (field.getTag() == tag.tag) {
272                return field;
273            }
274        }
275
276        if (failIfMissing) {
277            throw new ImageReadException("Missing expected field: "
278                    + tag.getDescription());
279        }
280
281        return null;
282    }
283
284    public Object getFieldValue(final TagInfo tag) throws ImageReadException {
285        final TiffField field = findField(tag);
286        if (field == null) {
287            return null;
288        }
289        return field.getValue();
290    }
291
292    public String getSingleFieldValue(final TagInfoAscii tag)
293            throws ImageReadException {
294        final String[] result = getFieldValue(tag, true);
295        if (result.length != 1) {
296            throw new ImageReadException("Field \"" + tag.name
297                    + "\" has incorrect length " + result.length);
298        }
299        return result[0];
300    }
301
302    public int getSingleFieldValue(final TagInfoShortOrLong tag) throws ImageReadException {
303        final int[] result = getFieldValue(tag, true);
304        if (result.length != 1) {
305            throw new ImageReadException("Field \"" + tag.name
306                    + "\" has incorrect length " + result.length);
307        }
308        return result[0];
309    }
310
311    public byte getFieldValue(final TagInfoByte tag)
312            throws ImageReadException {
313        final TiffField field = findField(tag);
314        if (field == null) {
315            throw new ImageReadException("Required field \"" + tag.name
316                    + "\" is missing");
317        }
318        if (!tag.dataTypes.contains(field.getFieldType())) {
319            throw new ImageReadException("Required field \"" + tag.name
320                    + "\" has incorrect type " + field.getFieldType().getName());
321        }
322        if (field.getCount() != 1) {
323            throw new ImageReadException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
324        }
325        return field.getByteArrayValue()[0];
326    }
327
328    public byte[] getFieldValue(final TagInfoBytes tag, final boolean mustExist)
329            throws ImageReadException {
330        final TiffField field = findField(tag);
331        if (field == null) {
332            if (mustExist) {
333                throw new ImageReadException("Required field \"" + tag.name
334                        + "\" is missing");
335            }
336            return null;
337        }
338        if (!tag.dataTypes.contains(field.getFieldType())) {
339            if (mustExist) {
340                throw new ImageReadException("Required field \"" + tag.name
341                        + "\" has incorrect type " + field.getFieldType().getName());
342            }
343            return null;
344        }
345        return field.getByteArrayValue();
346    }
347
348    public String[] getFieldValue(final TagInfoAscii tag, final boolean mustExist)
349            throws ImageReadException {
350        final TiffField field = findField(tag);
351        if (field == null) {
352            if (mustExist) {
353                throw new ImageReadException("Required field \"" + tag.name
354                        + "\" is missing");
355            }
356            return null;
357        }
358        if (!tag.dataTypes.contains(field.getFieldType())) {
359            if (mustExist) {
360                throw new ImageReadException("Required field \"" + tag.name
361                        + "\" has incorrect type " + field.getFieldType().getName());
362            }
363            return null;
364        }
365        final byte[] bytes = field.getByteArrayValue();
366        return tag.getValue(field.getByteOrder(), bytes);
367    }
368
369    public short getFieldValue(final TagInfoShort tag)
370            throws ImageReadException {
371        final TiffField field = findField(tag);
372        if (field == null) {
373            throw new ImageReadException("Required field \"" + tag.name
374                    + "\" is missing");
375        }
376        if (!tag.dataTypes.contains(field.getFieldType())) {
377            throw new ImageReadException("Required field \"" + tag.name
378                    + "\" has incorrect type " + field.getFieldType().getName());
379        }
380        if (field.getCount() != 1) {
381            throw new ImageReadException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
382        }
383        final byte[] bytes = field.getByteArrayValue();
384        return tag.getValue(field.getByteOrder(), bytes);
385    }
386
387    public short[] getFieldValue(final TagInfoShorts tag, final boolean mustExist)
388            throws ImageReadException {
389        final TiffField field = findField(tag);
390        if (field == null) {
391            if (mustExist) {
392                throw new ImageReadException("Required field \"" + tag.name
393                        + "\" is missing");
394            }
395            return null;
396        }
397        if (!tag.dataTypes.contains(field.getFieldType())) {
398            if (mustExist) {
399                throw new ImageReadException("Required field \"" + tag.name
400                        + "\" has incorrect type " + field.getFieldType().getName());
401            }
402            return null;
403        }
404        final byte[] bytes = field.getByteArrayValue();
405        return tag.getValue(field.getByteOrder(), bytes);
406    }
407
408    public int getFieldValue(final TagInfoLong tag)
409            throws ImageReadException {
410        final TiffField field = findField(tag);
411        if (field == null) {
412            throw new ImageReadException("Required field \"" + tag.name
413                    + "\" is missing");
414        }
415        if (!tag.dataTypes.contains(field.getFieldType())) {
416            throw new ImageReadException("Required field \"" + tag.name
417                    + "\" has incorrect type " + field.getFieldType().getName());
418        }
419        if (field.getCount() != 1) {
420            throw new ImageReadException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
421        }
422        final byte[] bytes = field.getByteArrayValue();
423        return tag.getValue(field.getByteOrder(), bytes);
424    }
425
426    public int[] getFieldValue(final TagInfoLongs tag, final boolean mustExist)
427            throws ImageReadException {
428        final TiffField field = findField(tag);
429        if (field == null) {
430            if (mustExist) {
431                throw new ImageReadException("Required field \"" + tag.name
432                        + "\" is missing");
433            }
434            return null;
435        }
436        if (!tag.dataTypes.contains(field.getFieldType())) {
437            if (mustExist) {
438                throw new ImageReadException("Required field \"" + tag.name
439                        + "\" has incorrect type " + field.getFieldType().getName());
440            }
441            return null;
442        }
443        final byte[] bytes = field.getByteArrayValue();
444        return tag.getValue(field.getByteOrder(), bytes);
445    }
446
447    public int[] getFieldValue(final TagInfoShortOrLong tag, final boolean mustExist)
448            throws ImageReadException {
449        final TiffField field = findField(tag);
450        if (field == null) {
451            if (mustExist) {
452                throw new ImageReadException("Required field \"" + tag.name
453                        + "\" is missing");
454            }
455            return null;
456        }
457        if (!tag.dataTypes.contains(field.getFieldType())) {
458            if (mustExist) {
459                throw new ImageReadException("Required field \"" + tag.name
460                        + "\" has incorrect type " + field.getFieldType().getName());
461            }
462            return null;
463        }
464        final byte[] bytes = field.getByteArrayValue();
465        if (field.getFieldType() == FieldType.SHORT) {
466            return ByteConversions.toUInt16s(bytes, field.getByteOrder());
467        }
468        return ByteConversions.toInts(bytes, field.getByteOrder());
469    }
470
471    public RationalNumber getFieldValue(final TagInfoRational tag)
472            throws ImageReadException {
473        final TiffField field = findField(tag);
474        if (field == null) {
475            throw new ImageReadException("Required field \"" + tag.name
476                    + "\" is missing");
477        }
478        if (!tag.dataTypes.contains(field.getFieldType())) {
479            throw new ImageReadException("Required field \"" + tag.name
480                    + "\" has incorrect type " + field.getFieldType().getName());
481        }
482        if (field.getCount() != 1) {
483            throw new ImageReadException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
484        }
485        final byte[] bytes = field.getByteArrayValue();
486        return tag.getValue(field.getByteOrder(), bytes);
487    }
488
489    public RationalNumber[] getFieldValue(final TagInfoRationals tag, final boolean mustExist)
490            throws ImageReadException {
491        final TiffField field = findField(tag);
492        if (field == null) {
493            if (mustExist) {
494                throw new ImageReadException("Required field \"" + tag.name
495                        + "\" is missing");
496            }
497            return null;
498        }
499        if (!tag.dataTypes.contains(field.getFieldType())) {
500            if (mustExist) {
501                throw new ImageReadException("Required field \"" + tag.name
502                        + "\" has incorrect type " + field.getFieldType().getName());
503            }
504            return null;
505        }
506        final byte[] bytes = field.getByteArrayValue();
507        return tag.getValue(field.getByteOrder(), bytes);
508    }
509
510    public byte getFieldValue(final TagInfoSByte tag)
511            throws ImageReadException {
512        final TiffField field = findField(tag);
513        if (field == null) {
514            throw new ImageReadException("Required field \"" + tag.name
515                    + "\" is missing");
516        }
517        if (!tag.dataTypes.contains(field.getFieldType())) {
518            throw new ImageReadException("Required field \"" + tag.name
519                    + "\" has incorrect type " + field.getFieldType().getName());
520        }
521        if (field.getCount() != 1) {
522            throw new ImageReadException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
523        }
524        return field.getByteArrayValue()[0];
525    }
526
527    public byte[] getFieldValue(final TagInfoSBytes tag, final boolean mustExist)
528            throws ImageReadException {
529        final TiffField field = findField(tag);
530        if (field == null) {
531            if (mustExist) {
532                throw new ImageReadException("Required field \"" + tag.name
533                        + "\" is missing");
534            }
535            return null;
536        }
537        if (!tag.dataTypes.contains(field.getFieldType())) {
538            if (mustExist) {
539                throw new ImageReadException("Required field \"" + tag.name
540                        + "\" has incorrect type " + field.getFieldType().getName());
541            }
542            return null;
543        }
544        return field.getByteArrayValue();
545    }
546
547    public short getFieldValue(final TagInfoSShort tag)
548            throws ImageReadException {
549        final TiffField field = findField(tag);
550        if (field == null) {
551            throw new ImageReadException("Required field \"" + tag.name
552                    + "\" is missing");
553        }
554        if (!tag.dataTypes.contains(field.getFieldType())) {
555            throw new ImageReadException("Required field \"" + tag.name
556                    + "\" has incorrect type " + field.getFieldType().getName());
557        }
558        if (field.getCount() != 1) {
559            throw new ImageReadException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
560        }
561        final byte[] bytes = field.getByteArrayValue();
562        return tag.getValue(field.getByteOrder(), bytes);
563    }
564
565    public short[] getFieldValue(final TagInfoSShorts tag, final boolean mustExist)
566            throws ImageReadException {
567        final TiffField field = findField(tag);
568        if (field == null) {
569            if (mustExist) {
570                throw new ImageReadException("Required field \"" + tag.name
571                        + "\" is missing");
572            }
573            return null;
574        }
575        if (!tag.dataTypes.contains(field.getFieldType())) {
576            if (mustExist) {
577                throw new ImageReadException("Required field \"" + tag.name
578                        + "\" has incorrect type " + field.getFieldType().getName());
579            }
580            return null;
581        }
582        final byte[] bytes = field.getByteArrayValue();
583        return tag.getValue(field.getByteOrder(), bytes);
584    }
585
586    public int getFieldValue(final TagInfoSLong tag)
587            throws ImageReadException {
588        final TiffField field = findField(tag);
589        if (field == null) {
590            throw new ImageReadException("Required field \"" + tag.name
591                    + "\" is missing");
592        }
593        if (!tag.dataTypes.contains(field.getFieldType())) {
594            throw new ImageReadException("Required field \"" + tag.name
595                    + "\" has incorrect type " + field.getFieldType().getName());
596        }
597        if (field.getCount() != 1) {
598            throw new ImageReadException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
599        }
600        final byte[] bytes = field.getByteArrayValue();
601        return tag.getValue(field.getByteOrder(), bytes);
602    }
603
604    public int[] getFieldValue(final TagInfoSLongs tag, final boolean mustExist)
605            throws ImageReadException {
606        final TiffField field = findField(tag);
607        if (field == null) {
608            if (mustExist) {
609                throw new ImageReadException("Required field \"" + tag.name
610                        + "\" is missing");
611            }
612            return null;
613        }
614        if (!tag.dataTypes.contains(field.getFieldType())) {
615            if (mustExist) {
616                throw new ImageReadException("Required field \"" + tag.name
617                        + "\" has incorrect type " + field.getFieldType().getName());
618            }
619            return null;
620        }
621        final byte[] bytes = field.getByteArrayValue();
622        return tag.getValue(field.getByteOrder(), bytes);
623    }
624
625    public RationalNumber getFieldValue(final TagInfoSRational tag) throws ImageReadException {
626        final TiffField field = findField(tag);
627        if (field == null) {
628            throw new ImageReadException("Required field \"" + tag.name
629                    + "\" is missing");
630        }
631        if (!tag.dataTypes.contains(field.getFieldType())) {
632            throw new ImageReadException("Required field \"" + tag.name
633                    + "\" has incorrect type " + field.getFieldType().getName());
634        }
635        if (field.getCount() != 1) {
636            throw new ImageReadException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
637        }
638        final byte[] bytes = field.getByteArrayValue();
639        return tag.getValue(field.getByteOrder(), bytes);
640    }
641
642    public RationalNumber[] getFieldValue(final TagInfoSRationals tag,
643            final boolean mustExist) throws ImageReadException {
644        final TiffField field = findField(tag);
645        if (field == null) {
646            if (mustExist) {
647                throw new ImageReadException("Required field \"" + tag.name
648                        + "\" is missing");
649            }
650            return null;
651        }
652        if (!tag.dataTypes.contains(field.getFieldType())) {
653            if (mustExist) {
654                throw new ImageReadException("Required field \"" + tag.name
655                        + "\" has incorrect type " + field.getFieldType().getName());
656            }
657            return null;
658        }
659        final byte[] bytes = field.getByteArrayValue();
660        return tag.getValue(field.getByteOrder(), bytes);
661    }
662
663    public float getFieldValue(final TagInfoFloat tag)
664            throws ImageReadException {
665        final TiffField field = findField(tag);
666        if (field == null) {
667            throw new ImageReadException("Required field \"" + tag.name
668                    + "\" is missing");
669        }
670        if (!tag.dataTypes.contains(field.getFieldType())) {
671            throw new ImageReadException("Required field \"" + tag.name
672                    + "\" has incorrect type " + field.getFieldType().getName());
673        }
674        if (field.getCount() != 1) {
675            throw new ImageReadException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
676        }
677        final byte[] bytes = field.getByteArrayValue();
678        return tag.getValue(field.getByteOrder(), bytes);
679    }
680
681    public float[] getFieldValue(final TagInfoFloats tag, final boolean mustExist)
682            throws ImageReadException {
683        final TiffField field = findField(tag);
684        if (field == null) {
685            if (mustExist) {
686                throw new ImageReadException("Required field \"" + tag.name
687                        + "\" is missing");
688            }
689            return null;
690        }
691        if (!tag.dataTypes.contains(field.getFieldType())) {
692            if (mustExist) {
693                throw new ImageReadException("Required field \"" + tag.name
694                        + "\" has incorrect type " + field.getFieldType().getName());
695            }
696            return null;
697        }
698        final byte[] bytes = field.getByteArrayValue();
699        return tag.getValue(field.getByteOrder(), bytes);
700    }
701
702    public double getFieldValue(final TagInfoDouble tag)
703            throws ImageReadException {
704        final TiffField field = findField(tag);
705        if (field == null) {
706            throw new ImageReadException("Required field \"" + tag.name
707                    + "\" is missing");
708        }
709        if (!tag.dataTypes.contains(field.getFieldType())) {
710            throw new ImageReadException("Required field \"" + tag.name
711                    + "\" has incorrect type " + field.getFieldType().getName());
712        }
713        if (field.getCount() != 1) {
714            throw new ImageReadException("Field \"" + tag.name + "\" has wrong count " + field.getCount());
715        }
716        final byte[] bytes = field.getByteArrayValue();
717        return tag.getValue(field.getByteOrder(), bytes);
718    }
719
720    public double[] getFieldValue(final TagInfoDoubles tag, final boolean mustExist)
721            throws ImageReadException {
722        final TiffField field = findField(tag);
723        if (field == null) {
724            if (mustExist) {
725                throw new ImageReadException("Required field \"" + tag.name
726                        + "\" is missing");
727            }
728            return null;
729        }
730        if (!tag.dataTypes.contains(field.getFieldType())) {
731            if (mustExist) {
732                throw new ImageReadException("Required field \"" + tag.name
733                        + "\" has incorrect type " + field.getFieldType().getName());
734            }
735            return null;
736        }
737        final byte[] bytes = field.getByteArrayValue();
738        return tag.getValue(field.getByteOrder(), bytes);
739    }
740
741    public String getFieldValue(final TagInfoGpsText tag, final boolean mustExist)
742            throws ImageReadException {
743        final TiffField field = findField(tag);
744        if (field == null) {
745            if (mustExist) {
746                throw new ImageReadException("Required field \"" + tag.name
747                        + "\" is missing");
748            }
749            return null;
750        }
751        return tag.getValue(field);
752    }
753
754    public String getFieldValue(final TagInfoXpString tag, final boolean mustExist)
755            throws ImageReadException {
756        final TiffField field = findField(tag);
757        if (field == null) {
758            if (mustExist) {
759                throw new ImageReadException("Required field \"" + tag.name
760                        + "\" is missing");
761            }
762            return null;
763        }
764        return tag.getValue(field);
765    }
766
767    public static final class ImageDataElement extends TiffElement {
768        public ImageDataElement(final long offset, final int length) {
769            super(offset, length);
770        }
771
772        @Override
773        public String getElementDescription() {
774            return "ImageDataElement";
775        }
776    }
777
778    private List<ImageDataElement> getRawImageDataElements(
779            final TiffField offsetsField, final TiffField byteCountsField)
780            throws ImageReadException {
781        final int[] offsets = offsetsField.getIntArrayValue();
782        final int[] byteCounts = byteCountsField.getIntArrayValue();
783
784        if (offsets.length != byteCounts.length) {
785            throw new ImageReadException("offsets.length(" + offsets.length
786                    + ") != byteCounts.length(" + byteCounts.length + ")");
787        }
788
789        final List<ImageDataElement> result = new ArrayList<>(offsets.length);
790        for (int i = 0; i < offsets.length; i++) {
791            result.add(new ImageDataElement(offsets[i], byteCounts[i]));
792        }
793        return result;
794    }
795
796    public List<ImageDataElement> getTiffRawImageDataElements()
797            throws ImageReadException {
798        final TiffField tileOffsets = findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS);
799        final TiffField tileByteCounts = findField(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS);
800        final TiffField stripOffsets = findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS);
801        final TiffField stripByteCounts = findField(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS);
802
803        if ((tileOffsets != null) && (tileByteCounts != null)) {
804            return getRawImageDataElements(tileOffsets, tileByteCounts);
805        }
806        if ((stripOffsets != null) && (stripByteCounts != null)) {
807            return getRawImageDataElements(stripOffsets, stripByteCounts);
808        }
809        throw new ImageReadException("Couldn't find image data.");
810    }
811
812    public boolean imageDataInStrips() throws ImageReadException {
813        final TiffField tileOffsets = findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS);
814        final TiffField tileByteCounts = findField(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS);
815        final TiffField stripOffsets = findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS);
816        final TiffField stripByteCounts = findField(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS);
817
818        if ((tileOffsets != null) && (tileByteCounts != null)) {
819            return false;
820        }
821        if ((stripOffsets != null) && (stripByteCounts != null)) {
822            return true;
823        }
824        throw new ImageReadException("Couldn't find image data.");
825    }
826
827    public ImageDataElement getJpegRawImageDataElement() throws ImageReadException {
828        final TiffField jpegInterchangeFormat = findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT);
829        final TiffField jpegInterchangeFormatLength = findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
830
831        if (jpegInterchangeFormat != null && jpegInterchangeFormatLength != null) {
832            final int offSet = jpegInterchangeFormat.getIntArrayValue()[0];
833            final int byteCount = jpegInterchangeFormatLength.getIntArrayValue()[0];
834
835            return new ImageDataElement(offSet, byteCount);
836        }
837        throw new ImageReadException("Couldn't find image data.");
838    }
839
840    public void setTiffImageData(final TiffImageData rawImageData) {
841        this.tiffImageData = rawImageData;
842    }
843
844    public TiffImageData getTiffImageData() {
845        return tiffImageData;
846    }
847
848    public void setJpegImageData(final JpegImageData value) {
849        this.jpegImageData = value;
850    }
851
852    public JpegImageData getJpegImageData() {
853        return jpegImageData;
854    }
855
856    /**
857     * Reads the numerical data stored in this TIFF directory, if available.
858     * Note that this method is defined only for TIFF directories that contain
859     * floating-point data or two-byte signed integer data.
860     * <p>
861     * TIFF directories that provide numerical data do not directly specify
862     * images, though it is possible to interpret the data as an image using
863     * this library. TIFF files may contain multiple directories which are
864     * allowed to have different formats. Thus it is possible for a TIFF file to
865     * contain a mix of image and floating-point raster data.
866     * <p>
867     * If desired, sub-image data can be read from the file by using a Java Map
868     * instance to specify the subsection of the image that is required. The
869     * following code illustrates the approach:
870     * <pre>
871     *   int x; // coordinate (column) of corner of sub-image
872     *   int y; // coordinate (row) of corner of sub-image
873     *   int width; // width of sub-image
874     *   int height; // height of sub-image
875     *
876     *   Map&lt;String, Object&gt;params = new HashMap&lt;&gt;();
877     *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_X, x);
878     *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_Y, y);
879     *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_WIDTH, width);
880     *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_HEIGHT, height);
881     *   TiffRasterData raster =
882     *        directory.readFloatingPointRasterData(params);
883     * </pre>
884     *
885     * @param params an optional parameter map instance
886     * @return a valid instance
887     * @throws ImageReadException in the event of incompatible or malformed data
888     * @throws IOException in the event of an I/O error
889     */
890    public TiffRasterData getRasterData(
891            final TiffImagingParameters params)
892            throws ImageReadException, IOException {
893
894        final TiffImageParser parser = new TiffImageParser();
895        return parser.getRasterData(this, headerByteOrder, params);
896    }
897
898    /**
899     * Indicates whether the directory definition specifies a float-point data
900     * format.
901     *
902     * @return {@code true} if the directory contains floating point data;
903     * otherwise, {@code false}
904     *
905     * @throws ImageReadException in the event of an invalid or malformed
906     * specification.
907     */
908    public boolean hasTiffFloatingPointRasterData() throws ImageReadException {
909        if (!this.hasTiffImageData()) {
910            return false;
911        }
912        final short[] s = getFieldValue(
913                TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false);
914        return s != null
915                && s.length > 0
916                && s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT;
917
918    }
919
920    /**
921     * Indicates whether the content associated with the directory is given in a
922     * supported numerical-data format. If this method returns {@code true}, the
923     * Imaging API will be able to extract a TiffRasterData instance from the
924     * associated TIFF file using this directory.
925     *
926     * @return {@code true} if the directory contains a supported raster data
927     * format; otherwise, {@code false}.
928     * @throws ImageReadException in the event of an invalid or malformed
929     * specification.
930     */
931    public boolean hasTiffRasterData() throws ImageReadException {
932        if (!this.hasTiffImageData()) {
933            return false;
934        }
935        final short[] s = getFieldValue(
936                TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false);
937        return s != null
938                && s.length > 0
939                && (s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT
940                || s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER);
941    }
942}