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