1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.imaging.formats.jpeg.exif;
19
20 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertFalse;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.junit.jupiter.api.Assertions.assertTrue;
25
26 import java.io.ByteArrayOutputStream;
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.OutputStream;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37
38 import org.apache.commons.imaging.ImageReadException;
39 import org.apache.commons.imaging.ImageWriteException;
40 import org.apache.commons.imaging.Imaging;
41 import org.apache.commons.imaging.common.ImageMetadata.ImageMetadataItem;
42 import org.apache.commons.imaging.common.bytesource.ByteSource;
43 import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
44 import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
45 import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
46 import org.apache.commons.imaging.formats.jpeg.JpegUtils;
47 import org.apache.commons.imaging.formats.tiff.TiffField;
48 import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
49 import org.apache.commons.imaging.formats.tiff.fieldtypes.FieldType;
50 import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
51 import org.apache.commons.imaging.internal.Debug;
52 import org.apache.commons.io.FileUtils;
53 import org.junit.jupiter.api.Assertions;
54 import org.junit.jupiter.api.Test;
55
56 public class ExifRewriteTest extends ExifBaseTest {
57
58
59
60
61
62 @Test
63 public void testRemove() throws Exception {
64 final List<File> images = getImagesWithExifData();
65 for (final File imageFile : images) {
66
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 Assertions.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 = File.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 assertFalse(hasExifData(tempFile));
95 }
96 }
97 }
98
99 @Test
100 public void testInsert() throws Exception {
101 final List<File> images = getImagesWithExifData();
102 for (final File imageFile : images) {
103
104 Debug.debug("imageFile", imageFile);
105
106 final boolean ignoreImageData = isPhilHarveyTestImage(imageFile);
107 if (ignoreImageData) {
108 continue;
109 }
110
111 final ByteSource byteSource = new ByteSourceFile(imageFile);
112 Debug.debug("Source Segments:");
113 new JpegUtils().dumpJFIF(byteSource);
114
115 final JpegImageMetadata originalMetadata = (JpegImageMetadata) Imaging.getMetadata(imageFile);
116 assertNotNull(originalMetadata);
117
118 final TiffImageMetadata oldExifMetadata = originalMetadata.getExif();
119 assertNotNull(oldExifMetadata);
120
121 ByteSource stripped;
122 {
123 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
124 new ExifRewriter().removeExifMetadata(byteSource, baos);
125 final byte[] bytes = baos.toByteArray();
126 final File tempFile = File.createTempFile("removed", ".jpg");
127 Debug.debug("tempFile", tempFile);
128 FileUtils.writeByteArrayToFile(tempFile, bytes);
129
130 Debug.debug("Output Segments:");
131 stripped = new ByteSourceArray(bytes);
132 new JpegUtils().dumpJFIF(stripped);
133
134 assertFalse(hasExifData(tempFile));
135 }
136
137 {
138 final TiffOutputSet outputSet = oldExifMetadata.getOutputSet();
139
140
141 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
142
143 new ExifRewriter().updateExifMetadataLossy(stripped, baos,
144 outputSet);
145
146 final byte[] bytes = baos.toByteArray();
147 final File tempFile = File.createTempFile("inserted" + "_", ".jpg");
148 Debug.debug("tempFile", tempFile);
149 FileUtils.writeByteArrayToFile(tempFile, bytes);
150
151 Debug.debug("Output Segments:");
152 new JpegUtils().dumpJFIF(new ByteSourceArray(bytes));
153
154
155
156 final JpegImageMetadata newMetadata = (JpegImageMetadata) Imaging.getMetadata(tempFile);
157 assertNotNull(newMetadata);
158 final TiffImageMetadata newExifMetadata = newMetadata.getExif();
159 assertNotNull(newExifMetadata);
160
161
162 compare(imageFile, oldExifMetadata, newExifMetadata);
163 }
164
165 }
166 }
167
168 private interface Rewriter {
169 void rewrite(ByteSource byteSource, OutputStream os,
170 TiffOutputSet outputSet) throws ImageReadException,
171 IOException, ImageWriteException;
172 }
173
174 private void rewrite(final Rewriter rewriter, final String name) throws IOException,
175 ImageReadException {
176 final List<File> images = getImagesWithExifData();
177 for (final File imageFile : images) {
178
179 try {
180
181 Debug.debug("imageFile", imageFile);
182
183 final boolean ignoreImageData = isPhilHarveyTestImage(imageFile);
184 if (ignoreImageData) {
185 continue;
186 }
187
188 final ByteSource byteSource = new ByteSourceFile(imageFile);
189 Debug.debug("Source Segments:");
190 new JpegUtils().dumpJFIF(byteSource);
191
192 final JpegImageMetadata oldMetadata = (JpegImageMetadata) Imaging.getMetadata(imageFile);
193 if (null == oldMetadata) {
194 continue;
195 }
196 assertNotNull(oldMetadata);
197
198 final TiffImageMetadata oldExifMetadata = oldMetadata.getExif();
199 if (null == oldExifMetadata) {
200 continue;
201 }
202 assertNotNull(oldExifMetadata);
203 oldMetadata.dump();
204
205
206
207
208 final TiffOutputSet outputSet = oldExifMetadata.getOutputSet();
209
210
211 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
212 rewriter.rewrite(byteSource, baos, outputSet);
213 final byte[] bytes = baos.toByteArray();
214 final File tempFile = File.createTempFile(name + "_", ".jpg");
215 Debug.debug("tempFile", tempFile);
216 FileUtils.writeByteArrayToFile(tempFile, bytes);
217
218 Debug.debug("Output Segments:");
219 new JpegUtils().dumpJFIF(new ByteSourceArray(bytes));
220
221
222
223 final JpegImageMetadata newMetadata = (JpegImageMetadata) Imaging.getMetadata(tempFile);
224 assertNotNull(newMetadata);
225 final TiffImageMetadata newExifMetadata = newMetadata.getExif();
226 assertNotNull(newExifMetadata);
227
228
229 compare(imageFile, oldExifMetadata, newExifMetadata);
230 } catch (final IOException | ImageReadException e) {
231 Debug.debug("imageFile", imageFile.getAbsoluteFile());
232 Debug.debug(e);
233 throw e;
234 } catch (final ImageWriteException e) {
235 Debug.debug("imageFile", imageFile.getAbsoluteFile());
236 Debug.debug(e);
237 }
238
239 }
240 }
241
242 @Test
243 public void testRewriteLossy() throws Exception {
244 final Rewriter rewriter = (byteSource, os, outputSet) -> new ExifRewriter().updateExifMetadataLossy(byteSource, os,
245 outputSet);
246
247 rewrite(rewriter, "lossy");
248 }
249
250 @Test
251 public void testRewriteLossless() throws Exception {
252 final Rewriter rewriter = (byteSource, os, outputSet) -> new ExifRewriter().updateExifMetadataLossless(byteSource, os,
253 outputSet);
254
255 rewrite(rewriter, "lossless");
256 }
257
258 private Map<Integer,TiffImageMetadata.Directory> makeDirectoryMap(final List<? extends ImageMetadataItem> directories) {
259 final Map<Integer,TiffImageMetadata.Directory> directoryMap = new HashMap<>();
260 for (final ImageMetadataItem element : directories) {
261 final TiffImageMetadata.Directory directory = (TiffImageMetadata.Directory) element;
262 directoryMap.put(directory.type, directory);
263 }
264 return directoryMap;
265 }
266
267 private Map<Integer,TiffField> makeFieldMap(final List<? extends ImageMetadataItem> items) {
268 final Map<Integer,TiffField> fieldMap = new HashMap<>();
269 for (final ImageMetadataItem item2 : items) {
270 final TiffImageMetadata.TiffMetadataItem item = (TiffImageMetadata.TiffMetadataItem) item2;
271 final TiffField field = item.getTiffField();
272 if (!fieldMap.containsKey(field.getTag())) {
273 fieldMap.put(field.getTag(), field);
274 }
275 }
276 return fieldMap;
277 }
278
279 private void compare(final File imageFile, final TiffImageMetadata oldExifMetadata,
280 final TiffImageMetadata newExifMetadata) throws ImageReadException {
281 assertNotNull(oldExifMetadata);
282 assertNotNull(newExifMetadata);
283
284 final List<? extends ImageMetadataItem> oldDirectories = oldExifMetadata.getDirectories();
285 final List<? extends ImageMetadataItem> newDirectories = newExifMetadata.getDirectories();
286
287 assertEquals(oldDirectories.size(), newDirectories.size());
288
289 final Map<Integer,TiffImageMetadata.Directory> oldDirectoryMap = makeDirectoryMap(oldDirectories);
290 final Map<Integer,TiffImageMetadata.Directory> newDirectoryMap = makeDirectoryMap(newDirectories);
291
292 assertEquals(oldDirectories.size(), oldDirectoryMap.size());
293 final List<Integer> oldDirectoryTypes = new ArrayList<>(oldDirectoryMap.keySet());
294 oldDirectoryTypes.sort(null);
295 final List<Integer> newDirectoryTypes = new ArrayList<>(newDirectoryMap.keySet());
296 newDirectoryTypes.sort(null);
297 assertEquals(oldDirectoryTypes, newDirectoryTypes);
298
299 for (final Integer dirType : oldDirectoryTypes) {
300
301
302
303
304 final TiffImageMetadata.Directory oldDirectory = oldDirectoryMap.get(dirType);
305 final TiffImageMetadata.Directory newDirectory = newDirectoryMap.get(dirType);
306 assertNotNull(oldDirectory);
307 assertNotNull(newDirectory);
308
309 final List<? extends ImageMetadataItem> oldItems = oldDirectory.getItems();
310 final List<? extends ImageMetadataItem> newItems = newDirectory.getItems();
311
312 final Map<Integer,TiffField> oldFieldMap = makeFieldMap(oldItems);
313 final Map<Integer,TiffField> newFieldMap = makeFieldMap(newItems);
314
315 final Set<Integer> missingInNew = new HashSet<>(oldFieldMap.keySet());
316 missingInNew.removeAll(newFieldMap.keySet());
317
318 final Set<Integer> missingInOld = new HashSet<>(newFieldMap.keySet());
319 missingInOld.removeAll(oldFieldMap.keySet());
320
321 assertTrue(missingInNew.isEmpty());
322 assertTrue(missingInOld.isEmpty());
323
324 assertEquals(oldItems.size(), oldFieldMap.size());
325 assertEquals(oldFieldMap.keySet(), newFieldMap.keySet());
326 assertEquals(oldFieldMap.keySet(), newFieldMap.keySet());
327
328 final List<Integer> oldFieldTags = new ArrayList<>(oldFieldMap.keySet());
329 oldFieldTags.sort(null);
330 final List<Integer> newFieldTags = new ArrayList<>(newFieldMap.keySet());
331 newFieldTags.sort(null);
332 assertEquals(oldFieldTags, newFieldTags);
333
334 for (final Integer fieldTag : oldFieldTags) {
335 final TiffField oldField = oldFieldMap.get(fieldTag);
336 final TiffField newField = newFieldMap.get(fieldTag);
337
338
339 assertNotNull(oldField);
340 assertNotNull(newField);
341
342 assertEquals(oldField.getTag(), newField.getTag());
343 assertEquals(dirType.intValue(), newField.getDirectoryType());
344 assertEquals(oldField.getDirectoryType(), newField.getDirectoryType());
345
346 if (oldField.getFieldType() == FieldType.ASCII) {
347
348
349
350 final byte[] rawBytes = oldField.getByteArrayValue();
351 boolean hasInvalidByte = false;
352 for (final byte rawByte : rawBytes) {
353 if ((rawByte & 0x80) != 0) {
354 hasInvalidByte = true;
355 break;
356 }
357 }
358 if (hasInvalidByte) {
359 continue;
360 }
361 }
362
363 assertEquals(oldField.getCount(), newField.getCount());
364 assertEquals(oldField.isLocalValue(), newField.isLocalValue());
365
366 if (oldField.getTag() == 0x202) {
367
368
369
370 continue;
371 }
372
373 if (!oldField.getTagInfo().isOffset()) {
374 if (oldField.getTagInfo().isText()) {
375 } else if (oldField.isLocalValue()) {
376 if (oldField.getTag() == 0x116 || oldField.getTag() == 0x117) {
377 assertEquals(oldField.getValue(), newField.getValue());
378 } else {
379 assertEquals(oldField.getBytesLength(), newField.getBytesLength());
380 assertArrayEquals(oldField.getByteArrayValue(), newField.getByteArrayValue());
381 }
382 } else {
383 assertArrayEquals(oldField.getByteArrayValue(), newField.getByteArrayValue());
384 }
385 }
386 }
387 }
388 }
389
390 }