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