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