    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.xmp;
    018
    019import java.io.ByteArrayOutputStream;
    020import java.io.File;
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.io.OutputStream;
    024import java.nio.charset.StandardCharsets;
    025import java.util.ArrayList;
    026import java.util.List;
    027
    028import org.apache.commons.imaging.ImageReadException;
    029import org.apache.commons.imaging.ImageWriteException;
    030import org.apache.commons.imaging.common.bytesource.ByteSource;
    031import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
    032import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
    033import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream;
    034import org.apache.commons.imaging.formats.jpeg.JpegConstants;
    035
    036/**
    037 * Interface for Exif write/update/remove functionality for Jpeg/JFIF images.
    038 */
    039public class JpegXmpRewriter extends JpegRewriter {
    040
    041    /**
    042     * Reads a Jpeg image, removes all XMP XML (by removing the APP1 segment),
    043     * and writes the result to a stream.
    044     * <p>
    045     *
    046     * @param src
    047     *            Image file.
    048     * @param os
    049     *            OutputStream to write the image to.
    050     *
    051     * @see java.io.File
    052     * @see java.io.OutputStream
    053     * @throws ImageReadException if it fails to read the JFIF segments
    054     * @throws IOException if it fails to read or write the data from the segments
    055     */
    056    public void removeXmpXml(final File src, final OutputStream os)
    057            throws ImageReadException, IOException {
    058        final ByteSource byteSource = new ByteSourceFile(src);
    059        removeXmpXml(byteSource, os);
    060    }
    061
    062    /**
    063     * Reads a Jpeg image, removes all XMP XML (by removing the APP1 segment),
    064     * and writes the result to a stream.
    065     * <p>
    066     *
    067     * @param src
    068     *            Byte array containing Jpeg image data.
    069     * @param os
    070     *            OutputStream to write the image to.
    071     * @throws ImageReadException if it fails to read the JFIF segments
    072     * @throws IOException if it fails to read or write the data from the segments
    073     */
    074    public void removeXmpXml(final byte[] src, final OutputStream os)
    075            throws ImageReadException, IOException {
    076        final ByteSource byteSource = new ByteSourceArray(src);
    077        removeXmpXml(byteSource, os);
    078    }
    079
    080    /**
    081     * Reads a Jpeg image, removes all XMP XML (by removing the APP1 segment),
    082     * and writes the result to a stream.
    083     * <p>
    084     *
    085     * @param src
    086     *            InputStream containing Jpeg image data.
    087     * @param os
    088     *            OutputStream to write the image to.
    089     * @throws ImageReadException if it fails to read the JFIF segments
    090     * @throws IOException if it fails to read or write the data from the segments
    091     */
    092    public void removeXmpXml(final InputStream src, final OutputStream os)
    093            throws ImageReadException, IOException {
    094        final ByteSource byteSource = new ByteSourceInputStream(src, null);
    095        removeXmpXml(byteSource, os);
    096    }
    097
    098    /**
    099     * Reads a Jpeg image, removes all XMP XML (by removing the APP1 segment),
    100     * and writes the result to a stream.
    101     * <p>
    102     *
    103     * @param byteSource
    104     *            ByteSource containing Jpeg image data.
    105     * @param os
    106     *            OutputStream to write the image to.
    107     * @throws ImageReadException if it fails to read the JFIF segments
    108     * @throws IOException if it fails to read or write the data from the segments
    109     */
    110    public void removeXmpXml(final ByteSource byteSource, final OutputStream os)
    111            throws ImageReadException, IOException {
    112        final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
    113        List<JFIFPiece> pieces = jfifPieces.pieces;
    114        pieces = removeXmpSegments(pieces);
    115        writeSegments(os, pieces);
    116    }
    117
    118    /**
    119     * Reads a Jpeg image, replaces the XMP XML and writes the result to a
    120     * stream.
    121     *
    122     * @param src
    123     *            Byte array containing Jpeg image data.
    124     * @param os
    125     *            OutputStream to write the image to.
    126     * @param xmpXml
    127     *            String containing XMP XML.
    128     * @throws ImageReadException if it fails to read the JFIF segments
    129     * @throws IOException if it fails to read or write the data from the segments
    130     * @throws ImageWriteException if it fails to write the JFIF segments
    131     */
    132    public void updateXmpXml(final byte[] src, final OutputStream os, final String xmpXml)
    133            throws ImageReadException, IOException, ImageWriteException {
    134        final ByteSource byteSource = new ByteSourceArray(src);
    135        updateXmpXml(byteSource, os, xmpXml);
    136    }
    137
    138    /**
    139     * Reads a Jpeg image, replaces the XMP XML and writes the result to a
    140     * stream.
    141     *
    142     * @param src
    143     *            InputStream containing Jpeg image data.
    144     * @param os
    145     *            OutputStream to write the image to.
    146     * @param xmpXml
    147     *            String containing XMP XML.
    148     * @throws ImageReadException if it fails to read the JFIF segments
    149     * @throws IOException if it fails to read or write the data from the segments
    150     * @throws ImageWriteException if it fails to write the JFIF segments
    151     */
    152    public void updateXmpXml(final InputStream src, final OutputStream os, final String xmpXml)
    153            throws ImageReadException, IOException, ImageWriteException {
    154        final ByteSource byteSource = new ByteSourceInputStream(src, null);
    155        updateXmpXml(byteSource, os, xmpXml);
    156    }
    157
    158    /**
    159     * Reads a Jpeg image, replaces the XMP XML and writes the result to a
    160     * stream.
    161     *
    162     * @param src
    163     *            Image file.
    164     * @param os
    165     *            OutputStream to write the image to.
    166     * @param xmpXml
    167     *            String containing XMP XML.
    168     * @throws ImageReadException if it fails to read the JFIF segments
    169     * @throws IOException if it fails to read or write the data from the segments
    170     * @throws ImageWriteException if it fails to write the JFIF segments
    171     */
    172    public void updateXmpXml(final File src, final OutputStream os, final String xmpXml)
    173            throws ImageReadException, IOException, ImageWriteException {
    174        final ByteSource byteSource = new ByteSourceFile(src);
    175        updateXmpXml(byteSource, os, xmpXml);
    176    }
    177
    178    /**
    179     * Reads a Jpeg image, replaces the XMP XML and writes the result to a
    180     * stream.
    181     *
    182     * @param byteSource
    183     *            ByteSource containing Jpeg image data.
    184     * @param os
    185     *            OutputStream to write the image to.
    186     * @param xmpXml
    187     *            String containing XMP XML.
    188     * @throws ImageReadException if it fails to read the JFIF segments
    189     * @throws IOException if it fails to read or write the data from the segments
    190     * @throws ImageWriteException if it fails to write the JFIF segments
    191     */
    192    public void updateXmpXml(final ByteSource byteSource, final OutputStream os,
    193            final String xmpXml) throws ImageReadException, IOException,
    194            ImageWriteException {
    195        final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
    196        List<JFIFPiece> pieces = jfifPieces.pieces;
    197        pieces = removeXmpSegments(pieces);
    198
    199        final List<JFIFPieceSegment> newPieces = new ArrayList<>();
    200        final byte[] xmpXmlBytes = xmpXml.getBytes(StandardCharsets.UTF_8);
    201        int index = 0;
    202        while (index < xmpXmlBytes.length) {
    203            final int segmentSize = Math.min(xmpXmlBytes.length, JpegConstants.MAX_SEGMENT_SIZE);
    204            final byte[] segmentData = writeXmpSegment(xmpXmlBytes, index,
    205                    segmentSize);
    206            newPieces.add(new JFIFPieceSegment(JpegConstants.JPEG_APP1_MARKER, segmentData));
    207            index += segmentSize;
    208        }
    209
    210        pieces = insertAfterLastAppSegments(pieces, newPieces);
    211
    212        writeSegments(os, pieces);
    213    }
    214
    215    private byte[] writeXmpSegment(final byte[] xmpXmlData, final int start, final int length)
    216            throws IOException {
    217        final ByteArrayOutputStream os = new ByteArrayOutputStream();
    218
    219        JpegConstants.XMP_IDENTIFIER.writeTo(os);
    220        os.write(xmpXmlData, start, length);
    221
    222        return os.toByteArray();
    223    }
    224
    225}