    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.ImageReadException;
    028import org.apache.commons.imaging.ImageWriteException;
    029import org.apache.commons.imaging.ImagingConstants;
    030import org.apache.commons.imaging.common.bytesource.ByteSource;
    031import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
    032import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
    033import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream;
    034import org.apache.commons.imaging.formats.jpeg.JpegConstants;
    035import org.apache.commons.imaging.formats.jpeg.JpegImagingParameters;
    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 JpegImagingParameters params = new JpegImagingParameters();
    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 = ImagingConstants.EMPTY_BYTE_ARRAY;
    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}