mirror of
https://github.com/Evolution-X/external_piex
synced 2026-01-27 16:24:09 +00:00
Add support for CR3s with embedded heif thumbnails
Change-Id: I2cb6b1e46ed91961744cc5b795cc7144c6cacb54
This commit is contained in:
2
piex.gyp
2
piex.gyp
@@ -18,11 +18,13 @@
|
|||||||
'type': 'static_library',
|
'type': 'static_library',
|
||||||
'sources': [
|
'sources': [
|
||||||
'src/piex.cc',
|
'src/piex.cc',
|
||||||
|
'src/piex_cr3.cc',
|
||||||
'src/tiff_parser.cc',
|
'src/tiff_parser.cc',
|
||||||
],
|
],
|
||||||
'variables': {
|
'variables': {
|
||||||
'headers': [
|
'headers': [
|
||||||
'src/piex.h',
|
'src/piex.h',
|
||||||
|
'src/piex_cr3.h',
|
||||||
'src/piex_types.h',
|
'src/piex_types.h',
|
||||||
'src/tiff_parser.h',
|
'src/tiff_parser.h',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -201,6 +201,25 @@ class ArwTypeChecker : public TypeChecker {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Canon RAW (CR3 extension).
|
||||||
|
class Cr3TypeChecker : public TypeChecker {
|
||||||
|
public:
|
||||||
|
static constexpr size_t kSignatureOffset = 4;
|
||||||
|
static constexpr const char* kSignature = "ftypcrx ";
|
||||||
|
|
||||||
|
virtual RawImageTypes Type() const { return kCr3Image; }
|
||||||
|
|
||||||
|
virtual size_t RequestedSize() const {
|
||||||
|
return kSignatureOffset + strlen(kSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for the ftyp box w/ brand 'crx '.
|
||||||
|
virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
|
||||||
|
RangeCheckedBytePtr limited_source = LimitSource(source);
|
||||||
|
return IsSignatureMatched(limited_source, kSignatureOffset, kSignature);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Canon RAW (CR2 extension).
|
// Canon RAW (CR2 extension).
|
||||||
class Cr2TypeChecker : public TypeChecker {
|
class Cr2TypeChecker : public TypeChecker {
|
||||||
public:
|
public:
|
||||||
@@ -749,6 +768,7 @@ class TypeCheckerList {
|
|||||||
TypeCheckerList() {
|
TypeCheckerList() {
|
||||||
// Add all supported RAW type checkers here.
|
// Add all supported RAW type checkers here.
|
||||||
checkers_.push_back(new ArwTypeChecker());
|
checkers_.push_back(new ArwTypeChecker());
|
||||||
|
checkers_.push_back(new Cr3TypeChecker());
|
||||||
checkers_.push_back(new Cr2TypeChecker());
|
checkers_.push_back(new Cr2TypeChecker());
|
||||||
checkers_.push_back(new CrwTypeChecker());
|
checkers_.push_back(new CrwTypeChecker());
|
||||||
checkers_.push_back(new DcrTypeChecker());
|
checkers_.push_back(new DcrTypeChecker());
|
||||||
@@ -841,6 +861,7 @@ bool IsRaw(const RawImageTypes type) {
|
|||||||
|
|
||||||
// Raw image types
|
// Raw image types
|
||||||
case kArwImage:
|
case kArwImage:
|
||||||
|
case kCr3Image:
|
||||||
case kCr2Image:
|
case kCr2Image:
|
||||||
case kCrwImage:
|
case kCrwImage:
|
||||||
case kDcrImage:
|
case kDcrImage:
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ enum RawImageTypes {
|
|||||||
// raw image types
|
// raw image types
|
||||||
kArwImage,
|
kArwImage,
|
||||||
kCr2Image,
|
kCr2Image,
|
||||||
|
kCr3Image,
|
||||||
kCrwImage,
|
kCrwImage,
|
||||||
kDcrImage,
|
kDcrImage,
|
||||||
kDngImage,
|
kDngImage,
|
||||||
|
|||||||
28
src/piex.cc
28
src/piex.cc
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include "src/binary_parse/range_checked_byte_ptr.h"
|
#include "src/binary_parse/range_checked_byte_ptr.h"
|
||||||
#include "src/image_type_recognition/image_type_recognition_lite.h"
|
#include "src/image_type_recognition/image_type_recognition_lite.h"
|
||||||
|
#include "src/piex_cr3.h"
|
||||||
#include "src/tiff_parser.h"
|
#include "src/tiff_parser.h"
|
||||||
|
|
||||||
namespace piex {
|
namespace piex {
|
||||||
@@ -649,7 +650,8 @@ bool IsRaw(StreamInterface* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Error GetPreviewImageData(StreamInterface* data,
|
Error GetPreviewImageData(StreamInterface* data,
|
||||||
PreviewImageData* preview_image_data) {
|
PreviewImageData* preview_image_data,
|
||||||
|
RawImageTypes* output_type) {
|
||||||
const size_t bytes = BytesRequiredForIsRaw();
|
const size_t bytes = BytesRequiredForIsRaw();
|
||||||
if (data == nullptr || bytes == 0) {
|
if (data == nullptr || bytes == 0) {
|
||||||
return kFail;
|
return kFail;
|
||||||
@@ -662,11 +664,15 @@ Error GetPreviewImageData(StreamInterface* data,
|
|||||||
}
|
}
|
||||||
RangeCheckedBytePtr header_buffer(file_header.data(), file_header.size());
|
RangeCheckedBytePtr header_buffer(file_header.data(), file_header.size());
|
||||||
|
|
||||||
switch (RecognizeRawImageTypeLite(header_buffer)) {
|
RawImageTypes type = RecognizeRawImageTypeLite(header_buffer);
|
||||||
|
if (output_type != nullptr) *output_type = type;
|
||||||
|
switch (type) {
|
||||||
case image_type_recognition::kArwImage:
|
case image_type_recognition::kArwImage:
|
||||||
return ArwGetPreviewData(data, preview_image_data);
|
return ArwGetPreviewData(data, preview_image_data);
|
||||||
case image_type_recognition::kCr2Image:
|
case image_type_recognition::kCr2Image:
|
||||||
return Cr2GetPreviewData(data, preview_image_data);
|
return Cr2GetPreviewData(data, preview_image_data);
|
||||||
|
case image_type_recognition::kCr3Image:
|
||||||
|
return Cr3GetPreviewData(data, preview_image_data);
|
||||||
case image_type_recognition::kDngImage:
|
case image_type_recognition::kDngImage:
|
||||||
return DngGetPreviewData(data, preview_image_data);
|
return DngGetPreviewData(data, preview_image_data);
|
||||||
case image_type_recognition::kNefImage:
|
case image_type_recognition::kNefImage:
|
||||||
@@ -703,24 +709,32 @@ bool GetOrientation(StreamInterface* data, std::uint32_t* orientation) {
|
|||||||
using image_type_recognition::GetNumberOfBytesForIsOfType;
|
using image_type_recognition::GetNumberOfBytesForIsOfType;
|
||||||
using image_type_recognition::IsOfType;
|
using image_type_recognition::IsOfType;
|
||||||
|
|
||||||
std::vector<std::uint8_t> file_header(
|
size_t min_header_bytes =
|
||||||
GetNumberOfBytesForIsOfType(image_type_recognition::kRafImage));
|
std::max(GetNumberOfBytesForIsOfType(image_type_recognition::kRafImage),
|
||||||
|
GetNumberOfBytesForIsOfType(image_type_recognition::kCr3Image));
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> file_header(min_header_bytes);
|
||||||
if (data->GetData(0, file_header.size(), file_header.data()) != kOk) {
|
if (data->GetData(0, file_header.size(), file_header.data()) != kOk) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For RAF files a special routine is necessary to get orientation. For others
|
// For RAF and CR# files a special routine is necessary to get orientation.
|
||||||
// the general approach is sufficient.
|
// For others the general approach is sufficient.
|
||||||
if (IsOfType(RangeCheckedBytePtr(file_header.data(), file_header.size()),
|
if (IsOfType(RangeCheckedBytePtr(file_header.data(), file_header.size()),
|
||||||
image_type_recognition::kRafImage)) {
|
image_type_recognition::kRafImage)) {
|
||||||
return RafGetOrientation(data, orientation);
|
return RafGetOrientation(data, orientation);
|
||||||
|
} else if (IsOfType(
|
||||||
|
RangeCheckedBytePtr(file_header.data(), file_header.size()),
|
||||||
|
image_type_recognition::kCr3Image)) {
|
||||||
|
return Cr3GetOrientation(data, orientation);
|
||||||
} else {
|
} else {
|
||||||
return GetExifOrientation(data, 0 /* offset */, orientation);
|
return GetExifOrientation(data, 0 /* offset */, orientation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> SupportedExtensions() {
|
std::vector<std::string> SupportedExtensions() {
|
||||||
return {"ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "PEF", "RAF", "RW2", "SRW"};
|
return {"ARW", "CR2", "CR3", "DNG", "NEF", "NRW",
|
||||||
|
"ORF", "PEF", "RAF", "RW2", "SRW"};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace piex
|
} // namespace piex
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "src/image_type_recognition/image_type_recognition_lite.h"
|
||||||
#include "src/piex_types.h"
|
#include "src/piex_types.h"
|
||||||
|
|
||||||
namespace piex {
|
namespace piex {
|
||||||
@@ -70,8 +71,11 @@ bool IsRaw(StreamInterface* data);
|
|||||||
//
|
//
|
||||||
// One could check the "preview_image_data->preview_length != 0" for the
|
// One could check the "preview_image_data->preview_length != 0" for the
|
||||||
// existance of a preview image.
|
// existance of a preview image.
|
||||||
Error GetPreviewImageData(StreamInterface* data,
|
//
|
||||||
PreviewImageData* preview_image_data);
|
// Updates output_type based on data, if output_type is non-null.
|
||||||
|
Error GetPreviewImageData(
|
||||||
|
StreamInterface* data, PreviewImageData* preview_image_data,
|
||||||
|
image_type_recognition::RawImageTypes* output_type = nullptr);
|
||||||
|
|
||||||
// Returns true if the full width and height and the mosaic pattern dimension of
|
// Returns true if the full width and height and the mosaic pattern dimension of
|
||||||
// a DNG image could be obtained. False otherwise.
|
// a DNG image could be obtained. False otherwise.
|
||||||
|
|||||||
559
src/piex_cr3.cc
Normal file
559
src/piex_cr3.cc
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
// Copyright 2020 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "src/piex_cr3.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include "src/binary_parse/range_checked_byte_ptr.h"
|
||||||
|
#include "src/piex_types.h"
|
||||||
|
#include "src/tiff_directory/tiff_directory.h"
|
||||||
|
#include "src/tiff_parser.h"
|
||||||
|
|
||||||
|
namespace piex {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr size_t kUuidSize = 16;
|
||||||
|
using Uuid = std::array<std::uint8_t, kUuidSize>;
|
||||||
|
// Uuid of uuid box under the moov box.
|
||||||
|
constexpr Uuid kUuidMoov = {0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0,
|
||||||
|
0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, 0x48};
|
||||||
|
|
||||||
|
// Uuid of uuid box containing PRVW box.
|
||||||
|
constexpr Uuid kUuidPrvw = {0xea, 0xf4, 0x2b, 0x5e, 0x1c, 0x98, 0x4b, 0x88,
|
||||||
|
0xb9, 0xfb, 0xb7, 0xdc, 0x40, 0x6e, 0x4d, 0x16};
|
||||||
|
|
||||||
|
constexpr size_t kTagSize = 4;
|
||||||
|
using BoxTag = std::array<std::uint8_t, kTagSize>;
|
||||||
|
|
||||||
|
constexpr BoxTag NewTag(const char s[kTagSize + 1]) {
|
||||||
|
return BoxTag{s[0], s[1], s[2], s[3]};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr BoxTag kUuidTag = NewTag("uuid");
|
||||||
|
constexpr BoxTag kPrvwTag = NewTag("PRVW");
|
||||||
|
constexpr BoxTag kThmbTag = NewTag("THMB");
|
||||||
|
constexpr BoxTag kCmt1Tag = NewTag("CMT1");
|
||||||
|
constexpr BoxTag kCmt2Tag = NewTag("CMT2");
|
||||||
|
constexpr BoxTag kStblTag = NewTag("stbl");
|
||||||
|
constexpr BoxTag kStsdTag = NewTag("stsd");
|
||||||
|
constexpr BoxTag kCrawTag = NewTag("CRAW");
|
||||||
|
constexpr BoxTag kStszTag = NewTag("stsz");
|
||||||
|
constexpr BoxTag kCo64Tag = NewTag("co64");
|
||||||
|
constexpr BoxTag kMdatTag = NewTag("mdat");
|
||||||
|
|
||||||
|
// Convenience class for a box.
|
||||||
|
class Box {
|
||||||
|
public:
|
||||||
|
Box()
|
||||||
|
: is_valid_(false),
|
||||||
|
tag_(BoxTag()),
|
||||||
|
offset_(0),
|
||||||
|
header_offset_(0),
|
||||||
|
next_box_offset_(0) {}
|
||||||
|
Box(const BoxTag& tag, size_t offset, size_t header_length, size_t length)
|
||||||
|
: is_valid_(true),
|
||||||
|
tag_(tag),
|
||||||
|
offset_(offset),
|
||||||
|
header_offset_(offset + header_length),
|
||||||
|
next_box_offset_(offset + length) {}
|
||||||
|
|
||||||
|
bool IsValid() const { return is_valid_ && next_box_offset_ > offset_; }
|
||||||
|
const BoxTag& tag() const { return tag_; }
|
||||||
|
|
||||||
|
// Returns offset from start of file.
|
||||||
|
size_t offset() const { return offset_; }
|
||||||
|
// Returns offset from start of file, including box's header.
|
||||||
|
size_t header_offset() const { return header_offset_; }
|
||||||
|
// Returns offset from start of file of the next box, accounting for size of
|
||||||
|
// this box.
|
||||||
|
size_t next_box_offset() const { return next_box_offset_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool is_valid_;
|
||||||
|
BoxTag tag_;
|
||||||
|
size_t offset_;
|
||||||
|
size_t header_offset_;
|
||||||
|
size_t next_box_offset_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ProcessData {
|
||||||
|
PreviewImageData* preview_image_data = nullptr;
|
||||||
|
Image mdat_image;
|
||||||
|
Image prvw_image;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wraps Get16u w/ assumption that CR3 is always big endian, based on
|
||||||
|
// ISO/IEC 14496-12 specification that all box fields are big endian.
|
||||||
|
bool Get16u(StreamInterface* stream, size_t offset, std::uint16_t* value) {
|
||||||
|
return Get16u(stream, offset, tiff_directory::kBigEndian, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wraps Get32u w/ assumption that CR3 is always big endian, based on
|
||||||
|
// ISO/IEC 14496-12 specification that all box fields are big endian.
|
||||||
|
bool Get32u(StreamInterface* stream, size_t offset, std::uint32_t* value) {
|
||||||
|
return Get32u(stream, offset, tiff_directory::kBigEndian, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always big endian, based on ISO/IEC 14496-12 specification that all box
|
||||||
|
// fields are big endian.
|
||||||
|
bool Get64u(StreamInterface* stream, size_t offset, std::uint64_t* value) {
|
||||||
|
std::uint8_t data[8];
|
||||||
|
if (stream->GetData(offset, 8, data) == kOk) {
|
||||||
|
*value = (data[0] * 0x1000000u) | (data[1] * 0x10000u) |
|
||||||
|
(data[2] * 0x100u) | data[3];
|
||||||
|
*value <<= 32;
|
||||||
|
*value = (data[4] * 0x1000000u) | (data[5] * 0x10000u) |
|
||||||
|
(data[6] * 0x100u) | data[7];
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jpeg box offsets based on the box tag. The expected layout is as follows:
|
||||||
|
// Byte Offset Type Meaning
|
||||||
|
// 0 [long] size of box
|
||||||
|
// 4 [char[]] box tag
|
||||||
|
// offset.width [short] width of jpeg
|
||||||
|
// offset.height [short] height of jpeg
|
||||||
|
// offset.jpeg_size [long] number of bytes in jpeg
|
||||||
|
// offset.jpeg_data [byte[]] start of jpeg data
|
||||||
|
struct JpegBoxOffset {
|
||||||
|
size_t width = 0;
|
||||||
|
size_t height = 0;
|
||||||
|
size_t jpeg_size = 0;
|
||||||
|
size_t jpeg_data = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Processes box w/ JPEG data. Box must be PRVW and THMB boxes.
|
||||||
|
bool ProcessJpegBox(StreamInterface* stream, const Box& box, Image* image) {
|
||||||
|
static constexpr JpegBoxOffset kPrvwJpegOffsets{14, 16, 20, 24};
|
||||||
|
static constexpr JpegBoxOffset kThmbJpegOffsets{12, 14, 16, 24};
|
||||||
|
if (box.tag() != kPrvwTag && box.tag() != kThmbTag) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const JpegBoxOffset& offsets =
|
||||||
|
box.tag() == kPrvwTag ? kPrvwJpegOffsets : kThmbJpegOffsets;
|
||||||
|
uint16_t width, height;
|
||||||
|
uint32_t jpeg_size;
|
||||||
|
if (!Get16u(stream, box.offset() + offsets.width, &width)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Get16u(stream, box.offset() + offsets.height, &height)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Get32u(stream, box.offset() + offsets.jpeg_size, &jpeg_size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
image->format = Image::kJpegCompressed;
|
||||||
|
image->width = width;
|
||||||
|
image->height = height;
|
||||||
|
image->offset = box.offset() + offsets.jpeg_data;
|
||||||
|
image->length = jpeg_size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses the Exif IFD0 tags at tiff_offset.
|
||||||
|
bool ParseExifIfd0(StreamInterface* stream, size_t tiff_offset,
|
||||||
|
PreviewImageData* preview_image_data) {
|
||||||
|
static const TagSet kIfd0TagSet = {kTiffTagModel, kTiffTagMake,
|
||||||
|
kTiffTagOrientation, kTiffTagImageWidth,
|
||||||
|
kTiffTagImageLength};
|
||||||
|
TiffContent content;
|
||||||
|
TiffParser(stream, tiff_offset).Parse(kIfd0TagSet, 1, &content);
|
||||||
|
if (content.tiff_directory.size() != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
content.tiff_directory[0].Get(kTiffTagModel, &preview_image_data->model);
|
||||||
|
content.tiff_directory[0].Get(kTiffTagMake, &preview_image_data->maker);
|
||||||
|
content.tiff_directory[0].Get(kTiffTagOrientation,
|
||||||
|
&preview_image_data->exif_orientation);
|
||||||
|
content.tiff_directory[0].Get(kTiffTagImageWidth,
|
||||||
|
&preview_image_data->full_width);
|
||||||
|
content.tiff_directory[0].Get(kTiffTagImageLength,
|
||||||
|
&preview_image_data->full_height);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses the Exif Exif IFD tags at tiff_offset.
|
||||||
|
bool ParseExifExifIfd(StreamInterface* stream, size_t tiff_offset,
|
||||||
|
PreviewImageData* preview_image_data) {
|
||||||
|
static const TagSet kExifIfdTagSet = {kExifTagDateTimeOriginal,
|
||||||
|
kExifTagExposureTime, kExifTagFnumber,
|
||||||
|
kExifTagFocalLength, kExifTagIsoSpeed};
|
||||||
|
TiffContent content;
|
||||||
|
TiffParser(stream, tiff_offset).Parse(kExifIfdTagSet, 1, &content);
|
||||||
|
if (content.tiff_directory.size() != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
content.tiff_directory[0].Get(kExifTagDateTimeOriginal,
|
||||||
|
&preview_image_data->date_time);
|
||||||
|
GetRational(kExifTagExposureTime, content.tiff_directory[0], 1,
|
||||||
|
&preview_image_data->exposure_time);
|
||||||
|
GetRational(kExifTagFnumber, content.tiff_directory[0], 1,
|
||||||
|
&preview_image_data->fnumber);
|
||||||
|
GetRational(kExifTagFocalLength, content.tiff_directory[0], 1,
|
||||||
|
&preview_image_data->focal_length);
|
||||||
|
content.tiff_directory[0].Get(kExifTagIsoSpeed, &preview_image_data->iso);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the next box or an invalid box.
|
||||||
|
//
|
||||||
|
// Based on ISO/IEC 14496-12: boxes start with a header: size and type. The size
|
||||||
|
// can be compact (32-bits) or extended (64-bit, e.g. mdat box).
|
||||||
|
// The type can be compact (32 bits) or extended (full UUID, e.g. uuid boxes).
|
||||||
|
// values are stored after the compact size/type.
|
||||||
|
//
|
||||||
|
// Fields in a box are big-endian.
|
||||||
|
Box GetNextBox(StreamInterface* stream, size_t offset) {
|
||||||
|
uint32_t length_32;
|
||||||
|
if (!Get32u(stream, offset, &length_32)) {
|
||||||
|
return Box();
|
||||||
|
}
|
||||||
|
BoxTag tag;
|
||||||
|
Error status =
|
||||||
|
stream->GetData(offset + sizeof(length_32), kTagSize, tag.data());
|
||||||
|
if (status != kOk) {
|
||||||
|
return Box();
|
||||||
|
}
|
||||||
|
size_t length;
|
||||||
|
size_t header_offset = sizeof(length_32) + sizeof(tag);
|
||||||
|
if (length_32 == 1) {
|
||||||
|
// Magic number of 1 implies extended size.
|
||||||
|
uint64_t length_64 = 0;
|
||||||
|
if (!Get64u(stream, offset + header_offset, &length_64)) {
|
||||||
|
return Box();
|
||||||
|
}
|
||||||
|
length = length_64;
|
||||||
|
header_offset += sizeof(length_64);
|
||||||
|
} else {
|
||||||
|
// Compact size.
|
||||||
|
length = length_32;
|
||||||
|
}
|
||||||
|
return Box(tag, offset, header_offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searches for the next box with the given tag.
|
||||||
|
Box GetNextBoxWithTag(StreamInterface* stream, size_t offset,
|
||||||
|
const BoxTag& expected_tag) {
|
||||||
|
while (true) {
|
||||||
|
Box box = GetNextBox(stream, offset);
|
||||||
|
if (!box.IsValid() || box.tag() == expected_tag) {
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
offset = box.next_box_offset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the width, height, and content type from the CRAW box.
|
||||||
|
bool ProcessCrawBox(StreamInterface* stream, const Box& craw_box,
|
||||||
|
uint16_t* width, uint16_t* height, uint16_t* content_type) {
|
||||||
|
constexpr size_t kWidthOffset = 32;
|
||||||
|
if (!Get16u(stream, craw_box.offset() + kWidthOffset, width)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t kHeightOffset = 34;
|
||||||
|
if (!Get16u(stream, craw_box.offset() + kHeightOffset, height)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t kTypeOffset = 86;
|
||||||
|
if (!Get16u(stream, craw_box.offset() + kTypeOffset, content_type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stsz box offset:
|
||||||
|
// Byte Offset Type Meaning
|
||||||
|
// 0 [long] size of box
|
||||||
|
// 4 [char[]] box tag
|
||||||
|
// 8 [long] version/flags
|
||||||
|
// 12 [long] sample size
|
||||||
|
// 16 [long] number of entries in sample table
|
||||||
|
// 20 [long[]] sample table if samples size is 0
|
||||||
|
bool ProcessStszBox(StreamInterface* stream, const Box& stsz_box,
|
||||||
|
uint32_t* image_size) {
|
||||||
|
uint32_t sample_size;
|
||||||
|
if (!Get32u(stream, stsz_box.offset() + 12, &sample_size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sample_size > 0) {
|
||||||
|
*image_size = sample_size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// sample_size of 0 implies the data is in the sample table. We expect only
|
||||||
|
// one entry. This is true of Canon EOS RP Cr3 files.
|
||||||
|
uint32_t count;
|
||||||
|
if (!Get32u(stream, stsz_box.offset() + 16, &count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (count != 1) {
|
||||||
|
// Expect at most one entry in the table.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Get32u(stream, stsz_box.offset() + 20, image_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// co64 box offsets:
|
||||||
|
// Byte Offset Type Meaning
|
||||||
|
// 0 [long] size of box
|
||||||
|
// 4 [char[]] box tag
|
||||||
|
// 8 [long] version
|
||||||
|
// 12 [long] count (expect to be value 1)
|
||||||
|
// 16 [long] offset of image data in mdat
|
||||||
|
bool ProcessCo64(StreamInterface* stream, const Box& co64_box,
|
||||||
|
uint32_t* image_offset) {
|
||||||
|
uint32_t count = 0;
|
||||||
|
if (!Get32u(stream, co64_box.header_offset() + 4, &count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (count != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Get32u(stream, co64_box.header_offset() + 8, image_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the stbl box. Expected box layout:
|
||||||
|
// stbl
|
||||||
|
// stsd
|
||||||
|
// CRAW (embedded image (JPEG) information)
|
||||||
|
// (0 or more skipped boxes)
|
||||||
|
// stsz (embedded image byte size)
|
||||||
|
// (0 or more skipped boxes)
|
||||||
|
// co64 (offset of embedded image, relative to mdat box)
|
||||||
|
bool ProcessStblBox(StreamInterface* stream, const Box& stbl_box,
|
||||||
|
ProcessData* data) {
|
||||||
|
Box stsd_box = GetNextBoxWithTag(stream, stbl_box.header_offset(), kStsdTag);
|
||||||
|
if (!stsd_box.IsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// This is either CRAW or CTMD. Skip when CTMD.
|
||||||
|
Box craw_box = GetNextBox(stream, stsd_box.header_offset() + 8);
|
||||||
|
if (!craw_box.IsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (craw_box.tag() != kCrawTag) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// CRAW contains info about the full-size image embedded in the mdat box.
|
||||||
|
// The image is either JPEG or HEVC.
|
||||||
|
uint16_t image_width = 0;
|
||||||
|
uint16_t image_height = 0;
|
||||||
|
uint16_t content_type = 0;
|
||||||
|
if (!ProcessCrawBox(stream, craw_box, &image_width, &image_height,
|
||||||
|
&content_type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Only continue if JPEG or HEVC content.
|
||||||
|
constexpr uint16_t kJpegContentType = 3;
|
||||||
|
constexpr uint16_t kHevcContentType = 4;
|
||||||
|
if (content_type != kJpegContentType && content_type != kHevcContentType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip until we find stsz, contains the size (# of bytes) of image data.
|
||||||
|
Box stsz_box =
|
||||||
|
GetNextBoxWithTag(stream, stsd_box.next_box_offset(), kStszTag);
|
||||||
|
if (!stsz_box.IsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint32_t image_size;
|
||||||
|
if (!ProcessStszBox(stream, stsz_box, &image_size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip until we find co64, contains the offset of image data.
|
||||||
|
Box co64_box =
|
||||||
|
GetNextBoxWithTag(stream, stsz_box.next_box_offset(), kCo64Tag);
|
||||||
|
if (!co64_box.IsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t image_offset = 0;
|
||||||
|
if (!ProcessCo64(stream, co64_box, &image_offset)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->mdat_image.format = content_type == kJpegContentType
|
||||||
|
? Image::kJpegCompressed
|
||||||
|
: Image::kHevcCompressed;
|
||||||
|
data->mdat_image.width = image_width;
|
||||||
|
data->mdat_image.height = image_height;
|
||||||
|
data->mdat_image.length = image_size;
|
||||||
|
// This offset is relative to the position of the mdat box. The value will
|
||||||
|
// be updated once mdat's offset is found.
|
||||||
|
data->mdat_image.offset = image_offset;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if we should parse the children of the box.
|
||||||
|
bool DoProcessChildren(const BoxTag& tag) {
|
||||||
|
static const std::set<BoxTag> kTags = {NewTag("trak"), NewTag("moov"),
|
||||||
|
NewTag("mdia"), NewTag("minf")};
|
||||||
|
return kTags.find(tag) != kTags.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processes box and returns offset of the next box to process.
|
||||||
|
// A return value of 0 indicates an error.
|
||||||
|
//
|
||||||
|
// Outline of hierarchy and important boxes:
|
||||||
|
// ftyp
|
||||||
|
// moov
|
||||||
|
// uuid (id is kUuidMoov)
|
||||||
|
// ... boxes we skip ...
|
||||||
|
// CMT1 (EXIF data)
|
||||||
|
// CMT2 (EXIF data)
|
||||||
|
// ... boxes we skip ...
|
||||||
|
// THMB (160x120 JPEG thumbnail, embedded in this box)
|
||||||
|
// trak
|
||||||
|
// tkhd
|
||||||
|
// mdia
|
||||||
|
// ... boxes we skip ...
|
||||||
|
// minf
|
||||||
|
// ... boxes we skip ...
|
||||||
|
// stbl
|
||||||
|
// stsd
|
||||||
|
// CRAW (Full image preview, type (JPEG or HEVC), width, height. The
|
||||||
|
// image data is found in mdat box, below.)
|
||||||
|
// ... boxes we skip ...
|
||||||
|
// stsz (Size of preview, in bytes)
|
||||||
|
// ... boxes we skip ...
|
||||||
|
// co64 (Location/offset of full preview data in mdat)
|
||||||
|
// .. boxes we skip ...
|
||||||
|
// uuid (id is kUuidPrvw)
|
||||||
|
// PRVW (1620x1080 JPEG preview, embedded in this box)
|
||||||
|
// mdat
|
||||||
|
// Full image preview (JPEG or HEVC)
|
||||||
|
// ... RAW image data ...
|
||||||
|
size_t ProcessBox(StreamInterface* stream, const Box& box, ProcessData* data) {
|
||||||
|
// Parse child boxes.
|
||||||
|
if (box.tag() == kUuidTag) {
|
||||||
|
// Uuid box have extended box types.
|
||||||
|
Uuid uuid;
|
||||||
|
if (stream->GetData(box.header_offset(), uuid.size(), uuid.data()) != kOk) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (uuid == kUuidPrvw) {
|
||||||
|
return box.header_offset() + uuid.size() + 8;
|
||||||
|
} else if (uuid == kUuidMoov) {
|
||||||
|
return box.header_offset() + uuid.size();
|
||||||
|
} // else skip the box, below.
|
||||||
|
} else if (DoProcessChildren(box.tag())) {
|
||||||
|
return box.header_offset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Potentially process the data contained in the box.
|
||||||
|
bool success;
|
||||||
|
if (box.tag() == kMdatTag) {
|
||||||
|
// mdat_image.offset is relative to mdat's header, update it to be absolute
|
||||||
|
// offset to the image data.
|
||||||
|
data->mdat_image.offset += box.header_offset();
|
||||||
|
success = true;
|
||||||
|
} else if (box.tag() == kStblTag) {
|
||||||
|
success = ProcessStblBox(stream, box, data);
|
||||||
|
} else if (box.tag() == kPrvwTag) {
|
||||||
|
// Preview jpeg. 1620x1080 for EOS R.
|
||||||
|
success = ProcessJpegBox(stream, box, &data->prvw_image);
|
||||||
|
} else if (box.tag() == kThmbTag) {
|
||||||
|
// Thumbnail jpeg. 160x120 for EOS R.
|
||||||
|
success = ProcessJpegBox(stream, box, &data->preview_image_data->thumbnail);
|
||||||
|
} else if (box.tag() == kCmt1Tag) {
|
||||||
|
success =
|
||||||
|
ParseExifIfd0(stream, box.header_offset(), data->preview_image_data);
|
||||||
|
} else if (box.tag() == kCmt2Tag) {
|
||||||
|
success =
|
||||||
|
ParseExifExifIfd(stream, box.header_offset(), data->preview_image_data);
|
||||||
|
} else {
|
||||||
|
// This box isn't interesting, skip it.
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
return success ? box.next_box_offset() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProcessStream(StreamInterface* stream, const BoxTag& last_chunk,
|
||||||
|
ProcessData* data) {
|
||||||
|
size_t offset = 0;
|
||||||
|
while (true) {
|
||||||
|
Box box = GetNextBox(stream, offset);
|
||||||
|
if (!box.IsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t new_offset = ProcessBox(stream, box, data);
|
||||||
|
if (new_offset <= offset) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (box.tag() == last_chunk) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
offset = new_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsImage(StreamInterface* stream, const Image& image) {
|
||||||
|
if (image.format != Image::kJpegCompressed) {
|
||||||
|
// Pass responsibility to the caller.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Check for JPEG magic number at start. This could be HEVC data.
|
||||||
|
constexpr std::array<uint8_t, 3> kJpegMagicNumber = {0xFF, 0xD8, 0xFF};
|
||||||
|
std::array<uint8_t, 3> magic_number;
|
||||||
|
if (stream->GetData(image.offset, magic_number.size(), magic_number.data()) !=
|
||||||
|
kOk) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return magic_number == kJpegMagicNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Error Cr3GetPreviewData(StreamInterface* stream,
|
||||||
|
PreviewImageData* preview_image_data) {
|
||||||
|
ProcessData data{preview_image_data};
|
||||||
|
if (!ProcessStream(stream, kMdatTag, &data)) {
|
||||||
|
return kFail;
|
||||||
|
}
|
||||||
|
// Prefer image in mdata box, as spec ensures it is the largest image.
|
||||||
|
if (data.mdat_image.length > 0 && IsImage(stream, data.mdat_image)) {
|
||||||
|
preview_image_data->preview = data.mdat_image;
|
||||||
|
} else if (data.prvw_image.length > 0 && IsImage(stream, data.prvw_image)) {
|
||||||
|
preview_image_data->preview = data.prvw_image;
|
||||||
|
} else {
|
||||||
|
return kFail;
|
||||||
|
}
|
||||||
|
return kOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cr3GetOrientation(StreamInterface* stream, std::uint32_t* orientation) {
|
||||||
|
PreviewImageData preview_image_data;
|
||||||
|
ProcessData data{&preview_image_data};
|
||||||
|
if (ProcessStream(stream, kCmt1Tag, &data)) {
|
||||||
|
*orientation = preview_image_data.exif_orientation;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace piex
|
||||||
43
src/piex_cr3.h
Normal file
43
src/piex_cr3.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2020 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "src/piex_types.h"
|
||||||
|
|
||||||
|
#ifndef PIEX_PIEX_CR3_H_
|
||||||
|
#define PIEX_PIEX_CR3_H_
|
||||||
|
|
||||||
|
namespace piex {
|
||||||
|
|
||||||
|
// Gets the EXIF orientation of a CR3 stream, returning true on success.
|
||||||
|
bool Cr3GetOrientation(StreamInterface* stream, std::uint32_t* orientation);
|
||||||
|
|
||||||
|
// Gets preview images of a CR3 stream, returning kOk on success. Assumes the
|
||||||
|
// stream is a CR3 stream.
|
||||||
|
//
|
||||||
|
// Canon's CR3 is based on ISO/IEC 14496-12: ISO base media file format. (CR2 is
|
||||||
|
// TIFF based.) A Canon CR3 contains multiple embedded images. Most cameras
|
||||||
|
// output CR3 files that contain a full-size JPEG, a 1620x1080 preview JPEG, and
|
||||||
|
// a 160x120 thumbnail JPEG.
|
||||||
|
// The Canon EOS 1D X Mark III, though, contains a full-size HEVC image, a
|
||||||
|
// 1620x1080 preview JPEG, and a 160x120 thumbnail JPEG.
|
||||||
|
// Until support for HEVC is added, this method returns the largest embedded
|
||||||
|
// JPEG in preview_image_data->preview.
|
||||||
|
//
|
||||||
|
Error Cr3GetPreviewData(StreamInterface* stream,
|
||||||
|
PreviewImageData* preview_image_data);
|
||||||
|
} // namespace piex
|
||||||
|
|
||||||
|
#endif // PIEX_PIEX_CR3_H_
|
||||||
@@ -37,6 +37,7 @@ struct Image {
|
|||||||
enum Format {
|
enum Format {
|
||||||
kJpegCompressed,
|
kJpegCompressed,
|
||||||
kUncompressedRgb,
|
kUncompressedRgb,
|
||||||
|
kHevcCompressed,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::uint16_t width = 0;
|
std::uint16_t width = 0;
|
||||||
|
|||||||
@@ -54,20 +54,6 @@ bool GetFullDimension16(const TiffDirectory& tiff_directory,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetRational(const TiffDirectory::Tag& tag, const TiffDirectory& directory,
|
|
||||||
const int data_size, PreviewImageData::Rational* data) {
|
|
||||||
std::vector<Rational> value;
|
|
||||||
if (directory.Get(tag, &value) &&
|
|
||||||
value.size() == static_cast<size_t>(data_size)) {
|
|
||||||
for (size_t i = 0; i < value.size(); ++i) {
|
|
||||||
data[i].numerator = value[i].numerator;
|
|
||||||
data[i].denominator = value[i].denominator;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FillGpsPreviewImageData(const TiffDirectory& gps_directory,
|
void FillGpsPreviewImageData(const TiffDirectory& gps_directory,
|
||||||
PreviewImageData* preview_image_data) {
|
PreviewImageData* preview_image_data) {
|
||||||
if (gps_directory.Has(kGpsTagLatitudeRef) &&
|
if (gps_directory.Has(kGpsTagLatitudeRef) &&
|
||||||
@@ -463,6 +449,20 @@ bool GetJpegDimensions(const std::uint32_t jpeg_offset, StreamInterface* stream,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GetRational(const TiffDirectory::Tag& tag, const TiffDirectory& directory,
|
||||||
|
const int data_size, PreviewImageData::Rational* data) {
|
||||||
|
std::vector<Rational> value;
|
||||||
|
if (directory.Get(tag, &value) &&
|
||||||
|
value.size() == static_cast<size_t>(data_size)) {
|
||||||
|
for (size_t i = 0; i < value.size(); ++i) {
|
||||||
|
data[i].numerator = value[i].numerator;
|
||||||
|
data[i].denominator = value[i].denominator;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsThumbnail(const Image& image, const int max_dimension) {
|
bool IsThumbnail(const Image& image, const int max_dimension) {
|
||||||
return image.width <= max_dimension && image.height <= max_dimension;
|
return image.width <= max_dimension && image.height <= max_dimension;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,12 @@ bool GetFullDimension32(const tiff_directory::TiffDirectory& tiff_directory,
|
|||||||
bool GetFullCropDimension(const tiff_directory::TiffDirectory& tiff_directory,
|
bool GetFullCropDimension(const tiff_directory::TiffDirectory& tiff_directory,
|
||||||
std::uint32_t* width, std::uint32_t* height);
|
std::uint32_t* width, std::uint32_t* height);
|
||||||
|
|
||||||
|
// Reads 1 or more rational values for a tag and stores results into data.
|
||||||
|
// Returns false if an error occurred.
|
||||||
|
bool GetRational(const tiff_directory::TiffDirectory::Tag& tag,
|
||||||
|
const tiff_directory::TiffDirectory& directory,
|
||||||
|
const int data_size, PreviewImageData::Rational* data);
|
||||||
|
|
||||||
// Enables us to parse through data that complies to the Tiff/EP specification.
|
// Enables us to parse through data that complies to the Tiff/EP specification.
|
||||||
class TiffParser {
|
class TiffParser {
|
||||||
public:
|
public:
|
||||||
|
|||||||
Reference in New Issue
Block a user