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}