blob: 8997b4dee2ac2ab1297e2d7cad652f0a967f6982 [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
Nick Deakin7dfe0162023-01-19 18:27:09 -0500151// GContainer XMP constants - URI and namespace prefix
152const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
153const string kContainerPrefix = "GContainer";
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000154
Nick Deakin7dfe0162023-01-19 18:27:09 -0500155// GContainer XMP constants - element and attribute names
156const string kConDirectory = Name(kContainerPrefix, "Directory");
157const string kConItem = Name(kContainerPrefix, "Item");
158const string kConItemLength = Name(kContainerPrefix, "ItemLength");
159const string kConItemMime = Name(kContainerPrefix, "ItemMime");
160const string kConItemSemantic = Name(kContainerPrefix, "ItemSemantic");
161const string kConVersion = Name(kContainerPrefix, "Version");
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000162
Nick Deakin7dfe0162023-01-19 18:27:09 -0500163// GContainer XMP constants - element and attribute values
164const string kSemanticPrimary = "Primary";
165const string kSemanticRecoveryMap = "RecoveryMap";
166const string kMimeImageJpeg = "image/jpeg";
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000167
Nick Deakin7dfe0162023-01-19 18:27:09 -0500168const int kGContainerVersion = 1;
169
170// GContainer XMP constants - names for XMP handlers
171const string XMPXmlHandler::gContainerItemName = kConItem;
172
173// RecoveryMap XMP constants - URI and namespace prefix
174const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
175const string kRecoveryMapPrefix = "RecoveryMap";
176
177// RecoveryMap XMP constants - element and attribute names
178const string kMapRangeScalingFactor = Name(kRecoveryMapPrefix, "RangeScalingFactor");
179const string kMapTransferFunction = Name(kRecoveryMapPrefix, "TransferFunction");
180const string kMapVersion = Name(kRecoveryMapPrefix, "Version");
181
182const string kMapHdr10Metadata = Name(kRecoveryMapPrefix, "HDR10Metadata");
183const string kMapHdr10MaxFall = Name(kRecoveryMapPrefix, "HDR10MaxFALL");
184const string kMapHdr10MaxCll = Name(kRecoveryMapPrefix, "HDR10MaxCLL");
185
186const string kMapSt2086Metadata = Name(kRecoveryMapPrefix, "ST2086Metadata");
187const string kMapSt2086MaxLum = Name(kRecoveryMapPrefix, "ST2086MaxLuminance");
188const string kMapSt2086MinLum = Name(kRecoveryMapPrefix, "ST2086MinLuminance");
189const string kMapSt2086Primary = Name(kRecoveryMapPrefix, "ST2086Primary");
190const string kMapSt2086Coordinate = Name(kRecoveryMapPrefix, "ST2086Coordinate");
191const string kMapSt2086CoordinateX = Name(kRecoveryMapPrefix, "ST2086CoordinateX");
192const string kMapSt2086CoordinateY = Name(kRecoveryMapPrefix, "ST2086CoordinateY");
193
194// RecoveryMap XMP constants - element and attribute values
195const int kSt2086PrimaryRed = 0;
196const int kSt2086PrimaryGreen = 1;
197const int kSt2086PrimaryBlue = 2;
198const int kSt2086PrimaryWhite = 3;
199
200// RecoveryMap XMP constants - names for XMP handlers
201const string XMPXmlHandler::rangeScalingFactorAttrName = kMapRangeScalingFactor;
202const string XMPXmlHandler::transferFunctionAttrName = kMapTransferFunction;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000203
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000204bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000205 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
206
207 if (xmp_size < nameSpace.size()+2) {
208 // Data too short
209 return false;
210 }
211
212 if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
213 // Not correct namespace
214 return false;
215 }
216
217 // Position the pointers to the start of XMP XML portion
218 xmp_data += nameSpace.size()+1;
219 xmp_size -= nameSpace.size()+1;
220 XMPXmlHandler handler;
221
222 // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
223 while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
224 xmp_size--;
225 }
226
227 string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
228 MessageHandler msg_handler;
229 unique_ptr<XmlRule> rule(new XmlElementRule);
230 XmlReader reader(&handler, &msg_handler);
231 reader.StartParse(std::move(rule));
232 reader.Parse(str);
233 reader.FinishParse();
234 if (reader.HasErrors()) {
235 // Parse error
236 return false;
237 }
238
239 if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) {
240 return false;
241 }
242
243 if (!handler.getTransferFunction(&metadata->transferFunction)) {
244 return false;
245 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000246 return true;
247}
248
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000249string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
Nick Deakin7dfe0162023-01-19 18:27:09 -0500250 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
251 const vector<string> kLiItem({string("rdf:li"), kConItem});
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000252
253 std::stringstream ss;
254 photos_editing_formats::image_io::XmlWriter writer(ss);
255 writer.StartWritingElement("x:xmpmeta");
256 writer.WriteXmlns("x", "adobe:ns:meta/");
257 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
258 writer.StartWritingElement("rdf:RDF");
259 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
260 writer.StartWritingElement("rdf:Description");
261 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500262 writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
263 writer.WriteElementAndContent(kConVersion, kGContainerVersion);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000264 writer.StartWritingElements(kConDirSeq);
265 size_t item_depth = writer.StartWritingElements(kLiItem);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500266 writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticPrimary);
267 writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
268 writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
269 writer.WriteAttributeNameAndValue(kMapRangeScalingFactor, metadata.rangeScalingFactor);
270 writer.WriteAttributeNameAndValue(kMapTransferFunction, metadata.transferFunction);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000271 if (metadata.transferFunction == JPEGR_TF_PQ) {
Nick Deakin7dfe0162023-01-19 18:27:09 -0500272 writer.StartWritingElement(kMapHdr10Metadata);
273 writer.WriteAttributeNameAndValue(kMapHdr10MaxFall, metadata.hdr10Metadata.maxFALL);
274 writer.WriteAttributeNameAndValue(kMapHdr10MaxCll, metadata.hdr10Metadata.maxCLL);
275 writer.StartWritingElement(kMapSt2086Metadata);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000276 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500277 kMapSt2086MaxLum, metadata.hdr10Metadata.st2086Metadata.maxLuminance);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000278 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500279 kMapSt2086MinLum, metadata.hdr10Metadata.st2086Metadata.minLuminance);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000280
281 // red
Nick Deakin7dfe0162023-01-19 18:27:09 -0500282 writer.StartWritingElement(kMapSt2086Coordinate);
283 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryRed);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000284 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500285 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000286 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500287 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000288 writer.FinishWritingElement();
289
290 // green
Nick Deakin7dfe0162023-01-19 18:27:09 -0500291 writer.StartWritingElement(kMapSt2086Coordinate);
292 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryGreen);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000293 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500294 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000295 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500296 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000297 writer.FinishWritingElement();
298
299 // blue
Nick Deakin7dfe0162023-01-19 18:27:09 -0500300 writer.StartWritingElement(kMapSt2086Coordinate);
301 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryBlue);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000302 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500303 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000304 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500305 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000306 writer.FinishWritingElement();
307
308 // white
Nick Deakin7dfe0162023-01-19 18:27:09 -0500309 writer.StartWritingElement(kMapSt2086Coordinate);
310 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryWhite);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000311 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500312 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000313 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500314 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000315 writer.FinishWritingElement();
316 }
317 writer.FinishWritingElementsToDepth(item_depth);
318 writer.StartWritingElements(kLiItem);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500319 writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticRecoveryMap);
320 writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
321 writer.WriteAttributeNameAndValue(kConItemLength, secondary_image_length);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000322 writer.FinishWriting();
323
324 return ss.str();
325}
326
Nick Deakin7dfe0162023-01-19 18:27:09 -0500327} // namespace android::recoverymap