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