1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.formats.jpeg.exif;
18
19 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertNotNull;
22 import static org.junit.jupiter.api.Assertions.assertTrue;
23
24 import java.io.BufferedOutputStream;
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.OutputStream;
28 import java.nio.file.Files;
29 import java.security.SecureRandom;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.stream.Stream;
33
34 import org.apache.commons.imaging.Imaging;
35 import org.apache.commons.imaging.ImagingException;
36 import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
37 import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
38 import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;
39 import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
40 import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
41 import org.apache.commons.imaging.formats.tiff.write.TiffOutputField;
42 import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
43 import org.apache.commons.io.FileUtils;
44 import org.junit.jupiter.api.AfterEach;
45 import org.junit.jupiter.params.ParameterizedTest;
46 import org.junit.jupiter.params.provider.MethodSource;
47 import org.opentest4j.TestSkippedException;
48
49
50
51
52 public class ExifRewriterRoundtripTest extends AbstractExifTest {
53
54
55
56
57
58
59 public static Stream<File> data() throws Exception {
60 return getImagesWithExifData().stream();
61 }
62
63 private final SecureRandom random = new SecureRandom();
64
65 private File duplicateFile;
66
67 private void assertTiffEquals(final TiffOutputSet sourceTiffOutputSet, final TiffOutputSet duplicateTiffOutputSet) {
68 final List<TiffOutputDirectory> sourceDirectories = sourceTiffOutputSet.getDirectories();
69 final List<TiffOutputDirectory> duplicatedDirectories = duplicateTiffOutputSet.getDirectories();
70
71 assertEquals(sourceDirectories.size(), duplicatedDirectories.size(), "The TiffOutputSets have different numbers of directories.");
72
73 for (int i = 0; i < sourceDirectories.size(); i++) {
74 final TiffOutputDirectory sourceDirectory = sourceDirectories.get(i);
75 final TiffOutputDirectory duplicateDirectory = duplicatedDirectories.get(i);
76
77 assertEquals(sourceDirectory.getType(), duplicateDirectory.getType(), "Directory type mismatch.");
78
79 final List<TiffOutputField> sourceFields = sourceDirectory.getFields();
80 final List<TiffOutputField> duplicateFields = duplicateDirectory.getFields();
81
82 final boolean fieldCountMatches = sourceFields.size() == duplicateFields.size();
83
84 if (!fieldCountMatches) {
85
86
87
88
89
90 final List<Integer> sourceTags = new ArrayList<>();
91 final List<Integer> duplicatedTags = new ArrayList<>();
92
93 for (final TiffOutputField field : sourceFields) {
94 sourceTags.add(field.tag);
95 }
96
97 for (final TiffOutputField field : duplicateFields) {
98 duplicatedTags.add(field.tag);
99 }
100
101 final List<Integer> missingTags = new ArrayList<>(sourceTags);
102 missingTags.removeAll(duplicatedTags);
103 missingTags.remove((Integer) ExifTagConstants.EXIF_TAG_EXIF_OFFSET.tag);
104 missingTags.remove((Integer) ExifTagConstants.EXIF_TAG_GPSINFO.tag);
105 missingTags.remove((Integer) ExifTagConstants.EXIF_TAG_INTEROP_OFFSET.tag);
106 missingTags.remove((Integer) TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT.tag);
107
108 assertTrue(missingTags.isEmpty(), "Missing tags: " + missingTags);
109 }
110
111 for (final TiffOutputField sourceField : sourceFields) {
112 final boolean isOffsetField =
113 sourceField.tag == ExifTagConstants.EXIF_TAG_EXIF_OFFSET.tag ||
114 sourceField.tag == ExifTagConstants.EXIF_TAG_GPSINFO.tag ||
115 sourceField.tag == ExifTagConstants.EXIF_TAG_INTEROP_OFFSET.tag ||
116 sourceField.tag == TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT.tag;
117
118
119
120
121
122 if (isOffsetField) {
123 continue;
124 }
125
126 final TiffOutputField duplicateField = duplicateDirectory.findField(sourceField.tag);
127
128 assertNotNull(duplicateField, "Field is missing: " + sourceField.tagInfo);
129
130 assertEquals(sourceField.tag, duplicateField.tag, "TiffOutputField tag mismatch.");
131 assertEquals(sourceField.abstractFieldType, duplicateField.abstractFieldType, "TiffOutputField fieldType mismatch.");
132 assertEquals(sourceField.count, duplicateField.count, "TiffOutputField count mismatch.");
133
134 assertArrayEquals(sourceField.getData(), duplicateField.getData(), "Bytes are different for field: " + sourceField);
135 }
136 }
137 }
138
139 private void copyToDuplicateFile(final File sourceFile, final TiffOutputSet duplicateTiffOutputSet) throws IOException {
140 final ExifRewriter exifRewriter = new ExifRewriter();
141
142 duplicateFile = createTempFile();
143
144 try (OutputStream duplicateOutputStream = new BufferedOutputStream(Files.newOutputStream(duplicateFile.toPath()))) {
145 exifRewriter.updateExifMetadataLossless(sourceFile, duplicateOutputStream, duplicateTiffOutputSet);
146 }
147 }
148
149 private File createTempFile() {
150 final String tempDir = FileUtils.getTempDirectoryPath();
151 final String tempFileName = this.getClass().getName() + "-" + random.nextLong() + ".tmp";
152
153 return new File(tempDir, tempFileName);
154 }
155
156 private TiffOutputSet duplicateTiffOutputSet(final TiffOutputSet sourceTiffOutputSet) throws ImagingException {
157 final TiffOutputSet duplicateTiffOutputSet = new TiffOutputSet(
158 sourceTiffOutputSet.byteOrder
159 );
160
161 for (final TiffOutputDirectory tiffOutputDirectory : sourceTiffOutputSet) {
162 duplicateTiffOutputSet.addDirectory(tiffOutputDirectory);
163 }
164
165 return duplicateTiffOutputSet;
166 }
167
168 private JpegImageMetadata getJpegImageMetadata(final File sourceFile) throws IOException {
169 final JpegImageMetadata jpegImageMetadata = (JpegImageMetadata) Imaging.getMetadata(sourceFile);
170
171 if (jpegImageMetadata == null) {
172 throw new TestSkippedException();
173 }
174
175 return jpegImageMetadata;
176 }
177
178 private TiffImageMetadata getTiffImageMetadata(final JpegImageMetadata sourceJpegImageMetadata) {
179 final TiffImageMetadata tiffImageMetadata = sourceJpegImageMetadata.getExif();
180
181 if (tiffImageMetadata == null) {
182 throw new TestSkippedException();
183 }
184
185 return tiffImageMetadata;
186 }
187
188 private TiffOutputSet getTiffOutputSet(final TiffImageMetadata sourceTiffImageMetadata) throws ImagingException {
189 final TiffOutputSet tiffOutputSet = sourceTiffImageMetadata.getOutputSet();
190
191 if (tiffOutputSet == null) {
192 throw new TestSkippedException();
193 }
194
195 return tiffOutputSet;
196 }
197
198 @AfterEach
199 void tearDown() {
200 if (duplicateFile != null && duplicateFile.exists()) {
201 duplicateFile.delete();
202 duplicateFile.deleteOnExit();
203 }
204 }
205
206
207
208
209
210
211
212
213 @ParameterizedTest
214 @MethodSource("data")
215 public void updateExifMetadataLossless_copyWithoutChanges_TiffOutputSetsAreIdentical(final File sourceFile) throws Exception {
216
217
218
219 final JpegImageMetadata sourceJpegImageMetadata = getJpegImageMetadata(sourceFile);
220 final TiffImageMetadata sourceTiffImageMetadata = getTiffImageMetadata(sourceJpegImageMetadata);
221 final TiffOutputSet sourceTiffOutputSet = getTiffOutputSet(sourceTiffImageMetadata);
222
223
224
225
226 TiffOutputSet duplicateTiffOutputSet = duplicateTiffOutputSet(sourceTiffOutputSet);
227
228
229
230
231 assertTiffEquals(sourceTiffOutputSet, duplicateTiffOutputSet);
232
233
234
235
236 copyToDuplicateFile(sourceFile, duplicateTiffOutputSet);
237
238
239
240
241 final JpegImageMetadata duplicateJpegImageMetadata = getJpegImageMetadata(duplicateFile);
242 final TiffImageMetadata duplicateTiffImageMetadata = getTiffImageMetadata(duplicateJpegImageMetadata);
243 duplicateTiffOutputSet = duplicateTiffImageMetadata.getOutputSet();
244
245
246
247
248 assertTiffEquals(sourceTiffOutputSet, duplicateTiffOutputSet);
249 }
250 }