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     * @see java.io.File
055     * @see java.io.OutputStream
056     */
057    public void removeIPTC(final File src, final OutputStream os)
058            throws ImageReadException, IOException, ImageWriteException {
059        removeIPTC(src, os, false);
060    }
061
062    /**
063     * Reads a Jpeg image, removes all IPTC data from the App13 segment but
064     * leaves the other data in that segment (if present) unchanged (unless
065     * removeSegment is true) and writes the result to a stream.
066     * <p>
067     *
068     * @param src
069     *            Image file.
070     * @param os
071     *            OutputStream to write the image to.
072     * @param removeSegment
073     *            Remove the App13 segment.
074     *
075     * @see java.io.File
076     * @see java.io.OutputStream
077     */
078    public void removeIPTC(final File src, final OutputStream os, final boolean removeSegment)
079            throws ImageReadException, IOException, ImageWriteException {
080        final ByteSource byteSource = new ByteSourceFile(src);
081        removeIPTC(byteSource, os, removeSegment);
082    }
083
084    /**
085     * Reads a Jpeg image, removes all IPTC data from the App13 segment but
086     * leaves the other data in that segment (if present) unchanged and writes
087     * the result to a stream.
088     * <p>
089     *
090     * @param src
091     *            Byte array containing Jpeg image data.
092     * @param os
093     *            OutputStream to write the image to.
094     */
095    public void removeIPTC(final byte[] src, final OutputStream os)
096            throws ImageReadException, IOException, ImageWriteException {
097        removeIPTC(src, os, false);
098    }
099
100    /**
101     * Reads a Jpeg image, removes all IPTC data from the App13 segment but
102     * leaves the other data in that segment (if present) unchanged (unless
103     * removeSegment is true) and writes the result to a stream.
104     * <p>
105     *
106     * @param src
107     *            Byte array containing Jpeg image data.
108     * @param os
109     *            OutputStream to write the image to.
110     * @param removeSegment
111     *            Remove the App13 segment.
112     */
113    public void removeIPTC(final byte[] src, final OutputStream os, final boolean removeSegment)
114            throws ImageReadException, IOException, ImageWriteException {
115        final ByteSource byteSource = new ByteSourceArray(src);
116        removeIPTC(byteSource, os, removeSegment);
117    }
118
119    /**
120     * Reads a Jpeg image, removes all IPTC data from the App13 segment but
121     * leaves the other data in that segment (if present) unchanged and writes
122     * the result to a stream.
123     * <p>
124     *
125     * @param src
126     *            InputStream containing Jpeg image data.
127     * @param os
128     *            OutputStream to write the image to.
129     */
130    public void removeIPTC(final InputStream src, final OutputStream os)
131            throws ImageReadException, IOException, ImageWriteException {
132        removeIPTC(src, os, false);
133    }
134
135    /**
136     * Reads a Jpeg image, removes all IPTC data from the App13 segment but
137     * leaves the other data in that segment (if present) unchanged (unless
138     * removeSegment is true) and writes the result to a stream.
139     * <p>
140     *
141     * @param src
142     *            InputStream containing Jpeg image data.
143     * @param os
144     *            OutputStream to write the image to.
145     * @param removeSegment
146     *            Remove the App13 segment.
147     */
148    public void removeIPTC(final InputStream src, final OutputStream os, final boolean removeSegment)
149            throws ImageReadException, IOException, ImageWriteException {
150        final ByteSource byteSource = new ByteSourceInputStream(src, null);
151        removeIPTC(byteSource, os, removeSegment);
152    }
153
154    /**
155     * Reads a Jpeg image, removes all IPTC data from the App13 segment but
156     * leaves the other data in that segment (if present) unchanged and writes
157     * the result to a stream.
158     * <p>
159     *
160     * @param byteSource
161     *            ByteSource containing Jpeg image data.
162     * @param os
163     *            OutputStream to write the image to.
164     */
165    public void removeIPTC(final ByteSource byteSource, final OutputStream os)
166            throws ImageReadException, IOException, ImageWriteException {
167        removeIPTC(byteSource, os, false);
168    }
169
170    /**
171     * Reads a Jpeg image, removes all IPTC data from the App13 segment but
172     * leaves the other data in that segment (if present) unchanged (unless
173     * removeSegment is true) and writes the result to a stream.
174     * <p>
175     *
176     * @param byteSource
177     *            ByteSource containing Jpeg image data.
178     * @param os
179     *            OutputStream to write the image to.
180     * @param removeSegment
181     *            Remove the App13 segment.
182     */
183    public void removeIPTC(final ByteSource byteSource, final OutputStream os, final boolean removeSegment)
184            throws ImageReadException, IOException, ImageWriteException {
185        final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
186        final List<JFIFPiece> oldPieces = jfifPieces.pieces;
187        final List<JFIFPiece> photoshopApp13Segments = findPhotoshopApp13Segments(oldPieces);
188
189        if (photoshopApp13Segments.size() > 1) {
190            throw new ImageReadException(
191                    "Image contains more than one Photoshop App13 segment.");
192        }
193        final List<JFIFPiece> newPieces = removePhotoshopApp13Segments(oldPieces);
194        if (!removeSegment && photoshopApp13Segments.size() == 1) {
195            final JFIFPieceSegment oldSegment = (JFIFPieceSegment) photoshopApp13Segments.get(0);
196            final Map<String, Object> params = new HashMap<>();
197            final PhotoshopApp13Data oldData = new IptcParser().parsePhotoshopSegment(oldSegment.getSegmentData(), params);
198            final List<IptcBlock> newBlocks = oldData.getNonIptcBlocks();
199            final List<IptcRecord> newRecords = new ArrayList<>();
200            final PhotoshopApp13Data newData = new PhotoshopApp13Data(newRecords,
201                    newBlocks);
202            final byte[] segmentBytes = new IptcParser().writePhotoshopApp13Segment(newData);
203            final JFIFPieceSegment newSegment = new JFIFPieceSegment(
204                    oldSegment.marker, segmentBytes);
205            newPieces.add(oldPieces.indexOf(oldSegment), newSegment);
206        }
207        writeSegments(os, newPieces);
208    }
209
210    /**
211     * Reads a Jpeg image, replaces the IPTC data in the App13 segment but
212     * leaves the other data in that segment (if present) unchanged and writes
213     * the result to a stream.
214     *
215     * @param src
216     *            Byte array containing Jpeg image data.
217     * @param os
218     *            OutputStream to write the image to.
219     * @param newData
220     *            structure containing IPTC data.
221     */
222    public void writeIPTC(final byte[] src, final OutputStream os,
223            final PhotoshopApp13Data newData) throws ImageReadException, IOException,
224            ImageWriteException {
225        final ByteSource byteSource = new ByteSourceArray(src);
226        writeIPTC(byteSource, os, newData);
227    }
228
229    /**
230     * Reads a Jpeg image, replaces the IPTC data in the App13 segment but
231     * leaves the other data in that segment (if present) unchanged and writes
232     * the result to a stream.
233     *
234     * @param src
235     *            InputStream containing Jpeg image data.
236     * @param os
237     *            OutputStream to write the image to.
238     * @param newData
239     *            structure containing IPTC data.
240     */
241    public void writeIPTC(final InputStream src, final OutputStream os,
242            final PhotoshopApp13Data newData) throws ImageReadException, IOException,
243            ImageWriteException {
244        final ByteSource byteSource = new ByteSourceInputStream(src, null);
245        writeIPTC(byteSource, os, newData);
246    }
247
248    /**
249     * Reads a Jpeg image, replaces the IPTC data in the App13 segment but
250     * leaves the other data in that segment (if present) unchanged and writes
251     * the result to a stream.
252     *
253     * @param src
254     *            Image file.
255     * @param os
256     *            OutputStream to write the image to.
257     * @param newData
258     *            structure containing IPTC data.
259     */
260    public void writeIPTC(final File src, final OutputStream os, final PhotoshopApp13Data newData)
261            throws ImageReadException, IOException, ImageWriteException {
262        final ByteSource byteSource = new ByteSourceFile(src);
263        writeIPTC(byteSource, os, newData);
264    }
265
266    /**
267     * Reads a Jpeg image, replaces the IPTC data in the App13 segment but
268     * leaves the other data in that segment (if present) unchanged and writes
269     * the result to a stream.
270     *
271     * @param byteSource
272     *            ByteSource containing Jpeg image data.
273     * @param os
274     *            OutputStream to write the image to.
275     * @param newData
276     *            structure containing IPTC data.
277     */
278    public void writeIPTC(final ByteSource byteSource, final OutputStream os,
279            PhotoshopApp13Data newData) throws ImageReadException, IOException,
280            ImageWriteException {
281        final JFIFPieces jfifPieces = analyzeJFIF(byteSource);
282        final List<JFIFPiece> oldPieces = jfifPieces.pieces;
283        final List<JFIFPiece> photoshopApp13Segments = findPhotoshopApp13Segments(oldPieces);
284
285        if (photoshopApp13Segments.size() > 1) {
286            throw new ImageReadException(
287                    "Image contains more than one Photoshop App13 segment.");
288        }
289        List<JFIFPiece> newPieces = removePhotoshopApp13Segments(oldPieces);
290
291        {
292            // discard old iptc blocks.
293            final List<IptcBlock> newBlocks = newData.getNonIptcBlocks();
294            final byte[] newBlockBytes = new IptcParser().writeIPTCBlock(newData.getRecords());
295
296            final int blockType = IptcConstants.IMAGE_RESOURCE_BLOCK_IPTC_DATA;
297            final byte[] blockNameBytes = new byte[0];
298            final IptcBlock newBlock = new IptcBlock(blockType, blockNameBytes, newBlockBytes);
299            newBlocks.add(newBlock);
300
301            newData = new PhotoshopApp13Data(newData.getRecords(), newBlocks);
302
303            final byte[] segmentBytes = new IptcParser().writePhotoshopApp13Segment(newData);
304            final JFIFPieceSegment newSegment = new JFIFPieceSegment(JpegConstants.JPEG_APP13_MARKER, segmentBytes);
305
306            newPieces = insertAfterLastAppSegments(newPieces, Arrays.asList(newSegment));
307        }
308
309        writeSegments(os, newPieces);
310    }
311
312}