blob: 63b25f7f5182570766a06fb353989980ef7635be [file] [log] [blame]
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001/*
2 * Copyright 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <jpegrecoverymap/recoverymaputils.h>
18#include <jpegrecoverymap/recoverymap.h>
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000019#include <image_io/xml/xml_reader.h>
Dichen Zhangdc8452b2022-11-23 17:17:56 +000020#include <image_io/xml/xml_writer.h>
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000021#include <image_io/base/message_handler.h>
22#include <image_io/xml/xml_element_rules.h>
23#include <image_io/xml/xml_handler.h>
24#include <image_io/xml/xml_rule.h>
25
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000026using namespace photos_editing_formats::image_io;
27using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000028
29namespace android::recoverymap {
30
Dichen Zhangdc8452b2022-11-23 17:17:56 +000031/*
32 * Helper function used for generating XMP metadata.
33 *
34 * @param prefix The prefix part of the name.
35 * @param suffix The suffix part of the name.
36 * @return A name of the form "prefix:suffix".
37 */
38string Name(const string &prefix, const string &suffix) {
39 std::stringstream ss;
40 ss << prefix << ":" << suffix;
41 return ss.str();
42}
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000043
44// Extremely simple XML Handler - just searches for interesting elements
45class XMPXmlHandler : public XmlHandler {
46public:
47
48 XMPXmlHandler() : XmlHandler() {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000049 gContainerItemState = NotStrarted;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000050 }
51
52 enum ParseState {
53 NotStrarted,
54 Started,
55 Done
56 };
57
58 virtual DataMatchResult StartElement(const XmlTokenContext& context) {
59 string val;
60 if (context.BuildTokenValue(&val)) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000061 if (!val.compare(gContainerItemName)) {
62 gContainerItemState = Started;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000063 } else {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000064 if (gContainerItemState != Done) {
65 gContainerItemState = NotStrarted;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000066 }
67 }
68 }
69 return context.GetResult();
70 }
71
72 virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000073 if (gContainerItemState == Started) {
74 gContainerItemState = Done;
75 lastAttributeName = "";
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000076 }
77 return context.GetResult();
78 }
79
Fyodor Kyslovad58a342022-12-12 02:10:35 +000080 virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000081 string val;
Fyodor Kyslovad58a342022-12-12 02:10:35 +000082 if (gContainerItemState == Started) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000083 if (context.BuildTokenValue(&val)) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000084 if (!val.compare(rangeScalingFactorAttrName)) {
85 lastAttributeName = rangeScalingFactorAttrName;
86 } else if (!val.compare(transferFunctionAttrName)) {
87 lastAttributeName = transferFunctionAttrName;
88 } else {
89 lastAttributeName = "";
90 }
91 }
92 }
93 return context.GetResult();
94 }
95
96 virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
97 string val;
98 if (gContainerItemState == Started) {
99 if (context.BuildTokenValue(&val, true)) {
100 if (!lastAttributeName.compare(rangeScalingFactorAttrName)) {
101 rangeScalingFactorStr = val;
102 } else if (!lastAttributeName.compare(transferFunctionAttrName)) {
103 transferFunctionStr = val;
104 }
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000105 }
106 }
107 return context.GetResult();
108 }
109
110 bool getRangeScalingFactor(float* scaling_factor) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000111 if (gContainerItemState == Done) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000112 stringstream ss(rangeScalingFactorStr);
113 float val;
114 if (ss >> val) {
115 *scaling_factor = val;
116 return true;
117 } else {
118 return false;
119 }
120 } else {
121 return false;
122 }
123 }
124
125 bool getTransferFunction(jpegr_transfer_function* transfer_function) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000126 if (gContainerItemState == Done) {
127 stringstream ss(transferFunctionStr);
128 int val;
129 if (ss >> val) {
130 *transfer_function = static_cast<jpegr_transfer_function>(val);
131 return true;
132 } else {
133 return false;
134 }
135 } else {
136 return false;
137 }
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000138 return true;
139 }
140
141private:
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000142 static const string gContainerItemName;
143 static const string rangeScalingFactorAttrName;
144 static const string transferFunctionAttrName;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000145 string rangeScalingFactorStr;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000146 string transferFunctionStr;
147 string lastAttributeName;
148 ParseState gContainerItemState;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000149};
150
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000151const string XMPXmlHandler::gContainerItemName = "GContainer:Item";
152const string XMPXmlHandler::rangeScalingFactorAttrName = "RecoveryMap:RangeScalingFactor";
153const string XMPXmlHandler::transferFunctionAttrName = "RecoveryMap:TransferFunction";
154
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000155
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000156const string kContainerPrefix = "GContainer";
157const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
158const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
159const string kItemPrefix = "Item";
160const string kRecoveryMap = "RecoveryMap";
161const string kDirectory = "Directory";
162const string kImageJpeg = "image/jpeg";
163const string kItem = "Item";
164const string kLength = "Length";
165const string kMime = "Mime";
166const string kPrimary = "Primary";
167const string kSemantic = "Semantic";
168const string kVersion = "Version";
169const string kHdr10Metadata = "HDR10Metadata";
170const string kSt2086Metadata = "ST2086Metadata";
171const string kSt2086Coordinate = "ST2086Coordinate";
172const string kSt2086CoordinateX = "ST2086CoordinateX";
173const string kSt2086CoordinateY = "ST2086CoordinateY";
174const string kSt2086Primary = "ST2086Primary";
175const int kSt2086PrimaryRed = 0;
176const int kSt2086PrimaryGreen = 1;
177const int kSt2086PrimaryBlue = 2;
178const int kSt2086PrimaryWhite = 3;
179const int kGContainerVersion = 1;
180
181const string kConDir = Name(kContainerPrefix, kDirectory);
182const string kContainerItem = Name(kContainerPrefix, kItem);
183const string kItemLength = Name(kItemPrefix, kLength);
184const string kItemMime = Name(kItemPrefix, kMime);
185const string kItemSemantic = Name(kItemPrefix, kSemantic);
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000186
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000187bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000188 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
189
190 if (xmp_size < nameSpace.size()+2) {
191 // Data too short
192 return false;
193 }
194
195 if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
196 // Not correct namespace
197 return false;
198 }
199
200 // Position the pointers to the start of XMP XML portion
201 xmp_data += nameSpace.size()+1;
202 xmp_size -= nameSpace.size()+1;
203 XMPXmlHandler handler;
204
205 // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
206 while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
207 xmp_size--;
208 }
209
210 string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
211 MessageHandler msg_handler;
212 unique_ptr<XmlRule> rule(new XmlElementRule);
213 XmlReader reader(&handler, &msg_handler);
214 reader.StartParse(std::move(rule));
215 reader.Parse(str);
216 reader.FinishParse();
217 if (reader.HasErrors()) {
218 // Parse error
219 return false;
220 }
221
222 if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) {
223 return false;
224 }
225
226 if (!handler.getTransferFunction(&metadata->transferFunction)) {
227 return false;
228 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000229 return true;
230}
231
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000232string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
233 const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
234 const vector<string> kLiItem({string("rdf:li"), kContainerItem});
235
236 std::stringstream ss;
237 photos_editing_formats::image_io::XmlWriter writer(ss);
238 writer.StartWritingElement("x:xmpmeta");
239 writer.WriteXmlns("x", "adobe:ns:meta/");
240 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
241 writer.StartWritingElement("rdf:RDF");
242 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
243 writer.StartWritingElement("rdf:Description");
244 writer.WriteXmlns(kContainerPrefix, kContainerUri);
245 writer.WriteXmlns(kRecoveryMap, kRecoveryMapUri);
246 writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kGContainerVersion);
247 writer.StartWritingElements(kConDirSeq);
248 size_t item_depth = writer.StartWritingElements(kLiItem);
249 writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
250 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
251 writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kVersion), metadata.version);
252 writer.WriteAttributeNameAndValue(
253 Name(kRecoveryMap, "RangeScalingFactor"), metadata.rangeScalingFactor);
254 writer.WriteAttributeNameAndValue(
255 Name(kRecoveryMap, "TransferFunction"), metadata.transferFunction);
256 if (metadata.transferFunction == JPEGR_TF_PQ) {
257 writer.StartWritingElement(Name(kRecoveryMap, kHdr10Metadata));
258 writer.WriteAttributeNameAndValue(
259 Name(kRecoveryMap, "HDR10MaxFALL"), metadata.hdr10Metadata.maxFALL);
260 writer.WriteAttributeNameAndValue(
261 Name(kRecoveryMap, "HDR10MaxCLL"), metadata.hdr10Metadata.maxCLL);
262 writer.StartWritingElement(Name(kRecoveryMap, kSt2086Metadata));
263 writer.WriteAttributeNameAndValue(
264 Name(kRecoveryMap, "ST2086MaxLuminance"),
265 metadata.hdr10Metadata.st2086Metadata.maxLuminance);
266 writer.WriteAttributeNameAndValue(
267 Name(kRecoveryMap, "ST2086MinLuminance"),
268 metadata.hdr10Metadata.st2086Metadata.minLuminance);
269
270 // red
271 writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
272 writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryRed);
273 writer.WriteAttributeNameAndValue(
274 Name(kRecoveryMap, kSt2086CoordinateX),
275 metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
276 writer.WriteAttributeNameAndValue(
277 Name(kRecoveryMap, kSt2086CoordinateY),
278 metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
279 writer.FinishWritingElement();
280
281 // green
282 writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
283 writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryGreen);
284 writer.WriteAttributeNameAndValue(
285 Name(kRecoveryMap, kSt2086CoordinateX),
286 metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
287 writer.WriteAttributeNameAndValue(
288 Name(kRecoveryMap, kSt2086CoordinateY),
289 metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
290 writer.FinishWritingElement();
291
292 // blue
293 writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
294 writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryBlue);
295 writer.WriteAttributeNameAndValue(
296 Name(kRecoveryMap, kSt2086CoordinateX),
297 metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
298 writer.WriteAttributeNameAndValue(
299 Name(kRecoveryMap, kSt2086CoordinateY),
300 metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
301 writer.FinishWritingElement();
302
303 // white
304 writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
305 writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryWhite);
306 writer.WriteAttributeNameAndValue(
307 Name(kRecoveryMap, kSt2086CoordinateX),
308 metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
309 writer.WriteAttributeNameAndValue(
310 Name(kRecoveryMap, kSt2086CoordinateY),
311 metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
312 writer.FinishWritingElement();
313 }
314 writer.FinishWritingElementsToDepth(item_depth);
315 writer.StartWritingElements(kLiItem);
316 writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
317 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
318 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
319 writer.FinishWriting();
320
321 return ss.str();
322}
323
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000324} // namespace android::recoverymap