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