View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.jpeg.xmp;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.nio.charset.StandardCharsets;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import org.apache.commons.imaging.ImagingException;
29  import org.apache.commons.imaging.bytesource.ByteSource;
30  import org.apache.commons.imaging.formats.jpeg.JpegConstants;
31  
32  /**
33   * Interface for Exif write/update/remove functionality for Jpeg/JFIF images.
34   */
35  public class JpegXmpRewriter extends JpegRewriter {
36  
37      /**
38       * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream.
39       * <p>
40       *
41       * @param src Byte array containing JPEG image data.
42       * @param os  OutputStream to write the image to.
43       * @throws ImagingException if it fails to read the JFIF segments
44       * @throws IOException      if it fails to read or write the data from the segments
45       */
46      public void removeXmpXml(final byte[] src, final OutputStream os) throws ImagingException, IOException {
47          final ByteSource byteSource = ByteSource.array(src);
48          removeXmpXml(byteSource, os);
49      }
50  
51      /**
52       * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream.
53       * <p>
54       *
55       * @param byteSource ByteSource containing JPEG image data.
56       * @param os         OutputStream to write the image to.
57       * @throws ImagingException if it fails to read the JFIF segments
58       * @throws IOException      if it fails to read or write the data from the segments
59       */
60      public void removeXmpXml(final ByteSource byteSource, final OutputStream os) throws ImagingException, IOException {
61          final JFIFPieces jfifPieces = analyzeJfif(byteSource);
62          List<JFIFPiece> pieces = jfifPieces.pieces;
63          pieces = removeXmpSegments(pieces);
64          writeSegments(os, pieces);
65      }
66  
67      /**
68       * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream.
69       * <p>
70       *
71       * @param src Image file.
72       * @param os  OutputStream to write the image to.
73       *
74       * @see java.io.File
75       * @see java.io.OutputStream
76       * @throws ImagingException if it fails to read the JFIF segments
77       * @throws IOException      if it fails to read or write the data from the segments
78       */
79      public void removeXmpXml(final File src, final OutputStream os) throws ImagingException, IOException {
80          final ByteSource byteSource = ByteSource.file(src);
81          removeXmpXml(byteSource, os);
82      }
83  
84      /**
85       * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream.
86       * <p>
87       *
88       * @param src InputStream containing JPEG image data.
89       * @param os  OutputStream to write the image to.
90       * @throws ImagingException if it fails to read the JFIF segments
91       * @throws IOException      if it fails to read or write the data from the segments
92       */
93      public void removeXmpXml(final InputStream src, final OutputStream os) throws ImagingException, IOException {
94          final ByteSource byteSource = ByteSource.inputStream(src, null);
95          removeXmpXml(byteSource, os);
96      }
97  
98      /**
99       * Reads a JPEG image, replaces the XMP XML and writes the result to a stream.
100      *
101      * @param src    Byte array containing JPEG image data.
102      * @param os     OutputStream to write the image to.
103      * @param xmpXml String containing XMP XML.
104      * @throws ImagingException if it fails to read the JFIF segments
105      * @throws IOException      if it fails to read or write the data from the segments
106      * @throws ImagingException if it fails to write the JFIF segments
107      */
108     public void updateXmpXml(final byte[] src, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException {
109         final ByteSource byteSource = ByteSource.array(src);
110         updateXmpXml(byteSource, os, xmpXml);
111     }
112 
113     /**
114      * Reads a JPEG image, replaces the XMP XML and writes the result to a stream.
115      *
116      * @param byteSource ByteSource containing JPEG image data.
117      * @param os         OutputStream to write the image to.
118      * @param xmpXml     String containing XMP XML.
119      * @throws ImagingException if it fails to read the JFIF segments
120      * @throws IOException      if it fails to read or write the data from the segments
121      * @throws ImagingException if it fails to write the JFIF segments
122      */
123     public void updateXmpXml(final ByteSource byteSource, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException {
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 the JFIF segments
150      * @throws IOException      if it fails to read or write the data from the segments
151      * @throws ImagingException if it fails to write the JFIF segments
152      */
153     public void updateXmpXml(final File src, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException {
154         final ByteSource byteSource = ByteSource.file(src);
155         updateXmpXml(byteSource, os, xmpXml);
156     }
157 
158     /**
159      * Reads a JPEG image, replaces the XMP XML and writes the result to a stream.
160      *
161      * @param src    InputStream containing JPEG image data.
162      * @param os     OutputStream to write the image to.
163      * @param xmpXml String containing XMP XML.
164      * @throws ImagingException if it fails to read the JFIF segments
165      * @throws IOException      if it fails to read or write the data from the segments
166      * @throws ImagingException if it fails to write the JFIF segments
167      */
168     public void updateXmpXml(final InputStream src, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException {
169         final ByteSource byteSource = ByteSource.inputStream(src, null);
170         updateXmpXml(byteSource, os, xmpXml);
171     }
172 
173     private byte[] writeXmpSegment(final byte[] xmpXmlData, final int start, final int length) throws IOException {
174         final ByteArrayOutputStream os = new ByteArrayOutputStream();
175 
176         JpegConstants.XMP_IDENTIFIER.writeTo(os);
177         os.write(xmpXmlData, start, length);
178 
179         return os.toByteArray();
180     }
181 
182 }