xref: /aosp_15_r20/external/piex/src/piex_cr3.cc (revision 4d671364a067eb4f124488347677d916765212d1)
1 // 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 
30 namespace piex {
31 namespace {
32 
33 constexpr size_t kUuidSize = 16;
34 using Uuid = std::array<std::uint8_t, kUuidSize>;
35 // Uuid of uuid box under the moov box.
36 constexpr 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.
40 constexpr Uuid kUuidPrvw = {0xea, 0xf4, 0x2b, 0x5e, 0x1c, 0x98, 0x4b, 0x88,
41                             0xb9, 0xfb, 0xb7, 0xdc, 0x40, 0x6e, 0x4d, 0x16};
42 
43 constexpr size_t kTagSize = 4;
44 using BoxTag = std::array<char, kTagSize>;
45 
NewTag(const char s[kTagSize+1])46 constexpr BoxTag NewTag(const char s[kTagSize + 1]) {
47   return BoxTag{s[0], s[1], s[2], s[3]};
48 }
49 
50 constexpr BoxTag kUuidTag = NewTag("uuid");
51 constexpr BoxTag kPrvwTag = NewTag("PRVW");
52 constexpr BoxTag kThmbTag = NewTag("THMB");
53 constexpr BoxTag kCmt1Tag = NewTag("CMT1");
54 constexpr BoxTag kCmt2Tag = NewTag("CMT2");
55 constexpr BoxTag kStblTag = NewTag("stbl");
56 constexpr BoxTag kStsdTag = NewTag("stsd");
57 constexpr BoxTag kCrawTag = NewTag("CRAW");
58 constexpr BoxTag kStszTag = NewTag("stsz");
59 constexpr BoxTag kCo64Tag = NewTag("co64");
60 constexpr BoxTag kMdatTag = NewTag("mdat");
61 
62 // Convenience class for a box.
63 class Box {
64  public:
Box()65   Box()
66       : is_valid_(false),
67         tag_(BoxTag()),
68         offset_(0),
69         header_offset_(0),
70         next_box_offset_(0) {}
Box(const BoxTag & tag,size_t offset,size_t header_length,size_t length)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 
IsValid() const78   bool IsValid() const { return is_valid_ && next_box_offset_ > offset_; }
tag() const79   const BoxTag& tag() const { return tag_; }
80 
81   // Returns offset from start of file.
offset() const82   size_t offset() const { return offset_; }
83   // Returns offset from start of file, including box's header.
header_offset() const84   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.
next_box_offset() const87   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 
97 struct 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.
Get16u(StreamInterface * stream,size_t offset,std::uint16_t * value)105 bool 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.
Get32u(StreamInterface * stream,size_t offset,std::uint32_t * value)111 bool 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.
Get64u(StreamInterface * stream,size_t offset,std::uint64_t * value)117 bool 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
139 struct 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.
ProcessJpegBox(StreamInterface * stream,const Box & box,Image * image)147 bool 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.
ParseExifIfd0(StreamInterface * stream,size_t tiff_offset,PreviewImageData * preview_image_data)175 bool 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.
ParseExifExifIfd(StreamInterface * stream,size_t tiff_offset,PreviewImageData * preview_image_data)198 bool 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.
GetNextBox(StreamInterface * stream,size_t offset)229 Box 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 = stream->GetData(offset + sizeof(length_32), kTagSize,
236                                  reinterpret_cast<std::uint8_t*>(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.
GetNextBoxWithTag(StreamInterface * stream,size_t offset,const BoxTag & expected_tag)258 Box 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.
ProcessCrawBox(StreamInterface * stream,const Box & craw_box,uint16_t * width,uint16_t * height,uint16_t * content_type)270 bool 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
ProcessStszBox(StreamInterface * stream,const Box & stsz_box,uint32_t * image_size)297 bool 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
ProcessCo64(StreamInterface * stream,const Box & co64_box,uint32_t * image_offset)327 bool 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)
ProcessStblBox(StreamInterface * stream,const Box & stbl_box,ProcessData * data)347 bool 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.
DoProcessChildren(const BoxTag & tag)413 bool 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 ...
ProcessBox(StreamInterface * stream,const Box & box,ProcessData * data)451 size_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 
ProcessStream(StreamInterface * stream,const BoxTag & last_chunk,ProcessData * data)496 bool 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 
IsImage(StreamInterface * stream,const Image & image)515 bool 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 
Cr3GetPreviewData(StreamInterface * stream,PreviewImageData * preview_image_data)532 Error Cr3GetPreviewData(StreamInterface* stream,
533                         PreviewImageData* preview_image_data) {
534   ProcessData data{.preview_image_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 
Cr3GetOrientation(StreamInterface * stream,std::uint32_t * orientation)549 bool Cr3GetOrientation(StreamInterface* stream, std::uint32_t* orientation) {
550   PreviewImageData preview_image_data;
551   ProcessData data{.preview_image_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
560