    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}