    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}