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.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.Allocator;
035import org.apache.commons.imaging.common.BinaryOutputStream;
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 TIFF_ENTRY_LENGTH * fields.size() + TIFF_DIRECTORY_HEADER_LENGTH + TIFF_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[TIFF_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        TiffOutputField imageDataOffsetField;
482        ImageDataOffsets imageDataInfo = null;
483        if (null != abstractTiffImageData) {
484            final boolean stripsNotTiles = abstractTiffImageData.stripsNotTiles();
485
486            TagInfo offsetTag;
487            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 BinaryOutputStream 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}