1 /*
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 <algorithm>
18 #include <cmath>
19
20 #include "ultrahdr/ultrahdrcommon.h"
21 #include "ultrahdr/jpegr.h"
22 #include "ultrahdr/jpegrutils.h"
23
24 #include "image_io/xml/xml_reader.h"
25 #include "image_io/xml/xml_writer.h"
26 #include "image_io/base/message_handler.h"
27 #include "image_io/xml/xml_element_rules.h"
28 #include "image_io/xml/xml_handler.h"
29 #include "image_io/xml/xml_rule.h"
30
31 using namespace photos_editing_formats::image_io;
32 using namespace std;
33
34 namespace ultrahdr {
35 /*
36 * Helper function used for generating XMP metadata.
37 *
38 * @param prefix The prefix part of the name.
39 * @param suffix The suffix part of the name.
40 * @return A name of the form "prefix:suffix".
41 */
Name(const string & prefix,const string & suffix)42 static inline string Name(const string& prefix, const string& suffix) {
43 std::stringstream ss;
44 ss << prefix << ":" << suffix;
45 return ss.str();
46 }
47
DataStruct(size_t s)48 DataStruct::DataStruct(size_t s) {
49 data = malloc(s);
50 length = s;
51 memset(data, 0, s);
52 writePos = 0;
53 }
54
~DataStruct()55 DataStruct::~DataStruct() {
56 if (data != nullptr) {
57 free(data);
58 }
59 }
60
getData()61 void* DataStruct::getData() { return data; }
62
getLength()63 size_t DataStruct::getLength() { return length; }
64
getBytesWritten()65 size_t DataStruct::getBytesWritten() { return writePos; }
66
write8(uint8_t value)67 bool DataStruct::write8(uint8_t value) {
68 uint8_t v = value;
69 return write(&v, 1);
70 }
71
write16(uint16_t value)72 bool DataStruct::write16(uint16_t value) {
73 uint16_t v = value;
74 return write(&v, 2);
75 }
76
write32(uint32_t value)77 bool DataStruct::write32(uint32_t value) {
78 uint32_t v = value;
79 return write(&v, 4);
80 }
81
write(const void * src,size_t size)82 bool DataStruct::write(const void* src, size_t size) {
83 if (writePos + size > length) {
84 ALOGE("Writing out of boundary: write position: %zd, size: %zd, capacity: %zd", writePos, size,
85 length);
86 return false;
87 }
88 memcpy((uint8_t*)data + writePos, src, size);
89 writePos += size;
90 return true;
91 }
92
93 /*
94 * Helper function used for writing data to destination.
95 */
Write(uhdr_compressed_image_t * destination,const void * source,size_t length,size_t & position)96 uhdr_error_info_t Write(uhdr_compressed_image_t* destination, const void* source, size_t length,
97 size_t& position) {
98 if (position + length > destination->capacity) {
99 uhdr_error_info_t status;
100 status.error_code = UHDR_CODEC_MEM_ERROR;
101 status.has_detail = 1;
102 snprintf(status.detail, sizeof status.detail,
103 "output buffer to store compressed data is too small: write position: %zd, size: %zd, "
104 "capacity: %zd",
105 position, length, destination->capacity);
106 return status;
107 }
108
109 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
110 position += length;
111 return g_no_error;
112 }
113
114 // Extremely simple XML Handler - just searches for interesting elements
115 class XMPXmlHandler : public XmlHandler {
116 public:
XMPXmlHandler()117 XMPXmlHandler() : XmlHandler() {
118 state = NotStrarted;
119 versionFound = false;
120 minContentBoostFound = false;
121 maxContentBoostFound = false;
122 gammaFound = false;
123 offsetSdrFound = false;
124 offsetHdrFound = false;
125 hdrCapacityMinFound = false;
126 hdrCapacityMaxFound = false;
127 baseRenditionIsHdrFound = false;
128 }
129
130 enum ParseState { NotStrarted, Started, Done };
131
StartElement(const XmlTokenContext & context)132 virtual DataMatchResult StartElement(const XmlTokenContext& context) {
133 string val;
134 if (context.BuildTokenValue(&val)) {
135 if (!val.compare(containerName)) {
136 state = Started;
137 } else {
138 if (state != Done) {
139 state = NotStrarted;
140 }
141 }
142 }
143 return context.GetResult();
144 }
145
FinishElement(const XmlTokenContext & context)146 virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
147 if (state == Started) {
148 state = Done;
149 lastAttributeName = "";
150 }
151 return context.GetResult();
152 }
153
AttributeName(const XmlTokenContext & context)154 virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
155 string val;
156 if (state == Started) {
157 if (context.BuildTokenValue(&val)) {
158 if (!val.compare(versionAttrName)) {
159 lastAttributeName = versionAttrName;
160 } else if (!val.compare(maxContentBoostAttrName)) {
161 lastAttributeName = maxContentBoostAttrName;
162 } else if (!val.compare(minContentBoostAttrName)) {
163 lastAttributeName = minContentBoostAttrName;
164 } else if (!val.compare(gammaAttrName)) {
165 lastAttributeName = gammaAttrName;
166 } else if (!val.compare(offsetSdrAttrName)) {
167 lastAttributeName = offsetSdrAttrName;
168 } else if (!val.compare(offsetHdrAttrName)) {
169 lastAttributeName = offsetHdrAttrName;
170 } else if (!val.compare(hdrCapacityMinAttrName)) {
171 lastAttributeName = hdrCapacityMinAttrName;
172 } else if (!val.compare(hdrCapacityMaxAttrName)) {
173 lastAttributeName = hdrCapacityMaxAttrName;
174 } else if (!val.compare(baseRenditionIsHdrAttrName)) {
175 lastAttributeName = baseRenditionIsHdrAttrName;
176 } else {
177 lastAttributeName = "";
178 }
179 }
180 }
181 return context.GetResult();
182 }
183
AttributeValue(const XmlTokenContext & context)184 virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
185 string val;
186 if (state == Started) {
187 if (context.BuildTokenValue(&val, true)) {
188 if (!lastAttributeName.compare(versionAttrName)) {
189 versionStr = val;
190 versionFound = true;
191 } else if (!lastAttributeName.compare(maxContentBoostAttrName)) {
192 maxContentBoostStr = val;
193 maxContentBoostFound = true;
194 } else if (!lastAttributeName.compare(minContentBoostAttrName)) {
195 minContentBoostStr = val;
196 minContentBoostFound = true;
197 } else if (!lastAttributeName.compare(gammaAttrName)) {
198 gammaStr = val;
199 gammaFound = true;
200 } else if (!lastAttributeName.compare(offsetSdrAttrName)) {
201 offsetSdrStr = val;
202 offsetSdrFound = true;
203 } else if (!lastAttributeName.compare(offsetHdrAttrName)) {
204 offsetHdrStr = val;
205 offsetHdrFound = true;
206 } else if (!lastAttributeName.compare(hdrCapacityMinAttrName)) {
207 hdrCapacityMinStr = val;
208 hdrCapacityMinFound = true;
209 } else if (!lastAttributeName.compare(hdrCapacityMaxAttrName)) {
210 hdrCapacityMaxStr = val;
211 hdrCapacityMaxFound = true;
212 } else if (!lastAttributeName.compare(baseRenditionIsHdrAttrName)) {
213 baseRenditionIsHdrStr = val;
214 baseRenditionIsHdrFound = true;
215 }
216 }
217 }
218 return context.GetResult();
219 }
220
getVersion(string * version,bool * present)221 bool getVersion(string* version, bool* present) {
222 if (state == Done) {
223 *version = versionStr;
224 *present = versionFound;
225 return true;
226 } else {
227 return false;
228 }
229 }
230
getMaxContentBoost(float * max_content_boost,bool * present)231 bool getMaxContentBoost(float* max_content_boost, bool* present) {
232 if (state == Done) {
233 *present = maxContentBoostFound;
234 stringstream ss(maxContentBoostStr);
235 float val;
236 if (ss >> val) {
237 *max_content_boost = exp2(val);
238 return true;
239 } else {
240 return false;
241 }
242 } else {
243 return false;
244 }
245 }
246
getMinContentBoost(float * min_content_boost,bool * present)247 bool getMinContentBoost(float* min_content_boost, bool* present) {
248 if (state == Done) {
249 *present = minContentBoostFound;
250 stringstream ss(minContentBoostStr);
251 float val;
252 if (ss >> val) {
253 *min_content_boost = exp2(val);
254 return true;
255 } else {
256 return false;
257 }
258 } else {
259 return false;
260 }
261 }
262
getGamma(float * gamma,bool * present)263 bool getGamma(float* gamma, bool* present) {
264 if (state == Done) {
265 *present = gammaFound;
266 stringstream ss(gammaStr);
267 float val;
268 if (ss >> val) {
269 *gamma = val;
270 return true;
271 } else {
272 return false;
273 }
274 } else {
275 return false;
276 }
277 }
278
getOffsetSdr(float * offset_sdr,bool * present)279 bool getOffsetSdr(float* offset_sdr, bool* present) {
280 if (state == Done) {
281 *present = offsetSdrFound;
282 stringstream ss(offsetSdrStr);
283 float val;
284 if (ss >> val) {
285 *offset_sdr = val;
286 return true;
287 } else {
288 return false;
289 }
290 } else {
291 return false;
292 }
293 }
294
getOffsetHdr(float * offset_hdr,bool * present)295 bool getOffsetHdr(float* offset_hdr, bool* present) {
296 if (state == Done) {
297 *present = offsetHdrFound;
298 stringstream ss(offsetHdrStr);
299 float val;
300 if (ss >> val) {
301 *offset_hdr = val;
302 return true;
303 } else {
304 return false;
305 }
306 } else {
307 return false;
308 }
309 }
310
getHdrCapacityMin(float * hdr_capacity_min,bool * present)311 bool getHdrCapacityMin(float* hdr_capacity_min, bool* present) {
312 if (state == Done) {
313 *present = hdrCapacityMinFound;
314 stringstream ss(hdrCapacityMinStr);
315 float val;
316 if (ss >> val) {
317 *hdr_capacity_min = exp2(val);
318 return true;
319 } else {
320 return false;
321 }
322 } else {
323 return false;
324 }
325 }
326
getHdrCapacityMax(float * hdr_capacity_max,bool * present)327 bool getHdrCapacityMax(float* hdr_capacity_max, bool* present) {
328 if (state == Done) {
329 *present = hdrCapacityMaxFound;
330 stringstream ss(hdrCapacityMaxStr);
331 float val;
332 if (ss >> val) {
333 *hdr_capacity_max = exp2(val);
334 return true;
335 } else {
336 return false;
337 }
338 } else {
339 return false;
340 }
341 }
342
getBaseRenditionIsHdr(bool * base_rendition_is_hdr,bool * present)343 bool getBaseRenditionIsHdr(bool* base_rendition_is_hdr, bool* present) {
344 if (state == Done) {
345 *present = baseRenditionIsHdrFound;
346 if (!baseRenditionIsHdrStr.compare("False")) {
347 *base_rendition_is_hdr = false;
348 return true;
349 } else if (!baseRenditionIsHdrStr.compare("True")) {
350 *base_rendition_is_hdr = true;
351 return true;
352 } else {
353 return false;
354 }
355 } else {
356 return false;
357 }
358 }
359
360 private:
361 static const string containerName;
362
363 static const string versionAttrName;
364 string versionStr;
365 bool versionFound;
366 static const string maxContentBoostAttrName;
367 string maxContentBoostStr;
368 bool maxContentBoostFound;
369 static const string minContentBoostAttrName;
370 string minContentBoostStr;
371 bool minContentBoostFound;
372 static const string gammaAttrName;
373 string gammaStr;
374 bool gammaFound;
375 static const string offsetSdrAttrName;
376 string offsetSdrStr;
377 bool offsetSdrFound;
378 static const string offsetHdrAttrName;
379 string offsetHdrStr;
380 bool offsetHdrFound;
381 static const string hdrCapacityMinAttrName;
382 string hdrCapacityMinStr;
383 bool hdrCapacityMinFound;
384 static const string hdrCapacityMaxAttrName;
385 string hdrCapacityMaxStr;
386 bool hdrCapacityMaxFound;
387 static const string baseRenditionIsHdrAttrName;
388 string baseRenditionIsHdrStr;
389 bool baseRenditionIsHdrFound;
390
391 string lastAttributeName;
392 ParseState state;
393 };
394
395 // GContainer XMP constants - URI and namespace prefix
396 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
397 const string kContainerPrefix = "Container";
398
399 // GContainer XMP constants - element and attribute names
400 const string kConDirectory = Name(kContainerPrefix, "Directory");
401 const string kConItem = Name(kContainerPrefix, "Item");
402
403 // GContainer XMP constants - names for XMP handlers
404 const string XMPXmlHandler::containerName = "rdf:Description";
405 // Item XMP constants - URI and namespace prefix
406 const string kItemUri = "http://ns.google.com/photos/1.0/container/item/";
407 const string kItemPrefix = "Item";
408
409 // Item XMP constants - element and attribute names
410 const string kItemLength = Name(kItemPrefix, "Length");
411 const string kItemMime = Name(kItemPrefix, "Mime");
412 const string kItemSemantic = Name(kItemPrefix, "Semantic");
413
414 // Item XMP constants - element and attribute values
415 const string kSemanticPrimary = "Primary";
416 const string kSemanticGainMap = "GainMap";
417 const string kMimeImageJpeg = "image/jpeg";
418
419 // GainMap XMP constants - URI and namespace prefix
420 const string kGainMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/";
421 const string kGainMapPrefix = "hdrgm";
422
423 // GainMap XMP constants - element and attribute names
424 const string kMapVersion = Name(kGainMapPrefix, "Version");
425 const string kMapGainMapMin = Name(kGainMapPrefix, "GainMapMin");
426 const string kMapGainMapMax = Name(kGainMapPrefix, "GainMapMax");
427 const string kMapGamma = Name(kGainMapPrefix, "Gamma");
428 const string kMapOffsetSdr = Name(kGainMapPrefix, "OffsetSDR");
429 const string kMapOffsetHdr = Name(kGainMapPrefix, "OffsetHDR");
430 const string kMapHDRCapacityMin = Name(kGainMapPrefix, "HDRCapacityMin");
431 const string kMapHDRCapacityMax = Name(kGainMapPrefix, "HDRCapacityMax");
432 const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR");
433
434 // GainMap XMP constants - names for XMP handlers
435 const string XMPXmlHandler::versionAttrName = kMapVersion;
436 const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin;
437 const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax;
438 const string XMPXmlHandler::gammaAttrName = kMapGamma;
439 const string XMPXmlHandler::offsetSdrAttrName = kMapOffsetSdr;
440 const string XMPXmlHandler::offsetHdrAttrName = kMapOffsetHdr;
441 const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin;
442 const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax;
443 const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR;
444
getMetadataFromXMP(uint8_t * xmp_data,size_t xmp_size,uhdr_gainmap_metadata_ext_t * metadata)445 uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size,
446 uhdr_gainmap_metadata_ext_t* metadata) {
447 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
448
449 if (xmp_size < nameSpace.size() + 2) {
450 uhdr_error_info_t status;
451 status.error_code = UHDR_CODEC_ERROR;
452 status.has_detail = 1;
453 snprintf(status.detail, sizeof status.detail,
454 "size of xmp block is expected to be atleast %zd bytes, received only %zd bytes",
455 nameSpace.size() + 2, xmp_size);
456 return status;
457 }
458
459 if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
460 uhdr_error_info_t status;
461 status.error_code = UHDR_CODEC_ERROR;
462 status.has_detail = 1;
463 snprintf(status.detail, sizeof status.detail,
464 "mismatch in namespace of xmp block. Expected %s, Got %.*s", nameSpace.c_str(),
465 (int)nameSpace.size(), reinterpret_cast<char*>(xmp_data));
466 return status;
467 }
468
469 // Position the pointers to the start of XMP XML portion
470 xmp_data += nameSpace.size() + 1;
471 xmp_size -= nameSpace.size() + 1;
472 XMPXmlHandler handler;
473
474 // xml parser fails to parse packet header, wrapper. remove them before handing the data to
475 // parser. if there is no packet header, do nothing otherwise go to the position of '<' without
476 // '?' after it.
477 size_t offset = 0;
478 for (size_t i = 0; i < xmp_size - 1; ++i) {
479 if (xmp_data[i] == '<') {
480 if (xmp_data[i + 1] != '?') {
481 offset = i;
482 break;
483 }
484 }
485 }
486 xmp_data += offset;
487 xmp_size -= offset;
488
489 // If there is no packet wrapper, do nothing other wise go to the position of last '>' without '?'
490 // before it.
491 offset = 0;
492 for (size_t i = xmp_size - 1; i >= 1; --i) {
493 if (xmp_data[i] == '>') {
494 if (xmp_data[i - 1] != '?') {
495 offset = xmp_size - (i + 1);
496 break;
497 }
498 }
499 }
500 xmp_size -= offset;
501
502 // remove padding
503 while (xmp_data[xmp_size - 1] != '>' && xmp_size > 1) {
504 xmp_size--;
505 }
506
507 string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
508 MessageHandler msg_handler;
509 unique_ptr<XmlRule> rule(new XmlElementRule);
510 XmlReader reader(&handler, &msg_handler);
511 reader.StartParse(std::move(rule));
512 reader.Parse(str);
513 reader.FinishParse();
514 if (reader.HasErrors()) {
515 uhdr_error_info_t status;
516 status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
517 status.has_detail = 1;
518 snprintf(status.detail, sizeof status.detail, "xml parser returned with error");
519 return status;
520 }
521
522 // Apply default values to any not-present fields, except for Version,
523 // maxContentBoost, and hdrCapacityMax, which are required. Return false if
524 // we encounter a present field that couldn't be parsed, since this
525 // indicates it is invalid (eg. string where there should be a float).
526 bool present = false;
527 if (!handler.getVersion(&metadata->version, &present) || !present) {
528 uhdr_error_info_t status;
529 status.error_code = UHDR_CODEC_ERROR;
530 status.has_detail = 1;
531 snprintf(status.detail, sizeof status.detail, "xml parse error, could not find attribute %s",
532 kMapVersion.c_str());
533 return status;
534 }
535 if (!handler.getMaxContentBoost(&metadata->max_content_boost, &present) || !present) {
536 uhdr_error_info_t status;
537 status.error_code = UHDR_CODEC_ERROR;
538 status.has_detail = 1;
539 snprintf(status.detail, sizeof status.detail, "xml parse error, could not find attribute %s",
540 kMapGainMapMax.c_str());
541 return status;
542 }
543 if (!handler.getHdrCapacityMax(&metadata->hdr_capacity_max, &present) || !present) {
544 uhdr_error_info_t status;
545 status.error_code = UHDR_CODEC_ERROR;
546 status.has_detail = 1;
547 snprintf(status.detail, sizeof status.detail, "xml parse error, could not find attribute %s",
548 kMapHDRCapacityMax.c_str());
549 return status;
550 }
551 if (!handler.getMinContentBoost(&metadata->min_content_boost, &present)) {
552 if (present) {
553 uhdr_error_info_t status;
554 status.error_code = UHDR_CODEC_ERROR;
555 status.has_detail = 1;
556 snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s",
557 kMapGainMapMin.c_str());
558 return status;
559 }
560 metadata->min_content_boost = 1.0f;
561 }
562 if (!handler.getGamma(&metadata->gamma, &present)) {
563 if (present) {
564 uhdr_error_info_t status;
565 status.error_code = UHDR_CODEC_ERROR;
566 status.has_detail = 1;
567 snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s",
568 kMapGamma.c_str());
569 return status;
570 }
571 metadata->gamma = 1.0f;
572 }
573 if (!handler.getOffsetSdr(&metadata->offset_sdr, &present)) {
574 if (present) {
575 uhdr_error_info_t status;
576 status.error_code = UHDR_CODEC_ERROR;
577 status.has_detail = 1;
578 snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s",
579 kMapOffsetSdr.c_str());
580 return status;
581 }
582 metadata->offset_sdr = 1.0f / 64.0f;
583 }
584 if (!handler.getOffsetHdr(&metadata->offset_hdr, &present)) {
585 if (present) {
586 uhdr_error_info_t status;
587 status.error_code = UHDR_CODEC_ERROR;
588 status.has_detail = 1;
589 snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s",
590 kMapOffsetHdr.c_str());
591 return status;
592 }
593 metadata->offset_hdr = 1.0f / 64.0f;
594 }
595 if (!handler.getHdrCapacityMin(&metadata->hdr_capacity_min, &present)) {
596 if (present) {
597 uhdr_error_info_t status;
598 status.error_code = UHDR_CODEC_ERROR;
599 status.has_detail = 1;
600 snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s",
601 kMapHDRCapacityMin.c_str());
602 return status;
603 }
604 metadata->hdr_capacity_min = 1.0f;
605 }
606
607 bool base_rendition_is_hdr;
608 if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) {
609 if (present) {
610 uhdr_error_info_t status;
611 status.error_code = UHDR_CODEC_ERROR;
612 status.has_detail = 1;
613 snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s",
614 kMapBaseRenditionIsHDR.c_str());
615 return status;
616 }
617 base_rendition_is_hdr = false;
618 }
619 if (base_rendition_is_hdr) {
620 uhdr_error_info_t status;
621 status.error_code = UHDR_CODEC_ERROR;
622 status.has_detail = 1;
623 snprintf(status.detail, sizeof status.detail, "hdr intent as base rendition is not supported");
624 return status;
625 }
626
627 return g_no_error;
628 }
629
generateXmpForPrimaryImage(size_t secondary_image_length,uhdr_gainmap_metadata_ext_t & metadata)630 string generateXmpForPrimaryImage(size_t secondary_image_length,
631 uhdr_gainmap_metadata_ext_t& metadata) {
632 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
633 const vector<string> kLiItem({string("rdf:li"), kConItem});
634
635 std::stringstream ss;
636 photos_editing_formats::image_io::XmlWriter writer(ss);
637 writer.StartWritingElement("x:xmpmeta");
638 writer.WriteXmlns("x", "adobe:ns:meta/");
639 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
640 writer.StartWritingElement("rdf:RDF");
641 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
642 writer.StartWritingElement("rdf:Description");
643 writer.WriteXmlns(kContainerPrefix, kContainerUri);
644 writer.WriteXmlns(kItemPrefix, kItemUri);
645 writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
646 writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
647
648 writer.StartWritingElements(kConDirSeq);
649
650 size_t item_depth = writer.StartWritingElement("rdf:li");
651 writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
652 writer.StartWritingElement(kConItem);
653 writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
654 writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
655 writer.FinishWritingElementsToDepth(item_depth);
656
657 writer.StartWritingElement("rdf:li");
658 writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
659 writer.StartWritingElement(kConItem);
660 writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticGainMap);
661 writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
662 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
663
664 writer.FinishWriting();
665
666 return ss.str();
667 }
668
generateXmpForSecondaryImage(uhdr_gainmap_metadata_ext_t & metadata)669 string generateXmpForSecondaryImage(uhdr_gainmap_metadata_ext_t& metadata) {
670 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
671
672 std::stringstream ss;
673 photos_editing_formats::image_io::XmlWriter writer(ss);
674 writer.StartWritingElement("x:xmpmeta");
675 writer.WriteXmlns("x", "adobe:ns:meta/");
676 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
677 writer.StartWritingElement("rdf:RDF");
678 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
679 writer.StartWritingElement("rdf:Description");
680 writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
681 writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
682 writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.min_content_boost));
683 writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.max_content_boost));
684 writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma);
685 writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offset_sdr);
686 writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offset_hdr);
687 writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdr_capacity_min));
688 writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdr_capacity_max));
689 writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False");
690 writer.FinishWriting();
691
692 return ss.str();
693 }
694
695 } // namespace ultrahdr
696