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