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