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

source code of flutter_engine/third_party/skia/src/codec/SkXmp.cpp