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}