blob: 3ceaa751a1e190d765557ce3da97c92e5454f707 [file] [log] [blame]
Jaesung Chung29345532016-01-14 02:28:47 +09001// 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>
Yujie Qin2f5d8ea2016-04-06 12:55:08 +020020#include <limits>
21#include <numeric>
Jaesung Chung29345532016-01-14 02:28:47 +090022
23#include "src/tiff_directory/tiff_directory.h"
24
25namespace piex {
26namespace {
27
28using tiff_directory::Endian;
29using tiff_directory::Rational;
Jaesung Chung29345532016-01-14 02:28:47 +090030using tiff_directory::SizeOfType;
31using tiff_directory::TIFF_TYPE_LONG;
32using tiff_directory::TIFF_TYPE_UNDEFINED;
33using tiff_directory::TiffDirectory;
34using tiff_directory::kBigEndian;
35using tiff_directory::kLittleEndian;
36
37// Specifies all tags that might be of interest to parse JPEG data.
38const std::uint32_t kStartOfFrame = 0xFFC0;
39const std::uint32_t kStartOfImage = 0xFFD8;
40const std::uint32_t kStartOfScan = 0xFFDA;
41
Yujie Qin2f5d8ea2016-04-06 12:55:08 +020042bool GetFullDimension16(const TiffDirectory& tiff_directory,
43 std::uint16_t* width, std::uint16_t* height) {
44 std::uint32_t tmp_width = 0;
45 std::uint32_t tmp_height = 0;
46 if (!GetFullDimension32(tiff_directory, &tmp_width, &tmp_height) ||
47 tmp_width > std::numeric_limits<std::uint16_t>::max() ||
48 tmp_height > std::numeric_limits<std::uint16_t>::max()) {
49 return false;
Jaesung Chung29345532016-01-14 02:28:47 +090050 }
Yujie Qin2f5d8ea2016-04-06 12:55:08 +020051 *width = static_cast<std::uint16_t>(tmp_width);
52 *height = static_cast<std::uint16_t>(tmp_height);
Jaesung Chung29345532016-01-14 02:28:47 +090053 return true;
54}
55
Jaesung Chung29345532016-01-14 02:28:47 +090056void FillGpsPreviewImageData(const TiffDirectory& gps_directory,
57 PreviewImageData* preview_image_data) {
58 if (gps_directory.Has(kGpsTagLatitudeRef) &&
59 gps_directory.Has(kGpsTagLatitude) &&
60 gps_directory.Has(kGpsTagLongitudeRef) &&
61 gps_directory.Has(kGpsTagLongitude) &&
62 gps_directory.Has(kGpsTagTimeStamp) &&
63 gps_directory.Has(kGpsTagDateStamp)) {
64 preview_image_data->gps.is_valid = false;
65 std::string value;
66 if (!gps_directory.Get(kGpsTagLatitudeRef, &value) || value.empty() ||
67 (value[0] != 'N' && value[0] != 'S') ||
68 !GetRational(kGpsTagLatitude, gps_directory, 3 /* data size */,
69 preview_image_data->gps.latitude)) {
70 return;
71 }
72 preview_image_data->gps.latitude_ref = value[0];
73
74 if (!gps_directory.Get(kGpsTagLongitudeRef, &value) || value.empty() ||
75 (value[0] != 'E' && value[0] != 'W') ||
76 !GetRational(kGpsTagLongitude, gps_directory, 3 /* data size */,
77 preview_image_data->gps.longitude)) {
78 return;
79 }
80 preview_image_data->gps.longitude_ref = value[0];
81
82 if (!GetRational(kGpsTagTimeStamp, gps_directory, 3 /* data size */,
83 preview_image_data->gps.time_stamp)) {
84 return;
85 }
86
Yujie Qinafc6ebd2016-01-25 13:12:07 +010087 const size_t kGpsDateStampSize = 11;
Jaesung Chung29345532016-01-14 02:28:47 +090088 if (!gps_directory.Get(kGpsTagDateStamp,
89 &preview_image_data->gps.date_stamp)) {
90 return;
91 }
92 if (preview_image_data->gps.date_stamp.size() == kGpsDateStampSize) {
93 // Resize the date_stamp to remove the "NULL" at the end of string.
94 preview_image_data->gps.date_stamp.resize(kGpsDateStampSize - 1);
95 } else {
96 return;
97 }
98
99 if (gps_directory.Has(kGpsTagAltitudeRef) &&
100 gps_directory.Has(kGpsTagAltitude)) {
101 std::vector<std::uint8_t> bytes;
102 if (!gps_directory.Get(kGpsTagAltitudeRef, &bytes) || bytes.empty() ||
103 !GetRational(kGpsTagAltitude, gps_directory, 1,
104 &preview_image_data->gps.altitude)) {
105 return;
106 }
107 preview_image_data->gps.altitude_ref = bytes[0] != 0;
108 }
109 preview_image_data->gps.is_valid = true;
110 }
111}
112
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200113void GetImageSize(const TiffDirectory& tiff_directory, StreamInterface* stream,
114 Image* image) {
115 switch (image->format) {
116 case Image::kUncompressedRgb: {
117 GetFullDimension16(tiff_directory, &image->width, &image->height);
118 break;
Jaesung Chung29345532016-01-14 02:28:47 +0900119 }
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200120 case Image::kJpegCompressed: {
121 GetJpegDimensions(image->offset, stream, &image->width, &image->height);
122 break;
Jaesung Chung29345532016-01-14 02:28:47 +0900123 }
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200124 default: { return; }
125 }
126}
Eik Brauer883a1e52016-02-23 12:19:25 +0100127
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200128bool FillPreviewImageData(const TiffDirectory& tiff_directory,
129 StreamInterface* stream,
130 PreviewImageData* preview_image_data) {
131 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 Image image;
135 if (GetImageData(tiff_directory, stream, &image)) {
136 if (IsThumbnail(image)) {
137 preview_image_data->thumbnail = image;
138 } else if (image.format == Image::kJpegCompressed) {
139 preview_image_data->preview = image;
Jaesung Chung29345532016-01-14 02:28:47 +0900140 }
141 }
142
Yujie Qin3eaa8312016-01-15 16:22:20 +0100143 // Get exif_orientation if it was not set already.
144 if (tiff_directory.Has(kTiffTagOrientation) &&
145 preview_image_data->exif_orientation == 1) {
Jaesung Chung29345532016-01-14 02:28:47 +0900146 success &= tiff_directory.Get(kTiffTagOrientation,
147 &preview_image_data->exif_orientation);
148 }
149
150 // Get color_space
151 if (tiff_directory.Has(kExifTagColorSpace)) {
152 std::uint32_t color_space;
Leon Scroggins IIIbb217ac2018-03-09 16:23:15 -0500153 if (tiff_directory.Get(kExifTagColorSpace, &color_space)) {
154 if (color_space == 1) {
155 preview_image_data->color_space = PreviewImageData::kSrgb;
156 } else if (color_space == 65535 || color_space == 2) {
157 preview_image_data->color_space = PreviewImageData::kAdobeRgb;
158 }
159 } else {
160 success = false;
Jaesung Chung29345532016-01-14 02:28:47 +0900161 }
162 }
163
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200164 success &= GetFullDimension32(tiff_directory, &preview_image_data->full_width,
165 &preview_image_data->full_height);
Jaesung Chung29345532016-01-14 02:28:47 +0900166
167 if (tiff_directory.Has(kTiffTagMake)) {
168 success &= tiff_directory.Get(kTiffTagMake, &preview_image_data->maker);
169 }
170
171 if (tiff_directory.Has(kTiffTagModel)) {
172 success &= tiff_directory.Get(kTiffTagModel, &preview_image_data->model);
173 }
174
Eik Brauerb415ce22016-02-01 12:26:23 +0100175 if (tiff_directory.Has(kTiffTagCfaPatternDim)) {
176 std::vector<std::uint32_t> cfa_pattern_dim;
177 if (tiff_directory.Get(kTiffTagCfaPatternDim, &cfa_pattern_dim) &&
178 cfa_pattern_dim.size() == 2) {
179 preview_image_data->cfa_pattern_dim[0] = cfa_pattern_dim[0];
180 preview_image_data->cfa_pattern_dim[1] = cfa_pattern_dim[1];
181 }
182 }
183
Jaesung Chung29345532016-01-14 02:28:47 +0900184 if (tiff_directory.Has(kExifTagDateTimeOriginal)) {
185 success &= tiff_directory.Get(kExifTagDateTimeOriginal,
186 &preview_image_data->date_time);
187 }
188
189 if (tiff_directory.Has(kExifTagIsoSpeed)) {
190 success &= tiff_directory.Get(kExifTagIsoSpeed, &preview_image_data->iso);
191 } else if (tiff_directory.Has(kPanaTagIso)) {
192 success &= tiff_directory.Get(kPanaTagIso, &preview_image_data->iso);
193 }
194
195 if (tiff_directory.Has(kExifTagExposureTime)) {
196 success &= GetRational(kExifTagExposureTime, tiff_directory, 1,
197 &preview_image_data->exposure_time);
198 }
199
200 if (tiff_directory.Has(kExifTagFnumber)) {
201 success &= GetRational(kExifTagFnumber, tiff_directory, 1,
202 &preview_image_data->fnumber);
203 }
204
205 if (tiff_directory.Has(kExifTagFocalLength)) {
206 success &= GetRational(kExifTagFocalLength, tiff_directory, 1,
207 &preview_image_data->focal_length);
208 }
209
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200210 return success;
Jaesung Chung29345532016-01-14 02:28:47 +0900211}
212
Eik Brauera9540112016-01-27 13:25:41 +0100213const TiffDirectory* FindFirstTagInIfds(const TiffDirectory::Tag& tag,
Jaesung Chung29345532016-01-14 02:28:47 +0900214 const IfdVector& tiff_directory) {
215 for (std::uint32_t i = 0; i < tiff_directory.size(); ++i) {
216 if (tiff_directory[i].Has(tag)) {
217 return &tiff_directory[i];
218 }
219
220 // Recursively search sub directories.
221 const TiffDirectory* sub_directory =
222 FindFirstTagInIfds(tag, tiff_directory[i].GetSubDirectories());
223 if (sub_directory != NULL) {
224 return sub_directory;
225 }
226 }
227 return NULL;
228}
229
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200230// Return true if all data blocks are ordered one after the other without gaps.
231bool OffsetsAreConsecutive(
232 const std::vector<std::uint32_t>& strip_offsets,
233 const std::vector<std::uint32_t>& strip_byte_counts) {
234 if (strip_offsets.size() != strip_byte_counts.size() ||
235 strip_offsets.empty()) {
236 return false;
237 }
238
239 for (size_t i = 0; i < strip_offsets.size() - 1; ++i) {
240 if (strip_offsets[i] + strip_byte_counts[i] != strip_offsets[i + 1]) {
241 return false;
242 }
243 }
244 return true;
245}
246
Jaesung Chung29345532016-01-14 02:28:47 +0900247// Gets the SubIfd content.
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200248bool ParseSubIfds(const std::uint32_t tiff_offset, const TagSet& desired_tags,
Jaesung Chung29345532016-01-14 02:28:47 +0900249 const std::uint32_t max_number_ifds, const Endian endian,
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200250 StreamInterface* stream, TiffDirectory* tiff_ifd) {
251 if (tiff_ifd->Has(kTiffTagSubIfd)) {
Jaesung Chung29345532016-01-14 02:28:47 +0900252 std::uint32_t offset = 0;
253 std::uint32_t length = 0;
254 tiff_ifd->GetOffsetAndLength(kTiffTagSubIfd, TIFF_TYPE_LONG, &offset,
255 &length);
256 length /= 4; // length in bytes divided by 4 gives number of IFDs.
257 for (std::uint32_t j = 0; j < length && j < max_number_ifds; ++j) {
258 std::uint32_t sub_offset;
259 if (!Get32u(stream, offset + 4 * j, endian, &sub_offset)) {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200260 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900261 }
262
263 std::uint32_t next_ifd_offset;
264 TiffDirectory sub_ifd(static_cast<Endian>(endian));
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200265 if (!ParseDirectory(tiff_offset, sub_offset, endian, desired_tags, stream,
266 &sub_ifd, &next_ifd_offset)) {
267 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900268 }
269
270 tiff_ifd->AddSubDirectory(sub_ifd);
271 }
272 }
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200273 return true;
Jaesung Chung29345532016-01-14 02:28:47 +0900274}
275
276} // namespace
277
278bool Get16u(StreamInterface* stream, const std::uint32_t offset,
279 const Endian& endian, std::uint16_t* value) {
280 std::uint8_t data[2];
281 if (stream->GetData(offset, 2, data) == kOk) {
282 if (endian == kBigEndian) {
283 *value = (data[0] * 0x100) | data[1];
284 } else {
285 *value = (data[1] * 0x100) | data[0];
286 }
287 return true;
288 } else {
289 return false;
290 }
291}
292
293bool Get32u(StreamInterface* stream, const std::uint32_t offset,
294 const Endian& endian, std::uint32_t* value) {
295 std::uint8_t data[4];
296 if (stream->GetData(offset, 4, data) == kOk) {
297 if (endian == kBigEndian) {
Eik Brauera9540112016-01-27 13:25:41 +0100298 *value = (data[0] * 0x1000000u) | (data[1] * 0x10000u) |
299 (data[2] * 0x100u) | data[3];
Jaesung Chung29345532016-01-14 02:28:47 +0900300 } else {
Eik Brauera9540112016-01-27 13:25:41 +0100301 *value = (data[3] * 0x1000000u) | (data[2] * 0x10000u) |
302 (data[1] * 0x100u) | data[0];
Jaesung Chung29345532016-01-14 02:28:47 +0900303 }
304 return true;
305 } else {
306 return false;
307 }
308}
309
310std::vector<std::uint8_t> GetData(const size_t offset, const size_t length,
311 StreamInterface* stream, Error* error) {
312 // Read in chunks with a maximum size of 1 MiB.
313 const size_t kChunkSize = 1048576;
314
315 std::vector<std::uint8_t> data;
316 size_t processed_data = 0;
317 while (*error == kOk && processed_data < length) {
318 size_t chunk_length = kChunkSize;
319 if (length - data.size() < kChunkSize) {
320 chunk_length = length - data.size();
321 }
322
323 data.resize(processed_data + chunk_length);
324 *error = stream->GetData(offset + processed_data, chunk_length,
325 &data[processed_data]);
326
327 processed_data += chunk_length;
328 }
329 return data;
330}
331
332bool GetEndianness(const std::uint32_t tiff_offset, StreamInterface* stream,
333 Endian* endian) {
334 const std::uint8_t kTiffBigEndianMagic[] = {'M', 'M'};
335 const std::uint8_t kTiffLittleEndianMagic[] = {'I', 'I'};
336 std::uint8_t tiff_endian[sizeof(kTiffBigEndianMagic)];
337 if (stream->GetData(tiff_offset, sizeof(tiff_endian), &tiff_endian[0]) !=
338 kOk) {
339 return false;
340 }
341
342 if (!memcmp(tiff_endian, kTiffLittleEndianMagic, sizeof(tiff_endian))) {
343 *endian = kLittleEndian;
344 return true;
345 } else if (!memcmp(tiff_endian, kTiffBigEndianMagic, sizeof(tiff_endian))) {
346 *endian = kBigEndian;
347 return true;
348 } else {
349 return false;
350 }
351}
352
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200353bool GetImageData(const TiffDirectory& tiff_directory, StreamInterface* stream,
354 Image* image) {
355 std::uint32_t length = 0;
356 std::uint32_t offset = 0;
357
358 if (tiff_directory.Has(kTiffTagJpegOffset) &&
359 tiff_directory.Has(kTiffTagJpegByteCount)) {
360 if (!tiff_directory.Get(kTiffTagJpegOffset, &offset) ||
361 !tiff_directory.Get(kTiffTagJpegByteCount, &length)) {
362 return false;
363 }
364 image->format = Image::kJpegCompressed;
365 } else if (tiff_directory.Has(kTiffTagStripOffsets) &&
366 tiff_directory.Has(kTiffTagStripByteCounts)) {
367 std::vector<std::uint32_t> strip_offsets;
368 std::vector<std::uint32_t> strip_byte_counts;
369 if (!tiff_directory.Get(kTiffTagStripOffsets, &strip_offsets) ||
370 !tiff_directory.Get(kTiffTagStripByteCounts, &strip_byte_counts)) {
371 return false;
372 }
373
374 std::uint32_t compression = 0;
375 if (!OffsetsAreConsecutive(strip_offsets, strip_byte_counts) ||
376 !tiff_directory.Get(kTiffTagCompression, &compression)) {
377 return false;
378 }
379
380 std::uint32_t photometric_interpretation = 0;
381 if (tiff_directory.Get(kTiffTagPhotometric, &photometric_interpretation) &&
382 photometric_interpretation != 2 /* RGB */ &&
383 photometric_interpretation != 6 /* YCbCr */) {
384 return false;
385 }
386
387 switch (compression) {
388 case 1: /*uncompressed*/
389 image->format = Image::kUncompressedRgb;
390 break;
391 case 6: /* JPEG(old) */
392 case 7: /* JPEG */
393 image->format = Image::kJpegCompressed;
394 break;
395 default:
396 return false;
397 }
Nick Chusidff6a5f92021-06-08 15:07:14 -0400398 length = static_cast<std::uint32_t>(std::accumulate(
399 strip_byte_counts.begin(), strip_byte_counts.end(), 0U));
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200400 offset = strip_offsets[0];
401 } else if (tiff_directory.Has(kPanaTagJpegImage)) {
402 if (!tiff_directory.GetOffsetAndLength(
403 kPanaTagJpegImage, TIFF_TYPE_UNDEFINED, &offset, &length)) {
404 return false;
405 }
406 image->format = Image::kJpegCompressed;
407 } else {
408 return false;
409 }
410
411 image->length = length;
412 image->offset = offset;
413 GetImageSize(tiff_directory, stream, image);
414 return true;
415}
416
417bool GetJpegDimensions(const std::uint32_t jpeg_offset, StreamInterface* stream,
418 std::uint16_t* width, std::uint16_t* height) {
Jaesung Chung29345532016-01-14 02:28:47 +0900419 const Endian endian = kBigEndian;
420 std::uint32_t offset = jpeg_offset;
421 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 do {
426 if (!Get16u(stream, offset, endian, &segment)) {
427 return false;
428 }
429 offset += 2;
430
431 switch (segment) {
432 case kStartOfImage:
433 break;
434 case kStartOfFrame:
435 return Get16u(stream, offset + 3, endian, height) &&
436 Get16u(stream, offset + 5, endian, width);
437 default: {
438 std::uint16_t length;
439 if (!Get16u(stream, offset, endian, &length)) {
440 return false;
441 }
442 offset += length;
443 }
444 }
445 } while (segment != kStartOfScan);
446
447 // No width and hight information found.
448 return false;
449}
450
Nick Chusid5c5b6712021-06-03 15:33:24 -0400451bool GetRational(const TiffDirectory::Tag& tag, const TiffDirectory& directory,
452 const int data_size, PreviewImageData::Rational* data) {
453 std::vector<Rational> value;
454 if (directory.Get(tag, &value) &&
455 value.size() == static_cast<size_t>(data_size)) {
456 for (size_t i = 0; i < value.size(); ++i) {
457 data[i].numerator = value[i].numerator;
458 data[i].denominator = value[i].denominator;
459 }
460 return true;
461 }
462 return false;
463}
464
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200465bool IsThumbnail(const Image& image, const int max_dimension) {
466 return image.width <= max_dimension && image.height <= max_dimension;
467}
468
469bool 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 std::uint32_t* next_ifd_offset) {
Jaesung Chung29345532016-01-14 02:28:47 +0900474 std::uint16_t number_of_entries;
475 if (!Get16u(stream, ifd_offset, endian, &number_of_entries)) {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200476 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900477 }
478
479 for (std::uint32_t i = 0;
480 i < static_cast<std::uint32_t>(number_of_entries) * 12; i += 12) {
481 std::uint16_t tag;
482 std::uint16_t type;
483 std::uint32_t number_of_elements;
484 if (Get16u(stream, ifd_offset + 2 + i, endian, &tag) &&
485 Get16u(stream, ifd_offset + 4 + i, endian, &type) &&
486 Get32u(stream, ifd_offset + 6 + i, endian, &number_of_elements)) {
487 // Check if the current tag should be handled.
Eik Brauera9540112016-01-27 13:25:41 +0100488 if (desired_tags.count(static_cast<TiffDirectory::Tag>(tag)) != 1) {
Jaesung Chung29345532016-01-14 02:28:47 +0900489 continue;
490 }
491 } else {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200492 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900493 }
494
495 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 if (type_size != 0 && number_of_elements > UINT32_MAX / type_size) {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200499 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900500 }
501 const size_t byte_count =
502 type_size * static_cast<size_t>(number_of_elements);
503
504 std::uint32_t value_offset;
505 if (byte_count > 4 &&
506 Get32u(stream, ifd_offset + 10 + i, endian, &value_offset)) {
507 value_offset += tiff_offset;
508 } else if (byte_count != 0) {
509 value_offset = ifd_offset + 10 + i;
510 } else {
511 // Ignore entries with an invalid byte count.
512 continue;
513 }
514
515 Error error = kOk;
516 const std::vector<std::uint8_t> data =
517 GetData(value_offset, byte_count, stream, &error);
518 if (error != kOk) {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200519 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900520 }
521 tiff_directory->AddEntry(tag, type, number_of_elements, value_offset, data);
522 }
523
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200524 return Get32u(stream, ifd_offset + 2u + number_of_entries * 12u, endian,
525 next_ifd_offset);
526}
527
528bool GetExifOrientation(StreamInterface* stream, const std::uint32_t offset,
529 std::uint32_t* orientation) {
530 const TagSet kOrientationTagSet = {kTiffTagOrientation};
531 const std::uint32_t kNumberOfIfds = 1;
532
533 TiffContent tiff_content;
534 if (!TiffParser(stream, offset)
535 .Parse(kOrientationTagSet, kNumberOfIfds, &tiff_content)) {
536 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900537 }
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200538
539 for (const auto& tiff_directory : tiff_content.tiff_directory) {
540 if (tiff_directory.Has(kTiffTagOrientation) &&
541 tiff_directory.Get(kTiffTagOrientation, orientation)) {
542 return true;
543 }
544 }
545
546 return false;
547}
548
549bool GetFullDimension32(const TiffDirectory& tiff_directory,
550 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 if (tiff_directory.Has(kTiffTagSubFileType)) {
554 std::uint32_t sub_file_type;
555 if (!tiff_directory.Get(kTiffTagSubFileType, &sub_file_type) ||
556 sub_file_type != 0) {
557 return false;
558 }
559 }
560
Yujie Qinbe908192016-04-14 14:40:21 +0200561 if (tiff_directory.Has(kExifTagDefaultCropSize)) {
Yujie Qin8f540f62016-11-23 12:37:05 +0100562 if (!GetFullCropDimension(tiff_directory, width, height)) {
Yujie Qinbe908192016-04-14 14:40:21 +0200563 return false;
564 }
565 } else if (tiff_directory.Has(kExifTagWidth) &&
566 tiff_directory.Has(kExifTagHeight)) {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200567 if (!tiff_directory.Get(kExifTagWidth, width) ||
568 !tiff_directory.Get(kExifTagHeight, height)) {
569 return false;
570 }
571 } else if (tiff_directory.Has(kTiffTagImageWidth) &&
572 tiff_directory.Has(kTiffTagImageLength)) {
573 if (!tiff_directory.Get(kTiffTagImageWidth, width) ||
574 !tiff_directory.Get(kTiffTagImageLength, height)) {
575 return false;
576 }
577 } else if (tiff_directory.Has(kPanaTagTopBorder) &&
578 tiff_directory.Has(kPanaTagLeftBorder) &&
579 tiff_directory.Has(kPanaTagBottomBorder) &&
580 tiff_directory.Has(kPanaTagRightBorder)) {
581 std::uint32_t left;
582 std::uint32_t right;
583 std::uint32_t top;
584 std::uint32_t bottom;
585 if (tiff_directory.Get(kPanaTagLeftBorder, &left) &&
586 tiff_directory.Get(kPanaTagRightBorder, &right) &&
587 tiff_directory.Get(kPanaTagTopBorder, &top) &&
588 tiff_directory.Get(kPanaTagBottomBorder, &bottom) && bottom > top &&
589 right > left) {
590 *height = bottom - top;
591 *width = right - left;
592 } else {
593 return false;
594 }
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200595 }
596 return true;
Jaesung Chung29345532016-01-14 02:28:47 +0900597}
598
Yujie Qin8f540f62016-11-23 12:37:05 +0100599bool GetFullCropDimension(const tiff_directory::TiffDirectory& tiff_directory,
600 std::uint32_t* width, std::uint32_t* height) {
Leon Scroggins IIIf7fc9052018-02-21 06:46:27 -0500601 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 return true;
609 }
610
611 std::vector<std::uint32_t> crop(2);
612 if (tiff_directory.Get(kExifTagDefaultCropSize, &crop)) {
613 if (crop.size() == 2 && crop[0] > 0 && crop[1] > 0) {
Yujie Qin8f540f62016-11-23 12:37:05 +0100614 *width = crop[0];
615 *height = crop[1];
Leon Scroggins IIIf7fc9052018-02-21 06:46:27 -0500616 return true;
Yujie Qin8f540f62016-11-23 12:37:05 +0100617 } else {
618 return false;
619 }
620 }
621
Leon Scroggins IIIf7fc9052018-02-21 06:46:27 -0500622 std::vector<Rational> crop_rational(2);
623 if (tiff_directory.Get(kExifTagDefaultCropSize, &crop_rational)) {
624 if (crop_rational.size() == 2 && crop_rational[0].numerator > 0 &&
625 crop_rational[0].denominator > 0 && crop_rational[1].numerator > 0 &&
626 crop_rational[1].denominator > 0) {
627 *width = crop_rational[0].numerator / crop_rational[0].denominator;
628 *height = crop_rational[1].numerator / crop_rational[1].denominator;
629 return true;
630 } else {
631 return false;
632 }
633 }
634
635 return false;
Yujie Qin8f540f62016-11-23 12:37:05 +0100636}
637
Jaesung Chung29345532016-01-14 02:28:47 +0900638TiffParser::TiffParser(StreamInterface* stream) : stream_(stream) {}
639
640TiffParser::TiffParser(StreamInterface* stream, const std::uint32_t offset)
641 : stream_(stream), tiff_offset_(offset) {}
642
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200643bool TiffParser::GetPreviewImageData(const TiffContent& tiff_content,
644 PreviewImageData* preview_image_data) {
645 bool success = true;
Jaesung Chung29345532016-01-14 02:28:47 +0900646 for (const auto& tiff_directory : tiff_content.tiff_directory) {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200647 success = FillPreviewImageData(tiff_directory, stream_, preview_image_data);
648 if (success && tiff_directory.Has(kTiffTagExifIfd) &&
Jaesung Chung29345532016-01-14 02:28:47 +0900649 tiff_content.exif_directory) {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200650 success = FillPreviewImageData(*tiff_content.exif_directory, stream_,
651 preview_image_data);
Jaesung Chung29345532016-01-14 02:28:47 +0900652 }
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200653 if (success && tiff_directory.Has(kExifTagGps) &&
Jaesung Chung29345532016-01-14 02:28:47 +0900654 tiff_content.gps_directory) {
655 FillGpsPreviewImageData(*tiff_content.gps_directory, preview_image_data);
656 }
657 for (const auto& sub_directory : tiff_directory.GetSubDirectories()) {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200658 if (success) {
659 success =
660 FillPreviewImageData(sub_directory, stream_, preview_image_data);
Jaesung Chung29345532016-01-14 02:28:47 +0900661 }
662 }
663 }
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200664 return success;
Jaesung Chung29345532016-01-14 02:28:47 +0900665}
666
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200667bool TiffParser::Parse(const TagSet& desired_tags,
668 const std::uint16_t max_number_ifds,
669 TiffContent* tiff_content) {
Jaesung Chung29345532016-01-14 02:28:47 +0900670 if (!tiff_content->tiff_directory.empty()) {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200671 return false; // You shall call Parse() only once.
Jaesung Chung29345532016-01-14 02:28:47 +0900672 }
673
674 const std::uint32_t kTiffIdentifierSize = 4;
675 std::uint32_t offset_to_ifd = 0;
676 if (!GetEndianness(tiff_offset_, stream_, &endian_) ||
677 !Get32u(stream_, tiff_offset_ + kTiffIdentifierSize, endian_,
678 &offset_to_ifd)) {
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200679 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900680 }
681
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200682 if (!ParseIfd(tiff_offset_ + offset_to_ifd, desired_tags, max_number_ifds,
683 &tiff_content->tiff_directory)) {
684 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900685 }
686
687 // Get the Exif data.
Eik Brauera9540112016-01-27 13:25:41 +0100688 if (FindFirstTagInIfds(kTiffTagExifIfd, tiff_content->tiff_directory) !=
689 nullptr) {
690 const TiffDirectory* tiff_ifd =
691 FindFirstTagInIfds(kTiffTagExifIfd, tiff_content->tiff_directory);
Jaesung Chung29345532016-01-14 02:28:47 +0900692 std::uint32_t offset;
693 if (tiff_ifd->Get(kTiffTagExifIfd, &offset)) {
694 tiff_content->exif_directory.reset(new TiffDirectory(endian_));
695 std::uint32_t next_ifd_offset;
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200696 if (!ParseDirectory(
697 tiff_offset_, tiff_offset_ + offset, endian_, desired_tags,
698 stream_, tiff_content->exif_directory.get(), &next_ifd_offset)) {
699 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900700 }
701
Eik Brauera9540112016-01-27 13:25:41 +0100702 return ParseGpsData(tiff_ifd, tiff_content);
Jaesung Chung29345532016-01-14 02:28:47 +0900703 }
704 }
705
Eik Brauera9540112016-01-27 13:25:41 +0100706 // Get the GPS data from the tiff ifd.
707 if (FindFirstTagInIfds(kExifTagGps, tiff_content->tiff_directory) !=
708 nullptr) {
709 const TiffDirectory* tiff_ifd =
710 FindFirstTagInIfds(kExifTagGps, tiff_content->tiff_directory);
711 return ParseGpsData(tiff_ifd, tiff_content);
712 }
713
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200714 return true;
Jaesung Chung29345532016-01-14 02:28:47 +0900715}
716
Nick Chusidff6a5f92021-06-08 15:07:14 -0400717bool TiffParser::ParseIfd(const std::uint32_t ifd_offset,
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200718 const TagSet& desired_tags,
719 const std::uint16_t max_number_ifds,
720 IfdVector* tiff_directory) {
Jaesung Chung29345532016-01-14 02:28:47 +0900721 std::uint32_t next_ifd_offset;
722 TiffDirectory tiff_ifd(static_cast<Endian>(endian_));
Jesse Evans79ea86a2022-03-22 12:53:25 -0700723 if (!ParseDirectory(tiff_offset_, ifd_offset, endian_, desired_tags, stream_,
724 &tiff_ifd, &next_ifd_offset) ||
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200725 !ParseSubIfds(tiff_offset_, desired_tags, max_number_ifds, endian_,
726 stream_, &tiff_ifd)) {
727 return false;
Jaesung Chung29345532016-01-14 02:28:47 +0900728 }
729
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200730 tiff_directory->push_back(tiff_ifd);
731 if (next_ifd_offset != 0 && tiff_directory->size() < max_number_ifds) {
732 return ParseIfd(tiff_offset_ + next_ifd_offset, desired_tags,
733 max_number_ifds, tiff_directory);
734 }
735 return true;
Jaesung Chung29345532016-01-14 02:28:47 +0900736}
737
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200738bool TiffParser::ParseGpsData(const TiffDirectory* tiff_ifd,
739 TiffContent* tiff_content) {
Eik Brauera9540112016-01-27 13:25:41 +0100740 std::uint32_t offset;
741 if (tiff_ifd->Get(kExifTagGps, &offset)) {
742 tiff_content->gps_directory.reset(new TiffDirectory(endian_));
743 const TagSet gps_tags = {kGpsTagLatitudeRef, kGpsTagLatitude,
744 kGpsTagLongitudeRef, kGpsTagLongitude,
745 kGpsTagAltitudeRef, kGpsTagAltitude,
746 kGpsTagTimeStamp, kGpsTagDateStamp};
747 std::uint32_t next_ifd_offset;
748 return ParseDirectory(tiff_offset_, tiff_offset_ + offset, endian_,
749 gps_tags, stream_, tiff_content->gps_directory.get(),
750 &next_ifd_offset);
751 }
Yujie Qin2f5d8ea2016-04-06 12:55:08 +0200752 return true;
Eik Brauera9540112016-01-27 13:25:41 +0100753}
754
Jaesung Chung29345532016-01-14 02:28:47 +0900755} // namespace piex