| 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 | |
| 28 | const char* kXmlnsPrefix = "xmlns:" ; |
| 29 | const size_t kXmlnsPrefixLength = 6; |
| 30 | |
| 31 | static 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 | */ |
| 51 | static 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 | */ |
| 85 | static 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 | */ |
| 124 | static 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. |
| 137 | static 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. |
| 160 | static 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. |
| 176 | static 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. |
| 195 | static 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 | |
| 254 | static 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 | |
| 270 | static 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. |
| 301 | static 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 | |
| 352 | class SK_API SkXmpImpl final : public SkXmp { |
| 353 | public: |
| 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 | |
| 364 | private: |
| 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 | |
| 375 | const 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 | |
| 387 | bool 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 | |
| 411 | bool 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. |
| 518 | bool 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 | |
| 566 | bool 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 | |
| 632 | bool 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 | |
| 645 | std::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 | |
| 653 | std::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 | |