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.iptc;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.List;
26  
27  import org.apache.commons.imaging.ImageReadException;
28  import org.apache.commons.imaging.ImageWriteException;
29  import org.apache.commons.imaging.ImagingConstants;
30  import org.apache.commons.imaging.common.bytesource.ByteSource;
31  import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
32  import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
33  import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream;
34  import org.apache.commons.imaging.formats.jpeg.JpegConstants;
35  import org.apache.commons.imaging.formats.jpeg.JpegImagingParameters;
36  import org.apache.commons.imaging.formats.jpeg.xmp.JpegRewriter;
37  
38  /**
39   * Interface for Exif write/update/remove functionality for Jpeg/JFIF images.
40   */
41  public class JpegIptcRewriter extends JpegRewriter {
42  
43      /**
44       * Reads a Jpeg image, removes all IPTC data from the App13 segment but
45       * leaves the other data in that segment (if present) unchanged and writes
46       * the result to a stream.
47       * <p>
48       *
49       * @param src
50       *            Image file.
51       * @param os
52       *            OutputStream to write the image to.
53       *
54       * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
55       *         the Photoshop segment cannot be parsed
56       * @throws IOException if it fails to read from the origin byte source, or to write to the
57       *         target byte source
58       * @throws ImageWriteException if it fails to write the target image
59       * @see java.io.File
60       * @see java.io.OutputStream
61       */
62      public void removeIPTC(final File src, final OutputStream os)
63              throws ImageReadException, IOException, ImageWriteException {
64          removeIPTC(src, os, false);
65      }
66  
67      /**
68       * Reads a Jpeg image, removes all IPTC data from the App13 segment but
69       * leaves the other data in that segment (if present) unchanged (unless
70       * removeSegment is true) and writes the result to a stream.
71       * <p>
72       *
73       * @param src
74       *            Image file.
75       * @param os
76       *            OutputStream to write the image to.
77       * @param removeSegment
78       *            Remove the App13 segment.
79       *
80       * @see java.io.File
81       * @see java.io.OutputStream
82       * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
83       *         the Photoshop segment cannot be parsed
84       * @throws IOException if it fails to read from the origin byte source, or to write to the
85       *         target byte source
86       * @throws ImageWriteException if it fails to write the target image
87       */
88      public void removeIPTC(final File src, final OutputStream os, final boolean removeSegment)
89              throws ImageReadException, IOException, ImageWriteException {
90          final ByteSource byteSource = new ByteSourceFile(src);
91          removeIPTC(byteSource, os, removeSegment);
92      }
93  
94      /**
95       * Reads a Jpeg image, removes all IPTC data from the App13 segment but
96       * leaves the other data in that segment (if present) unchanged and writes
97       * the result to a stream.
98       * <p>
99       *
100      * @param src
101      *            Byte array containing Jpeg image data.
102      * @param os
103      *            OutputStream to write the image to.
104      * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
105      *         the Photoshop segment cannot be parsed
106      * @throws IOException if it fails to read from the origin byte source, or to write to the
107      *         target byte source
108      * @throws ImageWriteException if it fails to write the target image
109      */
110     public void removeIPTC(final byte[] src, final OutputStream os)
111             throws ImageReadException, IOException, ImageWriteException {
112         removeIPTC(src, os, false);
113     }
114 
115     /**
116      * Reads a Jpeg image, removes all IPTC data from the App13 segment but
117      * leaves the other data in that segment (if present) unchanged (unless
118      * removeSegment is true) and writes the result to a stream.
119      * <p>
120      *
121      * @param src
122      *            Byte array containing Jpeg image data.
123      * @param os
124      *            OutputStream to write the image to.
125      * @param removeSegment
126      *            Remove the App13 segment.
127      * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
128      *         the Photoshop segment cannot be parsed
129      * @throws IOException if it fails to read from the origin byte source, or to write to the
130      *         target byte source
131      * @throws ImageWriteException if it fails to write the target image
132      */
133     public void removeIPTC(final byte[] src, final OutputStream os, final boolean removeSegment)
134             throws ImageReadException, IOException, ImageWriteException {
135         final ByteSource byteSource = new ByteSourceArray(src);
136         removeIPTC(byteSource, os, removeSegment);
137     }
138 
139     /**
140      * Reads a Jpeg image, removes all IPTC data from the App13 segment but
141      * leaves the other data in that segment (if present) unchanged and writes
142      * the result to a stream.
143      * <p>
144      *
145      * @param src
146      *            InputStream containing Jpeg image data.
147      * @param os
148      *            OutputStream to write the image to.
149      * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
150      *         the Photoshop segment cannot be parsed
151      * @throws IOException if it fails to read from the origin byte source, or to write to the
152      *         target byte source
153      * @throws ImageWriteException if it fails to write the target image
154      */
155     public void removeIPTC(final InputStream src, final OutputStream os)
156             throws ImageReadException, IOException, ImageWriteException {
157         removeIPTC(src, os, false);
158     }
159 
160     /**
161      * Reads a Jpeg image, removes all IPTC data from the App13 segment but
162      * leaves the other data in that segment (if present) unchanged (unless
163      * removeSegment is true) and writes the result to a stream.
164      * <p>
165      *
166      * @param src
167      *            InputStream containing Jpeg image data.
168      * @param os
169      *            OutputStream to write the image to.
170      * @param removeSegment
171      *            Remove the App13 segment.
172      * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
173      *         the Photoshop segment cannot be parsed
174      * @throws IOException if it fails to read from the origin byte source, or to write to the
175      *         target byte source
176      * @throws ImageWriteException if it fails to write the target image
177      */
178     public void removeIPTC(final InputStream src, final OutputStream os, final boolean removeSegment)
179             throws ImageReadException, IOException, ImageWriteException {
180         final ByteSource byteSource = new ByteSourceInputStream(src, null);
181         removeIPTC(byteSource, os, removeSegment);
182     }
183 
184     /**
185      * Reads a Jpeg image, removes all IPTC data from the App13 segment but
186      * leaves the other data in that segment (if present) unchanged and writes
187      * the result to a stream.
188      * <p>
189      *
190      * @param byteSource
191      *            ByteSource containing Jpeg image data.
192      * @param os
193      *            OutputStream to write the image to.
194      * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
195      *         the Photoshop segment cannot be parsed
196      * @throws IOException if it fails to read from the origin byte source, or to write to the
197      *         target byte source
198      * @throws ImageWriteException if it fails to write the target image
199      */
200     public void removeIPTC(final ByteSource byteSource, final OutputStream os)
201             throws ImageReadException, IOException, ImageWriteException {
202         removeIPTC(byteSource, os, false);
203     }
204 
205     /**
206      * Reads a Jpeg image, removes all IPTC data from the App13 segment but
207      * leaves the other data in that segment (if present) unchanged (unless
208      * removeSegment is true) and writes the result to a stream.
209      * <p>
210      *
211      * @param byteSource
212      *            ByteSource containing Jpeg image data.
213      * @param os
214      *            OutputStream to write the image to.
215      * @param removeSegment
216      *            Remove the App13 segment.
217      * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
218      *         the Photoshop segment cannot be parsed
219      * @throws IOException if it fails to read from the origin byte source, or to write to the
220      *         target byte source
221      * @throws ImageWriteException if it fails to write the target image
222      */
223     public void removeIPTC(final ByteSource byteSource, final OutputStream os, final boolean removeSegment)
224             throws ImageReadException, IOException, ImageWriteException {
225         final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
226         final List<JFIFPiece> oldPieces = jfifPieces.pieces;
227         final List<JFIFPiece> photoshopApp13Segments = findPhotoshopApp13Segments(oldPieces);
228 
229         if (photoshopApp13Segments.size() > 1) {
230             throw new ImageReadException(
231                     "Image contains more than one Photoshop App13 segment.");
232         }
233         final List<JFIFPiece> newPieces = removePhotoshopApp13Segments(oldPieces);
234         if (!removeSegment && photoshopApp13Segments.size() == 1) {
235             final JFIFPieceSegment oldSegment = (JFIFPieceSegment) photoshopApp13Segments.get(0);
236             final JpegImagingParameters/jpeg/JpegImagingParameters.html#JpegImagingParameters">JpegImagingParameters params = new JpegImagingParameters();
237             final PhotoshopApp13Data oldData = new IptcParser().parsePhotoshopSegment(oldSegment.getSegmentData(), params);
238             final List<IptcBlock> newBlocks = oldData.getNonIptcBlocks();
239             final List<IptcRecord> newRecords = new ArrayList<>();
240             final PhotoshopApp13Datats/jpeg/iptc/PhotoshopApp13Data.html#PhotoshopApp13Data">PhotoshopApp13Data newData = new PhotoshopApp13Data(newRecords,
241                     newBlocks);
242             final byte[] segmentBytes = new IptcParser().writePhotoshopApp13Segment(newData);
243             final JFIFPieceSegment newSegment = new JFIFPieceSegment(
244                     oldSegment.marker, segmentBytes);
245             newPieces.add(oldPieces.indexOf(oldSegment), newSegment);
246         }
247         writeSegments(os, newPieces);
248     }
249 
250     /**
251      * Reads a Jpeg image, replaces the IPTC data in the App13 segment but
252      * leaves the other data in that segment (if present) unchanged and writes
253      * the result to a stream.
254      *
255      * @param src
256      *            Byte array containing Jpeg image data.
257      * @param os
258      *            OutputStream to write the image to.
259      * @param newData
260      *            structure containing IPTC data.
261      * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
262      *         the Photoshop segment cannot be parsed
263      * @throws IOException if it fails to read from the origin byte source, or to write to the
264      *         target byte source
265      * @throws ImageWriteException if it fails to write the target image
266      */
267     public void writeIPTC(final byte[] src, final OutputStream os,
268             final PhotoshopApp13Data newData) throws ImageReadException, IOException,
269             ImageWriteException {
270         final ByteSource byteSource = new ByteSourceArray(src);
271         writeIPTC(byteSource, os, newData);
272     }
273 
274     /**
275      * Reads a Jpeg image, replaces the IPTC data in the App13 segment but
276      * leaves the other data in that segment (if present) unchanged and writes
277      * the result to a stream.
278      *
279      * @param src
280      *            InputStream containing Jpeg image data.
281      * @param os
282      *            OutputStream to write the image to.
283      * @param newData
284      *            structure containing IPTC data.
285      * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
286      *         the Photoshop segment cannot be parsed
287      * @throws IOException if it fails to read from the origin byte source, or to write to the
288      *         target byte source
289      * @throws ImageWriteException if it fails to write the target image
290      */
291     public void writeIPTC(final InputStream src, final OutputStream os,
292             final PhotoshopApp13Data newData) throws ImageReadException, IOException,
293             ImageWriteException {
294         final ByteSource byteSource = new ByteSourceInputStream(src, null);
295         writeIPTC(byteSource, os, newData);
296     }
297 
298     /**
299      * Reads a Jpeg image, replaces the IPTC data in the App13 segment but
300      * leaves the other data in that segment (if present) unchanged and writes
301      * the result to a stream.
302      *
303      * @param src
304      *            Image file.
305      * @param os
306      *            OutputStream to write the image to.
307      * @param newData
308      *            structure containing IPTC data.
309      * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
310      *         the Photoshop segment cannot be parsed
311      * @throws IOException if it fails to read from the origin byte source, or to write to the
312      *         target byte source
313      * @throws ImageWriteException if it fails to write the target image
314      */
315     public void writeIPTC(final File src, final OutputStream os, final PhotoshopApp13Data newData)
316             throws ImageReadException, IOException, ImageWriteException {
317         final ByteSource byteSource = new ByteSourceFile(src);
318         writeIPTC(byteSource, os, newData);
319     }
320 
321     /**
322      * Reads a Jpeg image, replaces the IPTC data in the App13 segment but
323      * leaves the other data in that segment (if present) unchanged and writes
324      * the result to a stream.
325      *
326      * @param byteSource
327      *            ByteSource containing Jpeg image data.
328      * @param os
329      *            OutputStream to write the image to.
330      * @param newData
331      *            structure containing IPTC data.
332      * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
333      *         the Photoshop segment cannot be parsed
334      * @throws IOException if it fails to read from the origin byte source, or to write to the
335      *         target byte source
336      * @throws ImageWriteException if it fails to write the target image
337      */
338     public void writeIPTC(final ByteSource byteSource, final OutputStream os,
339             PhotoshopApp13Data newData) throws ImageReadException, IOException,
340             ImageWriteException {
341         final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
342         final List<JFIFPiece> oldPieces = jfifPieces.pieces;
343         final List<JFIFPiece> photoshopApp13Segments = findPhotoshopApp13Segments(oldPieces);
344 
345         if (photoshopApp13Segments.size() > 1) {
346             throw new ImageReadException(
347                     "Image contains more than one Photoshop App13 segment.");
348         }
349         List<JFIFPiece> newPieces = removePhotoshopApp13Segments(oldPieces);
350 
351         {
352             // discard old iptc blocks.
353             final List<IptcBlock> newBlocks = newData.getNonIptcBlocks();
354             final byte[] newBlockBytes = new IptcParser().writeIPTCBlock(newData.getRecords());
355 
356             final int blockType = IptcConstants.IMAGE_RESOURCE_BLOCK_IPTC_DATA;
357             final byte[] blockNameBytes = ImagingConstants.EMPTY_BYTE_ARRAY;
358             final IptcBlockng/formats/jpeg/iptc/IptcBlock.html#IptcBlock">IptcBlock newBlock = new IptcBlock(blockType, blockNameBytes, newBlockBytes);
359             newBlocks.add(newBlock);
360 
361             newData = new PhotoshopApp13Data(newData.getRecords(), newBlocks);
362 
363             final byte[] segmentBytes = new IptcParser().writePhotoshopApp13Segment(newData);
364             final JFIFPieceSegment newSegment = new JFIFPieceSegment(JpegConstants.JPEG_APP13_MARKER, segmentBytes);
365 
366             newPieces = insertAfterLastAppSegments(newPieces, Arrays.asList(newSegment));
367         }
368 
369         writeSegments(os, newPieces);
370     }
371 
372 }