    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.ImagingException;
    029import org.apache.commons.imaging.bytesource.ByteSource;
    030import org.apache.commons.imaging.formats.jpeg.JpegConstants;
    031
    032/**
    033 * Interface for Exif write/update/remove functionality for Jpeg/JFIF images.
    034 */
    035public class JpegXmpRewriter extends JpegRewriter {
    036
    037    /**
    038     * Constructs a new instance with the default, big-endian, byte order.
    039     */
    040    public JpegXmpRewriter() {
    041        // empty
    042    }
    043
    044    /**
    045     * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream.
    046     *
    047     * @param src Byte array containing JPEG image data.
    048     * @param os  OutputStream to write the image to.
    049     * @throws ImagingException if it fails to read the JFIF segments
    050     * @throws IOException      if it fails to read or write the data from the segments
    051     */
    052    public void removeXmpXml(final byte[] src, final OutputStream os) throws ImagingException, IOException {
    053        final ByteSource byteSource = ByteSource.array(src);
    054        removeXmpXml(byteSource, os);
    055    }
    056
    057    /**
    058     * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream.
    059     *
    060     * @param byteSource ByteSource containing JPEG image data.
    061     * @param os         OutputStream to write the image to.
    062     * @throws ImagingException if it fails to read the JFIF segments
    063     * @throws IOException      if it fails to read or write the data from the segments
    064     */
    065    public void removeXmpXml(final ByteSource byteSource, final OutputStream os) throws ImagingException, IOException {
    066        final JFIFPieces jfifPieces = analyzeJfif(byteSource);
    067        List<JFIFPiece> pieces = jfifPieces.pieces;
    068        pieces = removeXmpSegments(pieces);
    069        writeSegments(os, pieces);
    070    }
    071
    072    /**
    073     * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream.
    074     *
    075     * @param src Image file.
    076     * @param os  OutputStream to write the image to.
    077     * @see java.io.File
    078     * @see java.io.OutputStream
    079     * @throws ImagingException if it fails to read the JFIF segments
    080     * @throws IOException      if it fails to read or write the data from the segments
    081     */
    082    public void removeXmpXml(final File src, final OutputStream os) throws ImagingException, IOException {
    083        final ByteSource byteSource = ByteSource.file(src);
    084        removeXmpXml(byteSource, os);
    085    }
    086
    087    /**
    088     * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream.
    089     *
    090     * @param src InputStream containing JPEG image data.
    091     * @param os  OutputStream to write the image to.
    092     * @throws ImagingException if it fails to read the JFIF segments
    093     * @throws IOException      if it fails to read or write the data from the segments
    094     */
    095    public void removeXmpXml(final InputStream src, final OutputStream os) throws ImagingException, IOException {
    096        final ByteSource byteSource = ByteSource.inputStream(src, null);
    097        removeXmpXml(byteSource, os);
    098    }
    099
    100    /**
    101     * Reads a JPEG image, replaces the XMP XML and writes the result to a stream.
    102     *
    103     * @param src    Byte array containing JPEG image data.
    104     * @param os     OutputStream to write the image to.
    105     * @param xmpXml String containing XMP XML.
    106     * @throws ImagingException if it fails to read or write the JFIF segments
    107     * @throws IOException      if it fails to read or write the data from the segments
    108     */
    109    public void updateXmpXml(final byte[] src, final OutputStream os, final String xmpXml) throws ImagingException, IOException {
    110        final ByteSource byteSource = ByteSource.array(src);
    111        updateXmpXml(byteSource, os, xmpXml);
    112    }
    113
    114    /**
    115     * Reads a JPEG image, replaces the XMP XML and writes the result to a stream.
    116     *
    117     * @param byteSource ByteSource containing JPEG image data.
    118     * @param os         OutputStream to write the image to.
    119     * @param xmpXml     String containing XMP XML.
    120     * @throws ImagingException if it fails to read or write the JFIF segments
    121     * @throws IOException      if it fails to read or write the data from the segments
    122     */
    123    public void updateXmpXml(final ByteSource byteSource, final OutputStream os, final String xmpXml) throws ImagingException, IOException {
    124        final JFIFPieces jfifPieces = analyzeJfif(byteSource);
    125        List<JFIFPiece> pieces = jfifPieces.pieces;
    126        pieces = removeXmpSegments(pieces);
    127
    128        final List<JFIFPieceSegment> newPieces = new ArrayList<>();
    129        final byte[] xmpXmlBytes = xmpXml.getBytes(StandardCharsets.UTF_8);
    130        int index = 0;
    131        while (index < xmpXmlBytes.length) {
    132            final int segmentSize = Math.min(xmpXmlBytes.length, JpegConstants.MAX_SEGMENT_SIZE);
    133            final byte[] segmentData = writeXmpSegment(xmpXmlBytes, index, segmentSize);
    134            newPieces.add(new JFIFPieceSegment(JpegConstants.JPEG_APP1_MARKER, segmentData));
    135            index += segmentSize;
    136        }
    137
    138        pieces = insertAfterLastAppSegments(pieces, newPieces);
    139
    140        writeSegments(os, pieces);
    141    }
    142
    143    /**
    144     * Reads a JPEG image, replaces the XMP XML and writes the result to a stream.
    145     *
    146     * @param src    Image file.
    147     * @param os     OutputStream to write the image to.
    148     * @param xmpXml String containing XMP XML.
    149     * @throws ImagingException if it fails to read or write the JFIF segments
    150     * @throws IOException      if it fails to read or write the data from the segments
    151     */
    152    public void updateXmpXml(final File src, final OutputStream os, final String xmpXml) throws ImagingException, IOException {
    153        final ByteSource byteSource = ByteSource.file(src);
    154        updateXmpXml(byteSource, os, xmpXml);
    155    }
    156
    157    /**
    158     * Reads a JPEG image, replaces the XMP XML and writes the result to a stream.
    159     *
    160     * @param src    InputStream containing JPEG image data.
    161     * @param os     OutputStream to write the image to.
    162     * @param xmpXml String containing XMP XML.
    163     * @throws ImagingException if it fails to read or write the JFIF segments
    164     * @throws IOException      if it fails to read or write the data from the segments
    165     */
    166    public void updateXmpXml(final InputStream src, final OutputStream os, final String xmpXml) throws ImagingException, IOException {
    167        final ByteSource byteSource = ByteSource.inputStream(src, null);
    168        updateXmpXml(byteSource, os, xmpXml);
    169    }
    170
    171    private byte[] writeXmpSegment(final byte[] xmpXmlData, final int start, final int length) throws IOException {
    172        final ByteArrayOutputStream os = new ByteArrayOutputStream();
    173
    174        JpegConstants.XMP_IDENTIFIER.writeTo(os);
    175        os.write(xmpXmlData, start, length);
    176
    177        return os.toByteArray();
    178    }
    179
    180}