    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.jpeg.exif;
    018
    019import static org.apache.commons.imaging.common.BinaryFunctions.remainingBytes;
    020import static org.apache.commons.imaging.common.BinaryFunctions.startsWith;
    021
    022import java.io.ByteArrayOutputStream;
    023import java.io.DataOutputStream;
    024import java.io.File;
    025import java.io.IOException;
    026import java.io.InputStream;
    027import java.io.OutputStream;
    028import java.nio.ByteOrder;
    029import java.util.ArrayList;
    030import java.util.List;
    031
    032import org.apache.commons.imaging.ImageReadException;
    033import org.apache.commons.imaging.ImageWriteException;
    034import org.apache.commons.imaging.common.BinaryFileParser;
    035import org.apache.commons.imaging.common.ByteConversions;
    036import org.apache.commons.imaging.common.bytesource.ByteSource;
    037import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
    038import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
    039import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream;
    040import org.apache.commons.imaging.formats.jpeg.JpegConstants;
    041import org.apache.commons.imaging.formats.jpeg.JpegUtils;
    042import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterBase;
    043import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterLossless;
    044import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterLossy;
    045import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
    046
    047/**
    048 * Interface for Exif write/update/remove functionality for Jpeg/JFIF images.
    049 *
    050 * <p>See the source of the ExifMetadataUpdateExample class for example usage.</p>
    051 *
    052 * @see <a
    053 *      href="https://svn.apache.org/repos/asf/commons/proper/imaging/trunk/src/test/java/org/apache/commons/imaging/examples/WriteExifMetadataExample.java">org.apache.commons.imaging.examples.WriteExifMetadataExample</a>
    054 */
    055public class ExifRewriter extends BinaryFileParser {
    056    /**
    057     * Constructor. to guess whether a file contains an image based on its file
    058     * extension.
    059     */
    060    public ExifRewriter() {
    061        this(ByteOrder.BIG_ENDIAN);
    062    }
    063
    064    /**
    065     * Constructor.
    066     * <p>
    067     *
    068     * @param byteOrder
    069     *            byte order of EXIF segment.
    070     */
    071    public ExifRewriter(final ByteOrder byteOrder) {
    072        setByteOrder(byteOrder);
    073    }
    074
    075    private static class JFIFPieces {
    076        public final List<JFIFPiece> pieces;
    077        public final List<JFIFPiece> exifPieces;
    078
    079        JFIFPieces(final List<JFIFPiece> pieces,
    080                final List<JFIFPiece> exifPieces) {
    081            this.pieces = pieces;
    082            this.exifPieces = exifPieces;
    083        }
    084
    085    }
    086
    087    private abstract static class JFIFPiece {
    088        protected abstract void write(OutputStream os) throws IOException;
    089    }
    090
    091    private static class JFIFPieceSegment extends JFIFPiece {
    092        public final int marker;
    093        public final byte[] markerBytes;
    094        public final byte[] markerLengthBytes;
    095        public final byte[] segmentData;
    096
    097        JFIFPieceSegment(final int marker, final byte[] markerBytes,
    098                final byte[] markerLengthBytes, final byte[] segmentData) {
    099            this.marker = marker;
    100            this.markerBytes = markerBytes;
    101            this.markerLengthBytes = markerLengthBytes;
    102            this.segmentData = segmentData;
    103        }
    104
    105        @Override
    106        protected void write(final OutputStream os) throws IOException {
    107            os.write(markerBytes);
    108            os.write(markerLengthBytes);
    109            os.write(segmentData);
    110        }
    111    }
    112
    113    private static class JFIFPieceSegmentExif extends JFIFPieceSegment {
    114
    115        JFIFPieceSegmentExif(final int marker, final byte[] markerBytes,
    116                final byte[] markerLengthBytes, final byte[] segmentData) {
    117            super(marker, markerBytes, markerLengthBytes, segmentData);
    118        }
    119    }
    120
    121    private static class JFIFPieceImageData extends JFIFPiece {
    122        public final byte[] markerBytes;
    123        public final byte[] imageData;
    124
    125        JFIFPieceImageData(final byte[] markerBytes, final byte[] imageData) {
    126            this.markerBytes = markerBytes;
    127            this.imageData = imageData;
    128        }
    129
    130        @Override
    131        protected void write(final OutputStream os) throws IOException {
    132            os.write(markerBytes);
    133            os.write(imageData);
    134        }
    135    }
    136
    137    private JFIFPieces analyzeJFIF(final ByteSource byteSource) throws ImageReadException, IOException {
    138        final List<JFIFPiece> pieces = new ArrayList<>();
    139        final List<JFIFPiece> exifPieces = new ArrayList<>();
    140
    141        final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
    142            // return false to exit before reading image data.
    143            @Override
    144            public boolean beginSOS() {
    145                return true;
    146            }
    147
    148            @Override
    149            public void visitSOS(final int marker, final byte[] markerBytes, final byte[] imageData) {
    150                pieces.add(new JFIFPieceImageData(markerBytes, imageData));
    151            }
    152
    153            // return false to exit traversal.
    154            @Override
    155            public boolean visitSegment(final int marker, final byte[] markerBytes,
    156                    final int markerLength, final byte[] markerLengthBytes,
    157                    final byte[] segmentData) throws
    158            // ImageWriteException,
    159                    ImageReadException, IOException {
    160                if (marker != JpegConstants.JPEG_APP1_MARKER) {
    161                    pieces.add(new JFIFPieceSegment(marker, markerBytes,
    162                            markerLengthBytes, segmentData));
    163                } else if (!startsWith(segmentData,
    164                        JpegConstants.EXIF_IDENTIFIER_CODE)) {
    165                    pieces.add(new JFIFPieceSegment(marker, markerBytes,
    166                            markerLengthBytes, segmentData));
    167                // } else if (exifSegmentArray[0] != null) {
    168                // // TODO: add support for multiple segments
    169                // throw new ImageReadException(
    170                // "More than one APP1 EXIF segment.");
    171                } else {
    172                    final JFIFPiece piece = new JFIFPieceSegmentExif(marker,
    173                            markerBytes, markerLengthBytes, segmentData);
    174                    pieces.add(piece);
    175                    exifPieces.add(piece);
    176                }
    177                return true;
    178            }
    179        };
    180
    181        new JpegUtils().traverseJFIF(byteSource, visitor);
    182
    183        // GenericSegment exifSegment = exifSegmentArray[0];
    184        // if (exifSegments.size() < 1)
    185        // {
    186        // // TODO: add support for adding, not just replacing.
    187        // throw new ImageReadException("No APP1 EXIF segment found.");
    188        // }
    189
    190        return new JFIFPieces(pieces, exifPieces);
    191    }
    192
    193    /**
    194     * Reads a Jpeg image, removes all EXIF metadata (by removing the APP1
    195     * segment), and writes the result to a stream.
    196     * <p>
    197     *
    198     * @param src
    199     *            Image file.
    200     * @param os
    201     *            OutputStream to write the image to.
    202     *
    203     * @throws ImageReadException if it fails to read the JFIF segments
    204     * @throws IOException if it fails to read the image data
    205     * @throws ImageWriteException if it fails to write the updated data
    206     * @see java.io.File
    207     * @see java.io.OutputStream
    208     * @see java.io.File
    209     * @see java.io.OutputStream
    210     */
    211    public void removeExifMetadata(final File src, final OutputStream os)
    212            throws ImageReadException, IOException, ImageWriteException {
    213        final ByteSource byteSource = new ByteSourceFile(src);
    214        removeExifMetadata(byteSource, os);
    215    }
    216
    217    /**
    218     * Reads a Jpeg image, removes all EXIF metadata (by removing the APP1
    219     * segment), and writes the result to a stream.
    220     *
    221     * @param src
    222     *            Byte array containing Jpeg image data.
    223     * @param os
    224     *            OutputStream to write the image to.
    225     * @throws ImageReadException if it fails to read the JFIF segments
    226     * @throws IOException if it fails to read the image data
    227     * @throws ImageWriteException if it fails to write the updated data
    228     */
    229    public void removeExifMetadata(final byte[] src, final OutputStream os)
    230            throws ImageReadException, IOException, ImageWriteException {
    231        final ByteSource byteSource = new ByteSourceArray(src);
    232        removeExifMetadata(byteSource, os);
    233    }
    234
    235    /**
    236     * Reads a Jpeg image, removes all EXIF metadata (by removing the APP1
    237     * segment), and writes the result to a stream.
    238     *
    239     * @param src
    240     *            InputStream containing Jpeg image data.
    241     * @param os
    242     *            OutputStream to write the image to.
    243     * @throws ImageReadException if it fails to read the JFIF segments
    244     * @throws IOException if it fails to read the image data
    245     * @throws ImageWriteException if it fails to write the updated data
    246     */
    247    public void removeExifMetadata(final InputStream src, final OutputStream os)
    248            throws ImageReadException, IOException, ImageWriteException {
    249        final ByteSource byteSource = new ByteSourceInputStream(src, null);
    250        removeExifMetadata(byteSource, os);
    251    }
    252
    253    /**
    254     * Reads a Jpeg image, removes all EXIF metadata (by removing the APP1
    255     * segment), and writes the result to a stream.
    256     *
    257     * @param byteSource
    258     *            ByteSource containing Jpeg image data.
    259     * @param os
    260     *            OutputStream to write the image to.
    261     * @throws ImageReadException if it fails to read the JFIF segments
    262     * @throws IOException if it fails to read the image data
    263     * @throws ImageWriteException if it fails to write the updated data
    264     */
    265    public void removeExifMetadata(final ByteSource byteSource, final OutputStream os)
    266            throws ImageReadException, IOException, ImageWriteException {
    267        final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
    268        final List<JFIFPiece> pieces = jfifPieces.pieces;
    269
    270        // Debug.debug("pieces", pieces);
    271
    272        // pieces.removeAll(jfifPieces.exifSegments);
    273
    274        // Debug.debug("pieces", pieces);
    275
    276        writeSegmentsReplacingExif(os, pieces, null);
    277    }
    278
    279    /**
    280     * Reads a Jpeg image, replaces the EXIF metadata and writes the result to a
    281     * stream.
    282     *
    283     * <p>Note that this uses the "Lossless" approach - in order to preserve data
    284     * embedded in the EXIF segment that it can't parse (such as Maker Notes),
    285     * this algorithm avoids overwriting any part of the original segment that
    286     * it couldn't parse. This can cause the EXIF segment to grow with each
    287     * update, which is a serious issue, since all EXIF data must fit in a
    288     * single APP1 segment of the Jpeg image.</p>
    289     *
    290     * @param src
    291     *            Image file.
    292     * @param os
    293     *            OutputStream to write the image to.
    294     * @param outputSet
    295     *            TiffOutputSet containing the EXIF data to write.
    296     * @throws ImageReadException if it fails to read the JFIF segments
    297     * @throws IOException if it fails to read the image data
    298     * @throws ImageWriteException if it fails to write the updated data
    299     */
    300    public void updateExifMetadataLossless(final File src, final OutputStream os,
    301            final TiffOutputSet outputSet) throws ImageReadException, IOException,
    302            ImageWriteException {
    303        final ByteSource byteSource = new ByteSourceFile(src);
    304        updateExifMetadataLossless(byteSource, os, outputSet);
    305    }
    306
    307    /**
    308     * Reads a Jpeg image, replaces the EXIF metadata and writes the result to a
    309     * stream.
    310     *
    311     * <p>Note that this uses the "Lossless" approach - in order to preserve data
    312     * embedded in the EXIF segment that it can't parse (such as Maker Notes),
    313     * this algorithm avoids overwriting any part of the original segment that
    314     * it couldn't parse. This can cause the EXIF segment to grow with each
    315     * update, which is a serious issue, since all EXIF data must fit in a
    316     * single APP1 segment of the Jpeg image.</p>
    317     *
    318     * @param src
    319     *            Byte array containing Jpeg image data.
    320     * @param os
    321     *            OutputStream to write the image to.
    322     * @param outputSet
    323     *            TiffOutputSet containing the EXIF data to write.
    324     * @throws ImageReadException if it fails to read the JFIF segments
    325     * @throws IOException if it fails to read the image data
    326     * @throws ImageWriteException if it fails to write the updated data
    327     */
    328    public void updateExifMetadataLossless(final byte[] src, final OutputStream os,
    329            final TiffOutputSet outputSet) throws ImageReadException, IOException,
    330            ImageWriteException {
    331        final ByteSource byteSource = new ByteSourceArray(src);
    332        updateExifMetadataLossless(byteSource, os, outputSet);
    333    }
    334
    335    /**
    336     * Reads a Jpeg image, replaces the EXIF metadata and writes the result to a
    337     * stream.
    338     *
    339     * <p>Note that this uses the "Lossless" approach - in order to preserve data
    340     * embedded in the EXIF segment that it can't parse (such as Maker Notes),
    341     * this algorithm avoids overwriting any part of the original segment that
    342     * it couldn't parse. This can cause the EXIF segment to grow with each
    343     * update, which is a serious issue, since all EXIF data must fit in a
    344     * single APP1 segment of the Jpeg image.</p>
    345     *
    346     * @param src
    347     *            InputStream containing Jpeg image data.
    348     * @param os
    349     *            OutputStream to write the image to.
    350     * @param outputSet
    351     *            TiffOutputSet containing the EXIF data to write.
    352     * @throws ImageReadException if it fails to read the JFIF segments
    353     * @throws IOException if it fails to read the image data
    354     * @throws ImageWriteException if it fails to write the updated data
    355     */
    356    public void updateExifMetadataLossless(final InputStream src, final OutputStream os,
    357            final TiffOutputSet outputSet) throws ImageReadException, IOException,
    358            ImageWriteException {
    359        final ByteSource byteSource = new ByteSourceInputStream(src, null);
    360        updateExifMetadataLossless(byteSource, os, outputSet);
    361    }
    362
    363    /**
    364     * Reads a Jpeg image, replaces the EXIF metadata and writes the result to a
    365     * stream.
    366     *
    367     * <p>Note that this uses the "Lossless" approach - in order to preserve data
    368     * embedded in the EXIF segment that it can't parse (such as Maker Notes),
    369     * this algorithm avoids overwriting any part of the original segment that
    370     * it couldn't parse. This can cause the EXIF segment to grow with each
    371     * update, which is a serious issue, since all EXIF data must fit in a
    372     * single APP1 segment of the Jpeg image.</p>
    373     *
    374     * @param byteSource
    375     *            ByteSource containing Jpeg image data.
    376     * @param os
    377     *            OutputStream to write the image to.
    378     * @param outputSet
    379     *            TiffOutputSet containing the EXIF data to write.
    380     * @throws ImageReadException if it fails to read the JFIF segments
    381     * @throws IOException if it fails to read the image data
    382     * @throws ImageWriteException if it fails to write the updated data
    383     */
    384    public void updateExifMetadataLossless(final ByteSource byteSource,
    385            final OutputStream os, final TiffOutputSet outputSet)
    386            throws ImageReadException, IOException, ImageWriteException {
    387        // List outputDirectories = outputSet.getDirectories();
    388        final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
    389        final List<JFIFPiece> pieces = jfifPieces.pieces;
    390
    391        TiffImageWriterBase writer;
    392        // Just use first APP1 segment for now.
    393        // Multiple APP1 segments are rare and poorly supported.
    394        if (!jfifPieces.exifPieces.isEmpty()) {
    395            final JFIFPieceSegment exifPiece = (JFIFPieceSegment) jfifPieces.exifPieces.get(0);
    396
    397            byte[] exifBytes = exifPiece.segmentData;
    398            exifBytes = remainingBytes("trimmed exif bytes", exifBytes, 6);
    399
    400            writer = new TiffImageWriterLossless(outputSet.byteOrder, exifBytes);
    401
    402        } else {
    403            writer = new TiffImageWriterLossy(outputSet.byteOrder);
    404        }
    405
    406        final boolean includeEXIFPrefix = true;
    407        final byte[] newBytes = writeExifSegment(writer, outputSet, includeEXIFPrefix);
    408
    409        writeSegmentsReplacingExif(os, pieces, newBytes);
    410    }
    411
    412    /**
    413     * Reads a Jpeg image, replaces the EXIF metadata and writes the result to a
    414     * stream.
    415     *
    416     * <p>Note that this uses the "Lossy" approach - the algorithm overwrites the
    417     * entire EXIF segment, ignoring the possibility that it may be discarding
    418     * data it couldn't parse (such as Maker Notes).</p>
    419     *
    420     * @param src
    421     *            Byte array containing Jpeg image data.
    422     * @param os
    423     *            OutputStream to write the image to.
    424     * @param outputSet
    425     *            TiffOutputSet containing the EXIF data to write.
    426     * @throws ImageReadException if it fails to read the JFIF segments
    427     * @throws IOException if it fails to read the image data
    428     * @throws ImageWriteException if it fails to write the updated data
    429     */
    430    public void updateExifMetadataLossy(final byte[] src, final OutputStream os,
    431            final TiffOutputSet outputSet) throws ImageReadException, IOException,
    432            ImageWriteException {
    433        final ByteSource byteSource = new ByteSourceArray(src);
    434        updateExifMetadataLossy(byteSource, os, outputSet);
    435    }
    436
    437    /**
    438     * Reads a Jpeg image, replaces the EXIF metadata and writes the result to a
    439     * stream.
    440     *
    441     * <p>Note that this uses the "Lossy" approach - the algorithm overwrites the
    442     * entire EXIF segment, ignoring the possibility that it may be discarding
    443     * data it couldn't parse (such as Maker Notes).</p>
    444     *
    445     * @param src
    446     *            InputStream containing Jpeg image data.
    447     * @param os
    448     *            OutputStream to write the image to.
    449     * @param outputSet
    450     *            TiffOutputSet containing the EXIF data to write.
    451     * @throws ImageReadException if it fails to read the JFIF segments
    452     * @throws IOException if it fails to read the image data
    453     * @throws ImageWriteException if it fails to write the updated data
    454     */
    455    public void updateExifMetadataLossy(final InputStream src, final OutputStream os,
    456            final TiffOutputSet outputSet) throws ImageReadException, IOException,
    457            ImageWriteException {
    458        final ByteSource byteSource = new ByteSourceInputStream(src, null);
    459        updateExifMetadataLossy(byteSource, os, outputSet);
    460    }
    461
    462    /**
    463     * Reads a Jpeg image, replaces the EXIF metadata and writes the result to a
    464     * stream.
    465     *
    466     * <p>Note that this uses the "Lossy" approach - the algorithm overwrites the
    467     * entire EXIF segment, ignoring the possibility that it may be discarding
    468     * data it couldn't parse (such as Maker Notes).</p>
    469     *
    470     * @param src
    471     *            Image file.
    472     * @param os
    473     *            OutputStream to write the image to.
    474     * @param outputSet
    475     *            TiffOutputSet containing the EXIF data to write.
    476     * @throws ImageReadException if it fails to read the JFIF segments
    477     * @throws IOException if it fails to read the image data
    478     * @throws ImageWriteException if it fails to write the updated data
    479     */
    480    public void updateExifMetadataLossy(final File src, final OutputStream os,
    481            final TiffOutputSet outputSet) throws ImageReadException, IOException,
    482            ImageWriteException {
    483        final ByteSource byteSource = new ByteSourceFile(src);
    484        updateExifMetadataLossy(byteSource, os, outputSet);
    485    }
    486
    487    /**
    488     * Reads a Jpeg image, replaces the EXIF metadata and writes the result to a
    489     * stream.
    490     *
    491     * <p>Note that this uses the "Lossy" approach - the algorithm overwrites the
    492     * entire EXIF segment, ignoring the possibility that it may be discarding
    493     * data it couldn't parse (such as Maker Notes).</p>
    494     *
    495     * @param byteSource
    496     *            ByteSource containing Jpeg image data.
    497     * @param os
    498     *            OutputStream to write the image to.
    499     * @param outputSet
    500     *            TiffOutputSet containing the EXIF data to write.
    501     * @throws ImageReadException if it fails to read the JFIF segments
    502     * @throws IOException if it fails to read the image data
    503     * @throws ImageWriteException if it fails to write the updated data
    504     */
    505    public void updateExifMetadataLossy(final ByteSource byteSource, final OutputStream os,
    506            final TiffOutputSet outputSet) throws ImageReadException, IOException,
    507            ImageWriteException {
    508        final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
    509        final List<JFIFPiece> pieces = jfifPieces.pieces;
    510
    511        final TiffImageWriterBase writer = new TiffImageWriterLossy(
    512                outputSet.byteOrder);
    513
    514        final boolean includeEXIFPrefix = true;
    515        final byte[] newBytes = writeExifSegment(writer, outputSet, includeEXIFPrefix);
    516
    517        writeSegmentsReplacingExif(os, pieces, newBytes);
    518    }
    519
    520    private void writeSegmentsReplacingExif(final OutputStream outputStream,
    521            final List<JFIFPiece> segments, final byte[] newBytes)
    522            throws ImageWriteException, IOException {
    523
    524        try (DataOutputStream os = new DataOutputStream(outputStream)) {
    525            JpegConstants.SOI.writeTo(os);
    526
    527            boolean hasExif = false;
    528
    529            for (final JFIFPiece piece : segments) {
    530                if (piece instanceof JFIFPieceSegmentExif) {
    531                    hasExif = true;
    532                    break;
    533                }
    534            }
    535
    536            if (!hasExif && newBytes != null) {
    537                final byte[] markerBytes = ByteConversions.toBytes((short) JpegConstants.JPEG_APP1_MARKER, getByteOrder());
    538                if (newBytes.length > 0xffff) {
    539                    throw new ExifOverflowException(
    540                            "APP1 Segment is too long: " + newBytes.length);
    541                }
    542                final int markerLength = newBytes.length + 2;
    543                final byte[] markerLengthBytes = ByteConversions.toBytes((short) markerLength, getByteOrder());
    544
    545                int index = 0;
    546                final JFIFPieceSegment firstSegment = (JFIFPieceSegment) segments.get(index);
    547                if (firstSegment.marker == JpegConstants.JFIF_MARKER) {
    548                    index = 1;
    549                }
    550                segments.add(index, new JFIFPieceSegmentExif(JpegConstants.JPEG_APP1_MARKER,
    551                        markerBytes, markerLengthBytes, newBytes));
    552            }
    553
    554            boolean APP1Written = false;
    555
    556            for (final JFIFPiece piece : segments) {
    557                if (piece instanceof JFIFPieceSegmentExif) {
    558                    // only replace first APP1 segment; skips others.
    559                    if (APP1Written) {
    560                        continue;
    561                    }
    562                    APP1Written = true;
    563
    564                    if (newBytes == null) {
    565                        continue;
    566                    }
    567
    568                    final byte[] markerBytes = ByteConversions.toBytes((short) JpegConstants.JPEG_APP1_MARKER, getByteOrder());
    569                    if (newBytes.length > 0xffff) {
    570                        throw new ExifOverflowException(
    571                                "APP1 Segment is too long: " + newBytes.length);
    572                    }
    573                    final int markerLength = newBytes.length + 2;
    574                    final byte[] markerLengthBytes = ByteConversions.toBytes((short) markerLength, getByteOrder());
    575
    576                    os.write(markerBytes);
    577                    os.write(markerLengthBytes);
    578                    os.write(newBytes);
    579                } else {
    580                    piece.write(os);
    581                }
    582            }
    583        }
    584    }
    585
    586    public static class ExifOverflowException extends ImageWriteException {
    587        private static final long serialVersionUID = 1401484357224931218L;
    588
    589        public ExifOverflowException(final String message) {
    590            super(message);
    591        }
    592    }
    593
    594    private byte[] writeExifSegment(final TiffImageWriterBase writer,
    595            final TiffOutputSet outputSet, final boolean includeEXIFPrefix)
    596            throws IOException, ImageWriteException {
    597        final ByteArrayOutputStream os = new ByteArrayOutputStream();
    598
    599        if (includeEXIFPrefix) {
    600            JpegConstants.EXIF_IDENTIFIER_CODE.writeTo(os);
    601            os.write(0);
    602            os.write(0);
    603        }
    604
    605        writer.write(os, outputSet);
    606
    607        return os.toByteArray();
    608    }
    609
    610}