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.xmp; 018 019import java.io.ByteArrayOutputStream; 020import java.io.File; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.nio.charset.StandardCharsets; 025import java.util.ArrayList; 026import java.util.List; 027 028import org.apache.commons.imaging.ImagingException; 029import org.apache.commons.imaging.bytesource.ByteSource; 030import org.apache.commons.imaging.formats.jpeg.JpegConstants; 031 032/** 033 * Interface for Exif write/update/remove functionality for Jpeg/JFIF images. 034 */ 035public class JpegXmpRewriter extends JpegRewriter { 036 037 /** 038 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 039 * <p> 040 * 041 * @param src Byte array containing JPEG image data. 042 * @param os OutputStream to write the image to. 043 * @throws ImagingException if it fails to read the JFIF segments 044 * @throws IOException if it fails to read or write the data from the segments 045 */ 046 public void removeXmpXml(final byte[] src, final OutputStream os) throws ImagingException, IOException { 047 final ByteSource byteSource = ByteSource.array(src); 048 removeXmpXml(byteSource, os); 049 } 050 051 /** 052 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 053 * <p> 054 * 055 * @param byteSource ByteSource containing JPEG image data. 056 * @param os OutputStream to write the image to. 057 * @throws ImagingException if it fails to read the JFIF segments 058 * @throws IOException if it fails to read or write the data from the segments 059 */ 060 public void removeXmpXml(final ByteSource byteSource, final OutputStream os) throws ImagingException, IOException { 061 final JFIFPieces jfifPieces = analyzeJfif(byteSource); 062 List<JFIFPiece> pieces = jfifPieces.pieces; 063 pieces = removeXmpSegments(pieces); 064 writeSegments(os, pieces); 065 } 066 067 /** 068 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 069 * <p> 070 * 071 * @param src Image file. 072 * @param os OutputStream to write the image to. 073 * 074 * @see java.io.File 075 * @see java.io.OutputStream 076 * @throws ImagingException if it fails to read the JFIF segments 077 * @throws IOException if it fails to read or write the data from the segments 078 */ 079 public void removeXmpXml(final File src, final OutputStream os) throws ImagingException, IOException { 080 final ByteSource byteSource = ByteSource.file(src); 081 removeXmpXml(byteSource, os); 082 } 083 084 /** 085 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 086 * <p> 087 * 088 * @param src InputStream containing JPEG image data. 089 * @param os OutputStream to write the image to. 090 * @throws ImagingException if it fails to read the JFIF segments 091 * @throws IOException if it fails to read or write the data from the segments 092 */ 093 public void removeXmpXml(final InputStream src, final OutputStream os) throws ImagingException, IOException { 094 final ByteSource byteSource = ByteSource.inputStream(src, null); 095 removeXmpXml(byteSource, os); 096 } 097 098 /** 099 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 100 * 101 * @param src Byte array containing JPEG image data. 102 * @param os OutputStream to write the image to. 103 * @param xmpXml String containing XMP XML. 104 * @throws ImagingException if it fails to read the JFIF segments 105 * @throws IOException if it fails to read or write the data from the segments 106 * @throws ImagingException if it fails to write the JFIF segments 107 */ 108 public void updateXmpXml(final byte[] src, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException { 109 final ByteSource byteSource = ByteSource.array(src); 110 updateXmpXml(byteSource, os, xmpXml); 111 } 112 113 /** 114 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 115 * 116 * @param byteSource ByteSource containing JPEG image data. 117 * @param os OutputStream to write the image to. 118 * @param xmpXml String containing XMP XML. 119 * @throws ImagingException if it fails to read the JFIF segments 120 * @throws IOException if it fails to read or write the data from the segments 121 * @throws ImagingException if it fails to write the JFIF segments 122 */ 123 public void updateXmpXml(final ByteSource byteSource, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException { 124 final JFIFPieces jfifPieces = analyzeJfif(byteSource); 125 List<JFIFPiece> pieces = jfifPieces.pieces; 126 pieces = removeXmpSegments(pieces); 127 128 final List<JFIFPieceSegment> newPieces = new ArrayList<>(); 129 final byte[] xmpXmlBytes = xmpXml.getBytes(StandardCharsets.UTF_8); 130 int index = 0; 131 while (index < xmpXmlBytes.length) { 132 final int segmentSize = Math.min(xmpXmlBytes.length, JpegConstants.MAX_SEGMENT_SIZE); 133 final byte[] segmentData = writeXmpSegment(xmpXmlBytes, index, segmentSize); 134 newPieces.add(new JFIFPieceSegment(JpegConstants.JPEG_APP1_MARKER, segmentData)); 135 index += segmentSize; 136 } 137 138 pieces = insertAfterLastAppSegments(pieces, newPieces); 139 140 writeSegments(os, pieces); 141 } 142 143 /** 144 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 145 * 146 * @param src Image file. 147 * @param os OutputStream to write the image to. 148 * @param xmpXml String containing XMP XML. 149 * @throws ImagingException if it fails to read the JFIF segments 150 * @throws IOException if it fails to read or write the data from the segments 151 * @throws ImagingException if it fails to write the JFIF segments 152 */ 153 public void updateXmpXml(final File src, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException { 154 final ByteSource byteSource = ByteSource.file(src); 155 updateXmpXml(byteSource, os, xmpXml); 156 } 157 158 /** 159 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 160 * 161 * @param src InputStream containing JPEG image data. 162 * @param os OutputStream to write the image to. 163 * @param xmpXml String containing XMP XML. 164 * @throws ImagingException if it fails to read the JFIF segments 165 * @throws IOException if it fails to read or write the data from the segments 166 * @throws ImagingException if it fails to write the JFIF segments 167 */ 168 public void updateXmpXml(final InputStream src, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException { 169 final ByteSource byteSource = ByteSource.inputStream(src, null); 170 updateXmpXml(byteSource, os, xmpXml); 171 } 172 173 private byte[] writeXmpSegment(final byte[] xmpXmlData, final int start, final int length) throws IOException { 174 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 175 176 JpegConstants.XMP_IDENTIFIER.writeTo(os); 177 os.write(xmpXmlData, start, length); 178 179 return os.toByteArray(); 180 } 181 182}