blob: 78edd27b093597a6160fa48f74b45cab31acea1d [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
56status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) {
57 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
58 position += length;
59 return NO_ERROR;
60}
61
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000062// Extremely simple XML Handler - just searches for interesting elements
63class XMPXmlHandler : public XmlHandler {
64public:
65
66 XMPXmlHandler() : XmlHandler() {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000067 gContainerItemState = NotStrarted;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000068 }
69
70 enum ParseState {
71 NotStrarted,
72 Started,
73 Done
74 };
75
76 virtual DataMatchResult StartElement(const XmlTokenContext& context) {
77 string val;
78 if (context.BuildTokenValue(&val)) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000079 if (!val.compare(gContainerItemName)) {
80 gContainerItemState = Started;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000081 } else {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000082 if (gContainerItemState != Done) {
83 gContainerItemState = NotStrarted;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000084 }
85 }
86 }
87 return context.GetResult();
88 }
89
90 virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000091 if (gContainerItemState == Started) {
92 gContainerItemState = Done;
93 lastAttributeName = "";
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000094 }
95 return context.GetResult();
96 }
97
Fyodor Kyslovad58a342022-12-12 02:10:35 +000098 virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000099 string val;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000100 if (gContainerItemState == Started) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000101 if (context.BuildTokenValue(&val)) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000102 if (!val.compare(rangeScalingFactorAttrName)) {
103 lastAttributeName = rangeScalingFactorAttrName;
104 } else if (!val.compare(transferFunctionAttrName)) {
105 lastAttributeName = transferFunctionAttrName;
106 } else {
107 lastAttributeName = "";
108 }
109 }
110 }
111 return context.GetResult();
112 }
113
114 virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
115 string val;
116 if (gContainerItemState == Started) {
117 if (context.BuildTokenValue(&val, true)) {
118 if (!lastAttributeName.compare(rangeScalingFactorAttrName)) {
119 rangeScalingFactorStr = val;
120 } else if (!lastAttributeName.compare(transferFunctionAttrName)) {
121 transferFunctionStr = val;
122 }
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000123 }
124 }
125 return context.GetResult();
126 }
127
128 bool getRangeScalingFactor(float* scaling_factor) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000129 if (gContainerItemState == Done) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000130 stringstream ss(rangeScalingFactorStr);
131 float val;
132 if (ss >> val) {
133 *scaling_factor = val;
134 return true;
135 } else {
136 return false;
137 }
138 } else {
139 return false;
140 }
141 }
142
143 bool getTransferFunction(jpegr_transfer_function* transfer_function) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000144 if (gContainerItemState == Done) {
145 stringstream ss(transferFunctionStr);
146 int val;
147 if (ss >> val) {
148 *transfer_function = static_cast<jpegr_transfer_function>(val);
149 return true;
150 } else {
151 return false;
152 }
153 } else {
154 return false;
155 }
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000156 return true;
157 }
158
159private:
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000160 static const string gContainerItemName;
161 static const string rangeScalingFactorAttrName;
162 static const string transferFunctionAttrName;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000163 string rangeScalingFactorStr;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000164 string transferFunctionStr;
165 string lastAttributeName;
166 ParseState gContainerItemState;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000167};
168
Nick Deakin7dfe0162023-01-19 18:27:09 -0500169// GContainer XMP constants - URI and namespace prefix
170const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
171const string kContainerPrefix = "GContainer";
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000172
Nick Deakin7dfe0162023-01-19 18:27:09 -0500173// GContainer XMP constants - element and attribute names
174const string kConDirectory = Name(kContainerPrefix, "Directory");
175const string kConItem = Name(kContainerPrefix, "Item");
176const string kConItemLength = Name(kContainerPrefix, "ItemLength");
177const string kConItemMime = Name(kContainerPrefix, "ItemMime");
178const string kConItemSemantic = Name(kContainerPrefix, "ItemSemantic");
179const string kConVersion = Name(kContainerPrefix, "Version");
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000180
Nick Deakin7dfe0162023-01-19 18:27:09 -0500181// GContainer XMP constants - element and attribute values
182const string kSemanticPrimary = "Primary";
183const string kSemanticRecoveryMap = "RecoveryMap";
184const string kMimeImageJpeg = "image/jpeg";
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000185
Nick Deakin7dfe0162023-01-19 18:27:09 -0500186const int kGContainerVersion = 1;
187
188// GContainer XMP constants - names for XMP handlers
189const string XMPXmlHandler::gContainerItemName = kConItem;
190
191// RecoveryMap XMP constants - URI and namespace prefix
192const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
193const string kRecoveryMapPrefix = "RecoveryMap";
194
195// RecoveryMap XMP constants - element and attribute names
196const string kMapRangeScalingFactor = Name(kRecoveryMapPrefix, "RangeScalingFactor");
197const string kMapTransferFunction = Name(kRecoveryMapPrefix, "TransferFunction");
198const string kMapVersion = Name(kRecoveryMapPrefix, "Version");
199
200const string kMapHdr10Metadata = Name(kRecoveryMapPrefix, "HDR10Metadata");
201const string kMapHdr10MaxFall = Name(kRecoveryMapPrefix, "HDR10MaxFALL");
202const string kMapHdr10MaxCll = Name(kRecoveryMapPrefix, "HDR10MaxCLL");
203
204const string kMapSt2086Metadata = Name(kRecoveryMapPrefix, "ST2086Metadata");
205const string kMapSt2086MaxLum = Name(kRecoveryMapPrefix, "ST2086MaxLuminance");
206const string kMapSt2086MinLum = Name(kRecoveryMapPrefix, "ST2086MinLuminance");
207const string kMapSt2086Primary = Name(kRecoveryMapPrefix, "ST2086Primary");
208const string kMapSt2086Coordinate = Name(kRecoveryMapPrefix, "ST2086Coordinate");
209const string kMapSt2086CoordinateX = Name(kRecoveryMapPrefix, "ST2086CoordinateX");
210const string kMapSt2086CoordinateY = Name(kRecoveryMapPrefix, "ST2086CoordinateY");
211
212// RecoveryMap XMP constants - element and attribute values
213const int kSt2086PrimaryRed = 0;
214const int kSt2086PrimaryGreen = 1;
215const int kSt2086PrimaryBlue = 2;
216const int kSt2086PrimaryWhite = 3;
217
218// RecoveryMap XMP constants - names for XMP handlers
219const string XMPXmlHandler::rangeScalingFactorAttrName = kMapRangeScalingFactor;
220const string XMPXmlHandler::transferFunctionAttrName = kMapTransferFunction;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000221
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000222bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000223 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
224
225 if (xmp_size < nameSpace.size()+2) {
226 // Data too short
227 return false;
228 }
229
230 if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
231 // Not correct namespace
232 return false;
233 }
234
235 // Position the pointers to the start of XMP XML portion
236 xmp_data += nameSpace.size()+1;
237 xmp_size -= nameSpace.size()+1;
238 XMPXmlHandler handler;
239
240 // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
241 while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
242 xmp_size--;
243 }
244
245 string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
246 MessageHandler msg_handler;
247 unique_ptr<XmlRule> rule(new XmlElementRule);
248 XmlReader reader(&handler, &msg_handler);
249 reader.StartParse(std::move(rule));
250 reader.Parse(str);
251 reader.FinishParse();
252 if (reader.HasErrors()) {
253 // Parse error
254 return false;
255 }
256
257 if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) {
258 return false;
259 }
260
261 if (!handler.getTransferFunction(&metadata->transferFunction)) {
262 return false;
263 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000264 return true;
265}
266
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000267string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
Nick Deakin7dfe0162023-01-19 18:27:09 -0500268 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
269 const vector<string> kLiItem({string("rdf:li"), kConItem});
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000270
271 std::stringstream ss;
272 photos_editing_formats::image_io::XmlWriter writer(ss);
273 writer.StartWritingElement("x:xmpmeta");
274 writer.WriteXmlns("x", "adobe:ns:meta/");
275 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
276 writer.StartWritingElement("rdf:RDF");
277 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
278 writer.StartWritingElement("rdf:Description");
279 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500280 writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
281 writer.WriteElementAndContent(kConVersion, kGContainerVersion);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000282 writer.StartWritingElements(kConDirSeq);
283 size_t item_depth = writer.StartWritingElements(kLiItem);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500284 writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticPrimary);
285 writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
286 writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
287 writer.WriteAttributeNameAndValue(kMapRangeScalingFactor, metadata.rangeScalingFactor);
288 writer.WriteAttributeNameAndValue(kMapTransferFunction, metadata.transferFunction);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000289 if (metadata.transferFunction == JPEGR_TF_PQ) {
Nick Deakin7dfe0162023-01-19 18:27:09 -0500290 writer.StartWritingElement(kMapHdr10Metadata);
291 writer.WriteAttributeNameAndValue(kMapHdr10MaxFall, metadata.hdr10Metadata.maxFALL);
292 writer.WriteAttributeNameAndValue(kMapHdr10MaxCll, metadata.hdr10Metadata.maxCLL);
293 writer.StartWritingElement(kMapSt2086Metadata);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000294 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500295 kMapSt2086MaxLum, metadata.hdr10Metadata.st2086Metadata.maxLuminance);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000296 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500297 kMapSt2086MinLum, metadata.hdr10Metadata.st2086Metadata.minLuminance);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000298
299 // red
Nick Deakin7dfe0162023-01-19 18:27:09 -0500300 writer.StartWritingElement(kMapSt2086Coordinate);
301 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryRed);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000302 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500303 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000304 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500305 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000306 writer.FinishWritingElement();
307
308 // green
Nick Deakin7dfe0162023-01-19 18:27:09 -0500309 writer.StartWritingElement(kMapSt2086Coordinate);
310 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryGreen);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000311 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500312 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000313 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500314 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000315 writer.FinishWritingElement();
316
317 // blue
Nick Deakin7dfe0162023-01-19 18:27:09 -0500318 writer.StartWritingElement(kMapSt2086Coordinate);
319 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryBlue);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000320 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500321 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000322 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500323 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000324 writer.FinishWritingElement();
325
326 // white
Nick Deakin7dfe0162023-01-19 18:27:09 -0500327 writer.StartWritingElement(kMapSt2086Coordinate);
328 writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryWhite);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000329 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500330 kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000331 writer.WriteAttributeNameAndValue(
Nick Deakin7dfe0162023-01-19 18:27:09 -0500332 kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000333 writer.FinishWritingElement();
334 }
335 writer.FinishWritingElementsToDepth(item_depth);
336 writer.StartWritingElements(kLiItem);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500337 writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticRecoveryMap);
338 writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
339 writer.WriteAttributeNameAndValue(kConItemLength, secondary_image_length);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000340 writer.FinishWriting();
341
342 return ss.str();
343}
344
Dichen Zhang91dfc572023-01-19 11:22:43 -0800345/*
346 * Helper function
347 * Add J R entry to existing exif, or create a new one with J R entry if it's null.
348 */
349status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) {
350 if (exif == nullptr || exif->data == nullptr) {
351 uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = {
352 0x45, 0x78, 0x69, 0x66, 0x00, 0x00,
353 0x49, 0x49, 0x2A, 0x00,
354 0x08, 0x00, 0x00, 0x00,
355 0x01, 0x00,
356 0x4A, 0x52,
357 0x07, 0x00,
358 0x01, 0x00, 0x00, 0x00,
359 0x00, 0x00, 0x00, 0x00};
360 int pos = 0;
361 Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos);
362 return NO_ERROR;
363 }
364
365 int num_entry = 0;
366 uint8_t num_entry_low = 0;
367 uint8_t num_entry_high = 0;
368 bool use_big_endian = false;
369 if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4949) {
370 num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[14];
371 num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[15];
372 } else if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4d4d) {
373 use_big_endian = true;
374 num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[14];
375 num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[15];
376 } else {
377 return ERROR_JPEGR_METADATA_ERROR;
378 }
379 num_entry = (num_entry_high << 8) | num_entry_low;
380 num_entry += 1;
381 num_entry_low = num_entry & 0xff;
382 num_entry_high = (num_entry >> 8) & 0xff;
383
384 int pos = 0;
385 Write(dest, (uint8_t*)exif->data, 14, pos);
386
387 if (use_big_endian) {
388 Write(dest, &num_entry_high, 1, pos);
389 Write(dest, &num_entry_low, 1, pos);
390 uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
391 0x4A, 0x52,
392 0x00, 0x07,
393 0x00, 0x00, 0x00, 0x01,
394 0x00, 0x00, 0x00, 0x00};
395 Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
396 } else {
397 Write(dest, &num_entry_low, 1, pos);
398 Write(dest, &num_entry_high, 1, pos);
399 uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
400 0x4A, 0x52,
401 0x07, 0x00,
402 0x01, 0x00, 0x00, 0x00,
403 0x00, 0x00, 0x00, 0x00};
404 Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
405 }
406
407 Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos);
408
409 return NO_ERROR;
410}
411
Nick Deakin7dfe0162023-01-19 18:27:09 -0500412} // namespace android::recoverymap