Coverage Report

Created: 2026-01-10 06:15

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