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