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     */
054    public void removeXmpXml(final File src, final OutputStream os)
055            throws ImageReadException, IOException {
056        final ByteSource byteSource = new ByteSourceFile(src);
057        removeXmpXml(byteSource, os);
058    }
059
060    /**
061     * Reads a Jpeg image, removes all XMP XML (by removing the APP1 segment),
062     * and writes the result to a stream.
063     * <p>
064     *
065     * @param src
066     *            Byte array containing Jpeg image data.
067     * @param os
068     *            OutputStream to write the image to.
069     */
070    public void removeXmpXml(final byte[] src, final OutputStream os)
071            throws ImageReadException, IOException {
072        final ByteSource byteSource = new ByteSourceArray(src);
073        removeXmpXml(byteSource, os);
074    }
075
076    /**
077     * Reads a Jpeg image, removes all XMP XML (by removing the APP1 segment),
078     * and writes the result to a stream.
079     * <p>
080     *
081     * @param src
082     *            InputStream containing Jpeg image data.
083     * @param os
084     *            OutputStream to write the image to.
085     */
086    public void removeXmpXml(final InputStream src, final OutputStream os)
087            throws ImageReadException, IOException {
088        final ByteSource byteSource = new ByteSourceInputStream(src, null);
089        removeXmpXml(byteSource, os);
090    }
091
092    /**
093     * Reads a Jpeg image, removes all XMP XML (by removing the APP1 segment),
094     * and writes the result to a stream.
095     * <p>
096     *
097     * @param byteSource
098     *            ByteSource containing Jpeg image data.
099     * @param os
100     *            OutputStream to write the image to.
101     */
102    public void removeXmpXml(final ByteSource byteSource, final OutputStream os)
103            throws ImageReadException, IOException {
104        final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
105        List<JFIFPiece> pieces = jfifPieces.pieces;
106        pieces = removeXmpSegments(pieces);
107        writeSegments(os, pieces);
108    }
109
110    /**
111     * Reads a Jpeg image, replaces the XMP XML and writes the result to a
112     * stream.
113     *
114     * @param src
115     *            Byte array containing Jpeg image data.
116     * @param os
117     *            OutputStream to write the image to.
118     * @param xmpXml
119     *            String containing XMP XML.
120     */
121    public void updateXmpXml(final byte[] src, final OutputStream os, final String xmpXml)
122            throws ImageReadException, IOException, ImageWriteException {
123        final ByteSource byteSource = new ByteSourceArray(src);
124        updateXmpXml(byteSource, os, xmpXml);
125    }
126
127    /**
128     * Reads a Jpeg image, replaces the XMP XML and writes the result to a
129     * stream.
130     *
131     * @param src
132     *            InputStream containing Jpeg image data.
133     * @param os
134     *            OutputStream to write the image to.
135     * @param xmpXml
136     *            String containing XMP XML.
137     */
138    public void updateXmpXml(final InputStream src, final OutputStream os, final String xmpXml)
139            throws ImageReadException, IOException, ImageWriteException {
140        final ByteSource byteSource = new ByteSourceInputStream(src, null);
141        updateXmpXml(byteSource, os, xmpXml);
142    }
143
144    /**
145     * Reads a Jpeg image, replaces the XMP XML and writes the result to a
146     * stream.
147     *
148     * @param src
149     *            Image file.
150     * @param os
151     *            OutputStream to write the image to.
152     * @param xmpXml
153     *            String containing XMP XML.
154     */
155    public void updateXmpXml(final File src, final OutputStream os, final String xmpXml)
156            throws ImageReadException, IOException, ImageWriteException {
157        final ByteSource byteSource = new ByteSourceFile(src);
158        updateXmpXml(byteSource, os, xmpXml);
159    }
160
161    /**
162     * Reads a Jpeg image, replaces the XMP XML and writes the result to a
163     * stream.
164     *
165     * @param byteSource
166     *            ByteSource containing Jpeg image data.
167     * @param os
168     *            OutputStream to write the image to.
169     * @param xmpXml
170     *            String containing XMP XML.
171     */
172    public void updateXmpXml(final ByteSource byteSource, final OutputStream os,
173            final String xmpXml) throws ImageReadException, IOException,
174            ImageWriteException {
175        final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
176        List<JFIFPiece> pieces = jfifPieces.pieces;
177        pieces = removeXmpSegments(pieces);
178
179        final List<JFIFPieceSegment> newPieces = new ArrayList<>();
180        final byte[] xmpXmlBytes = xmpXml.getBytes(StandardCharsets.UTF_8);
181        int index = 0;
182        while (index < xmpXmlBytes.length) {
183            final int segmentSize = Math.min(xmpXmlBytes.length, JpegConstants.MAX_SEGMENT_SIZE);
184            final byte[] segmentData = writeXmpSegment(xmpXmlBytes, index,
185                    segmentSize);
186            newPieces.add(new JFIFPieceSegment(JpegConstants.JPEG_APP1_MARKER, segmentData));
187            index += segmentSize;
188        }
189
190        pieces = insertAfterLastAppSegments(pieces, newPieces);
191
192        writeSegments(os, pieces);
193    }
194
195    private byte[] writeXmpSegment(final byte[] xmpXmlData, final int start, final int length)
196            throws IOException {
197        final ByteArrayOutputStream os = new ByteArrayOutputStream();
198
199        JpegConstants.XMP_IDENTIFIER.writeTo(os);
200        os.write(xmpXmlData, start, length);
201
202        return os.toByteArray();
203    }
204
205}