xref: /aosp_15_r20/external/piex/src/tiff_parser.cc (revision 4d671364a067eb4f124488347677d916765212d1)
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 
GetFullDimension16(const TiffDirectory & tiff_directory,std::uint16_t * width,std::uint16_t * height)42 bool 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;
50   }
51   *width = static_cast<std::uint16_t>(tmp_width);
52   *height = static_cast<std::uint16_t>(tmp_height);
53   return true;
54 }
55 
FillGpsPreviewImageData(const TiffDirectory & gps_directory,PreviewImageData * preview_image_data)56 void 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 
87     const size_t kGpsDateStampSize = 11;
88     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 
GetImageSize(const TiffDirectory & tiff_directory,StreamInterface * stream,Image * image)113 void 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;
119     }
120     case Image::kJpegCompressed: {
121       GetJpegDimensions(image->offset, stream, &image->width, &image->height);
122       break;
123     }
124     default: { return; }
125   }
126 }
127 
FillPreviewImageData(const TiffDirectory & tiff_directory,StreamInterface * stream,PreviewImageData * preview_image_data)128 bool 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;
140     }
141   }
142 
143   // Get exif_orientation if it was not set already.
144   if (tiff_directory.Has(kTiffTagOrientation) &&
145       preview_image_data->exif_orientation == 1) {
146     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;
153     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;
161     }
162   }
163 
164   success &= GetFullDimension32(tiff_directory, &preview_image_data->full_width,
165                                 &preview_image_data->full_height);
166 
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 
175   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 
184   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 
210   return success;
211 }
212 
FindFirstTagInIfds(const TiffDirectory::Tag & tag,const IfdVector & tiff_directory)213 const TiffDirectory* FindFirstTagInIfds(const TiffDirectory::Tag& tag,
214                                         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 
230 // Return true if all data blocks are ordered one after the other without gaps.
OffsetsAreConsecutive(const std::vector<std::uint32_t> & strip_offsets,const std::vector<std::uint32_t> & strip_byte_counts)231 bool 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 
247 // Gets the SubIfd content.
ParseSubIfds(const std::uint32_t tiff_offset,const TagSet & desired_tags,const std::uint32_t max_number_ifds,const Endian endian,StreamInterface * stream,TiffDirectory * tiff_ifd)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                   StreamInterface* stream, TiffDirectory* tiff_ifd) {
251   if (tiff_ifd->Has(kTiffTagSubIfd)) {
252     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)) {
260         return false;
261       }
262 
263       std::uint32_t next_ifd_offset;
264       TiffDirectory sub_ifd(static_cast<Endian>(endian));
265       if (!ParseDirectory(tiff_offset, sub_offset, endian, desired_tags, stream,
266                           &sub_ifd, &next_ifd_offset)) {
267         return false;
268       }
269 
270       tiff_ifd->AddSubDirectory(sub_ifd);
271     }
272   }
273   return true;
274 }
275 
276 }  // namespace
277 
Get16u(StreamInterface * stream,const std::uint32_t offset,const Endian & endian,std::uint16_t * value)278 bool 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 
Get32u(StreamInterface * stream,const std::uint32_t offset,const Endian & endian,std::uint32_t * value)293 bool 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) {
298       *value = (data[0] * 0x1000000u) | (data[1] * 0x10000u) |
299                (data[2] * 0x100u) | data[3];
300     } else {
301       *value = (data[3] * 0x1000000u) | (data[2] * 0x10000u) |
302                (data[1] * 0x100u) | data[0];
303     }
304     return true;
305   } else {
306     return false;
307   }
308 }
309 
GetData(const size_t offset,const size_t length,StreamInterface * stream,Error * error)310 std::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 
GetEndianness(const std::uint32_t tiff_offset,StreamInterface * stream,Endian * endian)332 bool 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 
GetImageData(const TiffDirectory & tiff_directory,StreamInterface * stream,Image * image)353 bool 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     }
398     length = static_cast<std::uint32_t>(std::accumulate(
399         strip_byte_counts.begin(), strip_byte_counts.end(), 0U));
400     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 
GetJpegDimensions(const std::uint32_t jpeg_offset,StreamInterface * stream,std::uint16_t * width,std::uint16_t * height)417 bool GetJpegDimensions(const std::uint32_t jpeg_offset, StreamInterface* stream,
418                        std::uint16_t* width, std::uint16_t* height) {
419   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 
GetRational(const TiffDirectory::Tag & tag,const TiffDirectory & directory,const int data_size,PreviewImageData::Rational * data)451 bool 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 
IsThumbnail(const Image & image,const int max_dimension)465 bool IsThumbnail(const Image& image, const int max_dimension) {
466   return image.width <= max_dimension && image.height <= max_dimension;
467 }
468 
ParseDirectory(const std::uint32_t tiff_offset,const std::uint32_t ifd_offset,const Endian endian,const TagSet & desired_tags,StreamInterface * stream,TiffDirectory * tiff_directory,std::uint32_t * next_ifd_offset)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                     std::uint32_t* next_ifd_offset) {
474   std::uint16_t number_of_entries;
475   if (!Get16u(stream, ifd_offset, endian, &number_of_entries)) {
476     return false;
477   }
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.
488       if (desired_tags.count(static_cast<TiffDirectory::Tag>(tag)) != 1) {
489         continue;
490       }
491     } else {
492       return false;
493     }
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) {
499       return false;
500     }
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) {
519       return false;
520     }
521     tiff_directory->AddEntry(tag, type, number_of_elements, value_offset, data);
522   }
523 
524   return Get32u(stream, ifd_offset + 2u + number_of_entries * 12u, endian,
525                 next_ifd_offset);
526 }
527 
GetExifOrientation(StreamInterface * stream,const std::uint32_t offset,std::uint32_t * orientation)528 bool 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;
537   }
538 
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 
GetFullDimension32(const TiffDirectory & tiff_directory,std::uint32_t * width,std::uint32_t * height)549 bool 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 
561   if (tiff_directory.Has(kExifTagDefaultCropSize)) {
562     if (!GetFullCropDimension(tiff_directory, width, height)) {
563       return false;
564     }
565   } else if (tiff_directory.Has(kExifTagWidth) &&
566              tiff_directory.Has(kExifTagHeight)) {
567     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     }
595   }
596   return true;
597 }
598 
GetFullCropDimension(const tiff_directory::TiffDirectory & tiff_directory,std::uint32_t * width,std::uint32_t * height)599 bool GetFullCropDimension(const tiff_directory::TiffDirectory& tiff_directory,
600                           std::uint32_t* width, std::uint32_t* height) {
601   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) {
614       *width = crop[0];
615       *height = crop[1];
616       return true;
617     } else {
618       return false;
619     }
620   }
621 
622   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;
636 }
637 
TiffParser(StreamInterface * stream)638 TiffParser::TiffParser(StreamInterface* stream) : stream_(stream) {}
639 
TiffParser(StreamInterface * stream,const std::uint32_t offset)640 TiffParser::TiffParser(StreamInterface* stream, const std::uint32_t offset)
641     : stream_(stream), tiff_offset_(offset) {}
642 
GetPreviewImageData(const TiffContent & tiff_content,PreviewImageData * preview_image_data)643 bool TiffParser::GetPreviewImageData(const TiffContent& tiff_content,
644                                      PreviewImageData* preview_image_data) {
645   bool success = true;
646   for (const auto& tiff_directory : tiff_content.tiff_directory) {
647     success = FillPreviewImageData(tiff_directory, stream_, preview_image_data);
648     if (success && tiff_directory.Has(kTiffTagExifIfd) &&
649         tiff_content.exif_directory) {
650       success = FillPreviewImageData(*tiff_content.exif_directory, stream_,
651                                      preview_image_data);
652     }
653     if (success && tiff_directory.Has(kExifTagGps) &&
654         tiff_content.gps_directory) {
655       FillGpsPreviewImageData(*tiff_content.gps_directory, preview_image_data);
656     }
657     for (const auto& sub_directory : tiff_directory.GetSubDirectories()) {
658       if (success) {
659         success =
660             FillPreviewImageData(sub_directory, stream_, preview_image_data);
661       }
662     }
663   }
664   return success;
665 }
666 
Parse(const TagSet & desired_tags,const std::uint16_t max_number_ifds,TiffContent * tiff_content)667 bool TiffParser::Parse(const TagSet& desired_tags,
668                        const std::uint16_t max_number_ifds,
669                        TiffContent* tiff_content) {
670   if (!tiff_content->tiff_directory.empty()) {
671     return false;  // You shall call Parse() only once.
672   }
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)) {
679     return false;
680   }
681 
682   if (!ParseIfd(tiff_offset_ + offset_to_ifd, desired_tags, max_number_ifds,
683                 &tiff_content->tiff_directory)) {
684     return false;
685   }
686 
687   // Get the Exif data.
688   if (FindFirstTagInIfds(kTiffTagExifIfd, tiff_content->tiff_directory) !=
689       nullptr) {
690     const TiffDirectory* tiff_ifd =
691         FindFirstTagInIfds(kTiffTagExifIfd, tiff_content->tiff_directory);
692     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;
696       if (!ParseDirectory(
697               tiff_offset_, tiff_offset_ + offset, endian_, desired_tags,
698               stream_, tiff_content->exif_directory.get(), &next_ifd_offset)) {
699         return false;
700       }
701 
702       return ParseGpsData(tiff_ifd, tiff_content);
703     }
704   }
705 
706   // 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 
714   return true;
715 }
716 
ParseIfd(const std::uint32_t ifd_offset,const TagSet & desired_tags,const std::uint16_t max_number_ifds,IfdVector * tiff_directory)717 bool TiffParser::ParseIfd(const std::uint32_t ifd_offset,
718                           const TagSet& desired_tags,
719                           const std::uint16_t max_number_ifds,
720                           IfdVector* tiff_directory) {
721   std::uint32_t next_ifd_offset;
722   TiffDirectory tiff_ifd(static_cast<Endian>(endian_));
723   if (!ParseDirectory(tiff_offset_, ifd_offset, endian_, desired_tags, stream_,
724                       &tiff_ifd, &next_ifd_offset) ||
725       !ParseSubIfds(tiff_offset_, desired_tags, max_number_ifds, endian_,
726                     stream_, &tiff_ifd)) {
727     return false;
728   }
729 
730   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;
736 }
737 
ParseGpsData(const TiffDirectory * tiff_ifd,TiffContent * tiff_content)738 bool TiffParser::ParseGpsData(const TiffDirectory* tiff_ifd,
739                               TiffContent* tiff_content) {
740   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   }
752   return true;
753 }
754 
755 }  // namespace piex
756