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