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