]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/jpeg/drew/TiffProcessor.java
Version 19, May 2018
[GpsPrune.git] / tim / prune / jpeg / drew / TiffProcessor.java
1 /*
2  * Copyright 2002-2015 Drew Noakes
3  *
4  * More information about this project is available at:
5  *
6  *    https://drewnoakes.com/code/exif/
7  *    https://github.com/drewnoakes/metadata-extractor
8  */
9 package tim.prune.jpeg.drew;
10
11
12 import java.io.IOException;
13 import java.util.HashSet;
14 import java.util.Set;
15
16 import tim.prune.jpeg.JpegData;
17
18 /**
19  * Processes TIFF-formatted data, using an ExifTiffHandler
20  *
21  * @author Drew Noakes https://drewnoakes.com
22  */
23 public class TiffProcessor
24 {
25         /**
26          * Processes a TIFF data sequence.
27          *
28          * @param reader the {@link RandomAccessReader} from which the data should be read
29          * @param jpegData the data to populate
30          * @param tiffHeaderOffset the offset within <code>reader</code> at which the TIFF header starts
31          * @throws ExifException if an error occurred during the processing of TIFF data that could
32          *                       not be ignored or recovered from
33          * @throws IOException an error occurred while accessing the required data
34          */
35         public static void processTiff(final ByteArrayReader reader, JpegData jpegData,
36                 final int tiffHeaderOffset) throws ExifException, IOException
37         {
38                 // This must be either "MM" or "II".
39                 short byteOrderIdentifier = reader.getInt16(tiffHeaderOffset);
40
41                 if (byteOrderIdentifier == 0x4d4d) { // "MM"
42                         reader.setMotorolaByteOrder(true);
43                 } else if (byteOrderIdentifier == 0x4949) { // "II"
44                         reader.setMotorolaByteOrder(false);
45                 } else {
46                         throw new ExifException("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);
47                 }
48
49
50                 int firstIfdOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;
51
52                 // David Ekholm sent a digital camera image that has this problem
53                 if (firstIfdOffset >= reader.getLength() - 1) {
54                         //handler.warn("First IFD offset is beyond the end of the TIFF data segment -- trying default offset");
55                         // First directory normally starts immediately after the offset bytes, so try that
56                         firstIfdOffset = tiffHeaderOffset + 2 + 2 + 4;
57                 }
58
59                 // Make a handler object to use for the processing
60                 ExifTiffHandler handler = new ExifTiffHandler(jpegData);
61
62                 Set<Integer> processedIfdOffsets = new HashSet<Integer>();
63                 processDirectory(handler, reader, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset, 0);
64
65                 handler.completed(reader, tiffHeaderOffset);
66         }
67
68         /**
69          * Processes a TIFF IFD.
70          *
71          * IFD Header:
72          * <ul>
73          *     <li><b>2 bytes</b> number of tags</li>
74          * </ul>
75          * Tag structure:
76          * <ul>
77          *     <li><b>2 bytes</b> tag type</li>
78          *     <li><b>2 bytes</b> format code (values 1 to 12, inclusive)</li>
79          *     <li><b>4 bytes</b> component count</li>
80          *     <li><b>4 bytes</b> inline value, or offset pointer if too large to fit in four bytes</li>
81          * </ul>
82          *
83          * @param handler the {@link ExifTiffHandler} that will coordinate processing and accept read values
84          * @param reader the byte reader from which the data should be read
85          * @param processedIfdOffsets the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop
86          * @param ifdOffset the offset within <code>reader</code> at which the IFD data starts
87          * @param tiffHeaderOffset the offset within <code>reader</code> at which the TIFF header starts
88          * @param inDirectoryId directory id
89          * @throws IOException an error occurred while accessing the required data
90          */
91         private static void processDirectory(final ExifTiffHandler handler,
92                 final ByteArrayReader reader, final Set<Integer> processedIfdOffsets,
93                 final int ifdOffset, final int tiffHeaderOffset, int inDirectoryId) throws ExifException
94         {
95                 // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist
96                 if (processedIfdOffsets.contains(Integer.valueOf(ifdOffset))) {
97                         return;
98                 }
99
100                 // remember that we've visited this directory so that we don't visit it again later
101                 processedIfdOffsets.add(ifdOffset);
102
103                 if (ifdOffset >= reader.getLength() || ifdOffset < 0) {
104                         //handler.error("Ignored IFD marked to start outside data segment");
105                         return;
106                 }
107
108                 // First two bytes in the IFD are the number of tags in this directory
109                 int dirTagCount = reader.getUInt16(ifdOffset);
110
111                 int dirLength = (2 + (12 * dirTagCount) + 4);
112                 if (dirLength + ifdOffset > reader.getLength()) {
113                         //handler.error("Illegally sized IFD");
114                         return;
115                 }
116
117
118                 // Handle each tag in this directory
119                 //
120                 int invalidTiffFormatCodeCount = 0;
121                 for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++)
122                 {
123                         final int tagOffset = calculateTagOffset(ifdOffset, tagNumber);
124
125                         // 2 bytes for the tag id
126                         final int childTagId = reader.getUInt16(tagOffset);
127
128                         // 2 bytes for the format code
129                         final int formatCode = reader.getUInt16(tagOffset + 2);
130                         final int componentSizeInBytes = TiffDataFormat.getComponentSize(formatCode);
131
132                         if (componentSizeInBytes == 0)
133                         {
134                                 // This error suggests that we are processing at an incorrect index and will generate
135                                 // rubbish until we go out of bounds (which may be a while).
136                                 if (++invalidTiffFormatCodeCount > 5) {
137                                         //handler.error("Stopping processing as too many errors seen in TIFF IFD");
138                                         return;
139                                 }
140                                 continue;
141                         }
142
143                         // 4 bytes dictate the number of components in this tag's data
144                         final int componentCount = reader.getInt32(tagOffset + 4);
145                         if (componentCount < 0) {
146                                 //handler.error("Negative TIFF tag component count");
147                                 continue;
148                         }
149
150                         final int byteCount = componentCount * componentSizeInBytes;
151
152                         final int tagValueOffset;
153                         if (byteCount > 4)
154                         {
155                                 // If it's bigger than 4 bytes, the dir entry contains an offset.
156                                 final int offsetVal = reader.getInt32(tagOffset + 8);
157                                 if (offsetVal + byteCount > reader.getLength()) {
158                                         // Bogus pointer offset and / or byteCount value
159                                         //handler.error("Illegal TIFF tag pointer offset");
160                                         continue;
161                                 }
162                                 tagValueOffset = tiffHeaderOffset + offsetVal;
163                         }
164                         else {
165                                 // 4 bytes or less and value is in the dir entry itself.
166                                 tagValueOffset = tagOffset + 8;
167                         }
168
169                         if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) {
170                                 //handler.error("Illegal TIFF tag pointer offset");
171                                 continue;
172                         }
173
174                         // Check that this tag isn't going to allocate outside the bounds of the data array.
175                         // This addresses an uncommon OutOfMemoryError.
176                         if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) {
177                                 //handler.error("Illegal number of bytes for TIFF tag data: " + byteCount);
178                                 continue;
179                         }
180
181                         // Special handling for tags that point to other IFDs
182                         if (byteCount == 4 && handler.isTagIfdPointer(childTagId)) {
183                                 final int subDirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
184                                 processDirectory(handler, reader, processedIfdOffsets, subDirOffset, tiffHeaderOffset, childTagId);
185                         }
186                         else if (handler.isInterestingTag(inDirectoryId, childTagId))
187                         {
188                                 processTag(handler, childTagId, tagValueOffset, componentCount, formatCode, reader);
189                         }
190                 }
191
192                 // at the end of each IFD is an optional link to the next IFD
193                 final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount);
194                 int nextIfdOffset = reader.getInt32(finalTagOffset);
195                 if (nextIfdOffset != 0)
196                 {
197                         nextIfdOffset += tiffHeaderOffset;
198                         if (nextIfdOffset >= reader.getLength()) {
199                                 // Last 4 bytes of IFD reference another IFD with an address that is out of bounds
200                                 // Note this could have been caused by jhead 1.3 cropping too much
201                                 return;
202                         }
203                         else if (nextIfdOffset < ifdOffset) {
204                                 // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory
205                                 return;
206                         }
207
208                         processDirectory(handler, reader, processedIfdOffsets, nextIfdOffset, tiffHeaderOffset, inDirectoryId);
209                 }
210         }
211
212
213         /**
214          * Process a single tag value
215          */
216         private static void processTag(final ExifTiffHandler handler,
217                 final int tagId, final int tagValueOffset,
218                 final int componentCount, final int formatCode,
219                 final ByteArrayReader reader) throws ExifException
220         {
221                 switch (formatCode)
222                 {
223                         case TiffDataFormat.CODE_STRING:
224                                 handler.setString(tagId, reader.getNullTerminatedString(tagValueOffset, componentCount));
225                                 break;
226                         case TiffDataFormat.CODE_RATIONAL_S:
227                                 if (componentCount == 1) {
228                                         handler.setRational(tagId, new Rational(reader.getInt32(tagValueOffset), reader.getInt32(tagValueOffset + 4)));
229                                 } else if (componentCount > 1) {
230                                         Rational[] array = new Rational[componentCount];
231                                         for (int i = 0; i < componentCount; i++)
232                                                 array[i] = new Rational(reader.getInt32(tagValueOffset + (8 * i)), reader.getInt32(tagValueOffset + 4 + (8 * i)));
233                                         handler.setRationalArray(tagId, array);
234                                 }
235                                 break;
236                         case TiffDataFormat.CODE_RATIONAL_U:
237                                 if (componentCount == 1) {
238                                         handler.setRational(tagId, new Rational(reader.getUInt32(tagValueOffset), reader.getUInt32(tagValueOffset + 4)));
239                                 } else if (componentCount > 1) {
240                                         Rational[] array = new Rational[componentCount];
241                                         for (int i = 0; i < componentCount; i++)
242                                                 array[i] = new Rational(reader.getUInt32(tagValueOffset + (8 * i)), reader.getUInt32(tagValueOffset + 4 + (8 * i)));
243                                         handler.setRationalArray(tagId, array);
244                                 }
245                                 break;
246                         case TiffDataFormat.CODE_INT8_S:
247                                 if (componentCount == 1) {
248                                         handler.setIntegerValue(tagId, reader.getInt8(tagValueOffset));
249                                 }
250                                 break;
251                         case TiffDataFormat.CODE_INT8_U:
252                                 if (componentCount == 1) {
253                                         handler.setIntegerValue(tagId, reader.getUInt8(tagValueOffset));
254                                 }
255                                 break;
256                         case TiffDataFormat.CODE_INT16_S:
257                                 if (componentCount == 1) {
258                                         handler.setIntegerValue(tagId, reader.getInt16(tagValueOffset));
259                                 }
260                                 break;
261                         case TiffDataFormat.CODE_INT16_U:
262                                 if (componentCount == 1) {
263                                         handler.setIntegerValue(tagId, reader.getUInt16(tagValueOffset));
264                                 }
265                                 break;
266                         case TiffDataFormat.CODE_INT32_S:
267                                 // NOTE 'long' in this case means 32 bit, not 64
268                                 if (componentCount == 1) {
269                                         handler.setIntegerValue(tagId, reader.getInt32(tagValueOffset));
270                                 }
271                                 break;
272                         case TiffDataFormat.CODE_INT32_U:
273                                 // NOTE 'long' in this case means 32 bit, not 64
274                                 if (componentCount == 1) {
275                                         handler.setRational(tagId, new Rational(reader.getUInt32(tagValueOffset), 1L));
276                                 }
277                                 break;
278                         case TiffDataFormat.CODE_SINGLE:
279                         case TiffDataFormat.CODE_DOUBLE:
280                         case TiffDataFormat.CODE_UNDEFINED:
281                         default:
282                                 break;
283                 }
284         }
285
286         /**
287          * Determine the offset of a given tag within the specified IFD.
288          *
289          * @param ifdStartOffset the offset at which the IFD starts
290          * @param entryNumber    the zero-based entry number
291          */
292         private static int calculateTagOffset(int ifdStartOffset, int entryNumber)
293         {
294                 // Add 2 bytes for the tag count.
295                 // Each entry is 12 bytes.
296                 return ifdStartOffset + 2 + (12 * entryNumber);
297         }
298 }