View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.imaging.formats.jpeg.exif;
19  
20  import static org.junit.Assert.assertArrayEquals;
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertNotNull;
23  import static org.junit.Assert.assertTrue;
24  
25  import java.io.ByteArrayOutputStream;
26  import java.io.File;
27  import java.io.IOException;
28  import java.io.OutputStream;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.HashMap;
32  import java.util.HashSet;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  
37  import org.apache.commons.imaging.ImageReadException;
38  import org.apache.commons.imaging.ImageWriteException;
39  import org.apache.commons.imaging.Imaging;
40  import org.apache.commons.imaging.common.ImageMetadata.ImageMetadataItem;
41  import org.apache.commons.imaging.common.bytesource.ByteSource;
42  import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
43  import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
44  import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
45  import org.apache.commons.imaging.formats.jpeg.JpegUtils;
46  import org.apache.commons.imaging.formats.tiff.TiffField;
47  import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
48  import org.apache.commons.imaging.formats.tiff.fieldtypes.FieldType;
49  import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
50  import org.apache.commons.imaging.util.Debug;
51  import org.apache.commons.io.FileUtils;
52  import org.junit.Assert;
53  import org.junit.Test;
54  
55  public class ExifRewriteTest extends ExifBaseTest {
56      // public ExifRewriteTest(String name)
57      // {
58      // super(name);
59      // }
60  
61      @Test
62      public void testRemove() throws Exception {
63          final List<File> images = getImagesWithExifData();
64          for (int i = 0; i < images.size(); i++) {
65  
66              final File imageFile = images.get(i);
67              Debug.debug("imageFile", imageFile);
68  
69              final boolean ignoreImageData = isPhilHarveyTestImage(imageFile);
70              if (ignoreImageData) {
71                  continue;
72              }
73  
74              final ByteSource byteSource = new ByteSourceFile(imageFile);
75              Debug.debug("Source Segments:");
76              new JpegUtils().dumpJFIF(byteSource);
77  
78              {
79                  final JpegImageMetadata metadata = (JpegImageMetadata) Imaging.getMetadata(imageFile);
80                  Assert.assertNotNull(metadata);
81              }
82  
83              {
84                  final ByteArrayOutputStream baos = new ByteArrayOutputStream();
85                  new ExifRewriter().removeExifMetadata(byteSource, baos);
86                  final byte[] bytes = baos.toByteArray();
87                  final File tempFile = createTempFile("test", ".jpg");
88                  Debug.debug("tempFile", tempFile);
89                  FileUtils.writeByteArrayToFile(tempFile, bytes);
90  
91                  Debug.debug("Output Segments:");
92                  new JpegUtils().dumpJFIF(new ByteSourceArray(bytes));
93  
94                  assertTrue(!hasExifData(tempFile));
95              }
96          }
97      }
98  
99      @Test
100     public void testInsert() throws Exception {
101         final List<File> images = getImagesWithExifData();
102         for (int i = 0; i < images.size(); i++) {
103 
104             final File imageFile = images.get(i);
105             Debug.debug("imageFile", imageFile);
106 
107             final boolean ignoreImageData = isPhilHarveyTestImage(imageFile);
108             if (ignoreImageData) {
109                 continue;
110             }
111 
112             final ByteSource byteSource = new ByteSourceFile(imageFile);
113             Debug.debug("Source Segments:");
114             new JpegUtils().dumpJFIF(byteSource);
115 
116             final JpegImageMetadata originalMetadata = (JpegImageMetadata) Imaging.getMetadata(imageFile);
117             assertNotNull(originalMetadata);
118 
119             final TiffImageMetadata oldExifMetadata = originalMetadata.getExif();
120             assertNotNull(oldExifMetadata);
121 
122             ByteSource stripped;
123             {
124                 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
125                 new ExifRewriter().removeExifMetadata(byteSource, baos);
126                 final byte[] bytes = baos.toByteArray();
127                 final File tempFile = createTempFile("removed", ".jpg");
128                 Debug.debug("tempFile", tempFile);
129                 FileUtils.writeByteArrayToFile(tempFile, bytes);
130 
131                 Debug.debug("Output Segments:");
132                 stripped = new ByteSourceArray(bytes);
133                 new JpegUtils().dumpJFIF(stripped);
134 
135                 assertTrue(!hasExifData(tempFile));
136             }
137 
138             {
139                 final TiffOutputSet outputSet = oldExifMetadata.getOutputSet();
140                 // outputSet.dump();
141 
142                 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
143 
144                 new ExifRewriter().updateExifMetadataLossy(stripped, baos,
145                         outputSet);
146 
147                 final byte[] bytes = baos.toByteArray();
148                 final File tempFile = createTempFile("inserted" + "_", ".jpg");
149                 Debug.debug("tempFile", tempFile);
150                 FileUtils.writeByteArrayToFile(tempFile, bytes);
151 
152                 Debug.debug("Output Segments:");
153                 new JpegUtils().dumpJFIF(new ByteSourceArray(bytes));
154 
155                 // assertTrue(!hasExifData(tempFile));
156 
157                 final JpegImageMetadata newMetadata = (JpegImageMetadata) Imaging.getMetadata(tempFile);
158                 assertNotNull(newMetadata);
159                 final TiffImageMetadata newExifMetadata = newMetadata.getExif();
160                 assertNotNull(newExifMetadata);
161                 // newMetadata.dump();
162 
163                 compare(imageFile, oldExifMetadata, newExifMetadata);
164             }
165 
166         }
167     }
168 
169     private interface Rewriter {
170         public void rewrite(ByteSource byteSource, OutputStream os,
171                 TiffOutputSet outputSet) throws ImageReadException,
172                 IOException, ImageWriteException;
173     }
174 
175     private void rewrite(final Rewriter rewriter, final String name) throws IOException,
176             ImageReadException, ImageWriteException {
177         final List<File> images = getImagesWithExifData();
178         for (int i = 0; i < images.size(); i++) {
179 
180             final File imageFile = images.get(i);
181 
182             try {
183 
184                 Debug.debug("imageFile", imageFile);
185 
186                 final boolean ignoreImageData = isPhilHarveyTestImage(imageFile);
187                 if (ignoreImageData) {
188                     continue;
189                 }
190 
191                 final ByteSource byteSource = new ByteSourceFile(imageFile);
192                 Debug.debug("Source Segments:");
193                 new JpegUtils().dumpJFIF(byteSource);
194 
195                 final JpegImageMetadata oldMetadata = (JpegImageMetadata) Imaging.getMetadata(imageFile);
196                 if (null == oldMetadata) {
197                     continue;
198                 }
199                 assertNotNull(oldMetadata);
200 
201                 final TiffImageMetadata oldExifMetadata = oldMetadata.getExif();
202                 if (null == oldExifMetadata) {
203                     continue;
204                 }
205                 assertNotNull(oldExifMetadata);
206                 oldMetadata.dump();
207 
208                 // TiffImageMetadata tiffImageMetadata = metadata.getExif();
209                 // Photoshop photoshop = metadata.getPhotoshop();
210 
211                 final TiffOutputSet outputSet = oldExifMetadata.getOutputSet();
212                 // outputSet.dump();
213 
214                 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
215                 rewriter.rewrite(byteSource, baos, outputSet);
216                 final byte[] bytes = baos.toByteArray();
217                 final File tempFile = createTempFile(name + "_", ".jpg");
218                 Debug.debug("tempFile", tempFile);
219                 FileUtils.writeByteArrayToFile(tempFile, bytes);
220 
221                 Debug.debug("Output Segments:");
222                 new JpegUtils().dumpJFIF(new ByteSourceArray(bytes));
223 
224                 // assertTrue(!hasExifData(tempFile));
225 
226                 final JpegImageMetadata newMetadata = (JpegImageMetadata) Imaging.getMetadata(tempFile);
227                 assertNotNull(newMetadata);
228                 final TiffImageMetadata newExifMetadata = newMetadata.getExif();
229                 assertNotNull(newExifMetadata);
230                 // newMetadata.dump();
231 
232                 compare(imageFile, oldExifMetadata, newExifMetadata);
233             } catch (final IOException e) {
234                 Debug.debug("imageFile", imageFile.getAbsoluteFile());
235                 Debug.debug(e);
236                 throw e;
237             } catch (final ImageReadException e) {
238                 Debug.debug("imageFile", imageFile.getAbsoluteFile());
239                 Debug.debug(e);
240                 throw e;
241             } catch (final ImageWriteException e) {
242                 Debug.debug("imageFile", imageFile.getAbsoluteFile());
243                 Debug.debug(e);
244             }
245 
246         }
247     }
248 
249     @Test
250     public void testRewriteLossy() throws Exception {
251         final Rewriter rewriter = new Rewriter() {
252             @Override
253             public void rewrite(final ByteSource byteSource, final OutputStream os,
254                     final TiffOutputSet outputSet) throws ImageReadException,
255                     IOException, ImageWriteException {
256                 new ExifRewriter().updateExifMetadataLossy(byteSource, os,
257                         outputSet);
258             }
259         };
260 
261         rewrite(rewriter, "lossy");
262     }
263 
264     @Test
265     public void testRewriteLossless() throws Exception {
266         final Rewriter rewriter = new Rewriter() {
267             @Override
268             public void rewrite(final ByteSource byteSource, final OutputStream os,
269                     final TiffOutputSet outputSet) throws ImageReadException,
270                     IOException, ImageWriteException {
271                 new ExifRewriter().updateExifMetadataLossless(byteSource, os,
272                         outputSet);
273             }
274         };
275 
276         rewrite(rewriter, "lossless");
277     }
278 
279     private Map<Integer,TiffImageMetadata.Directory> makeDirectoryMap(final List<? extends ImageMetadataItem> directories) {
280         final Map<Integer,TiffImageMetadata.Directory> directoryMap = new HashMap<>();
281         for (int i = 0; i < directories.size(); i++) {
282             final TiffImageMetadata.Directory directory = (TiffImageMetadata.Directory) directories.get(i);
283             directoryMap.put(directory.type, directory);
284         }
285         return directoryMap;
286     }
287 
288     private Map<Integer,TiffField> makeFieldMap(final List<? extends ImageMetadataItem> items) {
289         final Map<Integer,TiffField> fieldMap = new HashMap<>();
290         for (int i = 0; i < items.size(); i++) {
291             final TiffImageMetadata.TiffMetadataItem item = (TiffImageMetadata.TiffMetadataItem) items.get(i);
292             final TiffField field = item.getTiffField();
293             if (!fieldMap.containsKey(field.getTag())) {
294                 fieldMap.put(field.getTag(), field);
295             }
296         }
297         return fieldMap;
298     }
299 
300     private void compare(final File imageFile, final TiffImageMetadata oldExifMetadata,
301             final TiffImageMetadata newExifMetadata) throws ImageReadException {
302         assertNotNull(oldExifMetadata);
303         assertNotNull(newExifMetadata);
304 
305         final List<? extends ImageMetadataItem> oldDirectories = oldExifMetadata.getDirectories();
306         final List<? extends ImageMetadataItem> newDirectories = newExifMetadata.getDirectories();
307 
308         assertTrue(oldDirectories.size() == newDirectories.size());
309 
310         final Map<Integer,TiffImageMetadata.Directory> oldDirectoryMap = makeDirectoryMap(oldDirectories);
311         final Map<Integer,TiffImageMetadata.Directory> newDirectoryMap = makeDirectoryMap(newDirectories);
312 
313         assertEquals(oldDirectories.size(), oldDirectoryMap.keySet().size());
314         final List<Integer> oldDirectoryTypes = new ArrayList<>(oldDirectoryMap.keySet());
315         Collections.sort(oldDirectoryTypes);
316         final List<Integer> newDirectoryTypes = new ArrayList<>(newDirectoryMap.keySet());
317         Collections.sort(newDirectoryTypes);
318         assertEquals(oldDirectoryTypes, newDirectoryTypes);
319 
320         for (int i = 0; i < oldDirectoryTypes.size(); i++) {
321             final Integer dirType = oldDirectoryTypes.get(i);
322 
323             // Debug.debug("dirType", dirType);
324 
325             final TiffImageMetadata.Directory oldDirectory = oldDirectoryMap.get(dirType);
326             final TiffImageMetadata.Directory newDirectory = newDirectoryMap.get(dirType);
327             assertNotNull(oldDirectory);
328             assertNotNull(newDirectory);
329 
330             final List<? extends ImageMetadataItem> oldItems = oldDirectory.getItems();
331             final List<? extends ImageMetadataItem> newItems = newDirectory.getItems();
332 
333             final Map<Integer,TiffField> oldFieldMap = makeFieldMap(oldItems);
334             final Map<Integer,TiffField> newFieldMap = makeFieldMap(newItems);
335 
336             final Set<Integer> missingInNew = new HashSet<>(oldFieldMap.keySet());
337             missingInNew.removeAll(newFieldMap.keySet());
338 
339             final Set<Integer> missingInOld = new HashSet<>(newFieldMap.keySet());
340             missingInOld.removeAll(oldFieldMap.keySet());
341 
342             assertTrue(missingInNew.size() == 0);
343             assertTrue(missingInOld.size() == 0);
344 
345             assertEquals(oldItems.size(), oldFieldMap.keySet().size());
346             assertEquals(oldFieldMap.keySet(), newFieldMap.keySet());
347             assertEquals(oldFieldMap.keySet(), newFieldMap.keySet());
348 
349             final List<Integer> oldFieldTags = new ArrayList<>(oldFieldMap.keySet());
350             Collections.sort(oldFieldTags);
351             final List<Integer> newFieldTags = new ArrayList<>(newFieldMap.keySet());
352             Collections.sort(newFieldTags);
353             assertEquals(oldFieldTags, newFieldTags);
354 
355             for (int j = 0; j < oldFieldTags.size(); j++) {
356                 final Integer fieldTag = oldFieldTags.get(j);
357 
358                 final TiffField oldField = oldFieldMap.get(fieldTag);
359                 final TiffField newField = newFieldMap.get(fieldTag);
360 
361                 // fieldTag.
362                 assertNotNull(oldField);
363                 assertNotNull(newField);
364 
365                 assertEquals(oldField.getTag(), newField.getTag());
366                 assertEquals(dirType.intValue(), newField.getDirectoryType());
367                 assertEquals(oldField.getDirectoryType(), newField.getDirectoryType());
368 
369                 if (oldField.getFieldType() == FieldType.ASCII) {
370                     // Imaging currently doesn't correctly rewrite
371                     // strings if any byte had the highest bit set,
372                     // so if the source had that, all bets are off.
373                     final byte[] rawBytes = oldField.getByteArrayValue();
374                     boolean hasInvalidByte = false;
375                     for (final byte rawByte : rawBytes) {
376                         if ((rawByte & 0x80) != 0) {
377                             hasInvalidByte = true;
378                             break;
379                         }
380                     }
381                     if (hasInvalidByte) {
382                         continue;
383                     }
384                 }
385 
386                 assertEquals(oldField.getCount(), newField.getCount());
387                 assertEquals(oldField.isLocalValue(), newField.isLocalValue());
388 
389                 if (oldField.getTag() == 0x202) {
390                     // ignore "jpg from raw length" value. may have off-by-one
391                     // bug in certain cameras.
392                     // i.e. Sony DCR-PC110
393                     continue;
394                 }
395 
396                 if (!oldField.getTagInfo().isOffset()) {
397                     if (oldField.getTagInfo().isText()) { /* do nothing */
398                     } else if (oldField.isLocalValue()) {
399                         final String label = imageFile.getName() + ", dirType[" + i
400                                 + "]=" + dirType + ", fieldTag[" + j + "]="
401                                 + fieldTag;
402                         if (oldField.getTag() == 0x116 || oldField.getTag() == 0x117) {
403                             assertEquals(label, oldField.getValue(), newField.getValue());
404                         } else {
405                             assertEquals(oldField.getBytesLength(), newField.getBytesLength());
406                             assertArrayEquals(oldField.getByteArrayValue(), newField.getByteArrayValue());
407                         }
408                     } else {
409                         assertArrayEquals(oldField.getByteArrayValue(), newField.getByteArrayValue());
410                     }
411                 }
412             }
413         }
414     }
415 
416 }