    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.write;
    018
    019import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.DIRECTORY_FOOTER_LENGTH;
    020import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.DIRECTORY_HEADER_LENGTH;
    021import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.ENTRY_LENGTH;
    022import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.ENTRY_MAX_VALUE_LENGTH;
    023
    024import java.io.IOException;
    025import java.nio.ByteOrder;
    026import java.util.ArrayList;
    027import java.util.Arrays;
    028import java.util.Collections;
    029import java.util.Comparator;
    030import java.util.Iterator;
    031import java.util.List;
    032
    033import org.apache.commons.imaging.ImagingException;
    034import org.apache.commons.imaging.common.AbstractBinaryOutputStream;
    035import org.apache.commons.imaging.common.Allocator;
    036import org.apache.commons.imaging.common.RationalNumber;
    037import org.apache.commons.imaging.formats.tiff.AbstractTiffElement;
    038import org.apache.commons.imaging.formats.tiff.AbstractTiffImageData;
    039import org.apache.commons.imaging.formats.tiff.JpegImageData;
    040import org.apache.commons.imaging.formats.tiff.TiffDirectory;
    041import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryType;
    042import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
    043import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType;
    044import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
    045import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAscii;
    046import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAsciiOrByte;
    047import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAsciiOrRational;
    048import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoByte;
    049import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoByteOrShort;
    050import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoBytes;
    051import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDouble;
    052import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDoubles;
    053import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloat;
    054import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloats;
    055import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoGpsText;
    056import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLong;
    057import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLongs;
    058import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRational;
    059import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRationals;
    060import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSByte;
    061import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSBytes;
    062import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLong;
    063import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLongs;
    064import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRational;
    065import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRationals;
    066import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShort;
    067import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShorts;
    068import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShort;
    069import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShortOrLong;
    070import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShortOrLongOrRational;
    071import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShortOrRational;
    072import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShorts;
    073import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoXpString;
    074
    075public final class TiffOutputDirectory extends AbstractTiffOutputItem implements Iterable<TiffOutputField> {
    076    public static final Comparator<TiffOutputDirectory> COMPARATOR = Comparator.comparingInt(TiffOutputDirectory::getType);
    077    private final int type;
    078    private final List<TiffOutputField> fields = new ArrayList<>();
    079    private final ByteOrder byteOrder;
    080    private TiffOutputDirectory nextDirectory;
    081    private JpegImageData jpegImageData;
    082    private AbstractTiffImageData abstractTiffImageData;
    083
    084    public TiffOutputDirectory(final int type, final ByteOrder byteOrder) {
    085        this.type = type;
    086        this.byteOrder = byteOrder;
    087    }
    088
    089    public void add(final TagInfoAscii tagInfo, final String... values) throws ImagingException {
    090        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    091        if (tagInfo.length > 0 && tagInfo.length != bytes.length) {
    092            throw new ImagingException("Tag expects " + tagInfo.length + " byte(s), not " + values.length);
    093        }
    094        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.ASCII, bytes.length, bytes);
    095        add(tiffOutputField);
    096    }
    097
    098    public void add(final TagInfoAsciiOrByte tagInfo, final String... values) throws ImagingException {
    099        final byte[] bytes = tagInfo.encodeValue(AbstractFieldType.ASCII, values, byteOrder);
    100        if (tagInfo.length > 0 && tagInfo.length != bytes.length) {
    101            throw new ImagingException("Tag expects " + tagInfo.length + " byte(s), not " + values.length);
    102        }
    103        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.ASCII, bytes.length, bytes);
    104        add(tiffOutputField);
    105    }
    106
    107    public void add(final TagInfoAsciiOrRational tagInfo, final RationalNumber... values) throws ImagingException {
    108        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    109            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    110        }
    111        final byte[] bytes = tagInfo.encodeValue(AbstractFieldType.RATIONAL, values, byteOrder);
    112        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.RATIONAL, bytes.length, bytes);
    113        add(tiffOutputField);
    114    }
    115
    116    public void add(final TagInfoAsciiOrRational tagInfo, final String... values) throws ImagingException {
    117        final byte[] bytes = tagInfo.encodeValue(AbstractFieldType.ASCII, values, byteOrder);
    118        if (tagInfo.length > 0 && tagInfo.length != bytes.length) {
    119            throw new ImagingException("Tag expects " + tagInfo.length + " byte(s), not " + values.length);
    120        }
    121        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.ASCII, bytes.length, bytes);
    122        add(tiffOutputField);
    123    }
    124
    125    public void add(final TagInfoByte tagInfo, final byte value) throws ImagingException {
    126        if (tagInfo.length != 1) {
    127            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not 1");
    128        }
    129        final byte[] bytes = tagInfo.encodeValue(byteOrder, value);
    130        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.BYTE, bytes.length, bytes);
    131        add(tiffOutputField);
    132    }
    133
    134    public void add(final TagInfoByteOrShort tagInfo, final byte... values) throws ImagingException {
    135        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    136            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    137        }
    138        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    139        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.BYTE, values.length, bytes);
    140        add(tiffOutputField);
    141    }
    142
    143    public void add(final TagInfoByteOrShort tagInfo, final short... values) throws ImagingException {
    144        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    145            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    146        }
    147        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    148        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SHORT, values.length, bytes);
    149        add(tiffOutputField);
    150    }
    151
    152    public void add(final TagInfoBytes tagInfo, final byte... values) throws ImagingException {
    153        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    154            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    155        }
    156        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    157        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.BYTE, values.length, bytes);
    158        add(tiffOutputField);
    159    }
    160
    161    public void add(final TagInfoDouble tagInfo, final double value) throws ImagingException {
    162        if (tagInfo.length != 1) {
    163            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not 1");
    164        }
    165        final byte[] bytes = tagInfo.encodeValue(byteOrder, value);
    166        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.DOUBLE, 1, bytes);
    167        add(tiffOutputField);
    168    }
    169
    170    public void add(final TagInfoDoubles tagInfo, final double... values) throws ImagingException {
    171        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    172            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    173        }
    174        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    175        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.DOUBLE, values.length, bytes);
    176        add(tiffOutputField);
    177    }
    178
    179    public void add(final TagInfoFloat tagInfo, final float value) throws ImagingException {
    180        if (tagInfo.length != 1) {
    181            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not 1");
    182        }
    183        final byte[] bytes = tagInfo.encodeValue(byteOrder, value);
    184        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.FLOAT, 1, bytes);
    185        add(tiffOutputField);
    186    }
    187
    188    public void add(final TagInfoFloats tagInfo, final float... values) throws ImagingException {
    189        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    190            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    191        }
    192        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    193        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.FLOAT, values.length, bytes);
    194        add(tiffOutputField);
    195    }
    196
    197    public void add(final TagInfoGpsText tagInfo, final String value) throws ImagingException {
    198        final byte[] bytes = tagInfo.encodeValue(AbstractFieldType.UNDEFINED, value, byteOrder);
    199        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, tagInfo.dataTypes.get(0), bytes.length, bytes);
    200        add(tiffOutputField);
    201    }
    202
    203    public void add(final TagInfoLong tagInfo, final int value) throws ImagingException {
    204        if (tagInfo.length != 1) {
    205            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not 1");
    206        }
    207        final byte[] bytes = tagInfo.encodeValue(byteOrder, value);
    208        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.LONG, 1, bytes);
    209        add(tiffOutputField);
    210    }
    211
    212    public void add(final TagInfoLongs tagInfo, final int... values) throws ImagingException {
    213        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    214            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    215        }
    216        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    217        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.LONG, values.length, bytes);
    218        add(tiffOutputField);
    219    }
    220
    221    public void add(final TagInfoRational tagInfo, final RationalNumber value) throws ImagingException {
    222        if (tagInfo.length != 1) {
    223            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not 1");
    224        }
    225        final byte[] bytes = tagInfo.encodeValue(byteOrder, value);
    226        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.RATIONAL, 1, bytes);
    227        add(tiffOutputField);
    228    }
    229
    230    public void add(final TagInfoRationals tagInfo, final RationalNumber... values) throws ImagingException {
    231        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    232            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    233        }
    234        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    235        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.RATIONAL, values.length, bytes);
    236        add(tiffOutputField);
    237    }
    238
    239    public void add(final TagInfoSByte tagInfo, final byte value) throws ImagingException {
    240        if (tagInfo.length != 1) {
    241            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not 1");
    242        }
    243        final byte[] bytes = tagInfo.encodeValue(byteOrder, value);
    244        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SBYTE, 1, bytes);
    245        add(tiffOutputField);
    246    }
    247
    248    public void add(final TagInfoSBytes tagInfo, final byte... values) throws ImagingException {
    249        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    250            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    251        }
    252        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    253        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SBYTE, values.length, bytes);
    254        add(tiffOutputField);
    255    }
    256
    257    public void add(final TagInfoShort tagInfo, final short value) throws ImagingException {
    258        if (tagInfo.length != 1) {
    259            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not 1");
    260        }
    261        final byte[] bytes = tagInfo.encodeValue(byteOrder, value);
    262        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SHORT, 1, bytes);
    263        add(tiffOutputField);
    264    }
    265
    266    public void add(final TagInfoShortOrLong tagInfo, final int... values) throws ImagingException {
    267        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    268            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    269        }
    270        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    271        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.LONG, values.length, bytes);
    272        add(tiffOutputField);
    273    }
    274
    275    public void add(final TagInfoShortOrLong tagInfo, final short... values) throws ImagingException {
    276        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    277            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    278        }
    279        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    280        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SHORT, values.length, bytes);
    281        add(tiffOutputField);
    282    }
    283
    284    public void add(final TagInfoShortOrLongOrRational tagInfo, final int... values) throws ImagingException {
    285        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    286            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    287        }
    288        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    289        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.LONG, values.length, bytes);
    290        add(tiffOutputField);
    291    }
    292
    293    public void add(final TagInfoShortOrLongOrRational tagInfo, final RationalNumber... values) throws ImagingException {
    294        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    295            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    296        }
    297        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    298        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.RATIONAL, values.length, bytes);
    299        add(tiffOutputField);
    300    }
    301
    302    public void add(final TagInfoShortOrLongOrRational tagInfo, final short... values) throws ImagingException {
    303        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    304            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    305        }
    306        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    307        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SHORT, values.length, bytes);
    308        add(tiffOutputField);
    309    }
    310
    311    public void add(final TagInfoShortOrRational tagInfo, final RationalNumber... values) throws ImagingException {
    312        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    313            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    314        }
    315        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    316        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.RATIONAL, values.length, bytes);
    317        add(tiffOutputField);
    318    }
    319
    320    public void add(final TagInfoShortOrRational tagInfo, final short... values) throws ImagingException {
    321        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    322            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    323        }
    324        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    325        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SHORT, values.length, bytes);
    326        add(tiffOutputField);
    327    }
    328
    329    public void add(final TagInfoShorts tagInfo, final short... values) throws ImagingException {
    330        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    331            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    332        }
    333        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    334        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SHORT, values.length, bytes);
    335        add(tiffOutputField);
    336    }
    337
    338    public void add(final TagInfoSLong tagInfo, final int value) throws ImagingException {
    339        if (tagInfo.length != 1) {
    340            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not 1");
    341        }
    342        final byte[] bytes = tagInfo.encodeValue(byteOrder, value);
    343        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SLONG, 1, bytes);
    344        add(tiffOutputField);
    345    }
    346
    347    public void add(final TagInfoSLongs tagInfo, final int... values) throws ImagingException {
    348        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    349            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    350        }
    351        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    352        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SLONG, values.length, bytes);
    353        add(tiffOutputField);
    354    }
    355
    356    public void add(final TagInfoSRational tagInfo, final RationalNumber value) throws ImagingException {
    357        if (tagInfo.length != 1) {
    358            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not 1");
    359        }
    360        final byte[] bytes = tagInfo.encodeValue(byteOrder, value);
    361        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SRATIONAL, 1, bytes);
    362        add(tiffOutputField);
    363    }
    364
    365    public void add(final TagInfoSRationals tagInfo, final RationalNumber... values) throws ImagingException {
    366        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    367            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    368        }
    369        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    370        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SRATIONAL, values.length, bytes);
    371        add(tiffOutputField);
    372    }
    373
    374    public void add(final TagInfoSShort tagInfo, final short value) throws ImagingException {
    375        if (tagInfo.length != 1) {
    376            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not 1");
    377        }
    378        final byte[] bytes = tagInfo.encodeValue(byteOrder, value);
    379        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SSHORT, 1, bytes);
    380        add(tiffOutputField);
    381    }
    382
    383    public void add(final TagInfoSShorts tagInfo, final short... values) throws ImagingException {
    384        if (tagInfo.length > 0 && tagInfo.length != values.length) {
    385            throw new ImagingException("Tag expects " + tagInfo.length + " value(s), not " + values.length);
    386        }
    387        final byte[] bytes = tagInfo.encodeValue(byteOrder, values);
    388        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.SSHORT, values.length, bytes);
    389        add(tiffOutputField);
    390    }
    391
    392    public void add(final TagInfoXpString tagInfo, final String value) throws ImagingException {
    393        final byte[] bytes = tagInfo.encodeValue(AbstractFieldType.BYTE, value, byteOrder);
    394        final TiffOutputField tiffOutputField = new TiffOutputField(tagInfo.tag, tagInfo, AbstractFieldType.BYTE, bytes.length, bytes);
    395        add(tiffOutputField);
    396    }
    397
    398    public void add(final TiffOutputField field) {
    399        fields.add(field);
    400    }
    401
    402    public String description() {
    403        return TiffDirectory.description(getType());
    404    }
    405
    406    /**
    407     * Finds the TiffOutputField for the given tag from this TiffOutputDirectory.
    408     *
    409     * <p>
    410     * If there is no field matching the given tag, null will be returned.
    411     * </p>
    412     *
    413     * @param tag the tag specifying the field
    414     * @return the field matching tagInfo or null, if the field isn't present
    415     * @see #findField(TagInfo)
    416     */
    417    public TiffOutputField findField(final int tag) {
    418        for (final TiffOutputField field : fields) {
    419            if (field.tag == tag) {
    420                return field;
    421            }
    422        }
    423        return null;
    424    }
    425
    426    /**
    427     * Finds the TiffOutputField for the given TagInfo from this TiffOutputDirectory.
    428     *
    429     * <p>
    430     * If there is no field matching the given TagInfo, null will be returned.
    431     * </p>
    432     *
    433     * @param tagInfo the TagInfo specifying the field
    434     * @return the field matching tagInfo or null, if the field isn't present
    435     * @see #findField(int)
    436     */
    437    public TiffOutputField findField(final TagInfo tagInfo) {
    438        return findField(tagInfo.tag);
    439    }
    440
    441    public List<TiffOutputField> getFields() {
    442        return new ArrayList<>(fields);
    443    }
    444
    445    @Override
    446    public String getItemDescription() {
    447        final TiffDirectoryType dirType = TiffDirectoryType.getExifDirectoryType(getType());
    448        return "Directory: " + dirType.name + " (" + getType() + ")";
    449    }
    450
    451    @Override
    452    public int getItemLength() {
    453        return ENTRY_LENGTH * fields.size() + DIRECTORY_HEADER_LENGTH + DIRECTORY_FOOTER_LENGTH;
    454    }
    455
    456    protected List<AbstractTiffOutputItem> getOutputItems(final TiffOutputSummary outputSummary) throws ImagingException {
    457        // first validate directory fields.
    458
    459        removeFieldIfPresent(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT);
    460        removeFieldIfPresent(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
    461
    462        TiffOutputField jpegOffsetField = null;
    463        if (null != jpegImageData) {
    464            jpegOffsetField = new TiffOutputField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT, AbstractFieldType.LONG, 1,
    465                    new byte[ENTRY_MAX_VALUE_LENGTH]);
    466            add(jpegOffsetField);
    467
    468            final byte[] lengthValue = AbstractFieldType.LONG.writeData(jpegImageData.length, outputSummary.byteOrder);
    469
    470            final TiffOutputField jpegLengthField = new TiffOutputField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, AbstractFieldType.LONG, 1,
    471                    lengthValue);
    472            add(jpegLengthField);
    473
    474        }
    475
    476        removeFieldIfPresent(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS);
    477        removeFieldIfPresent(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS);
    478        removeFieldIfPresent(TiffTagConstants.TIFF_TAG_TILE_OFFSETS);
    479        removeFieldIfPresent(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS);
    480
    481        final TiffOutputField imageDataOffsetField;
    482        ImageDataOffsets imageDataInfo = null;
    483        if (null != abstractTiffImageData) {
    484            final boolean stripsNotTiles = abstractTiffImageData.stripsNotTiles();
    485
    486            final TagInfo offsetTag;
    487            final TagInfo byteCountsTag;
    488            if (stripsNotTiles) {
    489                offsetTag = TiffTagConstants.TIFF_TAG_STRIP_OFFSETS;
    490                byteCountsTag = TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS;
    491            } else {
    492                offsetTag = TiffTagConstants.TIFF_TAG_TILE_OFFSETS;
    493                byteCountsTag = TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS;
    494            }
    495
    496            final AbstractTiffElement.DataElement[] imageData = abstractTiffImageData.getImageData();
    497
    498            // TiffOutputField imageDataOffsetsField = null;
    499
    500            final int[] imageDataOffsets = Allocator.intArray(imageData.length);
    501            final int[] imageDataByteCounts = Allocator.intArray(imageData.length);
    502            Arrays.setAll(imageDataByteCounts, i -> imageData[i].length);
    503
    504            // Append imageData-related fields to first directory
    505            imageDataOffsetField = new TiffOutputField(offsetTag, AbstractFieldType.LONG, imageDataOffsets.length,
    506                    AbstractFieldType.LONG.writeData(imageDataOffsets, outputSummary.byteOrder));
    507            add(imageDataOffsetField);
    508
    509            final byte[] data = AbstractFieldType.LONG.writeData(imageDataByteCounts, outputSummary.byteOrder);
    510            final TiffOutputField byteCountsField = new TiffOutputField(byteCountsTag, AbstractFieldType.LONG, imageDataByteCounts.length, data);
    511            add(byteCountsField);
    512
    513            imageDataInfo = new ImageDataOffsets(imageData, imageDataOffsets, imageDataOffsetField);
    514        }
    515
    516        final List<AbstractTiffOutputItem> result = new ArrayList<>();
    517        result.add(this);
    518        sortFields();
    519
    520        for (final TiffOutputField field : fields) {
    521            if (field.isLocalValue()) {
    522                continue;
    523            }
    524
    525            final AbstractTiffOutputItem item = field.getSeperateValue();
    526            result.add(item);
    527            // outputSummary.add(item, field);
    528        }
    529
    530        if (null != imageDataInfo) {
    531            Collections.addAll(result, imageDataInfo.outputItems);
    532
    533            outputSummary.addTiffImageData(imageDataInfo);
    534        }
    535
    536        if (null != jpegImageData) {
    537            final AbstractTiffOutputItem item = new AbstractTiffOutputItem.Value("JPEG image data", jpegImageData.getData());
    538            result.add(item);
    539            outputSummary.add(item, jpegOffsetField);
    540        }
    541
    542        return result;
    543    }
    544
    545    public JpegImageData getRawJpegImageData() {
    546        return jpegImageData;
    547    }
    548
    549    public AbstractTiffImageData getRawTiffImageData() {
    550        return abstractTiffImageData;
    551    }
    552
    553    public int getType() {
    554        return type;
    555    }
    556
    557    @Override
    558    public Iterator<TiffOutputField> iterator() {
    559        return fields.iterator();
    560    }
    561
    562    public void removeField(final int tag) {
    563        final List<TiffOutputField> matches = new ArrayList<>();
    564        for (final TiffOutputField field : fields) {
    565            if (field.tag == tag) {
    566                matches.add(field);
    567            }
    568        }
    569        fields.removeAll(matches);
    570    }
    571
    572    public void removeField(final TagInfo tagInfo) {
    573        removeField(tagInfo.tag);
    574    }
    575
    576    private void removeFieldIfPresent(final TagInfo tagInfo) {
    577        final TiffOutputField field = findField(tagInfo);
    578        if (null != field) {
    579            fields.remove(field);
    580        }
    581    }
    582
    583    public void setJpegImageData(final JpegImageData rawJpegImageData) {
    584        this.jpegImageData = rawJpegImageData;
    585    }
    586
    587    public void setNextDirectory(final TiffOutputDirectory nextDirectory) {
    588        this.nextDirectory = nextDirectory;
    589    }
    590
    591    public void setTiffImageData(final AbstractTiffImageData rawTiffImageData) {
    592        this.abstractTiffImageData = rawTiffImageData;
    593    }
    594
    595    public void sortFields() {
    596        final Comparator<TiffOutputField> comparator = (e1, e2) -> {
    597            if (e1.tag != e2.tag) {
    598                return e1.tag - e2.tag;
    599            }
    600            return e1.getSortHint() - e2.getSortHint();
    601        };
    602        fields.sort(comparator);
    603    }
    604
    605    @Override
    606    public void writeItem(final AbstractBinaryOutputStream bos) throws IOException, ImagingException {
    607        // Write Directory Field Count
    608        bos.write2Bytes(fields.size()); // DirectoryFieldCount
    609
    610        // Write Fields
    611        for (final TiffOutputField field : fields) {
    612            field.writeField(bos);
    613
    614            // Debug.debug("\t" + "writing field (" + field.tag + ", 0x" +
    615            // Integer.toHexString(field.tag) + ")", field.tagInfo);
    616            // if (field.tagInfo.isOffset())
    617            // Debug.debug("\t\tOFFSET!", field.bytes);
    618        }
    619
    620        long nextDirectoryOffset = 0;
    621        if (nextDirectory != null) {
    622            nextDirectoryOffset = nextDirectory.getOffset();
    623        }
    624
    625        // Write nextDirectoryOffset
    626        if (nextDirectoryOffset == UNDEFINED_VALUE) {
    627            bos.write4Bytes(0);
    628        } else {
    629            bos.write4Bytes((int) nextDirectoryOffset);
    630        }
    631    }
    632}