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.List;
026
027import org.apache.commons.imaging.ImagingConstants;
028import org.apache.commons.imaging.ImagingException;
029import org.apache.commons.imaging.bytesource.ByteSource;
030import org.apache.commons.imaging.formats.jpeg.JpegConstants;
031import org.apache.commons.imaging.formats.jpeg.JpegImagingParameters;
032import org.apache.commons.imaging.formats.jpeg.xmp.JpegRewriter;
033
034/**
035 * Interface for Exif write/update/remove functionality for Jpeg/JFIF images.
036 */
037public class JpegIptcRewriter extends JpegRewriter {
038
039    /**
040     * Constructs a new instance with the default, big-endian, byte order.
041     */
042    public JpegIptcRewriter() {
043        // empty
044    }
045
046    /**
047     * Reads a JPEG image, removes all IPTC data from the App13 segment but leaves the other data in that segment (if present) unchanged and writes the result
048     * to a stream.
049     *
050     * @param src Byte array containing JPEG image data.
051     * @param os  OutputStream to write the image to.
052     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
053     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
054     * @throws ImagingException if it fails to write the target image
055     */
056    public void removeIptc(final byte[] src, final OutputStream os) throws ImagingException, IOException, ImagingException {
057        removeIptc(src, os, false);
058    }
059
060    /**
061     * Reads a JPEG image, removes all IPTC data from the App13 segment but leaves the other data in that segment (if present) unchanged (unless removeSegment
062     * is true) and writes the result to a stream.
063     *
064     * @param src           Byte array containing JPEG image data.
065     * @param os            OutputStream to write the image to.
066     * @param removeSegment Remove the App13 segment.
067     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
068     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
069     * @throws ImagingException if it fails to write the target image
070     */
071    public void removeIptc(final byte[] src, final OutputStream os, final boolean removeSegment) throws ImagingException, IOException, ImagingException {
072        final ByteSource byteSource = ByteSource.array(src);
073        removeIptc(byteSource, os, removeSegment);
074    }
075
076    /**
077     * Reads a JPEG image, removes all IPTC data from the App13 segment but leaves the other data in that segment (if present) unchanged and writes the result
078     * to a stream.
079     *
080     * @param byteSource ByteSource containing JPEG image data.
081     * @param os         OutputStream to write the image to.
082     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
083     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
084     * @throws ImagingException if it fails to write the target image
085     */
086    public void removeIptc(final ByteSource byteSource, final OutputStream os) throws ImagingException, IOException, ImagingException {
087        removeIptc(byteSource, os, false);
088    }
089
090    /**
091     * Reads a JPEG image, removes all IPTC data from the App13 segment but leaves the other data in that segment (if present) unchanged (unless removeSegment
092     * is true) and writes the result to a stream.
093     *
094     * @param byteSource    ByteSource containing JPEG image data.
095     * @param os            OutputStream to write the image to.
096     * @param removeSegment Remove the App13 segment.
097     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
098     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
099     * @throws ImagingException if it fails to write the target image
100     */
101    public void removeIptc(final ByteSource byteSource, final OutputStream os, final boolean removeSegment)
102            throws ImagingException, IOException, ImagingException {
103        final JFIFPieces jfifPieces = analyzeJfif(byteSource);
104        final List<JFIFPiece> oldPieces = jfifPieces.pieces;
105        final List<JFIFPiece> photoshopApp13Segments = findPhotoshopApp13Segments(oldPieces);
106
107        if (photoshopApp13Segments.size() > 1) {
108            throw new ImagingException("Image contains more than one Photoshop App13 segment.");
109        }
110        final List<JFIFPiece> newPieces = removePhotoshopApp13Segments(oldPieces);
111        if (!removeSegment && photoshopApp13Segments.size() == 1) {
112            final JFIFPieceSegment oldSegment = (JFIFPieceSegment) photoshopApp13Segments.get(0);
113            final JpegImagingParameters params = new JpegImagingParameters();
114            final PhotoshopApp13Data oldData = new IptcParser().parsePhotoshopSegment(oldSegment.getSegmentData(), params);
115            final List<IptcBlock> newBlocks = oldData.getNonIptcBlocks();
116            final List<IptcRecord> newRecords = new ArrayList<>();
117            final PhotoshopApp13Data newData = new PhotoshopApp13Data(newRecords, newBlocks);
118            final byte[] segmentBytes = new IptcParser().writePhotoshopApp13Segment(newData);
119            final JFIFPieceSegment newSegment = new JFIFPieceSegment(oldSegment.marker, segmentBytes);
120            newPieces.add(oldPieces.indexOf(oldSegment), newSegment);
121        }
122        writeSegments(os, newPieces);
123    }
124
125    /**
126     * Reads a JPEG image, removes all IPTC data from the App13 segment but leaves the other data in that segment (if present) unchanged and writes the result
127     * to a stream.
128     *
129     * @param src Image file.
130     * @param os  OutputStream to write the image to.
131     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
132     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
133     * @throws ImagingException if it fails to write the target image
134     * @see java.io.File
135     * @see java.io.OutputStream
136     */
137    public void removeIptc(final File src, final OutputStream os) throws ImagingException, IOException, ImagingException {
138        removeIptc(src, os, false);
139    }
140
141    /**
142     * Reads a JPEG image, removes all IPTC data from the App13 segment but leaves the other data in that segment (if present) unchanged (unless removeSegment
143     * is true) and writes the result to a stream.
144     *
145     * @param src           Image file.
146     * @param os            OutputStream to write the image to.
147     * @param removeSegment Remove the App13 segment.
148     * @see java.io.File
149     * @see java.io.OutputStream
150     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
151     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
152     * @throws ImagingException if it fails to write the target image
153     */
154    public void removeIptc(final File src, final OutputStream os, final boolean removeSegment) throws ImagingException, IOException, ImagingException {
155        final ByteSource byteSource = ByteSource.file(src);
156        removeIptc(byteSource, os, removeSegment);
157    }
158
159    /**
160     * Reads a JPEG image, removes all IPTC data from the App13 segment but leaves the other data in that segment (if present) unchanged and writes the result
161     * to a stream.
162     *
163     * @param src InputStream containing JPEG image data.
164     * @param os  OutputStream to write the image to.
165     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
166     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
167     * @throws ImagingException if it fails to write the target image
168     */
169    public void removeIptc(final InputStream src, final OutputStream os) throws ImagingException, IOException, ImagingException {
170        removeIptc(src, os, false);
171    }
172
173    /**
174     * Reads a JPEG image, removes all IPTC data from the App13 segment but leaves the other data in that segment (if present) unchanged (unless removeSegment
175     * is true) and writes the result to a stream.
176     *
177     * @param src           InputStream containing JPEG image data.
178     * @param os            OutputStream to write the image to.
179     * @param removeSegment Remove the App13 segment.
180     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
181     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
182     * @throws ImagingException if it fails to write the target image
183     */
184    public void removeIptc(final InputStream src, final OutputStream os, final boolean removeSegment) throws ImagingException, IOException, ImagingException {
185        final ByteSource byteSource = ByteSource.inputStream(src, null);
186        removeIptc(byteSource, os, removeSegment);
187    }
188
189    /**
190     * Reads a JPEG image, replaces the IPTC data in the App13 segment but leaves the other data in that segment (if present) unchanged and writes the result to
191     * a stream.
192     *
193     * @param src     Byte array containing JPEG image data.
194     * @param os      OutputStream to write the image to.
195     * @param newData structure containing IPTC data.
196     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
197     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
198     * @throws ImagingException if it fails to write the target image
199     */
200    public void writeIptc(final byte[] src, final OutputStream os, final PhotoshopApp13Data newData) throws ImagingException, IOException, ImagingException {
201        final ByteSource byteSource = ByteSource.array(src);
202        writeIptc(byteSource, os, newData);
203    }
204
205    /**
206     * Reads a JPEG image, replaces the IPTC data in the App13 segment but leaves the other data in that segment (if present) unchanged and writes the result to
207     * a stream.
208     *
209     * @param byteSource ByteSource containing JPEG image data.
210     * @param os         OutputStream to write the image to.
211     * @param newData    structure containing IPTC data.
212     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
213     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
214     * @throws ImagingException if it fails to write the target image
215     */
216    public void writeIptc(final ByteSource byteSource, final OutputStream os, PhotoshopApp13Data newData)
217            throws ImagingException, IOException, ImagingException {
218        final JFIFPieces jfifPieces = analyzeJfif(byteSource);
219        final List<JFIFPiece> oldPieces = jfifPieces.pieces;
220        final List<JFIFPiece> photoshopApp13Segments = findPhotoshopApp13Segments(oldPieces);
221
222        if (photoshopApp13Segments.size() > 1) {
223            throw new ImagingException("Image contains more than one Photoshop App13 segment.");
224        }
225        List<JFIFPiece> newPieces = removePhotoshopApp13Segments(oldPieces);
226
227        {
228            // discard old iptc blocks.
229            final List<IptcBlock> newBlocks = newData.getNonIptcBlocks();
230            final byte[] newBlockBytes = new IptcParser().writeIptcBlock(newData.getRecords(), newData.isForceUtf8Encoding());
231
232            final int blockType = IptcConstants.IMAGE_RESOURCE_BLOCK_IPTC_DATA;
233            final byte[] blockNameBytes = ImagingConstants.EMPTY_BYTE_ARRAY;
234            final IptcBlock newBlock = new IptcBlock(blockType, blockNameBytes, newBlockBytes);
235            newBlocks.add(newBlock);
236
237            newData = new PhotoshopApp13Data(newData.getRecords(), newBlocks, newData.isForceUtf8Encoding());
238
239            final byte[] segmentBytes = new IptcParser().writePhotoshopApp13Segment(newData);
240            final JFIFPieceSegment newSegment = new JFIFPieceSegment(JpegConstants.JPEG_APP13_MARKER, segmentBytes);
241
242            newPieces = insertAfterLastAppSegments(newPieces, Arrays.asList(newSegment));
243        }
244
245        writeSegments(os, newPieces);
246    }
247
248    /**
249     * Reads a JPEG image, replaces the IPTC data in the App13 segment but leaves the other data in that segment (if present) unchanged and writes the result to
250     * a stream.
251     *
252     * @param src     Image file.
253     * @param os      OutputStream to write the image to.
254     * @param newData structure containing IPTC data.
255     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
256     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
257     * @throws ImagingException if it fails to write the target image
258     */
259    public void writeIptc(final File src, final OutputStream os, final PhotoshopApp13Data newData) throws ImagingException, IOException, ImagingException {
260        final ByteSource byteSource = ByteSource.file(src);
261        writeIptc(byteSource, os, newData);
262    }
263
264    /**
265     * Reads a JPEG image, replaces the IPTC data in the App13 segment but leaves the other data in that segment (if present) unchanged and writes the result to
266     * a stream.
267     *
268     * @param src     InputStream containing JPEG image data.
269     * @param os      OutputStream to write the image to.
270     * @param newData structure containing IPTC data.
271     * @throws ImagingException if there are more than one Photoshop App13 segment, or if the Photoshop segment cannot be parsed
272     * @throws IOException      if it fails to read from the origin byte source, or to write to the target byte source
273     * @throws ImagingException if it fails to write the target image
274     */
275    public void writeIptc(final InputStream src, final OutputStream os, final PhotoshopApp13Data newData)
276            throws ImagingException, IOException, ImagingException {
277        final ByteSource byteSource = ByteSource.inputStream(src, null);
278        writeIptc(byteSource, os, newData);
279    }
280
281}