/src/piex/src/piex_cr3.cc
Line | Count | Source |
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 | | |
46 | 4 | constexpr BoxTag NewTag(const char s[kTagSize + 1]) { |
47 | 4 | return BoxTag{s[0], s[1], s[2], s[3]}; |
48 | 4 | } |
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: |
65 | | Box() |
66 | 358 | : is_valid_(false), |
67 | 358 | tag_(BoxTag()), |
68 | 358 | offset_(0), |
69 | 358 | header_offset_(0), |
70 | 358 | next_box_offset_(0) {} |
71 | | Box(const BoxTag& tag, size_t offset, size_t header_length, size_t length) |
72 | 9.12k | : is_valid_(true), |
73 | 9.12k | tag_(tag), |
74 | 9.12k | offset_(offset), |
75 | 9.12k | header_offset_(offset + header_length), |
76 | 9.12k | next_box_offset_(offset + length) {} |
77 | | |
78 | 10.6k | bool IsValid() const { return is_valid_ && next_box_offset_ > offset_; } |
79 | 47.7k | const BoxTag& tag() const { return tag_; } |
80 | | |
81 | | // Returns offset from start of file. |
82 | 3.39k | size_t offset() const { return offset_; } |
83 | | // Returns offset from start of file, including box's header. |
84 | 5.36k | 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 | 6.53k | 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. |
105 | 2.14k | bool Get16u(StreamInterface* stream, size_t offset, std::uint16_t* value) { |
106 | 2.14k | return Get16u(stream, offset, tiff_directory::kBigEndian, value); |
107 | 2.14k | } |
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. |
111 | 10.5k | bool Get32u(StreamInterface* stream, size_t offset, std::uint32_t* value) { |
112 | 10.5k | return Get32u(stream, offset, tiff_directory::kBigEndian, value); |
113 | 10.5k | } |
114 | | |
115 | | // Always big endian, based on ISO/IEC 14496-12 specification that all box |
116 | | // fields are big endian. |
117 | 361 | bool Get64u(StreamInterface* stream, size_t offset, std::uint64_t* value) { |
118 | 361 | std::uint8_t data[8]; |
119 | 361 | if (stream->GetData(offset, 8, data) == kOk) { |
120 | 360 | *value = (data[0] * 0x1000000u) | (data[1] * 0x10000u) | |
121 | 360 | (data[2] * 0x100u) | data[3]; |
122 | 360 | *value <<= 32; |
123 | 360 | *value = (data[4] * 0x1000000u) | (data[5] * 0x10000u) | |
124 | 360 | (data[6] * 0x100u) | data[7]; |
125 | 360 | return true; |
126 | 360 | } else { |
127 | 1 | return false; |
128 | 1 | } |
129 | 361 | } |
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. |
147 | 428 | bool ProcessJpegBox(StreamInterface* stream, const Box& box, Image* image) { |
148 | 428 | static constexpr JpegBoxOffset kPrvwJpegOffsets{14, 16, 20, 24}; |
149 | 428 | static constexpr JpegBoxOffset kThmbJpegOffsets{12, 14, 16, 24}; |
150 | 428 | if (box.tag() != kPrvwTag && box.tag() != kThmbTag) { |
151 | 0 | return false; |
152 | 0 | } |
153 | 428 | const JpegBoxOffset& offsets = |
154 | 428 | box.tag() == kPrvwTag ? kPrvwJpegOffsets : kThmbJpegOffsets; |
155 | 428 | uint16_t width, height; |
156 | 428 | uint32_t jpeg_size; |
157 | 428 | if (!Get16u(stream, box.offset() + offsets.width, &width)) { |
158 | 2 | return false; |
159 | 2 | } |
160 | 426 | if (!Get16u(stream, box.offset() + offsets.height, &height)) { |
161 | 1 | return false; |
162 | 1 | } |
163 | 425 | if (!Get32u(stream, box.offset() + offsets.jpeg_size, &jpeg_size)) { |
164 | 1 | return false; |
165 | 1 | } |
166 | 424 | image->format = Image::kJpegCompressed; |
167 | 424 | image->width = width; |
168 | 424 | image->height = height; |
169 | 424 | image->offset = box.offset() + offsets.jpeg_data; |
170 | 424 | image->length = jpeg_size; |
171 | 424 | return true; |
172 | 425 | } |
173 | | |
174 | | // Parses the Exif IFD0 tags at tiff_offset. |
175 | | bool ParseExifIfd0(StreamInterface* stream, size_t tiff_offset, |
176 | 346 | PreviewImageData* preview_image_data) { |
177 | 346 | static const TagSet kIfd0TagSet = {kTiffTagModel, kTiffTagMake, |
178 | 346 | kTiffTagOrientation, kTiffTagImageWidth, |
179 | 346 | kTiffTagImageLength}; |
180 | 346 | TiffContent content; |
181 | 346 | TiffParser(stream, tiff_offset).Parse(kIfd0TagSet, 1, &content); |
182 | 346 | if (content.tiff_directory.size() != 1) { |
183 | 16 | return false; |
184 | 16 | } |
185 | | |
186 | 330 | content.tiff_directory[0].Get(kTiffTagModel, &preview_image_data->model); |
187 | 330 | content.tiff_directory[0].Get(kTiffTagMake, &preview_image_data->maker); |
188 | 330 | content.tiff_directory[0].Get(kTiffTagOrientation, |
189 | 330 | &preview_image_data->exif_orientation); |
190 | 330 | content.tiff_directory[0].Get(kTiffTagImageWidth, |
191 | 330 | &preview_image_data->full_width); |
192 | 330 | content.tiff_directory[0].Get(kTiffTagImageLength, |
193 | 330 | &preview_image_data->full_height); |
194 | 330 | return true; |
195 | 346 | } |
196 | | |
197 | | // Parses the Exif Exif IFD tags at tiff_offset. |
198 | | bool ParseExifExifIfd(StreamInterface* stream, size_t tiff_offset, |
199 | 872 | PreviewImageData* preview_image_data) { |
200 | 872 | static const TagSet kExifIfdTagSet = {kExifTagDateTimeOriginal, |
201 | 872 | kExifTagExposureTime, kExifTagFnumber, |
202 | 872 | kExifTagFocalLength, kExifTagIsoSpeed}; |
203 | 872 | TiffContent content; |
204 | 872 | TiffParser(stream, tiff_offset).Parse(kExifIfdTagSet, 1, &content); |
205 | 872 | if (content.tiff_directory.size() != 1) { |
206 | 48 | return false; |
207 | 48 | } |
208 | | |
209 | 824 | content.tiff_directory[0].Get(kExifTagDateTimeOriginal, |
210 | 824 | &preview_image_data->date_time); |
211 | 824 | GetRational(kExifTagExposureTime, content.tiff_directory[0], 1, |
212 | 824 | &preview_image_data->exposure_time); |
213 | 824 | GetRational(kExifTagFnumber, content.tiff_directory[0], 1, |
214 | 824 | &preview_image_data->fnumber); |
215 | 824 | GetRational(kExifTagFocalLength, content.tiff_directory[0], 1, |
216 | 824 | &preview_image_data->focal_length); |
217 | 824 | content.tiff_directory[0].Get(kExifTagIsoSpeed, &preview_image_data->iso); |
218 | 824 | return true; |
219 | 872 | } |
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. |
229 | 9.48k | Box GetNextBox(StreamInterface* stream, size_t offset) { |
230 | 9.48k | uint32_t length_32; |
231 | 9.48k | if (!Get32u(stream, offset, &length_32)) { |
232 | 338 | return Box(); |
233 | 338 | } |
234 | 9.14k | BoxTag tag; |
235 | 9.14k | Error status = stream->GetData(offset + sizeof(length_32), kTagSize, |
236 | 9.14k | reinterpret_cast<std::uint8_t*>(tag.data())); |
237 | 9.14k | if (status != kOk) { |
238 | 19 | return Box(); |
239 | 19 | } |
240 | 9.13k | size_t length; |
241 | 9.13k | size_t header_offset = sizeof(length_32) + sizeof(tag); |
242 | 9.13k | if (length_32 == 1) { |
243 | | // Magic number of 1 implies extended size. |
244 | 361 | uint64_t length_64 = 0; |
245 | 361 | if (!Get64u(stream, offset + header_offset, &length_64)) { |
246 | 1 | return Box(); |
247 | 1 | } |
248 | 360 | length = length_64; |
249 | 360 | header_offset += sizeof(length_64); |
250 | 8.76k | } else { |
251 | | // Compact size. |
252 | 8.76k | length = length_32; |
253 | 8.76k | } |
254 | 9.12k | return Box(tag, offset, header_offset, length); |
255 | 9.13k | } |
256 | | |
257 | | // Searches for the next box with the given tag. |
258 | | Box GetNextBoxWithTag(StreamInterface* stream, size_t offset, |
259 | 1.14k | const BoxTag& expected_tag) { |
260 | 2.25k | while (true) { |
261 | 2.25k | Box box = GetNextBox(stream, offset); |
262 | 2.25k | if (!box.IsValid() || box.tag() == expected_tag) { |
263 | 1.14k | return box; |
264 | 1.14k | } |
265 | 1.11k | offset = box.next_box_offset(); |
266 | 1.11k | } |
267 | 1.14k | } |
268 | | |
269 | | // Returns the width, height, and content type from the CRAW box. |
270 | | bool ProcessCrawBox(StreamInterface* stream, const Box& craw_box, |
271 | 430 | uint16_t* width, uint16_t* height, uint16_t* content_type) { |
272 | 430 | constexpr size_t kWidthOffset = 32; |
273 | 430 | if (!Get16u(stream, craw_box.offset() + kWidthOffset, width)) { |
274 | 1 | return false; |
275 | 1 | } |
276 | | |
277 | 429 | constexpr size_t kHeightOffset = 34; |
278 | 429 | if (!Get16u(stream, craw_box.offset() + kHeightOffset, height)) { |
279 | 1 | return false; |
280 | 1 | } |
281 | | |
282 | 428 | constexpr size_t kTypeOffset = 86; |
283 | 428 | if (!Get16u(stream, craw_box.offset() + kTypeOffset, content_type)) { |
284 | 2 | return false; |
285 | 2 | } |
286 | 426 | return true; |
287 | 428 | } |
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 |
297 | | bool ProcessStszBox(StreamInterface* stream, const Box& stsz_box, |
298 | 297 | uint32_t* image_size) { |
299 | 297 | uint32_t sample_size; |
300 | 297 | if (!Get32u(stream, stsz_box.offset() + 12, &sample_size)) { |
301 | 2 | return false; |
302 | 2 | } |
303 | 295 | if (sample_size > 0) { |
304 | 217 | *image_size = sample_size; |
305 | 217 | return true; |
306 | 217 | } |
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 | 78 | uint32_t count; |
310 | 78 | if (!Get32u(stream, stsz_box.offset() + 16, &count)) { |
311 | 1 | return false; |
312 | 1 | } |
313 | 77 | if (count != 1) { |
314 | | // Expect at most one entry in the table. |
315 | 48 | return false; |
316 | 48 | } |
317 | 29 | return Get32u(stream, stsz_box.offset() + 20, image_size); |
318 | 77 | } |
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 |
327 | | bool ProcessCo64(StreamInterface* stream, const Box& co64_box, |
328 | 178 | uint32_t* image_offset) { |
329 | 178 | uint32_t count = 0; |
330 | 178 | if (!Get32u(stream, co64_box.header_offset() + 4, &count)) { |
331 | 1 | return false; |
332 | 1 | } |
333 | 177 | if (count != 1) { |
334 | 72 | return false; |
335 | 72 | } |
336 | 105 | return Get32u(stream, co64_box.header_offset() + 8, image_offset); |
337 | 177 | } |
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) |
347 | | bool ProcessStblBox(StreamInterface* stream, const Box& stbl_box, |
348 | 566 | ProcessData* data) { |
349 | 566 | Box stsd_box = GetNextBoxWithTag(stream, stbl_box.header_offset(), kStsdTag); |
350 | 566 | if (!stsd_box.IsValid()) { |
351 | 20 | return false; |
352 | 20 | } |
353 | | // This is either CRAW or CTMD. Skip when CTMD. |
354 | 546 | Box craw_box = GetNextBox(stream, stsd_box.header_offset() + 8); |
355 | 546 | if (!craw_box.IsValid()) { |
356 | 3 | return false; |
357 | 3 | } |
358 | 543 | if (craw_box.tag() != kCrawTag) { |
359 | 113 | return true; |
360 | 113 | } |
361 | | // CRAW contains info about the full-size image embedded in the mdat box. |
362 | | // The image is either JPEG or HEVC. |
363 | 430 | uint16_t image_width = 0; |
364 | 430 | uint16_t image_height = 0; |
365 | 430 | uint16_t content_type = 0; |
366 | 430 | if (!ProcessCrawBox(stream, craw_box, &image_width, &image_height, |
367 | 430 | &content_type)) { |
368 | 4 | return false; |
369 | 4 | } |
370 | | // Only continue if JPEG or HEVC content. |
371 | 426 | constexpr uint16_t kJpegContentType = 3; |
372 | 426 | constexpr uint16_t kHevcContentType = 4; |
373 | 426 | if (content_type != kJpegContentType && content_type != kHevcContentType) { |
374 | 96 | return true; |
375 | 96 | } |
376 | | |
377 | | // Skip until we find stsz, contains the size (# of bytes) of image data. |
378 | 330 | Box stsz_box = |
379 | 330 | GetNextBoxWithTag(stream, stsd_box.next_box_offset(), kStszTag); |
380 | 330 | if (!stsz_box.IsValid()) { |
381 | 33 | return false; |
382 | 33 | } |
383 | 297 | uint32_t image_size; |
384 | 297 | if (!ProcessStszBox(stream, stsz_box, &image_size)) { |
385 | 51 | return false; |
386 | 51 | } |
387 | | |
388 | | // Skip until we find co64, contains the offset of image data. |
389 | 246 | Box co64_box = |
390 | 246 | GetNextBoxWithTag(stream, stsz_box.next_box_offset(), kCo64Tag); |
391 | 246 | if (!co64_box.IsValid()) { |
392 | 68 | return false; |
393 | 68 | } |
394 | | |
395 | 178 | uint32_t image_offset = 0; |
396 | 178 | if (!ProcessCo64(stream, co64_box, &image_offset)) { |
397 | 73 | return false; |
398 | 73 | } |
399 | | |
400 | 105 | data->mdat_image.format = content_type == kJpegContentType |
401 | 105 | ? Image::kJpegCompressed |
402 | 105 | : Image::kHevcCompressed; |
403 | 105 | data->mdat_image.width = image_width; |
404 | 105 | data->mdat_image.height = image_height; |
405 | 105 | 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 | 105 | data->mdat_image.offset = image_offset; |
409 | 105 | return true; |
410 | 178 | } |
411 | | |
412 | | // Returns true if we should parse the children of the box. |
413 | 5.05k | bool DoProcessChildren(const BoxTag& tag) { |
414 | 5.05k | static const std::set<BoxTag> kTags = {NewTag("trak"), NewTag("moov"), |
415 | 5.05k | NewTag("mdia"), NewTag("minf")}; |
416 | 5.05k | return kTags.find(tag) != kTags.end(); |
417 | 5.05k | } |
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 ... |
451 | 6.41k | size_t ProcessBox(StreamInterface* stream, const Box& box, ProcessData* data) { |
452 | | // Parse child boxes. |
453 | 6.41k | if (box.tag() == kUuidTag) { |
454 | | // Uuid box have extended box types. |
455 | 1.36k | Uuid uuid; |
456 | 1.36k | if (stream->GetData(box.header_offset(), uuid.size(), uuid.data()) != kOk) { |
457 | 1 | return 0; |
458 | 1 | } |
459 | 1.36k | if (uuid == kUuidPrvw) { |
460 | 616 | return box.header_offset() + uuid.size() + 8; |
461 | 744 | } else if (uuid == kUuidMoov) { |
462 | 609 | return box.header_offset() + uuid.size(); |
463 | 609 | } // else skip the box, below. |
464 | 5.05k | } else if (DoProcessChildren(box.tag())) { |
465 | 27 | return box.header_offset(); |
466 | 27 | } |
467 | | |
468 | | // Potentially process the data contained in the box. |
469 | 5.16k | bool success; |
470 | 5.16k | 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 | 134 | data->mdat_image.offset += box.header_offset(); |
474 | 134 | success = true; |
475 | 5.02k | } else if (box.tag() == kStblTag) { |
476 | 566 | success = ProcessStblBox(stream, box, data); |
477 | 4.46k | } else if (box.tag() == kPrvwTag) { |
478 | | // Preview jpeg. 1620x1080 for EOS R. |
479 | 333 | success = ProcessJpegBox(stream, box, &data->prvw_image); |
480 | 4.13k | } else if (box.tag() == kThmbTag) { |
481 | | // Thumbnail jpeg. 160x120 for EOS R. |
482 | 95 | success = ProcessJpegBox(stream, box, &data->preview_image_data->thumbnail); |
483 | 4.03k | } else if (box.tag() == kCmt1Tag) { |
484 | 346 | success = |
485 | 346 | ParseExifIfd0(stream, box.header_offset(), data->preview_image_data); |
486 | 3.68k | } else if (box.tag() == kCmt2Tag) { |
487 | 872 | success = |
488 | 872 | ParseExifExifIfd(stream, box.header_offset(), data->preview_image_data); |
489 | 2.81k | } else { |
490 | | // This box isn't interesting, skip it. |
491 | 2.81k | success = true; |
492 | 2.81k | } |
493 | 5.16k | return success ? box.next_box_offset() : 0; |
494 | 6.41k | } |
495 | | |
496 | | bool ProcessStream(StreamInterface* stream, const BoxTag& last_chunk, |
497 | 723 | ProcessData* data) { |
498 | 723 | size_t offset = 0; |
499 | 6.68k | while (true) { |
500 | 6.68k | Box box = GetNextBox(stream, offset); |
501 | 6.68k | if (!box.IsValid()) { |
502 | 268 | return false; |
503 | 268 | } |
504 | 6.41k | size_t new_offset = ProcessBox(stream, box, data); |
505 | 6.41k | if (new_offset <= offset) { |
506 | 321 | return false; |
507 | 321 | } |
508 | 6.09k | if (box.tag() == last_chunk) { |
509 | 134 | return true; |
510 | 134 | } |
511 | 5.96k | offset = new_offset; |
512 | 5.96k | } |
513 | 723 | } |
514 | | |
515 | 129 | bool IsImage(StreamInterface* stream, const Image& image) { |
516 | 129 | if (image.format != Image::kJpegCompressed) { |
517 | | // Pass responsibility to the caller. |
518 | 5 | return true; |
519 | 5 | } |
520 | | // Check for JPEG magic number at start. This could be HEVC data. |
521 | 124 | constexpr std::array<uint8_t, 3> kJpegMagicNumber = {0xFF, 0xD8, 0xFF}; |
522 | 124 | std::array<uint8_t, 3> magic_number; |
523 | 124 | if (stream->GetData(image.offset, magic_number.size(), magic_number.data()) != |
524 | 124 | kOk) { |
525 | 15 | return false; |
526 | 15 | } |
527 | 109 | return magic_number == kJpegMagicNumber; |
528 | 124 | } |
529 | | |
530 | | } // namespace |
531 | | |
532 | | Error Cr3GetPreviewData(StreamInterface* stream, |
533 | 723 | PreviewImageData* preview_image_data) { |
534 | 723 | ProcessData data{.preview_image_data = preview_image_data}; |
535 | 723 | if (!ProcessStream(stream, kMdatTag, &data)) { |
536 | 589 | return kFail; |
537 | 589 | } |
538 | | // Prefer image in mdata box, as spec ensures it is the largest image. |
539 | 134 | if (data.mdat_image.length > 0 && IsImage(stream, data.mdat_image)) { |
540 | 7 | preview_image_data->preview = data.mdat_image; |
541 | 127 | } else if (data.prvw_image.length > 0 && IsImage(stream, data.prvw_image)) { |
542 | 2 | preview_image_data->preview = data.prvw_image; |
543 | 125 | } else { |
544 | 125 | return kFail; |
545 | 125 | } |
546 | 9 | return kOk; |
547 | 134 | } |
548 | | |
549 | 0 | bool Cr3GetOrientation(StreamInterface* stream, std::uint32_t* orientation) { |
550 | 0 | PreviewImageData preview_image_data; |
551 | 0 | ProcessData data{.preview_image_data = &preview_image_data}; |
552 | 0 | if (ProcessStream(stream, kCmt1Tag, &data)) { |
553 | 0 | *orientation = preview_image_data.exif_orientation; |
554 | 0 | return true; |
555 | 0 | } |
556 | 0 | return false; |
557 | 0 | } |
558 | | |
559 | | } // namespace piex |