Nick Chusid | 5c5b671 | 2021-06-03 15:33:24 -0400 | [diff] [blame] | 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<std::uint8_t, kTagSize>; |
| 45 | |
| 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: |
| 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 | |
| 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 | 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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 = |
| 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. |
| 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. |
| 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 |
| 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 |
| 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) |
| 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. |
| 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 ... |
| 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 | |
| 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 | |
| 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 | |
| 532 | Error 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 | |
| 549 | bool 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 |