blob: 29ad8bc0681fc283c3294320f84f43da2b3db43b [file] [log] [blame]
#include "image_io/jpeg/jpeg_info_builder.h"
#include <sstream>
#include <string>
#include "image_io/base/message_handler.h"
#include "image_io/jpeg/jpeg_marker.h"
#include "image_io/jpeg/jpeg_scanner.h"
#include "image_io/jpeg/jpeg_segment.h"
namespace photos_editing_formats {
namespace image_io {
using std::string;
using std::stringstream;
using std::vector;
JpegInfoBuilder::JpegInfoBuilder()
: image_limit_(std::numeric_limits<int>::max()), image_count_(0),
gdepth_info_builder_(JpegXmpInfo::kGDepthInfoType),
gimage_info_builder_(JpegXmpInfo::kGImageInfoType) {}
void JpegInfoBuilder::SetCaptureSegmentBytes(
const std::string& segment_info_type) {
capture_segment_bytes_types_.insert(segment_info_type);
}
void JpegInfoBuilder::Start(JpegScanner* scanner) {
JpegMarker::Flags marker_flags;
marker_flags[JpegMarker::kSOI] = true;
marker_flags[JpegMarker::kEOI] = true;
marker_flags[JpegMarker::kAPP0] = true;
marker_flags[JpegMarker::kAPP1] = true;
marker_flags[JpegMarker::kAPP2] = true;
scanner->UpdateInterestingMarkerFlags(marker_flags);
}
void JpegInfoBuilder::Process(JpegScanner* scanner,
const JpegSegment& segment) {
// SOI segments are used to track of the number of images in the JPEG file.
// Apple depth images start with a SOI marker, so store its range for later.
JpegMarker marker = segment.GetMarker();
if (marker.GetType() == JpegMarker::kSOI) {
image_count_++;
image_mpf_count_.push_back(0);
image_xmp_apple_depth_count_.push_back(0);
image_xmp_apple_matte_count_.push_back(0);
most_recent_soi_marker_range_ =
DataRange(segment.GetBegin(), segment.GetBegin() + JpegMarker::kLength);
} else if (marker.GetType() == JpegMarker::kEOI) {
if (most_recent_soi_marker_range_.IsValid()) {
DataRange image_range(most_recent_soi_marker_range_.GetBegin(),
segment.GetBegin() + JpegMarker::kLength);
jpeg_info_.AddImageRange(image_range);
// This image range might represent the Apple depth or matte image if
// other info indicates such an image is in progress and the apple image
// range has not yet been set.
if (HasAppleDepth() && !jpeg_info_.GetAppleDepthImageRange().IsValid()) {
jpeg_info_.SetAppleDepthImageRange(image_range);
}
if (HasAppleMatte() && !jpeg_info_.GetAppleMatteImageRange().IsValid()) {
jpeg_info_.SetAppleMatteImageRange(image_range);
}
if (image_count_ >= image_limit_) {
scanner->SetDone();
}
}
} else if (marker.GetType() == JpegMarker::kAPP0) {
// APP0/JFIF segments are interesting.
if (image_count_ > 0 && IsJfifSegment(segment)) {
const auto& data_range = segment.GetDataRange();
JpegSegmentInfo segment_info(image_count_ - 1, data_range, kJfif);
MaybeCaptureSegmentBytes(kJfif, segment, segment_info.GetMutableBytes());
jpeg_info_.AddSegmentInfo(segment_info);
}
} else if (marker.GetType() == JpegMarker::kAPP2) {
// APP2/MPF segments. JPEG files with Apple depth information have this
// segment in the primary (first) image of the file, but note their presence
// where ever they are found.
if (image_count_ > 0 && IsMpfSegment(segment)) {
++image_mpf_count_[image_count_ - 1];
const auto& data_range = segment.GetDataRange();
JpegSegmentInfo segment_info(image_count_ - 1, data_range, kMpf);
MaybeCaptureSegmentBytes(kMpf, segment, segment_info.GetMutableBytes());
jpeg_info_.AddSegmentInfo(segment_info);
}
} else if (marker.GetType() == JpegMarker::kAPP1) {
// APP1/XMP segments. Both Apple depth and GDepthV1 image formats have
// APP1/XMP segments with important information in them. There are two types
// of XMP segments, a primary one (that starts with kXmpId) and an extended
// one (that starts with kExtendedXmpId). Apple depth information is only in
// the former, while GDepthV1/GImageV1 information is in both.
if (IsPrimaryXmpSegment(segment)) {
// The primary XMP segment in a non-primary image (i.e., not the first
// image in the file) may contain Apple depth/matte information.
if (image_count_ > 1 && HasId(segment, kXmpAppleDepthId)) {
++image_xmp_apple_depth_count_[image_count_ - 1];
} else if (image_count_ > 1 && HasId(segment, kXmpAppleMatteId)) {
++image_xmp_apple_matte_count_[image_count_ - 1];
} else if (image_count_ == 1 && (HasId(segment, kXmpGDepthV1Id) ||
HasId(segment, kXmpGImageV1Id))) {
// The primary XMP segment in the primary image may contain GDepthV1
// and/or GImageV1 data.
SetPrimaryXmpGuid(segment);
SetXmpMimeType(segment, JpegXmpInfo::kGDepthInfoType);
SetXmpMimeType(segment, JpegXmpInfo::kGImageInfoType);
}
} else if (image_count_ == 1 && IsExtendedXmpSegment(segment)) {
// The extended XMP segment in the primary image may contain GDepth and/or
// GImage data.
if (HasMatchingExtendedXmpGuid(segment)) {
gdepth_info_builder_.ProcessSegment(segment);
gimage_info_builder_.ProcessSegment(segment);
}
} else if (image_count_ > 0 && IsExifSegment(segment)) {
const auto& data_range = segment.GetDataRange();
JpegSegmentInfo segment_info(image_count_ - 1, data_range, kExif);
MaybeCaptureSegmentBytes(kExif, segment, segment_info.GetMutableBytes());
jpeg_info_.AddSegmentInfo(segment_info);
}
}
}
void JpegInfoBuilder::Finish(JpegScanner* scanner) {
jpeg_info_.SetSegmentDataRanges(
JpegXmpInfo::kGDepthInfoType,
gdepth_info_builder_.GetPropertySegmentRanges());
jpeg_info_.SetSegmentDataRanges(
JpegXmpInfo::kGImageInfoType,
gimage_info_builder_.GetPropertySegmentRanges());
}
bool JpegInfoBuilder::HasAppleDepth() const {
if (image_count_ > 1 && image_mpf_count_[0]) {
for (size_t image = 1; image < image_xmp_apple_depth_count_.size();
++image) {
if (image_xmp_apple_depth_count_[image]) {
return true;
}
}
}
return false;
}
bool JpegInfoBuilder::HasAppleMatte() const {
if (image_count_ > 1 && image_mpf_count_[0]) {
for (size_t image = 1; image < image_xmp_apple_matte_count_.size();
++image) {
if (image_xmp_apple_matte_count_[image]) {
return true;
}
}
}
return false;
}
bool JpegInfoBuilder::IsPrimaryXmpSegment(const JpegSegment& segment) const {
size_t location = segment.GetPayloadDataLocation();
return segment.BytesAtLocationStartWith(location, kXmpId);
}
bool JpegInfoBuilder::IsExtendedXmpSegment(const JpegSegment& segment) const {
size_t location = segment.GetPayloadDataLocation();
return segment.BytesAtLocationStartWith(location, kXmpExtendedId);
}
bool JpegInfoBuilder::IsMpfSegment(const JpegSegment& segment) const {
size_t payload_data_location = segment.GetPayloadDataLocation();
return segment.BytesAtLocationStartWith(payload_data_location, kMpf);
}
bool JpegInfoBuilder::IsExifSegment(const JpegSegment& segment) const {
size_t payload_data_location = segment.GetPayloadDataLocation();
return segment.BytesAtLocationStartWith(payload_data_location, kExif);
}
bool JpegInfoBuilder::IsJfifSegment(const JpegSegment& segment) const {
size_t payload_data_location = segment.GetPayloadDataLocation();
return segment.BytesAtLocationStartWith(payload_data_location, kJfif);
}
void JpegInfoBuilder::MaybeCaptureSegmentBytes(const std::string& type,
const JpegSegment& segment,
std::vector<Byte>* bytes) const {
if (capture_segment_bytes_types_.count(type) == 0) {
return;
}
bytes->clear();
bytes->reserve(segment.GetLength());
size_t segment_begin = segment.GetBegin();
size_t segment_end = segment.GetEnd();
for (size_t location = segment_begin; location < segment_end; ++location) {
ValidatedByte validated_byte = segment.GetValidatedByte(location);
if (!validated_byte.is_valid) {
bytes->clear();
return;
}
bytes->emplace_back(validated_byte.value);
}
}
bool JpegInfoBuilder::HasMatchingExtendedXmpGuid(
const JpegSegment& segment) const {
if (primary_xmp_guid_.empty()) {
return false;
}
if (segment.GetLength() <= kXmpExtendedHeaderSize) {
return false;
}
size_t start = segment.GetPayloadDataLocation() + sizeof(kXmpExtendedId);
return segment.BytesAtLocationStartWith(start, primary_xmp_guid_.c_str());
}
bool JpegInfoBuilder::HasId(const JpegSegment& segment, const char* id) const {
return segment.BytesAtLocationContain(segment.GetPayloadDataLocation(), id);
}
void JpegInfoBuilder::SetPrimaryXmpGuid(const JpegSegment& segment) {
primary_xmp_guid_ = segment.ExtractXmpPropertyValue(
segment.GetPayloadDataLocation(), kXmpHasExtendedId);
}
void JpegInfoBuilder::SetXmpMimeType(const JpegSegment& segment,
JpegXmpInfo::Type xmp_info_type) {
string property_name = JpegXmpInfo::GetMimePropertyName(xmp_info_type);
jpeg_info_.SetMimeType(xmp_info_type, segment.ExtractXmpPropertyValue(
segment.GetPayloadDataLocation(),
property_name.c_str()));
}
} // namespace image_io
} // namespace photos_editing_formats