xref: /aosp_15_r20/external/libultrahdr/lib/src/jpegrutils.cpp (revision 89a0ef05262152531a00a15832a2d3b1e3990773)
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