blob: 1e9a50a372492e6ff8a23c6734ae1bbe174bea65 [file] [log] [blame]
Nick Chusid5c5b6712021-06-03 15:33:24 -04001// Copyright 2020 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/piex_cr3.h"
18
19#include <array>
20#include <cstddef>
21#include <cstdint>
22#include <limits>
23#include <unordered_set>
24
25#include "src/binary_parse/range_checked_byte_ptr.h"
26#include "src/piex_types.h"
27#include "src/tiff_directory/tiff_directory.h"
28#include "src/tiff_parser.h"
29
30namespace piex {
31namespace {
32
33constexpr size_t kUuidSize = 16;
34using Uuid = std::array<std::uint8_t, kUuidSize>;
35// Uuid of uuid box under the moov box.
36constexpr Uuid kUuidMoov = {0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0,
37 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, 0x48};
38
39// Uuid of uuid box containing PRVW box.
40constexpr Uuid kUuidPrvw = {0xea, 0xf4, 0x2b, 0x5e, 0x1c, 0x98, 0x4b, 0x88,
41 0xb9, 0xfb, 0xb7, 0xdc, 0x40, 0x6e, 0x4d, 0x16};
42
43constexpr size_t kTagSize = 4;
44using BoxTag = std::array<std::uint8_t, kTagSize>;
45
46constexpr BoxTag NewTag(const char s[kTagSize + 1]) {
47 return BoxTag{s[0], s[1], s[2], s[3]};
48}
49
50constexpr BoxTag kUuidTag = NewTag("uuid");
51constexpr BoxTag kPrvwTag = NewTag("PRVW");
52constexpr BoxTag kThmbTag = NewTag("THMB");
53constexpr BoxTag kCmt1Tag = NewTag("CMT1");
54constexpr BoxTag kCmt2Tag = NewTag("CMT2");
55constexpr BoxTag kStblTag = NewTag("stbl");
56constexpr BoxTag kStsdTag = NewTag("stsd");
57constexpr BoxTag kCrawTag = NewTag("CRAW");
58constexpr BoxTag kStszTag = NewTag("stsz");
59constexpr BoxTag kCo64Tag = NewTag("co64");
60constexpr BoxTag kMdatTag = NewTag("mdat");
61
62// Convenience class for a box.
63class Box {
64 public:
65 Box()
66 : is_valid_(false),
67 tag_(BoxTag()),
68 offset_(0),
69 header_offset_(0),
70 next_box_offset_(0) {}
71 Box(const BoxTag& tag, size_t offset, size_t header_length, size_t length)
72 : is_valid_(true),
73 tag_(tag),
74 offset_(offset),
75 header_offset_(offset + header_length),
76 next_box_offset_(offset + length) {}
77
78 bool IsValid() const { return is_valid_ && next_box_offset_ > offset_; }
79 const BoxTag& tag() const { return tag_; }
80
81 // Returns offset from start of file.
82 size_t offset() const { return offset_; }
83 // Returns offset from start of file, including box's header.
84 size_t header_offset() const { return header_offset_; }
85 // Returns offset from start of file of the next box, accounting for size of
86 // this box.
87 size_t next_box_offset() const { return next_box_offset_; }
88
89 private:
90 bool is_valid_;
91 BoxTag tag_;
92 size_t offset_;
93 size_t header_offset_;
94 size_t next_box_offset_;
95};
96
97struct ProcessData {
98 PreviewImageData* preview_image_data = nullptr;
99 Image mdat_image;
100 Image prvw_image;
101};
102
103// Wraps Get16u w/ assumption that CR3 is always big endian, based on
104// ISO/IEC 14496-12 specification that all box fields are big endian.
105bool Get16u(StreamInterface* stream, size_t offset, std::uint16_t* value) {
106 return Get16u(stream, offset, tiff_directory::kBigEndian, value);
107}
108
109// Wraps Get32u w/ assumption that CR3 is always big endian, based on
110// ISO/IEC 14496-12 specification that all box fields are big endian.
111bool Get32u(StreamInterface* stream, size_t offset, std::uint32_t* value) {
112 return Get32u(stream, offset, tiff_directory::kBigEndian, value);
113}
114
115// Always big endian, based on ISO/IEC 14496-12 specification that all box
116// fields are big endian.
117bool Get64u(StreamInterface* stream, size_t offset, std::uint64_t* value) {
118 std::uint8_t data[8];
119 if (stream->GetData(offset, 8, data) == kOk) {
120 *value = (data[0] * 0x1000000u) | (data[1] * 0x10000u) |
121 (data[2] * 0x100u) | data[3];
122 *value <<= 32;
123 *value = (data[4] * 0x1000000u) | (data[5] * 0x10000u) |
124 (data[6] * 0x100u) | data[7];
125 return true;
126 } else {
127 return false;
128 }
129}
130
131// Jpeg box offsets based on the box tag. The expected layout is as follows:
132// Byte Offset Type Meaning
133// 0 [long] size of box
134// 4 [char[]] box tag
135// offset.width [short] width of jpeg
136// offset.height [short] height of jpeg
137// offset.jpeg_size [long] number of bytes in jpeg
138// offset.jpeg_data [byte[]] start of jpeg data
139struct JpegBoxOffset {
140 size_t width = 0;
141 size_t height = 0;
142 size_t jpeg_size = 0;
143 size_t jpeg_data = 0;
144};
145
146// Processes box w/ JPEG data. Box must be PRVW and THMB boxes.
147bool ProcessJpegBox(StreamInterface* stream, const Box& box, Image* image) {
148 static constexpr JpegBoxOffset kPrvwJpegOffsets{14, 16, 20, 24};
149 static constexpr JpegBoxOffset kThmbJpegOffsets{12, 14, 16, 24};
150 if (box.tag() != kPrvwTag && box.tag() != kThmbTag) {
151 return false;
152 }
153 const JpegBoxOffset& offsets =
154 box.tag() == kPrvwTag ? kPrvwJpegOffsets : kThmbJpegOffsets;
155 uint16_t width, height;
156 uint32_t jpeg_size;
157 if (!Get16u(stream, box.offset() + offsets.width, &width)) {
158 return false;
159 }
160 if (!Get16u(stream, box.offset() + offsets.height, &height)) {
161 return false;
162 }
163 if (!Get32u(stream, box.offset() + offsets.jpeg_size, &jpeg_size)) {
164 return false;
165 }
166 image->format = Image::kJpegCompressed;
167 image->width = width;
168 image->height = height;
169 image->offset = box.offset() + offsets.jpeg_data;
170 image->length = jpeg_size;
171 return true;
172}
173
174// Parses the Exif IFD0 tags at tiff_offset.
175bool ParseExifIfd0(StreamInterface* stream, size_t tiff_offset,
176 PreviewImageData* preview_image_data) {
177 static const TagSet kIfd0TagSet = {kTiffTagModel, kTiffTagMake,
178 kTiffTagOrientation, kTiffTagImageWidth,
179 kTiffTagImageLength};
180 TiffContent content;
181 TiffParser(stream, tiff_offset).Parse(kIfd0TagSet, 1, &content);
182 if (content.tiff_directory.size() != 1) {
183 return false;
184 }
185
186 content.tiff_directory[0].Get(kTiffTagModel, &preview_image_data->model);
187 content.tiff_directory[0].Get(kTiffTagMake, &preview_image_data->maker);
188 content.tiff_directory[0].Get(kTiffTagOrientation,
189 &preview_image_data->exif_orientation);
190 content.tiff_directory[0].Get(kTiffTagImageWidth,
191 &preview_image_data->full_width);
192 content.tiff_directory[0].Get(kTiffTagImageLength,
193 &preview_image_data->full_height);
194 return true;
195}
196
197// Parses the Exif Exif IFD tags at tiff_offset.
198bool ParseExifExifIfd(StreamInterface* stream, size_t tiff_offset,
199 PreviewImageData* preview_image_data) {
200 static const TagSet kExifIfdTagSet = {kExifTagDateTimeOriginal,
201 kExifTagExposureTime, kExifTagFnumber,
202 kExifTagFocalLength, kExifTagIsoSpeed};
203 TiffContent content;
204 TiffParser(stream, tiff_offset).Parse(kExifIfdTagSet, 1, &content);
205 if (content.tiff_directory.size() != 1) {
206 return false;
207 }
208
209 content.tiff_directory[0].Get(kExifTagDateTimeOriginal,
210 &preview_image_data->date_time);
211 GetRational(kExifTagExposureTime, content.tiff_directory[0], 1,
212 &preview_image_data->exposure_time);
213 GetRational(kExifTagFnumber, content.tiff_directory[0], 1,
214 &preview_image_data->fnumber);
215 GetRational(kExifTagFocalLength, content.tiff_directory[0], 1,
216 &preview_image_data->focal_length);
217 content.tiff_directory[0].Get(kExifTagIsoSpeed, &preview_image_data->iso);
218 return true;
219}
220
221// Returns the next box or an invalid box.
222//
223// Based on ISO/IEC 14496-12: boxes start with a header: size and type. The size
224// can be compact (32-bits) or extended (64-bit, e.g. mdat box).
225// The type can be compact (32 bits) or extended (full UUID, e.g. uuid boxes).
226// values are stored after the compact size/type.
227//
228// Fields in a box are big-endian.
229Box GetNextBox(StreamInterface* stream, size_t offset) {
230 uint32_t length_32;
231 if (!Get32u(stream, offset, &length_32)) {
232 return Box();
233 }
234 BoxTag tag;
235 Error status =
236 stream->GetData(offset + sizeof(length_32), kTagSize, tag.data());
237 if (status != kOk) {
238 return Box();
239 }
240 size_t length;
241 size_t header_offset = sizeof(length_32) + sizeof(tag);
242 if (length_32 == 1) {
243 // Magic number of 1 implies extended size.
244 uint64_t length_64 = 0;
245 if (!Get64u(stream, offset + header_offset, &length_64)) {
246 return Box();
247 }
248 length = length_64;
249 header_offset += sizeof(length_64);
250 } else {
251 // Compact size.
252 length = length_32;
253 }
254 return Box(tag, offset, header_offset, length);
255}
256
257// Searches for the next box with the given tag.
258Box GetNextBoxWithTag(StreamInterface* stream, size_t offset,
259 const BoxTag& expected_tag) {
260 while (true) {
261 Box box = GetNextBox(stream, offset);
262 if (!box.IsValid() || box.tag() == expected_tag) {
263 return box;
264 }
265 offset = box.next_box_offset();
266 }
267}
268
269// Returns the width, height, and content type from the CRAW box.
270bool ProcessCrawBox(StreamInterface* stream, const Box& craw_box,
271 uint16_t* width, uint16_t* height, uint16_t* content_type) {
272 constexpr size_t kWidthOffset = 32;
273 if (!Get16u(stream, craw_box.offset() + kWidthOffset, width)) {
274 return false;
275 }
276
277 constexpr size_t kHeightOffset = 34;
278 if (!Get16u(stream, craw_box.offset() + kHeightOffset, height)) {
279 return false;
280 }
281
282 constexpr size_t kTypeOffset = 86;
283 if (!Get16u(stream, craw_box.offset() + kTypeOffset, content_type)) {
284 return false;
285 }
286 return true;
287}
288
289// stsz box offset:
290// Byte Offset Type Meaning
291// 0 [long] size of box
292// 4 [char[]] box tag
293// 8 [long] version/flags
294// 12 [long] sample size
295// 16 [long] number of entries in sample table
296// 20 [long[]] sample table if samples size is 0
297bool ProcessStszBox(StreamInterface* stream, const Box& stsz_box,
298 uint32_t* image_size) {
299 uint32_t sample_size;
300 if (!Get32u(stream, stsz_box.offset() + 12, &sample_size)) {
301 return false;
302 }
303 if (sample_size > 0) {
304 *image_size = sample_size;
305 return true;
306 }
307 // sample_size of 0 implies the data is in the sample table. We expect only
308 // one entry. This is true of Canon EOS RP Cr3 files.
309 uint32_t count;
310 if (!Get32u(stream, stsz_box.offset() + 16, &count)) {
311 return false;
312 }
313 if (count != 1) {
314 // Expect at most one entry in the table.
315 return false;
316 }
317 return Get32u(stream, stsz_box.offset() + 20, image_size);
318}
319
320// co64 box offsets:
321// Byte Offset Type Meaning
322// 0 [long] size of box
323// 4 [char[]] box tag
324// 8 [long] version
325// 12 [long] count (expect to be value 1)
326// 16 [long] offset of image data in mdat
327bool ProcessCo64(StreamInterface* stream, const Box& co64_box,
328 uint32_t* image_offset) {
329 uint32_t count = 0;
330 if (!Get32u(stream, co64_box.header_offset() + 4, &count)) {
331 return false;
332 }
333 if (count != 1) {
334 return false;
335 }
336 return Get32u(stream, co64_box.header_offset() + 8, image_offset);
337}
338
339// Process the stbl box. Expected box layout:
340// stbl
341// stsd
342// CRAW (embedded image (JPEG) information)
343// (0 or more skipped boxes)
344// stsz (embedded image byte size)
345// (0 or more skipped boxes)
346// co64 (offset of embedded image, relative to mdat box)
347bool ProcessStblBox(StreamInterface* stream, const Box& stbl_box,
348 ProcessData* data) {
349 Box stsd_box = GetNextBoxWithTag(stream, stbl_box.header_offset(), kStsdTag);
350 if (!stsd_box.IsValid()) {
351 return false;
352 }
353 // This is either CRAW or CTMD. Skip when CTMD.
354 Box craw_box = GetNextBox(stream, stsd_box.header_offset() + 8);
355 if (!craw_box.IsValid()) {
356 return false;
357 }
358 if (craw_box.tag() != kCrawTag) {
359 return true;
360 }
361 // CRAW contains info about the full-size image embedded in the mdat box.
362 // The image is either JPEG or HEVC.
363 uint16_t image_width = 0;
364 uint16_t image_height = 0;
365 uint16_t content_type = 0;
366 if (!ProcessCrawBox(stream, craw_box, &image_width, &image_height,
367 &content_type)) {
368 return false;
369 }
370 // Only continue if JPEG or HEVC content.
371 constexpr uint16_t kJpegContentType = 3;
372 constexpr uint16_t kHevcContentType = 4;
373 if (content_type != kJpegContentType && content_type != kHevcContentType) {
374 return true;
375 }
376
377 // Skip until we find stsz, contains the size (# of bytes) of image data.
378 Box stsz_box =
379 GetNextBoxWithTag(stream, stsd_box.next_box_offset(), kStszTag);
380 if (!stsz_box.IsValid()) {
381 return false;
382 }
383 uint32_t image_size;
384 if (!ProcessStszBox(stream, stsz_box, &image_size)) {
385 return false;
386 }
387
388 // Skip until we find co64, contains the offset of image data.
389 Box co64_box =
390 GetNextBoxWithTag(stream, stsz_box.next_box_offset(), kCo64Tag);
391 if (!co64_box.IsValid()) {
392 return false;
393 }
394
395 uint32_t image_offset = 0;
396 if (!ProcessCo64(stream, co64_box, &image_offset)) {
397 return false;
398 }
399
400 data->mdat_image.format = content_type == kJpegContentType
401 ? Image::kJpegCompressed
402 : Image::kHevcCompressed;
403 data->mdat_image.width = image_width;
404 data->mdat_image.height = image_height;
405 data->mdat_image.length = image_size;
406 // This offset is relative to the position of the mdat box. The value will
407 // be updated once mdat's offset is found.
408 data->mdat_image.offset = image_offset;
409 return true;
410}
411
412// Returns true if we should parse the children of the box.
413bool DoProcessChildren(const BoxTag& tag) {
414 static const std::set<BoxTag> kTags = {NewTag("trak"), NewTag("moov"),
415 NewTag("mdia"), NewTag("minf")};
416 return kTags.find(tag) != kTags.end();
417}
418
419// Processes box and returns offset of the next box to process.
420// A return value of 0 indicates an error.
421//
422// Outline of hierarchy and important boxes:
423// ftyp
424// moov
425// uuid (id is kUuidMoov)
426// ... boxes we skip ...
427// CMT1 (EXIF data)
428// CMT2 (EXIF data)
429// ... boxes we skip ...
430// THMB (160x120 JPEG thumbnail, embedded in this box)
431// trak
432// tkhd
433// mdia
434// ... boxes we skip ...
435// minf
436// ... boxes we skip ...
437// stbl
438// stsd
439// CRAW (Full image preview, type (JPEG or HEVC), width, height. The
440// image data is found in mdat box, below.)
441// ... boxes we skip ...
442// stsz (Size of preview, in bytes)
443// ... boxes we skip ...
444// co64 (Location/offset of full preview data in mdat)
445// .. boxes we skip ...
446// uuid (id is kUuidPrvw)
447// PRVW (1620x1080 JPEG preview, embedded in this box)
448// mdat
449// Full image preview (JPEG or HEVC)
450// ... RAW image data ...
451size_t ProcessBox(StreamInterface* stream, const Box& box, ProcessData* data) {
452 // Parse child boxes.
453 if (box.tag() == kUuidTag) {
454 // Uuid box have extended box types.
455 Uuid uuid;
456 if (stream->GetData(box.header_offset(), uuid.size(), uuid.data()) != kOk) {
457 return 0;
458 }
459 if (uuid == kUuidPrvw) {
460 return box.header_offset() + uuid.size() + 8;
461 } else if (uuid == kUuidMoov) {
462 return box.header_offset() + uuid.size();
463 } // else skip the box, below.
464 } else if (DoProcessChildren(box.tag())) {
465 return box.header_offset();
466 }
467
468 // Potentially process the data contained in the box.
469 bool success;
470 if (box.tag() == kMdatTag) {
471 // mdat_image.offset is relative to mdat's header, update it to be absolute
472 // offset to the image data.
473 data->mdat_image.offset += box.header_offset();
474 success = true;
475 } else if (box.tag() == kStblTag) {
476 success = ProcessStblBox(stream, box, data);
477 } else if (box.tag() == kPrvwTag) {
478 // Preview jpeg. 1620x1080 for EOS R.
479 success = ProcessJpegBox(stream, box, &data->prvw_image);
480 } else if (box.tag() == kThmbTag) {
481 // Thumbnail jpeg. 160x120 for EOS R.
482 success = ProcessJpegBox(stream, box, &data->preview_image_data->thumbnail);
483 } else if (box.tag() == kCmt1Tag) {
484 success =
485 ParseExifIfd0(stream, box.header_offset(), data->preview_image_data);
486 } else if (box.tag() == kCmt2Tag) {
487 success =
488 ParseExifExifIfd(stream, box.header_offset(), data->preview_image_data);
489 } else {
490 // This box isn't interesting, skip it.
491 success = true;
492 }
493 return success ? box.next_box_offset() : 0;
494}
495
496bool ProcessStream(StreamInterface* stream, const BoxTag& last_chunk,
497 ProcessData* data) {
498 size_t offset = 0;
499 while (true) {
500 Box box = GetNextBox(stream, offset);
501 if (!box.IsValid()) {
502 return false;
503 }
504 size_t new_offset = ProcessBox(stream, box, data);
505 if (new_offset <= offset) {
506 return false;
507 }
508 if (box.tag() == last_chunk) {
509 return true;
510 }
511 offset = new_offset;
512 }
513}
514
515bool IsImage(StreamInterface* stream, const Image& image) {
516 if (image.format != Image::kJpegCompressed) {
517 // Pass responsibility to the caller.
518 return true;
519 }
520 // Check for JPEG magic number at start. This could be HEVC data.
521 constexpr std::array<uint8_t, 3> kJpegMagicNumber = {0xFF, 0xD8, 0xFF};
522 std::array<uint8_t, 3> magic_number;
523 if (stream->GetData(image.offset, magic_number.size(), magic_number.data()) !=
524 kOk) {
525 return false;
526 }
527 return magic_number == kJpegMagicNumber;
528}
529
530} // namespace
531
532Error Cr3GetPreviewData(StreamInterface* stream,
533 PreviewImageData* preview_image_data) {
534 ProcessData data{preview_image_data};
535 if (!ProcessStream(stream, kMdatTag, &data)) {
536 return kFail;
537 }
538 // Prefer image in mdata box, as spec ensures it is the largest image.
539 if (data.mdat_image.length > 0 && IsImage(stream, data.mdat_image)) {
540 preview_image_data->preview = data.mdat_image;
541 } else if (data.prvw_image.length > 0 && IsImage(stream, data.prvw_image)) {
542 preview_image_data->preview = data.prvw_image;
543 } else {
544 return kFail;
545 }
546 return kOk;
547}
548
549bool Cr3GetOrientation(StreamInterface* stream, std::uint32_t* orientation) {
550 PreviewImageData preview_image_data;
551 ProcessData data{&preview_image_data};
552 if (ProcessStream(stream, kCmt1Tag, &data)) {
553 *orientation = preview_image_data.exif_orientation;
554 return true;
555 }
556 return false;
557}
558
559} // namespace piex