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