1 #include "image_io/jpeg/jpeg_info_builder.h"
2
3 #include <limits>
4 #include <sstream>
5 #include <string>
6
7 #include "image_io/base/message_handler.h"
8 #include "image_io/jpeg/jpeg_marker.h"
9 #include "image_io/jpeg/jpeg_scanner.h"
10 #include "image_io/jpeg/jpeg_segment.h"
11
12 namespace photos_editing_formats {
13 namespace image_io {
14
15 using std::string;
16 using std::stringstream;
17 using std::vector;
18
JpegInfoBuilder()19 JpegInfoBuilder::JpegInfoBuilder()
20 : image_limit_(std::numeric_limits<int>::max()), image_count_(0),
21 gdepth_info_builder_(JpegXmpInfo::kGDepthInfoType),
22 gimage_info_builder_(JpegXmpInfo::kGImageInfoType) {}
23
SetCaptureSegmentBytes(const std::string & segment_info_type)24 void JpegInfoBuilder::SetCaptureSegmentBytes(
25 const std::string& segment_info_type) {
26 capture_segment_bytes_types_.insert(segment_info_type);
27 }
28
Start(JpegScanner * scanner)29 void JpegInfoBuilder::Start(JpegScanner* scanner) {
30 JpegMarker::Flags marker_flags;
31 marker_flags[JpegMarker::kSOI] = true;
32 marker_flags[JpegMarker::kEOI] = true;
33 marker_flags[JpegMarker::kAPP0] = true;
34 marker_flags[JpegMarker::kAPP1] = true;
35 marker_flags[JpegMarker::kAPP2] = true;
36 scanner->UpdateInterestingMarkerFlags(marker_flags);
37 }
38
Process(JpegScanner * scanner,const JpegSegment & segment)39 void JpegInfoBuilder::Process(JpegScanner* scanner,
40 const JpegSegment& segment) {
41 // SOI segments are used to track of the number of images in the JPEG file.
42 // Apple depth images start with a SOI marker, so store its range for later.
43 JpegMarker marker = segment.GetMarker();
44 if (marker.GetType() == JpegMarker::kSOI) {
45 image_count_++;
46 image_mpf_count_.push_back(0);
47 image_xmp_apple_depth_count_.push_back(0);
48 image_xmp_apple_matte_count_.push_back(0);
49 most_recent_soi_marker_range_ =
50 DataRange(segment.GetBegin(), segment.GetBegin() + JpegMarker::kLength);
51 } else if (marker.GetType() == JpegMarker::kEOI) {
52 if (most_recent_soi_marker_range_.IsValid()) {
53 DataRange image_range(most_recent_soi_marker_range_.GetBegin(),
54 segment.GetBegin() + JpegMarker::kLength);
55 jpeg_info_.AddImageRange(image_range);
56 // This image range might represent the Apple depth or matte image if
57 // other info indicates such an image is in progress and the apple image
58 // range has not yet been set.
59 if (HasAppleDepth() && !jpeg_info_.GetAppleDepthImageRange().IsValid()) {
60 jpeg_info_.SetAppleDepthImageRange(image_range);
61 }
62 if (HasAppleMatte() && !jpeg_info_.GetAppleMatteImageRange().IsValid()) {
63 jpeg_info_.SetAppleMatteImageRange(image_range);
64 }
65 if (image_count_ >= image_limit_) {
66 scanner->SetDone();
67 }
68 }
69 } else if (marker.GetType() == JpegMarker::kAPP0) {
70 // APP0/JFIF segments are interesting.
71 if (image_count_ > 0 && IsJfifSegment(segment)) {
72 const auto& data_range = segment.GetDataRange();
73 JpegSegmentInfo segment_info(image_count_ - 1, data_range, kJfif);
74 MaybeCaptureSegmentBytes(kJfif, segment, segment_info.GetMutableBytes());
75 jpeg_info_.AddSegmentInfo(segment_info);
76 }
77 } else if (marker.GetType() == JpegMarker::kAPP2) {
78 // APP2/MPF segments. JPEG files with Apple depth information have this
79 // segment in the primary (first) image of the file, but note their presence
80 // where ever they are found.
81 if (image_count_ > 0 && IsMpfSegment(segment)) {
82 ++image_mpf_count_[image_count_ - 1];
83 const auto& data_range = segment.GetDataRange();
84 JpegSegmentInfo segment_info(image_count_ - 1, data_range, kMpf);
85 MaybeCaptureSegmentBytes(kMpf, segment, segment_info.GetMutableBytes());
86 jpeg_info_.AddSegmentInfo(segment_info);
87 }
88 } else if (marker.GetType() == JpegMarker::kAPP1) {
89 // APP1/XMP segments. Both Apple depth and GDepthV1 image formats have
90 // APP1/XMP segments with important information in them. There are two types
91 // of XMP segments, a primary one (that starts with kXmpId) and an extended
92 // one (that starts with kExtendedXmpId). Apple depth information is only in
93 // the former, while GDepthV1/GImageV1 information is in both.
94 if (IsPrimaryXmpSegment(segment)) {
95 // The primary XMP segment in a non-primary image (i.e., not the first
96 // image in the file) may contain Apple depth/matte information.
97 if (image_count_ > 1 && HasId(segment, kXmpAppleDepthId)) {
98 ++image_xmp_apple_depth_count_[image_count_ - 1];
99 } else if (image_count_ > 1 && HasId(segment, kXmpAppleMatteId)) {
100 ++image_xmp_apple_matte_count_[image_count_ - 1];
101 } else if (image_count_ == 1 && (HasId(segment, kXmpGDepthV1Id) ||
102 HasId(segment, kXmpGImageV1Id))) {
103 // The primary XMP segment in the primary image may contain GDepthV1
104 // and/or GImageV1 data.
105 SetPrimaryXmpGuid(segment);
106 SetXmpMimeType(segment, JpegXmpInfo::kGDepthInfoType);
107 SetXmpMimeType(segment, JpegXmpInfo::kGImageInfoType);
108 }
109 } else if (image_count_ == 1 && IsExtendedXmpSegment(segment)) {
110 // The extended XMP segment in the primary image may contain GDepth and/or
111 // GImage data.
112 if (HasMatchingExtendedXmpGuid(segment)) {
113 gdepth_info_builder_.ProcessSegment(segment);
114 gimage_info_builder_.ProcessSegment(segment);
115 }
116 } else if (image_count_ > 0 && IsExifSegment(segment)) {
117 const auto& data_range = segment.GetDataRange();
118 JpegSegmentInfo segment_info(image_count_ - 1, data_range, kExif);
119 MaybeCaptureSegmentBytes(kExif, segment, segment_info.GetMutableBytes());
120 jpeg_info_.AddSegmentInfo(segment_info);
121 }
122 }
123 }
124
Finish(JpegScanner * scanner)125 void JpegInfoBuilder::Finish(JpegScanner* scanner) {
126 jpeg_info_.SetSegmentDataRanges(
127 JpegXmpInfo::kGDepthInfoType,
128 gdepth_info_builder_.GetPropertySegmentRanges());
129 jpeg_info_.SetSegmentDataRanges(
130 JpegXmpInfo::kGImageInfoType,
131 gimage_info_builder_.GetPropertySegmentRanges());
132 }
133
HasAppleDepth() const134 bool JpegInfoBuilder::HasAppleDepth() const {
135 if (image_count_ > 1 && image_mpf_count_[0]) {
136 for (size_t image = 1; image < image_xmp_apple_depth_count_.size();
137 ++image) {
138 if (image_xmp_apple_depth_count_[image]) {
139 return true;
140 }
141 }
142 }
143 return false;
144 }
145
HasAppleMatte() const146 bool JpegInfoBuilder::HasAppleMatte() const {
147 if (image_count_ > 1 && image_mpf_count_[0]) {
148 for (size_t image = 1; image < image_xmp_apple_matte_count_.size();
149 ++image) {
150 if (image_xmp_apple_matte_count_[image]) {
151 return true;
152 }
153 }
154 }
155 return false;
156 }
157
IsPrimaryXmpSegment(const JpegSegment & segment) const158 bool JpegInfoBuilder::IsPrimaryXmpSegment(const JpegSegment& segment) const {
159 size_t location = segment.GetPayloadDataLocation();
160 return segment.BytesAtLocationStartWith(location, kXmpId);
161 }
162
IsExtendedXmpSegment(const JpegSegment & segment) const163 bool JpegInfoBuilder::IsExtendedXmpSegment(const JpegSegment& segment) const {
164 size_t location = segment.GetPayloadDataLocation();
165 return segment.BytesAtLocationStartWith(location, kXmpExtendedId);
166 }
167
IsMpfSegment(const JpegSegment & segment) const168 bool JpegInfoBuilder::IsMpfSegment(const JpegSegment& segment) const {
169 size_t payload_data_location = segment.GetPayloadDataLocation();
170 return segment.BytesAtLocationStartWith(payload_data_location, kMpf);
171 }
172
IsExifSegment(const JpegSegment & segment) const173 bool JpegInfoBuilder::IsExifSegment(const JpegSegment& segment) const {
174 size_t payload_data_location = segment.GetPayloadDataLocation();
175 return segment.BytesAtLocationStartWith(payload_data_location, kExif);
176 }
177
IsJfifSegment(const JpegSegment & segment) const178 bool JpegInfoBuilder::IsJfifSegment(const JpegSegment& segment) const {
179 size_t payload_data_location = segment.GetPayloadDataLocation();
180 return segment.BytesAtLocationStartWith(payload_data_location, kJfif);
181 }
182
MaybeCaptureSegmentBytes(const std::string & type,const JpegSegment & segment,std::vector<Byte> * bytes) const183 void JpegInfoBuilder::MaybeCaptureSegmentBytes(const std::string& type,
184 const JpegSegment& segment,
185 std::vector<Byte>* bytes) const {
186 if (capture_segment_bytes_types_.count(type) == 0) {
187 return;
188 }
189 bytes->clear();
190 bytes->reserve(segment.GetLength());
191 size_t segment_begin = segment.GetBegin();
192 size_t segment_end = segment.GetEnd();
193 for (size_t location = segment_begin; location < segment_end; ++location) {
194 ValidatedByte validated_byte = segment.GetValidatedByte(location);
195 if (!validated_byte.is_valid) {
196 bytes->clear();
197 return;
198 }
199 bytes->emplace_back(validated_byte.value);
200 }
201 }
202
HasMatchingExtendedXmpGuid(const JpegSegment & segment) const203 bool JpegInfoBuilder::HasMatchingExtendedXmpGuid(
204 const JpegSegment& segment) const {
205 if (primary_xmp_guid_.empty()) {
206 return false;
207 }
208 if (segment.GetLength() <= kXmpExtendedHeaderSize) {
209 return false;
210 }
211 size_t start = segment.GetPayloadDataLocation() + sizeof(kXmpExtendedId);
212 return segment.BytesAtLocationStartWith(start, primary_xmp_guid_.c_str());
213 }
214
HasId(const JpegSegment & segment,const char * id) const215 bool JpegInfoBuilder::HasId(const JpegSegment& segment, const char* id) const {
216 return segment.BytesAtLocationContain(segment.GetPayloadDataLocation(), id);
217 }
218
SetPrimaryXmpGuid(const JpegSegment & segment)219 void JpegInfoBuilder::SetPrimaryXmpGuid(const JpegSegment& segment) {
220 primary_xmp_guid_ = segment.ExtractXmpPropertyValue(
221 segment.GetPayloadDataLocation(), kXmpHasExtendedId);
222 }
223
SetXmpMimeType(const JpegSegment & segment,JpegXmpInfo::Type xmp_info_type)224 void JpegInfoBuilder::SetXmpMimeType(const JpegSegment& segment,
225 JpegXmpInfo::Type xmp_info_type) {
226 string property_name = JpegXmpInfo::GetMimePropertyName(xmp_info_type);
227 jpeg_info_.SetMimeType(xmp_info_type, segment.ExtractXmpPropertyValue(
228 segment.GetPayloadDataLocation(),
229 property_name.c_str()));
230 }
231
232 } // namespace image_io
233 } // namespace photos_editing_formats
234