| #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 |