blob: 1617b8b97ab491d6fc08308f55aa092784740157 [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>
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000018#include <image_io/xml/xml_reader.h>
Dichen Zhangdc8452b2022-11-23 17:17:56 +000019#include <image_io/xml/xml_writer.h>
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000020#include <image_io/base/message_handler.h>
21#include <image_io/xml/xml_element_rules.h>
22#include <image_io/xml/xml_handler.h>
23#include <image_io/xml/xml_rule.h>
24
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000025using namespace photos_editing_formats::image_io;
26using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000027
28namespace android::recoverymap {
29
Dichen Zhangdc8452b2022-11-23 17:17:56 +000030/*
31 * Helper function used for generating XMP metadata.
32 *
33 * @param prefix The prefix part of the name.
34 * @param suffix The suffix part of the name.
35 * @return A name of the form "prefix:suffix".
36 */
37string Name(const string &prefix, const string &suffix) {
38 std::stringstream ss;
39 ss << prefix << ":" << suffix;
40 return ss.str();
41}
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000042
Dichen Zhang91dfc572023-01-19 11:22:43 -080043/*
44 * Helper function used for writing data to destination.
45 */
46status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
47 if (position + length > destination->maxLength) {
48 return ERROR_JPEGR_BUFFER_TOO_SMALL;
49 }
50
51 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
52 position += length;
53 return NO_ERROR;
54}
55
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000056// Extremely simple XML Handler - just searches for interesting elements
57class XMPXmlHandler : public XmlHandler {
58public:
59
60 XMPXmlHandler() : XmlHandler() {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000061 gContainerItemState = NotStrarted;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000062 }
63
64 enum ParseState {
65 NotStrarted,
66 Started,
67 Done
68 };
69
70 virtual DataMatchResult StartElement(const XmlTokenContext& context) {
71 string val;
72 if (context.BuildTokenValue(&val)) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000073 if (!val.compare(gContainerItemName)) {
74 gContainerItemState = Started;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000075 } else {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000076 if (gContainerItemState != Done) {
77 gContainerItemState = NotStrarted;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000078 }
79 }
80 }
81 return context.GetResult();
82 }
83
84 virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000085 if (gContainerItemState == Started) {
86 gContainerItemState = Done;
87 lastAttributeName = "";
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000088 }
89 return context.GetResult();
90 }
91
Fyodor Kyslovad58a342022-12-12 02:10:35 +000092 virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000093 string val;
Fyodor Kyslovad58a342022-12-12 02:10:35 +000094 if (gContainerItemState == Started) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000095 if (context.BuildTokenValue(&val)) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000096 if (!val.compare(rangeScalingFactorAttrName)) {
97 lastAttributeName = rangeScalingFactorAttrName;
98 } else if (!val.compare(transferFunctionAttrName)) {
99 lastAttributeName = transferFunctionAttrName;
100 } else {
101 lastAttributeName = "";
102 }
103 }
104 }
105 return context.GetResult();
106 }
107
108 virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
109 string val;
110 if (gContainerItemState == Started) {
111 if (context.BuildTokenValue(&val, true)) {
112 if (!lastAttributeName.compare(rangeScalingFactorAttrName)) {
113 rangeScalingFactorStr = val;
114 } else if (!lastAttributeName.compare(transferFunctionAttrName)) {
115 transferFunctionStr = val;
116 }
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000117 }
118 }
119 return context.GetResult();
120 }
121
122 bool getRangeScalingFactor(float* scaling_factor) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000123 if (gContainerItemState == Done) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000124 stringstream ss(rangeScalingFactorStr);
125 float val;
126 if (ss >> val) {
127 *scaling_factor = val;
128 return true;
129 } else {
130 return false;
131 }
132 } else {
133 return false;
134 }
135 }
136
137 bool getTransferFunction(jpegr_transfer_function* transfer_function) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000138 if (gContainerItemState == Done) {
139 stringstream ss(transferFunctionStr);
140 int val;
141 if (ss >> val) {
142 *transfer_function = static_cast<jpegr_transfer_function>(val);
143 return true;
144 } else {
145 return false;
146 }
147 } else {
148 return false;
149 }
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000150 return true;
151 }
152
153private:
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000154 static const string gContainerItemName;
155 static const string rangeScalingFactorAttrName;
156 static const string transferFunctionAttrName;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000157 string rangeScalingFactorStr;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000158 string transferFunctionStr;
159 string lastAttributeName;
160 ParseState gContainerItemState;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000161};
162
Nick Deakin7dfe0162023-01-19 18:27:09 -0500163// GContainer XMP constants - URI and namespace prefix
164const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
165const string kContainerPrefix = "GContainer";
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000166
Nick Deakin7dfe0162023-01-19 18:27:09 -0500167// GContainer XMP constants - element and attribute names
168const string kConDirectory = Name(kContainerPrefix, "Directory");
169const string kConItem = Name(kContainerPrefix, "Item");
170const string kConItemLength = Name(kContainerPrefix, "ItemLength");
171const string kConItemMime = Name(kContainerPrefix, "ItemMime");
172const string kConItemSemantic = Name(kContainerPrefix, "ItemSemantic");
173const string kConVersion = Name(kContainerPrefix, "Version");
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000174
Nick Deakin7dfe0162023-01-19 18:27:09 -0500175// GContainer XMP constants - element and attribute values
176const string kSemanticPrimary = "Primary";
177const string kSemanticRecoveryMap = "RecoveryMap";
178const string kMimeImageJpeg = "image/jpeg";
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000179
Nick Deakin7dfe0162023-01-19 18:27:09 -0500180const int kGContainerVersion = 1;
181
182// GContainer XMP constants - names for XMP handlers
183const string XMPXmlHandler::gContainerItemName = kConItem;
184
185// RecoveryMap XMP constants - URI and namespace prefix
186const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
187const string kRecoveryMapPrefix = "RecoveryMap";
188
189// RecoveryMap XMP constants - element and attribute names
190const string kMapRangeScalingFactor = Name(kRecoveryMapPrefix, "RangeScalingFactor");
191const string kMapTransferFunction = Name(kRecoveryMapPrefix, "TransferFunction");
192const string kMapVersion = Name(kRecoveryMapPrefix, "Version");
193
194const string kMapHdr10Metadata = Name(kRecoveryMapPrefix, "HDR10Metadata");
195const string kMapHdr10MaxFall = Name(kRecoveryMapPrefix, "HDR10MaxFALL");
196const string kMapHdr10MaxCll = Name(kRecoveryMapPrefix, "HDR10MaxCLL");
197
198const string kMapSt2086Metadata = Name(kRecoveryMapPrefix, "ST2086Metadata");
199const string kMapSt2086MaxLum = Name(kRecoveryMapPrefix, "ST2086MaxLuminance");
200const string kMapSt2086MinLum = Name(kRecoveryMapPrefix, "ST2086MinLuminance");
201const string kMapSt2086Primary = Name(kRecoveryMapPrefix, "ST2086Primary");
202const string kMapSt2086Coordinate = Name(kRecoveryMapPrefix, "ST2086Coordinate");
203const string kMapSt2086CoordinateX = Name(kRecoveryMapPrefix, "ST2086CoordinateX");
204const string kMapSt2086CoordinateY = Name(kRecoveryMapPrefix, "ST2086CoordinateY");
205
206// RecoveryMap XMP constants - element and attribute values
207const int kSt2086PrimaryRed = 0;
208const int kSt2086PrimaryGreen = 1;
209const int kSt2086PrimaryBlue = 2;
210const int kSt2086PrimaryWhite = 3;
211
212// RecoveryMap XMP constants - names for XMP handlers
213const string XMPXmlHandler::rangeScalingFactorAttrName = kMapRangeScalingFactor;
214const string XMPXmlHandler::transferFunctionAttrName = kMapTransferFunction;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000215
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000216bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000217 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
218
219 if (xmp_size < nameSpace.size()+2) {
220 // Data too short
221 return false;
222 }
223
224 if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
225 // Not correct namespace
226 return false;
227 }
228
229 // Position the pointers to the start of XMP XML portion
230 xmp_data += nameSpace.size()+1;
231 xmp_size -= nameSpace.size()+1;
232 XMPXmlHandler handler;
233
234 // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
235 while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
236 xmp_size--;
237 }
238
239 string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
240 MessageHandler msg_handler;
241 unique_ptr<XmlRule> rule(new XmlElementRule);
242 XmlReader reader(&handler, &msg_handler);
243 reader.StartParse(std::move(rule));
244 reader.Parse(str);
245 reader.FinishParse();
246 if (reader.HasErrors()) {
247 // Parse error
248 return false;
249 }
250
251 if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) {
252 return false;
253 }
254
255 if (!handler.getTransferFunction(&metadata->transferFunction)) {
256 return false;
257 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000258 return true;
259}
260
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000261string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
Nick Deakin7dfe0162023-01-19 18:27:09 -0500262 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
263 const vector<string> kLiItem({string("rdf:li"), kConItem});
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000264
265 std::stringstream ss;
266 photos_editing_formats::image_io::XmlWriter writer(ss);
267 writer.StartWritingElement("x:xmpmeta");
268 writer.WriteXmlns("x", "adobe:ns:meta/");
269 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
270 writer.StartWritingElement("rdf:RDF");
271 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
272 writer.StartWritingElement("rdf:Description");
273 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500274 writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
275 writer.WriteElementAndContent(kConVersion, kGContainerVersion);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000276 writer.StartWritingElements(kConDirSeq);
277 size_t item_depth = writer.StartWritingElements(kLiItem);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500278 writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticPrimary);
279 writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
280 writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
281 writer.WriteAttributeNameAndValue(kMapRangeScalingFactor, metadata.rangeScalingFactor);
282 writer.WriteAttributeNameAndValue(kMapTransferFunction, metadata.transferFunction);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000283 if (metadata.transferFunction == JPEGR_TF_PQ) {
Nick Deakin7dfe0162023-01-19 18:27:09 -0500284 writer.StartWritingElement(kMapHdr10Metadata);
285 writer.WriteAttributeNameAndValue(kMapHdr10MaxFall, metadata.hdr10Metadata.maxFALL);
286 writer.WriteAttributeNameAndValue(kMapHdr10MaxCll, metadata.hdr10Metadata.maxCLL);
287 writer.StartWritingElement(kMapSt2086Metadata);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000288 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500289 kMapSt2086MaxLum, metadata.hdr10Metadata.st2086Metadata.maxLuminance);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000290 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500291 kMapSt2086MinLum, metadata.hdr10Metadata.st2086Metadata.minLuminance);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000292
293 // red
Nick Deakin7dfe0162023-01-19 18:27:09 -0500294 writer.StartWritingElement(kMapSt2086Coordinate);
295 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryRed);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000296 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500297 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000298 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500299 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000300 writer.FinishWritingElement();
301
302 // green
Nick Deakin7dfe0162023-01-19 18:27:09 -0500303 writer.StartWritingElement(kMapSt2086Coordinate);
304 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryGreen);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000305 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500306 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000307 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500308 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000309 writer.FinishWritingElement();
310
311 // blue
Nick Deakin7dfe0162023-01-19 18:27:09 -0500312 writer.StartWritingElement(kMapSt2086Coordinate);
313 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryBlue);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000314 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500315 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000316 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500317 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000318 writer.FinishWritingElement();
319
320 // white
Nick Deakin7dfe0162023-01-19 18:27:09 -0500321 writer.StartWritingElement(kMapSt2086Coordinate);
322 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryWhite);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000323 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500324 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000325 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500326 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000327 writer.FinishWritingElement();
328 }
329 writer.FinishWritingElementsToDepth(item_depth);
330 writer.StartWritingElements(kLiItem);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500331 writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticRecoveryMap);
332 writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
333 writer.WriteAttributeNameAndValue(kConItemLength, secondary_image_length);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000334 writer.FinishWriting();
335
336 return ss.str();
337}
338
Dichen Zhang50ff1292023-01-27 18:03:43 +0000339} // namespace android::recoverymap