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