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.iptc;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028
029import org.apache.commons.imaging.ImageReadException;
030import org.apache.commons.imaging.ImageWriteException;
031import org.apache.commons.imaging.common.bytesource.ByteSource;
032import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
033import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
034import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream;
035import org.apache.commons.imaging.formats.jpeg.JpegConstants;
036import org.apache.commons.imaging.formats.jpeg.xmp.JpegRewriter;
037
038/**
039 * Interface for Exif write/update/remove functionality for Jpeg/JFIF images.
040 */
041public class JpegIptcRewriter extends JpegRewriter {
042
043    /**
044     * Reads a Jpeg image, removes all IPTC data from the App13 segment but
045     * leaves the other data in that segment (if present) unchanged and writes
046     * the result to a stream.
047     * <p>
048     *
049     * @param src
050     *            Image file.
051     * @param os
052     *            OutputStream to write the image to.
053     *
054     * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
055     *         the Photoshop segment cannot be parsed
056     * @throws IOException if it fails to read from the origin byte source, or to write to the
057     *         target byte source
058     * @throws ImageWriteException if it fails to write the target image
059     * @see java.io.File
060     * @see java.io.OutputStream
061     */
062    public void removeIPTC(final File src, final OutputStream os)
063            throws ImageReadException, IOException, ImageWriteException {
064        removeIPTC(src, os, false);
065    }
066
067    /**
068     * Reads a Jpeg image, removes all IPTC data from the App13 segment but
069     * leaves the other data in that segment (if present) unchanged (unless
070     * removeSegment is true) and writes the result to a stream.
071     * <p>
072     *
073     * @param src
074     *            Image file.
075     * @param os
076     *            OutputStream to write the image to.
077     * @param removeSegment
078     *            Remove the App13 segment.
079     *
080     * @see java.io.File
081     * @see java.io.OutputStream
082     * @throws ImageReadException if there are more than one Photoshop App13 segment, or if
083     *         the Photoshop segment cannot be parsed
084     * @throws IOException if it fails to read from the origin byte source, or to write to the
085     *         target byte source
086     * @throws ImageWriteException if it fails to write the target image
087     */
088    public void removeIPTC(final File src, final OutputStream os, final boolean removeSegment)
089            throws ImageReadException, IOException, ImageWriteException {
090        final ByteSource byteSource = new ByteSourceFile(src);
091        removeIPTC(byteSource, os, removeSegment);
092    }
093
094    /**
095     * Reads a Jpeg image, removes all IPTC data from the App13 segment but
096     * leaves the other data in that segment (if present) unchanged and writes
097     * the result to a stream.
098     * <p>
099     *
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 Map<String, Object> params = new HashMap<>();
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 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 = new byte[0];
358            final 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}