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