    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}