/src/piex/src/tiff_parser.cc
Line | Count | Source |
1 | | // Copyright 2015 Google Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | // |
15 | | //////////////////////////////////////////////////////////////////////////////// |
16 | | |
17 | | #include "src/tiff_parser.h" |
18 | | |
19 | | #include <cstring> |
20 | | #include <limits> |
21 | | #include <numeric> |
22 | | |
23 | | #include "src/tiff_directory/tiff_directory.h" |
24 | | |
25 | | namespace piex { |
26 | | namespace { |
27 | | |
28 | | using tiff_directory::Endian; |
29 | | using tiff_directory::Rational; |
30 | | using tiff_directory::SizeOfType; |
31 | | using tiff_directory::TIFF_TYPE_LONG; |
32 | | using tiff_directory::TIFF_TYPE_UNDEFINED; |
33 | | using tiff_directory::TiffDirectory; |
34 | | using tiff_directory::kBigEndian; |
35 | | using tiff_directory::kLittleEndian; |
36 | | |
37 | | // Specifies all tags that might be of interest to parse JPEG data. |
38 | | const std::uint32_t kStartOfFrame = 0xFFC0; |
39 | | const std::uint32_t kStartOfImage = 0xFFD8; |
40 | | const std::uint32_t kStartOfScan = 0xFFDA; |
41 | | |
42 | | bool GetFullDimension16(const TiffDirectory& tiff_directory, |
43 | 1.31k | std::uint16_t* width, std::uint16_t* height) { |
44 | 1.31k | std::uint32_t tmp_width = 0; |
45 | 1.31k | std::uint32_t tmp_height = 0; |
46 | 1.31k | if (!GetFullDimension32(tiff_directory, &tmp_width, &tmp_height) || |
47 | 1.18k | tmp_width > std::numeric_limits<std::uint16_t>::max() || |
48 | 943 | tmp_height > std::numeric_limits<std::uint16_t>::max()) { |
49 | 533 | return false; |
50 | 533 | } |
51 | 785 | *width = static_cast<std::uint16_t>(tmp_width); |
52 | 785 | *height = static_cast<std::uint16_t>(tmp_height); |
53 | 785 | return true; |
54 | 1.31k | } |
55 | | |
56 | | void FillGpsPreviewImageData(const TiffDirectory& gps_directory, |
57 | 345 | PreviewImageData* preview_image_data) { |
58 | 345 | if (gps_directory.Has(kGpsTagLatitudeRef) && |
59 | 319 | gps_directory.Has(kGpsTagLatitude) && |
60 | 303 | gps_directory.Has(kGpsTagLongitudeRef) && |
61 | 293 | gps_directory.Has(kGpsTagLongitude) && |
62 | 283 | gps_directory.Has(kGpsTagTimeStamp) && |
63 | 273 | gps_directory.Has(kGpsTagDateStamp)) { |
64 | 263 | preview_image_data->gps.is_valid = false; |
65 | 263 | std::string value; |
66 | 263 | if (!gps_directory.Get(kGpsTagLatitudeRef, &value) || value.empty() || |
67 | 253 | (value[0] != 'N' && value[0] != 'S') || |
68 | 211 | !GetRational(kGpsTagLatitude, gps_directory, 3 /* data size */, |
69 | 211 | preview_image_data->gps.latitude)) { |
70 | 65 | return; |
71 | 65 | } |
72 | 198 | preview_image_data->gps.latitude_ref = value[0]; |
73 | | |
74 | 198 | if (!gps_directory.Get(kGpsTagLongitudeRef, &value) || value.empty() || |
75 | 188 | (value[0] != 'E' && value[0] != 'W') || |
76 | 144 | !GetRational(kGpsTagLongitude, gps_directory, 3 /* data size */, |
77 | 144 | preview_image_data->gps.longitude)) { |
78 | 69 | return; |
79 | 69 | } |
80 | 129 | preview_image_data->gps.longitude_ref = value[0]; |
81 | | |
82 | 129 | if (!GetRational(kGpsTagTimeStamp, gps_directory, 3 /* data size */, |
83 | 129 | preview_image_data->gps.time_stamp)) { |
84 | 28 | return; |
85 | 28 | } |
86 | | |
87 | 101 | const size_t kGpsDateStampSize = 11; |
88 | 101 | if (!gps_directory.Get(kGpsTagDateStamp, |
89 | 101 | &preview_image_data->gps.date_stamp)) { |
90 | 10 | return; |
91 | 10 | } |
92 | 91 | if (preview_image_data->gps.date_stamp.size() == kGpsDateStampSize) { |
93 | | // Resize the date_stamp to remove the "NULL" at the end of string. |
94 | 69 | preview_image_data->gps.date_stamp.resize(kGpsDateStampSize - 1); |
95 | 69 | } else { |
96 | 22 | return; |
97 | 22 | } |
98 | | |
99 | 69 | if (gps_directory.Has(kGpsTagAltitudeRef) && |
100 | 59 | gps_directory.Has(kGpsTagAltitude)) { |
101 | 49 | std::vector<std::uint8_t> bytes; |
102 | 49 | if (!gps_directory.Get(kGpsTagAltitudeRef, &bytes) || bytes.empty() || |
103 | 33 | !GetRational(kGpsTagAltitude, gps_directory, 1, |
104 | 33 | &preview_image_data->gps.altitude)) { |
105 | 26 | return; |
106 | 26 | } |
107 | 23 | preview_image_data->gps.altitude_ref = bytes[0] != 0; |
108 | 23 | } |
109 | 43 | preview_image_data->gps.is_valid = true; |
110 | 43 | } |
111 | 345 | } |
112 | | |
113 | | void GetImageSize(const TiffDirectory& tiff_directory, StreamInterface* stream, |
114 | 2.04k | Image* image) { |
115 | 2.04k | switch (image->format) { |
116 | 1.31k | case Image::kUncompressedRgb: { |
117 | 1.31k | GetFullDimension16(tiff_directory, &image->width, &image->height); |
118 | 1.31k | break; |
119 | 0 | } |
120 | 725 | case Image::kJpegCompressed: { |
121 | 725 | GetJpegDimensions(image->offset, stream, &image->width, &image->height); |
122 | 725 | break; |
123 | 0 | } |
124 | 0 | default: { return; } |
125 | 2.04k | } |
126 | 2.04k | } |
127 | | |
128 | | bool FillPreviewImageData(const TiffDirectory& tiff_directory, |
129 | | StreamInterface* stream, |
130 | 4.11k | PreviewImageData* preview_image_data) { |
131 | 4.11k | bool success = true; |
132 | | // Get preview or thumbnail. The code assumes that only thumbnails can be |
133 | | // uncompressed. Preview images are always JPEG compressed. |
134 | 4.11k | Image image; |
135 | 4.11k | if (GetImageData(tiff_directory, stream, &image)) { |
136 | 1.56k | if (IsThumbnail(image)) { |
137 | 964 | preview_image_data->thumbnail = image; |
138 | 964 | } else if (image.format == Image::kJpegCompressed) { |
139 | 307 | preview_image_data->preview = image; |
140 | 307 | } |
141 | 1.56k | } |
142 | | |
143 | | // Get exif_orientation if it was not set already. |
144 | 4.11k | if (tiff_directory.Has(kTiffTagOrientation) && |
145 | 902 | preview_image_data->exif_orientation == 1) { |
146 | 431 | success &= tiff_directory.Get(kTiffTagOrientation, |
147 | 431 | &preview_image_data->exif_orientation); |
148 | 431 | } |
149 | | |
150 | | // Get color_space |
151 | 4.11k | if (tiff_directory.Has(kExifTagColorSpace)) { |
152 | 722 | std::uint32_t color_space; |
153 | 722 | if (tiff_directory.Get(kExifTagColorSpace, &color_space)) { |
154 | 674 | if (color_space == 1) { |
155 | 146 | preview_image_data->color_space = PreviewImageData::kSrgb; |
156 | 528 | } else if (color_space == 65535 || color_space == 2) { |
157 | 119 | preview_image_data->color_space = PreviewImageData::kAdobeRgb; |
158 | 119 | } |
159 | 674 | } else { |
160 | 48 | success = false; |
161 | 48 | } |
162 | 722 | } |
163 | | |
164 | 4.11k | success &= GetFullDimension32(tiff_directory, &preview_image_data->full_width, |
165 | 4.11k | &preview_image_data->full_height); |
166 | | |
167 | 4.11k | if (tiff_directory.Has(kTiffTagMake)) { |
168 | 312 | success &= tiff_directory.Get(kTiffTagMake, &preview_image_data->maker); |
169 | 312 | } |
170 | | |
171 | 4.11k | if (tiff_directory.Has(kTiffTagModel)) { |
172 | 534 | success &= tiff_directory.Get(kTiffTagModel, &preview_image_data->model); |
173 | 534 | } |
174 | | |
175 | 4.11k | if (tiff_directory.Has(kTiffTagCfaPatternDim)) { |
176 | 713 | std::vector<std::uint32_t> cfa_pattern_dim; |
177 | 713 | if (tiff_directory.Get(kTiffTagCfaPatternDim, &cfa_pattern_dim) && |
178 | 484 | cfa_pattern_dim.size() == 2) { |
179 | 122 | preview_image_data->cfa_pattern_dim[0] = cfa_pattern_dim[0]; |
180 | 122 | preview_image_data->cfa_pattern_dim[1] = cfa_pattern_dim[1]; |
181 | 122 | } |
182 | 713 | } |
183 | | |
184 | 4.11k | if (tiff_directory.Has(kExifTagDateTimeOriginal)) { |
185 | 274 | success &= tiff_directory.Get(kExifTagDateTimeOriginal, |
186 | 274 | &preview_image_data->date_time); |
187 | 274 | } |
188 | | |
189 | 4.11k | if (tiff_directory.Has(kExifTagIsoSpeed)) { |
190 | 726 | success &= tiff_directory.Get(kExifTagIsoSpeed, &preview_image_data->iso); |
191 | 3.38k | } else if (tiff_directory.Has(kPanaTagIso)) { |
192 | 9 | success &= tiff_directory.Get(kPanaTagIso, &preview_image_data->iso); |
193 | 9 | } |
194 | | |
195 | 4.11k | if (tiff_directory.Has(kExifTagExposureTime)) { |
196 | 302 | success &= GetRational(kExifTagExposureTime, tiff_directory, 1, |
197 | 302 | &preview_image_data->exposure_time); |
198 | 302 | } |
199 | | |
200 | 4.11k | if (tiff_directory.Has(kExifTagFnumber)) { |
201 | 225 | success &= GetRational(kExifTagFnumber, tiff_directory, 1, |
202 | 225 | &preview_image_data->fnumber); |
203 | 225 | } |
204 | | |
205 | 4.11k | if (tiff_directory.Has(kExifTagFocalLength)) { |
206 | 229 | success &= GetRational(kExifTagFocalLength, tiff_directory, 1, |
207 | 229 | &preview_image_data->focal_length); |
208 | 229 | } |
209 | | |
210 | 4.11k | return success; |
211 | 4.11k | } |
212 | | |
213 | | const TiffDirectory* FindFirstTagInIfds(const TiffDirectory::Tag& tag, |
214 | 13.1k | const IfdVector& tiff_directory) { |
215 | 20.6k | for (std::uint32_t i = 0; i < tiff_directory.size(); ++i) { |
216 | 9.22k | if (tiff_directory[i].Has(tag)) { |
217 | 1.74k | return &tiff_directory[i]; |
218 | 1.74k | } |
219 | | |
220 | | // Recursively search sub directories. |
221 | 7.47k | const TiffDirectory* sub_directory = |
222 | 7.47k | FindFirstTagInIfds(tag, tiff_directory[i].GetSubDirectories()); |
223 | 7.47k | if (sub_directory != NULL) { |
224 | 32 | return sub_directory; |
225 | 32 | } |
226 | 7.47k | } |
227 | 11.3k | return NULL; |
228 | 13.1k | } |
229 | | |
230 | | // Return true if all data blocks are ordered one after the other without gaps. |
231 | | bool OffsetsAreConsecutive( |
232 | | const std::vector<std::uint32_t>& strip_offsets, |
233 | 2.55k | const std::vector<std::uint32_t>& strip_byte_counts) { |
234 | 2.55k | if (strip_offsets.size() != strip_byte_counts.size() || |
235 | 2.32k | strip_offsets.empty()) { |
236 | 233 | return false; |
237 | 233 | } |
238 | | |
239 | 3.03k | for (size_t i = 0; i < strip_offsets.size() - 1; ++i) { |
240 | 784 | if (strip_offsets[i] + strip_byte_counts[i] != strip_offsets[i + 1]) { |
241 | 67 | return false; |
242 | 67 | } |
243 | 784 | } |
244 | 2.25k | return true; |
245 | 2.32k | } |
246 | | |
247 | | // Gets the SubIfd content. |
248 | | bool ParseSubIfds(const std::uint32_t tiff_offset, const TagSet& desired_tags, |
249 | | const std::uint32_t max_number_ifds, const Endian endian, |
250 | 3.85k | StreamInterface* stream, TiffDirectory* tiff_ifd) { |
251 | 3.85k | if (tiff_ifd->Has(kTiffTagSubIfd)) { |
252 | 796 | std::uint32_t offset = 0; |
253 | 796 | std::uint32_t length = 0; |
254 | 796 | tiff_ifd->GetOffsetAndLength(kTiffTagSubIfd, TIFF_TYPE_LONG, &offset, |
255 | 796 | &length); |
256 | 796 | length /= 4; // length in bytes divided by 4 gives number of IFDs. |
257 | 2.41k | for (std::uint32_t j = 0; j < length && j < max_number_ifds; ++j) { |
258 | 1.63k | std::uint32_t sub_offset; |
259 | 1.63k | if (!Get32u(stream, offset + 4 * j, endian, &sub_offset)) { |
260 | 0 | return false; |
261 | 0 | } |
262 | | |
263 | 1.63k | std::uint32_t next_ifd_offset; |
264 | 1.63k | TiffDirectory sub_ifd(static_cast<Endian>(endian)); |
265 | 1.63k | if (!ParseDirectory(tiff_offset, sub_offset, endian, desired_tags, stream, |
266 | 1.63k | &sub_ifd, &next_ifd_offset)) { |
267 | 11 | return false; |
268 | 11 | } |
269 | | |
270 | 1.62k | tiff_ifd->AddSubDirectory(sub_ifd); |
271 | 1.62k | } |
272 | 796 | } |
273 | 3.83k | return true; |
274 | 3.85k | } |
275 | | |
276 | | } // namespace |
277 | | |
278 | | bool Get16u(StreamInterface* stream, const std::uint32_t offset, |
279 | 41.3M | const Endian& endian, std::uint16_t* value) { |
280 | 41.3M | std::uint8_t data[2]; |
281 | 41.3M | if (stream->GetData(offset, 2, data) == kOk) { |
282 | 41.3M | if (endian == kBigEndian) { |
283 | 1.38M | *value = (data[0] * 0x100) | data[1]; |
284 | 39.9M | } else { |
285 | 39.9M | *value = (data[1] * 0x100) | data[0]; |
286 | 39.9M | } |
287 | 41.3M | return true; |
288 | 41.3M | } else { |
289 | 627 | return false; |
290 | 627 | } |
291 | 41.3M | } |
292 | | |
293 | | bool Get32u(StreamInterface* stream, const std::uint32_t offset, |
294 | 20.7M | const Endian& endian, std::uint32_t* value) { |
295 | 20.7M | std::uint8_t data[4]; |
296 | 20.7M | if (stream->GetData(offset, 4, data) == kOk) { |
297 | 20.7M | if (endian == kBigEndian) { |
298 | 667k | *value = (data[0] * 0x1000000u) | (data[1] * 0x10000u) | |
299 | 667k | (data[2] * 0x100u) | data[3]; |
300 | 20.1M | } else { |
301 | 20.1M | *value = (data[3] * 0x1000000u) | (data[2] * 0x10000u) | |
302 | 20.1M | (data[1] * 0x100u) | data[0]; |
303 | 20.1M | } |
304 | 20.7M | return true; |
305 | 20.7M | } else { |
306 | 433 | return false; |
307 | 433 | } |
308 | 20.7M | } |
309 | | |
310 | | std::vector<std::uint8_t> GetData(const size_t offset, const size_t length, |
311 | 203k | StreamInterface* stream, Error* error) { |
312 | | // Read in chunks with a maximum size of 1 MiB. |
313 | 203k | const size_t kChunkSize = 1048576; |
314 | | |
315 | 203k | std::vector<std::uint8_t> data; |
316 | 203k | size_t processed_data = 0; |
317 | 407k | while (*error == kOk && processed_data < length) { |
318 | 203k | size_t chunk_length = kChunkSize; |
319 | 203k | if (length - data.size() < kChunkSize) { |
320 | 202k | chunk_length = length - data.size(); |
321 | 202k | } |
322 | | |
323 | 203k | data.resize(processed_data + chunk_length); |
324 | 203k | *error = stream->GetData(offset + processed_data, chunk_length, |
325 | 203k | &data[processed_data]); |
326 | | |
327 | 203k | processed_data += chunk_length; |
328 | 203k | } |
329 | 203k | return data; |
330 | 203k | } |
331 | | |
332 | | bool GetEndianness(const std::uint32_t tiff_offset, StreamInterface* stream, |
333 | 3.21k | Endian* endian) { |
334 | 3.21k | const std::uint8_t kTiffBigEndianMagic[] = {'M', 'M'}; |
335 | 3.21k | const std::uint8_t kTiffLittleEndianMagic[] = {'I', 'I'}; |
336 | 3.21k | std::uint8_t tiff_endian[sizeof(kTiffBigEndianMagic)]; |
337 | 3.21k | if (stream->GetData(tiff_offset, sizeof(tiff_endian), &tiff_endian[0]) != |
338 | 3.21k | kOk) { |
339 | 13 | return false; |
340 | 13 | } |
341 | | |
342 | 3.19k | if (!memcmp(tiff_endian, kTiffLittleEndianMagic, sizeof(tiff_endian))) { |
343 | 2.48k | *endian = kLittleEndian; |
344 | 2.48k | return true; |
345 | 2.48k | } else if (!memcmp(tiff_endian, kTiffBigEndianMagic, sizeof(tiff_endian))) { |
346 | 635 | *endian = kBigEndian; |
347 | 635 | return true; |
348 | 635 | } else { |
349 | 79 | return false; |
350 | 79 | } |
351 | 3.19k | } |
352 | | |
353 | | bool GetImageData(const TiffDirectory& tiff_directory, StreamInterface* stream, |
354 | 5.02k | Image* image) { |
355 | 5.02k | std::uint32_t length = 0; |
356 | 5.02k | std::uint32_t offset = 0; |
357 | | |
358 | 5.02k | if (tiff_directory.Has(kTiffTagJpegOffset) && |
359 | 561 | tiff_directory.Has(kTiffTagJpegByteCount)) { |
360 | 458 | if (!tiff_directory.Get(kTiffTagJpegOffset, &offset) || |
361 | 386 | !tiff_directory.Get(kTiffTagJpegByteCount, &length)) { |
362 | 102 | return false; |
363 | 102 | } |
364 | 356 | image->format = Image::kJpegCompressed; |
365 | 4.57k | } else if (tiff_directory.Has(kTiffTagStripOffsets) && |
366 | 2.98k | tiff_directory.Has(kTiffTagStripByteCounts)) { |
367 | 2.72k | std::vector<std::uint32_t> strip_offsets; |
368 | 2.72k | std::vector<std::uint32_t> strip_byte_counts; |
369 | 2.72k | if (!tiff_directory.Get(kTiffTagStripOffsets, &strip_offsets) || |
370 | 2.64k | !tiff_directory.Get(kTiffTagStripByteCounts, &strip_byte_counts)) { |
371 | 169 | return false; |
372 | 169 | } |
373 | | |
374 | 2.55k | std::uint32_t compression = 0; |
375 | 2.55k | if (!OffsetsAreConsecutive(strip_offsets, strip_byte_counts) || |
376 | 2.25k | !tiff_directory.Get(kTiffTagCompression, &compression)) { |
377 | 538 | return false; |
378 | 538 | } |
379 | | |
380 | 2.01k | std::uint32_t photometric_interpretation = 0; |
381 | 2.01k | if (tiff_directory.Get(kTiffTagPhotometric, &photometric_interpretation) && |
382 | 512 | photometric_interpretation != 2 /* RGB */ && |
383 | 309 | photometric_interpretation != 6 /* YCbCr */) { |
384 | 245 | return false; |
385 | 245 | } |
386 | | |
387 | 1.77k | switch (compression) { |
388 | 1.31k | case 1: /*uncompressed*/ |
389 | 1.31k | image->format = Image::kUncompressedRgb; |
390 | 1.31k | break; |
391 | 111 | case 6: /* JPEG(old) */ |
392 | 363 | case 7: /* JPEG */ |
393 | 363 | image->format = Image::kJpegCompressed; |
394 | 363 | break; |
395 | 89 | default: |
396 | 89 | return false; |
397 | 1.77k | } |
398 | 1.68k | length = static_cast<std::uint32_t>(std::accumulate( |
399 | 1.68k | strip_byte_counts.begin(), strip_byte_counts.end(), 0U)); |
400 | 1.68k | offset = strip_offsets[0]; |
401 | 1.84k | } else if (tiff_directory.Has(kPanaTagJpegImage)) { |
402 | 23 | if (!tiff_directory.GetOffsetAndLength( |
403 | 23 | kPanaTagJpegImage, TIFF_TYPE_UNDEFINED, &offset, &length)) { |
404 | 17 | return false; |
405 | 17 | } |
406 | 6 | image->format = Image::kJpegCompressed; |
407 | 1.82k | } else { |
408 | 1.82k | return false; |
409 | 1.82k | } |
410 | | |
411 | 2.04k | image->length = length; |
412 | 2.04k | image->offset = offset; |
413 | 2.04k | GetImageSize(tiff_directory, stream, image); |
414 | 2.04k | return true; |
415 | 5.02k | } |
416 | | |
417 | | bool GetJpegDimensions(const std::uint32_t jpeg_offset, StreamInterface* stream, |
418 | 814 | std::uint16_t* width, std::uint16_t* height) { |
419 | 814 | const Endian endian = kBigEndian; |
420 | 814 | std::uint32_t offset = jpeg_offset; |
421 | 814 | std::uint16_t segment; |
422 | | |
423 | | // Parse the JPEG header until we find Frame0 which contains the image width |
424 | | // and height or the actual image data starts (StartOfScan) |
425 | 34.8k | do { |
426 | 34.8k | if (!Get16u(stream, offset, endian, &segment)) { |
427 | 281 | return false; |
428 | 281 | } |
429 | 34.5k | offset += 2; |
430 | | |
431 | 34.5k | switch (segment) { |
432 | 305 | case kStartOfImage: |
433 | 305 | break; |
434 | 461 | case kStartOfFrame: |
435 | 461 | return Get16u(stream, offset + 3, endian, height) && |
436 | 425 | Get16u(stream, offset + 5, endian, width); |
437 | 33.8k | default: { |
438 | 33.8k | std::uint16_t length; |
439 | 33.8k | if (!Get16u(stream, offset, endian, &length)) { |
440 | 38 | return false; |
441 | 38 | } |
442 | 33.7k | offset += length; |
443 | 33.7k | } |
444 | 34.5k | } |
445 | 34.5k | } while (segment != kStartOfScan); |
446 | | |
447 | | // No width and hight information found. |
448 | 34 | return false; |
449 | 814 | } |
450 | | |
451 | | bool GetRational(const TiffDirectory::Tag& tag, const TiffDirectory& directory, |
452 | 3.67k | const int data_size, PreviewImageData::Rational* data) { |
453 | 3.67k | std::vector<Rational> value; |
454 | 3.67k | if (directory.Get(tag, &value) && |
455 | 1.69k | value.size() == static_cast<size_t>(data_size)) { |
456 | 3.33k | for (size_t i = 0; i < value.size(); ++i) { |
457 | 2.09k | data[i].numerator = value[i].numerator; |
458 | 2.09k | data[i].denominator = value[i].denominator; |
459 | 2.09k | } |
460 | 1.23k | return true; |
461 | 1.23k | } |
462 | 2.43k | return false; |
463 | 3.67k | } |
464 | | |
465 | 2.04k | bool IsThumbnail(const Image& image, const int max_dimension) { |
466 | 2.04k | return image.width <= max_dimension && image.height <= max_dimension; |
467 | 2.04k | } |
468 | | |
469 | | bool ParseDirectory(const std::uint32_t tiff_offset, |
470 | | const std::uint32_t ifd_offset, const Endian endian, |
471 | | const TagSet& desired_tags, StreamInterface* stream, |
472 | | TiffDirectory* tiff_directory, |
473 | 6.77k | std::uint32_t* next_ifd_offset) { |
474 | 6.77k | std::uint16_t number_of_entries; |
475 | 6.77k | if (!Get16u(stream, ifd_offset, endian, &number_of_entries)) { |
476 | 138 | return false; |
477 | 138 | } |
478 | | |
479 | 6.64k | for (std::uint32_t i = 0; |
480 | 20.6M | i < static_cast<std::uint32_t>(number_of_entries) * 12; i += 12) { |
481 | 20.6M | std::uint16_t tag; |
482 | 20.6M | std::uint16_t type; |
483 | 20.6M | std::uint32_t number_of_elements; |
484 | 20.6M | if (Get16u(stream, ifd_offset + 2 + i, endian, &tag) && |
485 | 20.6M | Get16u(stream, ifd_offset + 4 + i, endian, &type) && |
486 | 20.6M | Get32u(stream, ifd_offset + 6 + i, endian, &number_of_elements)) { |
487 | | // Check if the current tag should be handled. |
488 | 20.6M | if (desired_tags.count(static_cast<TiffDirectory::Tag>(tag)) != 1) { |
489 | 20.2M | continue; |
490 | 20.2M | } |
491 | 20.6M | } else { |
492 | 62 | return false; |
493 | 62 | } |
494 | | |
495 | 324k | const size_t type_size = SizeOfType(type, nullptr /* no error */); |
496 | | |
497 | | // Check that type_size * number_of_elements does not exceed UINT32_MAX. |
498 | 324k | if (type_size != 0 && number_of_elements > UINT32_MAX / type_size) { |
499 | 18 | return false; |
500 | 18 | } |
501 | 324k | const size_t byte_count = |
502 | 324k | type_size * static_cast<size_t>(number_of_elements); |
503 | | |
504 | 324k | std::uint32_t value_offset; |
505 | 324k | if (byte_count > 4 && |
506 | 135k | Get32u(stream, ifd_offset + 10 + i, endian, &value_offset)) { |
507 | 135k | value_offset += tiff_offset; |
508 | 188k | } else if (byte_count != 0) { |
509 | 67.8k | value_offset = ifd_offset + 10 + i; |
510 | 120k | } else { |
511 | | // Ignore entries with an invalid byte count. |
512 | 120k | continue; |
513 | 120k | } |
514 | | |
515 | 203k | Error error = kOk; |
516 | 203k | const std::vector<std::uint8_t> data = |
517 | 203k | GetData(value_offset, byte_count, stream, &error); |
518 | 203k | if (error != kOk) { |
519 | 251 | return false; |
520 | 251 | } |
521 | 203k | tiff_directory->AddEntry(tag, type, number_of_elements, value_offset, data); |
522 | 203k | } |
523 | | |
524 | 6.30k | return Get32u(stream, ifd_offset + 2u + number_of_entries * 12u, endian, |
525 | 6.30k | next_ifd_offset); |
526 | 6.64k | } |
527 | | |
528 | | bool GetExifOrientation(StreamInterface* stream, const std::uint32_t offset, |
529 | 0 | std::uint32_t* orientation) { |
530 | 0 | const TagSet kOrientationTagSet = {kTiffTagOrientation}; |
531 | 0 | const std::uint32_t kNumberOfIfds = 1; |
532 | |
|
533 | 0 | TiffContent tiff_content; |
534 | 0 | if (!TiffParser(stream, offset) |
535 | 0 | .Parse(kOrientationTagSet, kNumberOfIfds, &tiff_content)) { |
536 | 0 | return false; |
537 | 0 | } |
538 | | |
539 | 0 | for (const auto& tiff_directory : tiff_content.tiff_directory) { |
540 | 0 | if (tiff_directory.Has(kTiffTagOrientation) && |
541 | 0 | tiff_directory.Get(kTiffTagOrientation, orientation)) { |
542 | 0 | return true; |
543 | 0 | } |
544 | 0 | } |
545 | | |
546 | 0 | return false; |
547 | 0 | } |
548 | | |
549 | | bool GetFullDimension32(const TiffDirectory& tiff_directory, |
550 | 5.43k | std::uint32_t* width, std::uint32_t* height) { |
551 | | // The sub file type needs to be 0 (main image) to contain a valid full |
552 | | // dimensions. This is important in particular for DNG. |
553 | 5.43k | if (tiff_directory.Has(kTiffTagSubFileType)) { |
554 | 0 | std::uint32_t sub_file_type; |
555 | 0 | if (!tiff_directory.Get(kTiffTagSubFileType, &sub_file_type) || |
556 | 0 | sub_file_type != 0) { |
557 | 0 | return false; |
558 | 0 | } |
559 | 0 | } |
560 | | |
561 | 5.43k | if (tiff_directory.Has(kExifTagDefaultCropSize)) { |
562 | 2.37k | if (!GetFullCropDimension(tiff_directory, width, height)) { |
563 | 342 | return false; |
564 | 342 | } |
565 | 3.05k | } else if (tiff_directory.Has(kExifTagWidth) && |
566 | 73 | tiff_directory.Has(kExifTagHeight)) { |
567 | 28 | if (!tiff_directory.Get(kExifTagWidth, width) || |
568 | 25 | !tiff_directory.Get(kExifTagHeight, height)) { |
569 | 9 | return false; |
570 | 9 | } |
571 | 3.02k | } else if (tiff_directory.Has(kTiffTagImageWidth) && |
572 | 1.05k | tiff_directory.Has(kTiffTagImageLength)) { |
573 | 649 | if (!tiff_directory.Get(kTiffTagImageWidth, width) || |
574 | 600 | !tiff_directory.Get(kTiffTagImageLength, height)) { |
575 | 113 | return false; |
576 | 113 | } |
577 | 2.38k | } else if (tiff_directory.Has(kPanaTagTopBorder) && |
578 | 101 | tiff_directory.Has(kPanaTagLeftBorder) && |
579 | 96 | tiff_directory.Has(kPanaTagBottomBorder) && |
580 | 92 | tiff_directory.Has(kPanaTagRightBorder)) { |
581 | 85 | std::uint32_t left; |
582 | 85 | std::uint32_t right; |
583 | 85 | std::uint32_t top; |
584 | 85 | std::uint32_t bottom; |
585 | 85 | if (tiff_directory.Get(kPanaTagLeftBorder, &left) && |
586 | 84 | tiff_directory.Get(kPanaTagRightBorder, &right) && |
587 | 82 | tiff_directory.Get(kPanaTagTopBorder, &top) && |
588 | 81 | tiff_directory.Get(kPanaTagBottomBorder, &bottom) && bottom > top && |
589 | 76 | right > left) { |
590 | 54 | *height = bottom - top; |
591 | 54 | *width = right - left; |
592 | 54 | } else { |
593 | 31 | return false; |
594 | 31 | } |
595 | 85 | } |
596 | 4.93k | return true; |
597 | 5.43k | } |
598 | | |
599 | | bool GetFullCropDimension(const tiff_directory::TiffDirectory& tiff_directory, |
600 | 2.75k | std::uint32_t* width, std::uint32_t* height) { |
601 | 2.75k | if (!tiff_directory.Has(kExifTagDefaultCropSize)) { |
602 | | // This doesn't look right to return true here, as we have not written |
603 | | // anything to *width and *height. However, changing the return value here |
604 | | // causes a whole bunch of tests to fail. |
605 | | // TODO(timurrrr): Return false and fix the tests. |
606 | | // In fact, this whole if() seems to be not needed, |
607 | | // as tiff_directory(kExifTagDefaultCropSize) will return false below. |
608 | 140 | return true; |
609 | 140 | } |
610 | | |
611 | 2.61k | std::vector<std::uint32_t> crop(2); |
612 | 2.61k | if (tiff_directory.Get(kExifTagDefaultCropSize, &crop)) { |
613 | 983 | if (crop.size() == 2 && crop[0] > 0 && crop[1] > 0) { |
614 | 830 | *width = crop[0]; |
615 | 830 | *height = crop[1]; |
616 | 830 | return true; |
617 | 830 | } else { |
618 | 153 | return false; |
619 | 153 | } |
620 | 983 | } |
621 | | |
622 | 1.62k | std::vector<Rational> crop_rational(2); |
623 | 1.62k | if (tiff_directory.Get(kExifTagDefaultCropSize, &crop_rational)) { |
624 | 1.52k | if (crop_rational.size() == 2 && crop_rational[0].numerator > 0 && |
625 | 1.45k | crop_rational[0].denominator > 0 && crop_rational[1].numerator > 0 && |
626 | 1.42k | crop_rational[1].denominator > 0) { |
627 | 1.42k | *width = crop_rational[0].numerator / crop_rational[0].denominator; |
628 | 1.42k | *height = crop_rational[1].numerator / crop_rational[1].denominator; |
629 | 1.42k | return true; |
630 | 1.42k | } else { |
631 | 98 | return false; |
632 | 98 | } |
633 | 1.52k | } |
634 | | |
635 | 103 | return false; |
636 | 1.62k | } |
637 | | |
638 | 0 | TiffParser::TiffParser(StreamInterface* stream) : stream_(stream) {} |
639 | | |
640 | | TiffParser::TiffParser(StreamInterface* stream, const std::uint32_t offset) |
641 | 3.13k | : stream_(stream), tiff_offset_(offset) {} |
642 | | |
643 | | bool TiffParser::GetPreviewImageData(const TiffContent& tiff_content, |
644 | 1.49k | PreviewImageData* preview_image_data) { |
645 | 1.49k | bool success = true; |
646 | 2.54k | for (const auto& tiff_directory : tiff_content.tiff_directory) { |
647 | 2.54k | success = FillPreviewImageData(tiff_directory, stream_, preview_image_data); |
648 | 2.54k | if (success && tiff_directory.Has(kTiffTagExifIfd) && |
649 | 733 | tiff_content.exif_directory) { |
650 | 437 | success = FillPreviewImageData(*tiff_content.exif_directory, stream_, |
651 | 437 | preview_image_data); |
652 | 437 | } |
653 | 2.54k | if (success && tiff_directory.Has(kExifTagGps) && |
654 | 428 | tiff_content.gps_directory) { |
655 | 345 | FillGpsPreviewImageData(*tiff_content.gps_directory, preview_image_data); |
656 | 345 | } |
657 | 2.54k | for (const auto& sub_directory : tiff_directory.GetSubDirectories()) { |
658 | 1.49k | if (success) { |
659 | 1.13k | success = |
660 | 1.13k | FillPreviewImageData(sub_directory, stream_, preview_image_data); |
661 | 1.13k | } |
662 | 1.49k | } |
663 | 2.54k | } |
664 | 1.49k | return success; |
665 | 1.49k | } |
666 | | |
667 | | bool TiffParser::Parse(const TagSet& desired_tags, |
668 | | const std::uint16_t max_number_ifds, |
669 | 3.13k | TiffContent* tiff_content) { |
670 | 3.13k | if (!tiff_content->tiff_directory.empty()) { |
671 | 0 | return false; // You shall call Parse() only once. |
672 | 0 | } |
673 | | |
674 | 3.13k | const std::uint32_t kTiffIdentifierSize = 4; |
675 | 3.13k | std::uint32_t offset_to_ifd = 0; |
676 | 3.13k | if (!GetEndianness(tiff_offset_, stream_, &endian_) || |
677 | 3.04k | !Get32u(stream_, tiff_offset_ + kTiffIdentifierSize, endian_, |
678 | 3.04k | &offset_to_ifd)) { |
679 | 94 | return false; |
680 | 94 | } |
681 | | |
682 | 3.04k | if (!ParseIfd(tiff_offset_ + offset_to_ifd, desired_tags, max_number_ifds, |
683 | 3.04k | &tiff_content->tiff_directory)) { |
684 | 436 | return false; |
685 | 436 | } |
686 | | |
687 | | // Get the Exif data. |
688 | 2.60k | if (FindFirstTagInIfds(kTiffTagExifIfd, tiff_content->tiff_directory) != |
689 | 2.60k | nullptr) { |
690 | 591 | const TiffDirectory* tiff_ifd = |
691 | 591 | FindFirstTagInIfds(kTiffTagExifIfd, tiff_content->tiff_directory); |
692 | 591 | std::uint32_t offset; |
693 | 591 | if (tiff_ifd->Get(kTiffTagExifIfd, &offset)) { |
694 | 400 | tiff_content->exif_directory.reset(new TiffDirectory(endian_)); |
695 | 400 | std::uint32_t next_ifd_offset; |
696 | 400 | if (!ParseDirectory( |
697 | 400 | tiff_offset_, tiff_offset_ + offset, endian_, desired_tags, |
698 | 400 | stream_, tiff_content->exif_directory.get(), &next_ifd_offset)) { |
699 | 14 | return false; |
700 | 14 | } |
701 | | |
702 | 386 | return ParseGpsData(tiff_ifd, tiff_content); |
703 | 400 | } |
704 | 591 | } |
705 | | |
706 | | // Get the GPS data from the tiff ifd. |
707 | 2.20k | if (FindFirstTagInIfds(kExifTagGps, tiff_content->tiff_directory) != |
708 | 2.20k | nullptr) { |
709 | 281 | const TiffDirectory* tiff_ifd = |
710 | 281 | FindFirstTagInIfds(kExifTagGps, tiff_content->tiff_directory); |
711 | 281 | return ParseGpsData(tiff_ifd, tiff_content); |
712 | 281 | } |
713 | | |
714 | 1.92k | return true; |
715 | 2.20k | } |
716 | | |
717 | | bool TiffParser::ParseIfd(const std::uint32_t ifd_offset, |
718 | | const TagSet& desired_tags, |
719 | | const std::uint16_t max_number_ifds, |
720 | 4.27k | IfdVector* tiff_directory) { |
721 | 4.27k | std::uint32_t next_ifd_offset; |
722 | 4.27k | TiffDirectory tiff_ifd(static_cast<Endian>(endian_)); |
723 | 4.27k | if (!ParseDirectory(tiff_offset_, ifd_offset, endian_, desired_tags, stream_, |
724 | 4.27k | &tiff_ifd, &next_ifd_offset) || |
725 | 3.85k | !ParseSubIfds(tiff_offset_, desired_tags, max_number_ifds, endian_, |
726 | 3.85k | stream_, &tiff_ifd)) { |
727 | 436 | return false; |
728 | 436 | } |
729 | | |
730 | 3.83k | tiff_directory->push_back(tiff_ifd); |
731 | 3.83k | if (next_ifd_offset != 0 && tiff_directory->size() < max_number_ifds) { |
732 | 1.23k | return ParseIfd(tiff_offset_ + next_ifd_offset, desired_tags, |
733 | 1.23k | max_number_ifds, tiff_directory); |
734 | 1.23k | } |
735 | 2.60k | return true; |
736 | 3.83k | } |
737 | | |
738 | | bool TiffParser::ParseGpsData(const TiffDirectory* tiff_ifd, |
739 | 667 | TiffContent* tiff_content) { |
740 | 667 | std::uint32_t offset; |
741 | 667 | if (tiff_ifd->Get(kExifTagGps, &offset)) { |
742 | 278 | tiff_content->gps_directory.reset(new TiffDirectory(endian_)); |
743 | 278 | const TagSet gps_tags = {kGpsTagLatitudeRef, kGpsTagLatitude, |
744 | 278 | kGpsTagLongitudeRef, kGpsTagLongitude, |
745 | 278 | kGpsTagAltitudeRef, kGpsTagAltitude, |
746 | 278 | kGpsTagTimeStamp, kGpsTagDateStamp}; |
747 | 278 | std::uint32_t next_ifd_offset; |
748 | 278 | return ParseDirectory(tiff_offset_, tiff_offset_ + offset, endian_, |
749 | 278 | gps_tags, stream_, tiff_content->gps_directory.get(), |
750 | 278 | &next_ifd_offset); |
751 | 278 | } |
752 | 389 | return true; |
753 | 667 | } |
754 | | |
755 | | } // namespace piex |