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