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 * Constructs a new instance with the default, big-endian, byte order. 039 */ 040 public JpegXmpRewriter() { 041 // empty 042 } 043 044 /** 045 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 046 * 047 * @param src Byte array containing JPEG image data. 048 * @param os OutputStream to write the image to. 049 * @throws ImagingException if it fails to read the JFIF segments 050 * @throws IOException if it fails to read or write the data from the segments 051 */ 052 public void removeXmpXml(final byte[] src, final OutputStream os) throws ImagingException, IOException { 053 final ByteSource byteSource = ByteSource.array(src); 054 removeXmpXml(byteSource, os); 055 } 056 057 /** 058 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 059 * 060 * @param byteSource ByteSource containing JPEG image data. 061 * @param os OutputStream to write the image to. 062 * @throws ImagingException if it fails to read the JFIF segments 063 * @throws IOException if it fails to read or write the data from the segments 064 */ 065 public void removeXmpXml(final ByteSource byteSource, final OutputStream os) throws ImagingException, IOException { 066 final JFIFPieces jfifPieces = analyzeJfif(byteSource); 067 List<JFIFPiece> pieces = jfifPieces.pieces; 068 pieces = removeXmpSegments(pieces); 069 writeSegments(os, pieces); 070 } 071 072 /** 073 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 074 * 075 * @param src Image file. 076 * @param os OutputStream to write the image to. 077 * @see java.io.File 078 * @see java.io.OutputStream 079 * @throws ImagingException if it fails to read the JFIF segments 080 * @throws IOException if it fails to read or write the data from the segments 081 */ 082 public void removeXmpXml(final File src, final OutputStream os) throws ImagingException, IOException { 083 final ByteSource byteSource = ByteSource.file(src); 084 removeXmpXml(byteSource, os); 085 } 086 087 /** 088 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 089 * 090 * @param src InputStream containing JPEG image data. 091 * @param os OutputStream to write the image to. 092 * @throws ImagingException if it fails to read the JFIF segments 093 * @throws IOException if it fails to read or write the data from the segments 094 */ 095 public void removeXmpXml(final InputStream src, final OutputStream os) throws ImagingException, IOException { 096 final ByteSource byteSource = ByteSource.inputStream(src, null); 097 removeXmpXml(byteSource, os); 098 } 099 100 /** 101 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 102 * 103 * @param src Byte array containing JPEG image data. 104 * @param os OutputStream to write the image to. 105 * @param xmpXml String containing XMP XML. 106 * @throws ImagingException if it fails to read or write the JFIF segments 107 * @throws IOException if it fails to read or write the data from the segments 108 */ 109 public void updateXmpXml(final byte[] src, final OutputStream os, final String xmpXml) throws ImagingException, IOException { 110 final ByteSource byteSource = ByteSource.array(src); 111 updateXmpXml(byteSource, os, xmpXml); 112 } 113 114 /** 115 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 116 * 117 * @param byteSource ByteSource containing JPEG image data. 118 * @param os OutputStream to write the image to. 119 * @param xmpXml String containing XMP XML. 120 * @throws ImagingException if it fails to read or write the JFIF segments 121 * @throws IOException if it fails to read or write the data from the segments 122 */ 123 public void updateXmpXml(final ByteSource byteSource, final OutputStream os, final String xmpXml) throws ImagingException, IOException { 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 or write the JFIF segments 150 * @throws IOException if it fails to read or write the data from the segments 151 */ 152 public void updateXmpXml(final File src, final OutputStream os, final String xmpXml) throws ImagingException, IOException { 153 final ByteSource byteSource = ByteSource.file(src); 154 updateXmpXml(byteSource, os, xmpXml); 155 } 156 157 /** 158 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 159 * 160 * @param src InputStream containing JPEG image data. 161 * @param os OutputStream to write the image to. 162 * @param xmpXml String containing XMP XML. 163 * @throws ImagingException if it fails to read or write the JFIF segments 164 * @throws IOException if it fails to read or write the data from the segments 165 */ 166 public void updateXmpXml(final InputStream src, final OutputStream os, final String xmpXml) throws ImagingException, IOException { 167 final ByteSource byteSource = ByteSource.inputStream(src, null); 168 updateXmpXml(byteSource, os, xmpXml); 169 } 170 171 private byte[] writeXmpSegment(final byte[] xmpXmlData, final int start, final int length) throws IOException { 172 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 173 174 JpegConstants.XMP_IDENTIFIER.writeTo(os); 175 os.write(xmpXmlData, start, length); 176 177 return os.toByteArray(); 178 } 179 180}