xref: /aosp_15_r20/external/skia/src/codec/SkXmp.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2023 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/private/SkXmp.h"
9 
10 #include "include/core/SkColor.h"
11 #include "include/core/SkData.h"
12 #include "include/core/SkScalar.h"
13 #include "include/core/SkStream.h"
14 #include "include/private/SkGainmapInfo.h"
15 #include "include/utils/SkParse.h"
16 #include "src/codec/SkCodecPriv.h"
17 #include "src/xml/SkDOM.h"
18 
19 #include <cmath>
20 #include <cstdint>
21 #include <cstring>
22 #include <string>
23 #include <vector>
24 #include <utility>
25 
26 ////////////////////////////////////////////////////////////////////////////////////////////////////
27 // XMP parsing helper functions
28 
29 const char* kXmlnsPrefix = "xmlns:";
30 const size_t kXmlnsPrefixLength = 6;
31 
get_namespace_prefix(const char * name)32 static const char* get_namespace_prefix(const char* name) {
33     if (strlen(name) <= kXmlnsPrefixLength) {
34         return nullptr;
35     }
36     return name + kXmlnsPrefixLength;
37 }
38 
39 /*
40  * Given a node, see if that node has only one child with the indicated name. If so, see if that
41  * child has only a single child of its own, and that child is text. If all of that is the case
42  * then return the text, otherwise return nullptr.
43  *
44  * In the following example, innerText will be returned.
45  *    <node><childName>innerText</childName></node>
46  *
47  * In the following examples, nullptr will be returned (because there are multiple children with
48  * childName in the first case, and because the child has children of its own in the second).
49  *    <node><childName>innerTextA</childName><childName>innerTextB</childName></node>
50  *    <node><childName>innerText<otherGrandChild/></childName></node>
51  */
get_unique_child_text(const SkDOM & dom,const SkDOM::Node * node,const std::string & childName)52 static const char* get_unique_child_text(const SkDOM& dom,
53                                          const SkDOM::Node* node,
54                                          const std::string& childName) {
55     // Fail if there are multiple children with childName.
56     if (dom.countChildren(node, childName.c_str()) != 1) {
57         return nullptr;
58     }
59     const auto* child = dom.getFirstChild(node, childName.c_str());
60     if (!child) {
61         return nullptr;
62     }
63     // Fail if the child has any children besides text.
64     if (dom.countChildren(child) != 1) {
65         return nullptr;
66     }
67     const auto* grandChild = dom.getFirstChild(child);
68     if (dom.getType(grandChild) != SkDOM::kText_Type) {
69         return nullptr;
70     }
71     // Return the text.
72     return dom.getName(grandChild);
73 }
74 
75 /*
76  * Given a node, find a child node of the specified type.
77  *
78  * If there exists a child node with name |prefix| + ":" + |type|, then return that child.
79  *
80  * If there exists a child node with name "rdf:type" that has attribute "rdf:resource" with value
81  * of |type|, then if there also exists a child node with name "rdf:value" with attribute
82  * "rdf:parseType" of "Resource", then return that child node with name "rdf:value". See Example
83  * 3 in section 7.9.2.5: RDF Typed Nodes.
84  * TODO(ccameron): This should also accept a URI for the type.
85  */
get_typed_child(const SkDOM * dom,const SkDOM::Node * node,const std::string & prefix,const std::string & type)86 static const SkDOM::Node* get_typed_child(const SkDOM* dom,
87                                           const SkDOM::Node* node,
88                                           const std::string& prefix,
89                                           const std::string& type) {
90     const auto name = prefix + std::string(":") + type;
91     const SkDOM::Node* child = dom->getFirstChild(node, name.c_str());
92     if (child) {
93         return child;
94     }
95 
96     const SkDOM::Node* typeChild = dom->getFirstChild(node, "rdf:type");
97     if (!typeChild) {
98         return nullptr;
99     }
100     const char* typeChildResource = dom->findAttr(typeChild, "rdf:resource");
101     if (!typeChildResource || typeChildResource != type) {
102         return nullptr;
103     }
104 
105     const SkDOM::Node* valueChild = dom->getFirstChild(node, "rdf:value");
106     if (!valueChild) {
107         return nullptr;
108     }
109     const char* valueChildParseType = dom->findAttr(valueChild, "rdf:parseType");
110     if (!valueChildParseType || strcmp(valueChildParseType, "Resource") != 0) {
111         return nullptr;
112     }
113     return valueChild;
114 }
115 
116 /*
117  * Given a node, return its value for the specified attribute.
118  *
119  * This will first look for an attribute with the name |prefix| + ":" + |key|, and return the value
120  * for that attribute.
121  *
122  * This will then look for a child node of name |prefix| + ":" + |key|, and return the field value
123  * for that child.
124  */
get_attr(const SkDOM * dom,const SkDOM::Node * node,const std::string & prefix,const std::string & key)125 static const char* get_attr(const SkDOM* dom,
126                             const SkDOM::Node* node,
127                             const std::string& prefix,
128                             const std::string& key) {
129     const auto name = prefix + ":" + key;
130     const char* attr = dom->findAttr(node, name.c_str());
131     if (attr) {
132         return attr;
133     }
134     return get_unique_child_text(*dom, node, name);
135 }
136 
137 // Perform get_attr and parse the result as a bool.
get_attr_bool(const SkDOM * dom,const SkDOM::Node * node,const std::string & prefix,const std::string & key,bool * outValue)138 static bool get_attr_bool(const SkDOM* dom,
139                           const SkDOM::Node* node,
140                           const std::string& prefix,
141                           const std::string& key,
142                           bool* outValue) {
143     const char* attr = get_attr(dom, node, prefix, key);
144     if (!attr) {
145         return false;
146     }
147     switch (SkParse::FindList(attr, "False,True")) {
148         case 0:
149             *outValue = false;
150             return true;
151         case 1:
152             *outValue = true;
153             return true;
154         default:
155             break;
156     }
157     return false;
158 }
159 
160 // Perform get_attr and parse the result as an int32_t.
get_attr_int32(const SkDOM * dom,const SkDOM::Node * node,const std::string & prefix,const std::string & key,int32_t * value)161 static bool get_attr_int32(const SkDOM* dom,
162                            const SkDOM::Node* node,
163                            const std::string& prefix,
164                            const std::string& key,
165                            int32_t* value) {
166     const char* attr = get_attr(dom, node, prefix, key);
167     if (!attr) {
168         return false;
169     }
170     if (!SkParse::FindS32(attr, value)) {
171         return false;
172     }
173     return true;
174 }
175 
176 // Perform get_attr and parse the result as a float.
get_attr_float(const SkDOM * dom,const SkDOM::Node * node,const std::string & prefix,const std::string & key,float * outValue)177 static bool get_attr_float(const SkDOM* dom,
178                            const SkDOM::Node* node,
179                            const std::string& prefix,
180                            const std::string& key,
181                            float* outValue) {
182     const char* attr = get_attr(dom, node, prefix, key);
183     if (!attr) {
184         return false;
185     }
186     SkScalar value = 0.f;
187     if (SkParse::FindScalar(attr, &value)) {
188         *outValue = value;
189         return true;
190     }
191     return false;
192 }
193 
194 // Perform get_attr and parse the result as three comma-separated floats. Return the result as an
195 // SkColor4f with the alpha component set to 1.
get_attr_float3_as_list(const SkDOM * dom,const SkDOM::Node * node,const std::string & prefix,const std::string & key,SkColor4f * outValue)196 static bool get_attr_float3_as_list(const SkDOM* dom,
197                                     const SkDOM::Node* node,
198                                     const std::string& prefix,
199                                     const std::string& key,
200                                     SkColor4f* outValue) {
201     const auto name = prefix + ":" + key;
202 
203     // Fail if there are multiple children with childName.
204     if (dom->countChildren(node, name.c_str()) != 1) {
205         return false;
206     }
207     // Find the child.
208     const auto* child = dom->getFirstChild(node, name.c_str());
209     if (!child) {
210         return false;
211     }
212 
213     // Search for the rdf:Seq child.
214     const auto* seq = dom->getFirstChild(child, "rdf:Seq");
215     if (!seq) {
216         return false;
217     }
218 
219     size_t count = 0;
220     SkScalar values[3] = {0.f, 0.f, 0.f};
221     for (const auto* liNode = dom->getFirstChild(seq, "rdf:li"); liNode;
222          liNode = dom->getNextSibling(liNode, "rdf:li")) {
223         if (count > 2) {
224             SkCodecPrintf("Too many items in list.\n");
225             return false;
226         }
227         if (dom->countChildren(liNode) != 1) {
228             SkCodecPrintf("Item can only have one child.\n");
229             return false;
230         }
231         const auto* liTextNode = dom->getFirstChild(liNode);
232         if (dom->getType(liTextNode) != SkDOM::kText_Type) {
233             SkCodecPrintf("Item's only child must be text.\n");
234             return false;
235         }
236         const char* liText = dom->getName(liTextNode);
237         if (!liText) {
238             SkCodecPrintf("Failed to get item's text.\n");
239             return false;
240         }
241         if (!SkParse::FindScalar(liText, values + count)) {
242             SkCodecPrintf("Failed to parse item's text to float.\n");
243             return false;
244         }
245         count += 1;
246     }
247     if (count < 3) {
248         SkCodecPrintf("List didn't have enough items.\n");
249         return false;
250     }
251     *outValue = {values[0], values[1], values[2], 1.f};
252     return true;
253 }
254 
get_attr_float3(const SkDOM * dom,const SkDOM::Node * node,const std::string & prefix,const std::string & key,SkColor4f * outValue)255 static bool get_attr_float3(const SkDOM* dom,
256                             const SkDOM::Node* node,
257                             const std::string& prefix,
258                             const std::string& key,
259                             SkColor4f* outValue) {
260     if (get_attr_float3_as_list(dom, node, prefix, key, outValue)) {
261         return true;
262     }
263     SkScalar value = -1.0;
264     if (get_attr_float(dom, node, prefix, key, &value)) {
265         *outValue = {value, value, value, 1.f};
266         return true;
267     }
268     return false;
269 }
270 
find_uri_namespaces(const SkDOM & dom,const SkDOM::Node * node,size_t count,const char * uris[],const char * outNamespaces[])271 static void find_uri_namespaces(const SkDOM& dom,
272                                 const SkDOM::Node* node,
273                                 size_t count,
274                                 const char* uris[],
275                                 const char* outNamespaces[]) {
276     // Search all attributes for xmlns:NAMESPACEi="URIi".
277     for (const auto* attr = dom.getFirstAttr(node); attr; attr = dom.getNextAttr(node, attr)) {
278         const char* attrName = dom.getAttrName(node, attr);
279         const char* attrValue = dom.getAttrValue(node, attr);
280         if (!attrName || !attrValue) {
281             continue;
282         }
283         // Make sure the name starts with "xmlns:".
284         if (strlen(attrName) <= kXmlnsPrefixLength) {
285             continue;
286         }
287         if (memcmp(attrName, kXmlnsPrefix, kXmlnsPrefixLength) != 0) {
288             continue;
289         }
290         // Search for a requested URI that matches.
291         for (size_t i = 0; i < count; ++i) {
292             if (strcmp(attrValue, uris[i]) != 0) {
293                 continue;
294             }
295             outNamespaces[i] = attrName;
296         }
297     }
298 }
299 
300 // See SkXmp::findUriNamespaces. This function has the same behavior, but only searches
301 // a single SkDOM.
find_uri_namespaces(const SkDOM & dom,size_t count,const char * uris[],const char * outNamespaces[])302 static const SkDOM::Node* find_uri_namespaces(const SkDOM& dom,
303                                               size_t count,
304                                               const char* uris[],
305                                               const char* outNamespaces[]) {
306     const SkDOM::Node* root = dom.getRootNode();
307     if (!root) {
308         return nullptr;
309     }
310 
311     // Ensure that the root node identifies itself as XMP metadata.
312     const char* rootName = dom.getName(root);
313     if (!rootName || strcmp(rootName, "x:xmpmeta") != 0) {
314         return nullptr;
315     }
316 
317     //  Iterate the children with name rdf:RDF.
318     const char* kRdf = "rdf:RDF";
319     for (const auto* rdf = dom.getFirstChild(root, kRdf); rdf;
320          rdf = dom.getNextSibling(rdf, kRdf)) {
321         std::vector<const char*> rdfNamespaces(count, nullptr);
322         find_uri_namespaces(dom, rdf, count, uris, rdfNamespaces.data());
323 
324         // Iterate the children with name rdf::Description.
325         const char* kDesc = "rdf:Description";
326         for (const auto* desc = dom.getFirstChild(rdf, kDesc); desc;
327              desc = dom.getNextSibling(desc, kDesc)) {
328             std::vector<const char*> descNamespaces = rdfNamespaces;
329             find_uri_namespaces(dom, desc, count, uris, descNamespaces.data());
330 
331             // If we have a match for all the requested URIs, return.
332             bool foundAllUris = true;
333             for (size_t i = 0; i < count; ++i) {
334                 if (!descNamespaces[i]) {
335                     foundAllUris = false;
336                     break;
337                 }
338             }
339             if (foundAllUris) {
340                 for (size_t i = 0; i < count; ++i) {
341                     outNamespaces[i] = descNamespaces[i];
342                 }
343                 return desc;
344             }
345         }
346     }
347     return nullptr;
348 }
349 
350 ////////////////////////////////////////////////////////////////////////////////////////////////////
351 // SkXmpImpl
352 
353 class SkXmpImpl final : public SkXmp {
354 public:
355     SkXmpImpl() = default;
356 
357     bool getGainmapInfoAdobe(SkGainmapInfo* info) const override;
358     bool getGainmapInfoApple(float exifHdrHeadroom, SkGainmapInfo* info) const override;
359     bool getContainerGainmapLocation(size_t* offset, size_t* size) const override;
360     const char* getExtendedXmpGuid() const override;
361     // Parse the given xmp data and store it into either the standard (main) DOM or the extended
362     // DOM. Returns true on successful parsing.
363     bool parseDom(sk_sp<SkData> xmpData, bool extended);
364 
365 private:
366     bool findUriNamespaces(size_t count,
367                            const char* uris[],
368                            const char* outNamespaces[],
369                            const SkDOM** outDom,
370                            const SkDOM::Node** outNode) const;
371 
372     SkDOM fStandardDOM;
373     SkDOM fExtendedDOM;
374 };
375 
getExtendedXmpGuid() const376 const char* SkXmpImpl::getExtendedXmpGuid() const {
377     const char* namespaces[1] = {nullptr};
378     const char* uris[1] = {"http://ns.adobe.com/xmp/note/"};
379     const auto* extendedNode = find_uri_namespaces(fStandardDOM, 1, uris, namespaces);
380     if (!extendedNode) {
381         return nullptr;
382     }
383     const auto xmpNotePrefix = get_namespace_prefix(namespaces[0]);
384     // Extract the GUID (the MD5 hash) of the extended metadata.
385     return get_attr(&fStandardDOM, extendedNode, xmpNotePrefix, "HasExtendedXMP");
386 }
387 
findUriNamespaces(size_t count,const char * uris[],const char * outNamespaces[],const SkDOM ** outDom,const SkDOM::Node ** outNode) const388 bool SkXmpImpl::findUriNamespaces(size_t count,
389                                   const char* uris[],
390                                   const char* outNamespaces[],
391                                   const SkDOM** outDom,
392                                   const SkDOM::Node** outNode) const {
393     // See XMP Specification Part 3: Storage in files, Section 1.1.3.1: Extended XMP in JPEG:
394     // A JPEG reader must recompose the StandardXMP and ExtendedXMP into a single data model tree
395     // containing all of the XMP for the JPEG file, and remove the xmpNote:HasExtendedXMP property.
396     // This code does not do that. Instead, it maintains the two separate trees and searches them
397     // sequentially.
398     *outNode = find_uri_namespaces(fStandardDOM, count, uris, outNamespaces);
399     if (*outNode) {
400         *outDom = &fStandardDOM;
401         return true;
402     }
403     *outNode = find_uri_namespaces(fExtendedDOM, count, uris, outNamespaces);
404     if (*outNode) {
405         *outDom = &fExtendedDOM;
406         return true;
407     }
408     *outDom = nullptr;
409     return false;
410 }
411 
getContainerGainmapLocation(size_t * outOffset,size_t * outSize) const412 bool SkXmpImpl::getContainerGainmapLocation(size_t* outOffset, size_t* outSize) const {
413     // Find a node that matches the requested namespaces and URIs.
414     const char* namespaces[2] = {nullptr, nullptr};
415     const char* uris[2] = {"http://ns.google.com/photos/1.0/container/",
416                            "http://ns.google.com/photos/1.0/container/item/"};
417     const SkDOM* dom = nullptr;
418     const SkDOM::Node* node = nullptr;
419     if (!findUriNamespaces(2, uris, namespaces, &dom, &node)) {
420         return false;
421     }
422     const char* containerPrefix = get_namespace_prefix(namespaces[0]);
423     const char* itemPrefix = get_namespace_prefix(namespaces[1]);
424 
425     // The node must have a Container:Directory child.
426     const auto* directory = get_typed_child(dom, node, containerPrefix, "Directory");
427     if (!directory) {
428         SkCodecPrintf("Missing Container Directory");
429         return false;
430     }
431 
432     // That Container:Directory must have a sequence of  items.
433     const auto* seq = dom->getFirstChild(directory, "rdf:Seq");
434     if (!seq) {
435         SkCodecPrintf("Missing rdf:Seq");
436         return false;
437     }
438 
439     // Iterate through the items in the Container:Directory's sequence. Keep a running sum of the
440     // Item:Length of all items that appear before the GainMap.
441     bool isFirstItem = true;
442     size_t offset = 0;
443     for (const auto* li = dom->getFirstChild(seq, "rdf:li"); li;
444          li = dom->getNextSibling(li, "rdf:li")) {
445         // Each list item must contain a Container:Item.
446         const auto* item = get_typed_child(dom, li, containerPrefix, "Item");
447         if (!item) {
448             SkCodecPrintf("List item does not have container Item.\n");
449             return false;
450         }
451         // A Semantic is required for every item.
452         const char* itemSemantic = get_attr(dom, item, itemPrefix, "Semantic");
453         if (!itemSemantic) {
454             SkCodecPrintf("Item is missing Semantic.\n");
455             return false;
456         }
457         // A Mime is required for every item.
458         const char* itemMime = get_attr(dom, item, itemPrefix, "Mime");
459         if (!itemMime) {
460             SkCodecPrintf("Item is missing Mime.\n");
461             return false;
462         }
463 
464         if (isFirstItem) {
465             isFirstItem = false;
466             // The first item must be Primary.
467             if (strcmp(itemSemantic, "Primary") != 0) {
468                 SkCodecPrintf("First item is not Primary.\n");
469                 return false;
470             }
471             // The first item has mime type image/jpeg (we are decoding a jpeg).
472             if (strcmp(itemMime, "image/jpeg") != 0) {
473                 SkCodecPrintf("Primary does not report that it is image/jpeg.\n");
474                 return false;
475             }
476             // The first media item can contain a Padding attribute, which specifies additional
477             // padding between the end of the encoded primary image and the beginning of the next
478             // media item. Only the first media item can contain a Padding attribute.
479             int32_t padding = 0;
480             if (get_attr_int32(dom, item, itemPrefix, "Padding", &padding)) {
481                 if (padding < 0) {
482                     SkCodecPrintf("Item padding must be non-negative.");
483                     return false;
484                 }
485                 offset += padding;
486             }
487         } else {
488             // A Length is required for all non-Primary items.
489             int32_t length = 0;
490             if (!get_attr_int32(dom, item, itemPrefix, "Length", &length)) {
491                 SkCodecPrintf("Item length is absent.");
492                 return false;
493             }
494             if (length < 0) {
495                 SkCodecPrintf("Item length must be non-negative.");
496                 return false;
497             }
498             // If this is not the recovery map, then read past it.
499             if (strcmp(itemSemantic, "GainMap") != 0) {
500                 offset += length;
501                 continue;
502             }
503             // The recovery map must have mime type image/jpeg in this implementation.
504             if (strcmp(itemMime, "image/jpeg") != 0) {
505                 SkCodecPrintf("GainMap does not report that it is image/jpeg.\n");
506                 return false;
507             }
508 
509             // Populate the location in the file at which to find the gainmap image.
510             *outOffset = offset;
511             *outSize = length;
512             return true;
513         }
514     }
515     return false;
516 }
517 
518 // Return true if the specified XMP metadata identifies this image as an HDR gainmap.
getGainmapInfoApple(float exifHdrHeadroom,SkGainmapInfo * info) const519 bool SkXmpImpl::getGainmapInfoApple(float exifHdrHeadroom, SkGainmapInfo* info) const {
520     // Find a node that matches the requested namespaces and URIs.
521     const char* namespaces[2] = {nullptr, nullptr};
522     const char* uris[2] = {"http://ns.apple.com/pixeldatainfo/1.0/",
523                            "http://ns.apple.com/HDRGainMap/1.0/"};
524     const SkDOM* dom = nullptr;
525     const SkDOM::Node* node = nullptr;
526     if (!findUriNamespaces(2, uris, namespaces, &dom, &node)) {
527         return false;
528     }
529     const char* adpiPrefix = get_namespace_prefix(namespaces[0]);
530     const char* hdrGainMapPrefix = get_namespace_prefix(namespaces[1]);
531 
532     const char* auxiliaryImageType = get_attr(dom, node, adpiPrefix, "AuxiliaryImageType");
533     if (!auxiliaryImageType) {
534         SkCodecPrintf("Did not find AuxiliaryImageType.\n");
535         return false;
536     }
537     if (strcmp(auxiliaryImageType, "urn:com:apple:photo:2020:aux:hdrgainmap") != 0) {
538         SkCodecPrintf("AuxiliaryImageType was not HDR gain map.\n");
539         return false;
540     }
541 
542     // Require that the gainmap version be present, but do not require a specific version.
543     int32_t version = 0;
544     if (!get_attr_int32(dom, node, hdrGainMapPrefix, "HDRGainMapVersion", &version)) {
545         SkCodecPrintf("Did not find HDRGainMapVersion.\n");
546         return false;
547     }
548 
549     // If the XMP also specifies a HDRGainMapHeadroom parameter, then prefer that parameter to the
550     // parameter specified in the base image Exif.
551     float hdrHeadroom = exifHdrHeadroom;
552     float xmpHdrHeadroom = 0.f;
553     if (get_attr_float(dom, node, hdrGainMapPrefix, "HDRGainMapHeadroom", &xmpHdrHeadroom)) {
554         hdrHeadroom = xmpHdrHeadroom;
555     }
556 
557     // This node will often have StoredFormat and NativeFormat children that have inner text that
558     // specifies the integer 'L008' (also known as kCVPixelFormatType_OneComponent8).
559     info->fGainmapRatioMin = {1.f, 1.f, 1.f, 1.f};
560     info->fGainmapRatioMax = {hdrHeadroom, hdrHeadroom, hdrHeadroom, 1.f};
561     info->fGainmapGamma = {1.f, 1.f, 1.f, 1.f};
562     info->fEpsilonSdr = {0.f, 0.f, 0.f, 1.f};
563     info->fEpsilonHdr = {0.f, 0.f, 0.f, 1.f};
564     info->fDisplayRatioSdr = 1.f;
565     info->fDisplayRatioHdr = hdrHeadroom;
566     info->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
567     info->fType = SkGainmapInfo::Type::kApple;
568     return true;
569 }
570 
getGainmapInfoAdobe(SkGainmapInfo * outGainmapInfo) const571 bool SkXmpImpl::getGainmapInfoAdobe(SkGainmapInfo* outGainmapInfo) const {
572     // Find a node that matches the requested namespace and URI.
573     const char* namespaces[1] = {nullptr};
574     const char* uris[1] = {"http://ns.adobe.com/hdr-gain-map/1.0/"};
575     const SkDOM* dom = nullptr;
576     const SkDOM::Node* node = nullptr;
577     if (!findUriNamespaces(1, uris, namespaces, &dom, &node)) {
578         return false;
579     }
580     const char* hdrgmPrefix = get_namespace_prefix(namespaces[0]);
581 
582     // Require that hdrgm:Version="1.0" be present.
583     const char* version = get_attr(dom, node, hdrgmPrefix, "Version");
584     if (!version) {
585         SkCodecPrintf("Version attribute is absent.\n");
586         return false;
587     }
588     if (strcmp(version, "1.0") != 0) {
589         SkCodecPrintf("Version is \"%s\", not \"1.0\".\n", version);
590         return false;
591     }
592 
593     // Initialize the parameters to their defaults.
594     bool baseRenditionIsHDR = false;
595     SkColor4f gainMapMin = {0.f, 0.f, 0.f, 1.f};  // log2 value
596     SkColor4f gainMapMax = {1.f, 1.f, 1.f, 1.f};  // log2 value
597     SkColor4f gamma = {1.f, 1.f, 1.f, 1.f};
598     SkColor4f offsetSdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
599     SkColor4f offsetHdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
600     SkScalar hdrCapacityMin = 0.f;  // log2 value
601     SkScalar hdrCapacityMax = 1.f;  // log2 value
602 
603     // Read all parameters that are present.
604     get_attr_bool(dom, node, hdrgmPrefix, "BaseRenditionIsHDR", &baseRenditionIsHDR);
605     get_attr_float3(dom, node, hdrgmPrefix, "GainMapMin", &gainMapMin);
606     get_attr_float3(dom, node, hdrgmPrefix, "GainMapMax", &gainMapMax);
607     get_attr_float3(dom, node, hdrgmPrefix, "Gamma", &gamma);
608     get_attr_float3(dom, node, hdrgmPrefix, "OffsetSDR", &offsetSdr);
609     get_attr_float3(dom, node, hdrgmPrefix, "OffsetHDR", &offsetHdr);
610     get_attr_float(dom, node, hdrgmPrefix, "HDRCapacityMin", &hdrCapacityMin);
611     get_attr_float(dom, node, hdrgmPrefix, "HDRCapacityMax", &hdrCapacityMax);
612 
613     // Translate all parameters to SkGainmapInfo's expected format.
614     if (!outGainmapInfo) {
615         return true;
616     }
617     const float kLog2 = std::log(2.f);
618     outGainmapInfo->fGainmapRatioMin = {std::exp(gainMapMin.fR * kLog2),
619                                         std::exp(gainMapMin.fG * kLog2),
620                                         std::exp(gainMapMin.fB * kLog2),
621                                         1.f};
622     outGainmapInfo->fGainmapRatioMax = {std::exp(gainMapMax.fR * kLog2),
623                                         std::exp(gainMapMax.fG * kLog2),
624                                         std::exp(gainMapMax.fB * kLog2),
625                                         1.f};
626     outGainmapInfo->fGainmapGamma = {1.f / gamma.fR, 1.f / gamma.fG, 1.f / gamma.fB, 1.f};
627     outGainmapInfo->fEpsilonSdr = offsetSdr;
628     outGainmapInfo->fEpsilonHdr = offsetHdr;
629     outGainmapInfo->fDisplayRatioSdr = std::exp(hdrCapacityMin * kLog2);
630     outGainmapInfo->fDisplayRatioHdr = std::exp(hdrCapacityMax * kLog2);
631     if (baseRenditionIsHDR) {
632         outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kHDR;
633     } else {
634         outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
635     }
636     return true;
637 }
638 
parseDom(sk_sp<SkData> xmpData,bool extended)639 bool SkXmpImpl::parseDom(sk_sp<SkData> xmpData, bool extended) {
640     SkDOM* dom = extended ? &fExtendedDOM : &fStandardDOM;
641     auto xmpdStream = SkMemoryStream::Make(std::move(xmpData));
642     if (!dom->build(*xmpdStream)) {
643         SkCodecPrintf("Failed to parse XMP %s metadata.\n", extended ? "extended" : "standard");
644         return false;
645     }
646     return true;
647 }
648 
649 ////////////////////////////////////////////////////////////////////////////////////////////////////
650 // SkXmp
651 
Make(sk_sp<SkData> xmpData)652 std::unique_ptr<SkXmp> SkXmp::Make(sk_sp<SkData> xmpData) {
653     std::unique_ptr<SkXmpImpl> xmp(new SkXmpImpl);
654     if (!xmp->parseDom(std::move(xmpData), /*extended=*/false)) {
655         return nullptr;
656     }
657     return xmp;
658 }
659 
Make(sk_sp<SkData> xmpStandard,sk_sp<SkData> xmpExtended)660 std::unique_ptr<SkXmp> SkXmp::Make(sk_sp<SkData> xmpStandard, sk_sp<SkData> xmpExtended) {
661     std::unique_ptr<SkXmpImpl> xmp(new SkXmpImpl);
662     if (!xmp->parseDom(std::move(xmpStandard), /*extended=*/false)) {
663         return nullptr;
664     }
665     // Try to parse extended xmp but ignore the return value: if parsing fails, we'll still return
666     // the standard xmp.
667     (void)xmp->parseDom(std::move(xmpExtended), /*extended=*/true);
668     return xmp;
669 }
670