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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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. |
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. |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
466 | bool IsThumbnail(const Image& image, const int max_dimension) { |
467 | return image.width <= max_dimension && image.height <= max_dimension; |
468 | } |
469 | |
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 | |
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 | |
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 | |
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 | |
639 | TiffParser::TiffParser(StreamInterface* stream) : stream_(stream) {} |
640 | |
641 | TiffParser::TiffParser(StreamInterface* stream, const std::uint32_t offset) |
642 | : stream_(stream), tiff_offset_(offset) {} |
643 | |
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 | |
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 | |
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 | |
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 | |