• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::SRational;
31 using tiff_directory::SizeOfType;
32 using tiff_directory::TIFF_TYPE_LONG;
33 using tiff_directory::TIFF_TYPE_UNDEFINED;
34 using tiff_directory::TiffDirectory;
35 using tiff_directory::kBigEndian;
36 using tiff_directory::kLittleEndian;
37 
38 // Specifies all tags that might be of interest to parse JPEG data.
39 const std::uint32_t kStartOfFrame = 0xFFC0;
40 const std::uint32_t kStartOfImage = 0xFFD8;
41 const std::uint32_t kStartOfScan = 0xFFDA;
42 
GetFullDimension16(const TiffDirectory & tiff_directory,std::uint16_t * width,std::uint16_t * height)43 bool 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 
GetRational(const TiffDirectory::Tag & tag,const TiffDirectory & directory,const int data_size,PreviewImageData::Rational * data)57 bool 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 
FillGpsPreviewImageData(const TiffDirectory & gps_directory,PreviewImageData * preview_image_data)71 void 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 
GetImageSize(const TiffDirectory & tiff_directory,StreamInterface * stream,Image * image)128 void 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 
FillPreviewImageData(const TiffDirectory & tiff_directory,StreamInterface * stream,PreviewImageData * preview_image_data)143 bool 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 
FindFirstTagInIfds(const TiffDirectory::Tag & tag,const IfdVector & tiff_directory)228 const 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.
OffsetsAreConsecutive(const std::vector<std::uint32_t> & strip_offsets,const std::vector<std::uint32_t> & strip_byte_counts)246 bool 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.
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)263 bool 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 
Get16u(StreamInterface * stream,const std::uint32_t offset,const Endian & endian,std::uint16_t * value)293 bool 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 
Get32u(StreamInterface * stream,const std::uint32_t offset,const Endian & endian,std::uint32_t * value)308 bool 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 
GetData(const size_t offset,const size_t length,StreamInterface * stream,Error * error)325 std::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 
GetEndianness(const std::uint32_t tiff_offset,StreamInterface * stream,Endian * endian)347 bool 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 
GetImageData(const TiffDirectory & tiff_directory,StreamInterface * stream,Image * image)368 bool 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 
GetJpegDimensions(const std::uint32_t jpeg_offset,StreamInterface * stream,std::uint16_t * width,std::uint16_t * height)432 bool 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 
IsThumbnail(const Image & image,const int max_dimension)466 bool IsThumbnail(const Image& image, const int max_dimension) {
467   return image.width <= max_dimension && image.height <= max_dimension;
468 }
469 
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)470 bool 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 
GetExifOrientation(StreamInterface * stream,const std::uint32_t offset,std::uint32_t * orientation)529 bool 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 
GetFullDimension32(const TiffDirectory & tiff_directory,std::uint32_t * width,std::uint32_t * height)550 bool 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 
GetFullCropDimension(const tiff_directory::TiffDirectory & tiff_directory,std::uint32_t * width,std::uint32_t * height)600 bool 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 
TiffParser(StreamInterface * stream)639 TiffParser::TiffParser(StreamInterface* stream) : stream_(stream) {}
640 
TiffParser(StreamInterface * stream,const std::uint32_t offset)641 TiffParser::TiffParser(StreamInterface* stream, const std::uint32_t offset)
642     : stream_(stream), tiff_offset_(offset) {}
643 
GetPreviewImageData(const TiffContent & tiff_content,PreviewImageData * preview_image_data)644 bool 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 
Parse(const TagSet & desired_tags,const std::uint16_t max_number_ifds,TiffContent * tiff_content)668 bool 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 
ParseIfd(const std::uint32_t offset_to_ifd,const TagSet & desired_tags,const std::uint16_t max_number_ifds,IfdVector * tiff_directory)718 bool 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 
ParseGpsData(const TiffDirectory * tiff_ifd,TiffContent * tiff_content)739 bool 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