• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "src/codec/SkJpegXmp.h"
9 
10 #include "include/private/SkGainmapInfo.h"
11 #include "include/utils/SkParse.h"
12 #include "src/codec/SkCodecPriv.h"
13 #include "src/codec/SkJpegConstants.h"
14 #include "src/core/SkMD5.h"
15 #include "src/xml/SkDOM.h"
16 
17 #include <string>
18 
19 SkJpegXmp::SkJpegXmp() = default;
20 
21 ////////////////////////////////////////////////////////////////////////////////////////////////////
22 // XMP JPEG extraction helper functions
23 
24 constexpr size_t kGuidAsciiSize = 32;
25 
26 /*
27  * Extract standard XMP metadata.
28  *
29  * See XMP Specification Part 3: Storage in files, Section 1.1.3: JPEG.
30  */
read_xmp_standard(const std::vector<sk_sp<SkData>> & decoderApp1Params)31 static sk_sp<SkData> read_xmp_standard(const std::vector<sk_sp<SkData>>& decoderApp1Params) {
32     constexpr size_t kSigSize = sizeof(kXMPStandardSig);
33     // Iterate through the image's segments.
34     for (const auto& params : decoderApp1Params) {
35         // Skip segments that don't have the right marker, signature, or are too small.
36         if (params->size() <= kSigSize) {
37             continue;
38         }
39         if (memcmp(params->bytes(), kXMPStandardSig, kSigSize) != 0) {
40             continue;
41         }
42         return SkData::MakeWithoutCopy(params->bytes() + kSigSize, params->size() - kSigSize);
43     }
44     return nullptr;
45 }
46 
47 /*
48  * Extract and validate extended XMP metadata.
49  *
50  * See XMP Specification Part 3: Storage in files, Section 1.1.3.1: Extended XMP in JPEG:
51  * Each chunk is written into the JPEG file within a separate APP1 marker segment. Each ExtendedXMP
52  * marker segment contains:
53  *   - A null-terminated signature string
54  *   - A 128-bit GUID stored as a 32-byte ASCII hex string, capital A-F, no null termination. The
55  *     GUID is a 128-bit MD5 digest of the full ExtendedXMP serialization.
56  *   - The full length of the ExtendedXMP serialization as a 32-bit unsigned integer.
57  *   - The offset of this portion as a 32-bit unsigned integer.
58  *   - The portion of the ExtendedXMP
59  */
read_xmp_extended(const std::vector<sk_sp<SkData>> & decoderApp1Params,const char * guidAscii)60 static sk_sp<SkData> read_xmp_extended(const std::vector<sk_sp<SkData>>& decoderApp1Params,
61                                        const char* guidAscii) {
62     constexpr size_t kSigSize = sizeof(kXMPExtendedSig);
63     constexpr size_t kFullLengthSize = 4;
64     constexpr size_t kOffsetSize = 4;
65     constexpr size_t kHeaderSize = kSigSize + kGuidAsciiSize + kFullLengthSize + kOffsetSize;
66 
67     // Validate the provided ASCII guid.
68     SkMD5::Digest guidAsDigest;
69     if (strlen(guidAscii) != kGuidAsciiSize) {
70         SkCodecPrintf("Invalid ASCII GUID size.\n");
71         return nullptr;
72     }
73     for (size_t i = 0; i < kGuidAsciiSize; ++i) {
74         uint8_t digit = 0;
75         if (guidAscii[i] >= '0' && guidAscii[i] <= '9') {
76             digit = guidAscii[i] - '0';
77         } else if (guidAscii[i] >= 'A' && guidAscii[i] <= 'F') {
78             digit = guidAscii[i] - 'A' + 10;
79         } else {
80             SkCodecPrintf("GUID is not upper-case hex.\n");
81             return nullptr;
82         }
83         if (i % 2 == 0) {
84             guidAsDigest.data[i / 2] = 16 * digit;
85         } else {
86             guidAsDigest.data[i / 2] += digit;
87         }
88     }
89 
90     // Iterate through the image's segments.
91     uint32_t fullLength = 0;
92     using Part = std::tuple<uint32_t, sk_sp<SkData>>;
93     std::vector<Part> parts;
94     for (const auto& params : decoderApp1Params) {
95         // Skip segments that don't have the right marker, signature, or are too small.
96         if (params->size() <= kHeaderSize) {
97             continue;
98         }
99         if (memcmp(params->bytes(), kXMPExtendedSig, kSigSize) != 0) {
100             continue;
101         }
102 
103         // Ignore parts that do not match the expected GUID.
104         const uint8_t* partGuidAscii = params->bytes() + kSigSize;
105         if (memcmp(guidAscii, partGuidAscii, kGuidAsciiSize) != 0) {
106             SkCodecPrintf("Ignoring unexpected GUID.\n");
107             continue;
108         }
109 
110         // Read the full length and the offset for this part.
111         uint32_t partFullLength = 0;
112         uint32_t partOffset = 0;
113         const uint8_t* partFullLengthBytes = params->bytes() + kSigSize + kGuidAsciiSize;
114         const uint8_t* partOffsetBytes =
115                 params->bytes() + kSigSize + kGuidAsciiSize + kFullLengthSize;
116         for (size_t i = 0; i < 4; ++i) {
117             partFullLength *= 256;
118             partOffset *= 256;
119             partFullLength += partFullLengthBytes[i];
120             partOffset += partOffsetBytes[i];
121         }
122 
123         // If this is the first part, set our global full length size.
124         if (parts.empty()) {
125             fullLength = partFullLength;
126         }
127 
128         // Ensure all parts agree on the full length.
129         if (partFullLength != fullLength) {
130             SkCodecPrintf("Multiple parts had different total lengths.\n");
131             return nullptr;
132         }
133 
134         // Add it to the list.
135         auto partData = SkData::MakeWithoutCopy(params->bytes() + kHeaderSize,
136                                                 params->size() - kHeaderSize);
137         parts.push_back({partOffset, partData});
138     }
139     if (parts.empty() || fullLength == 0) {
140         return nullptr;
141     }
142 
143     // Sort the list of parts by offset.
144     std::sort(parts.begin(), parts.end(), [](const Part& a, const Part& b) {
145         return std::get<0>(a) < std::get<0>(b);
146     });
147 
148     // Stitch the parts together. Fail if we find that they are not contiguous.
149     auto xmpExtendedData = SkData::MakeUninitialized(fullLength);
150     uint8_t* xmpExtendedBase = reinterpret_cast<uint8_t*>(xmpExtendedData->writable_data());
151     uint8_t* xmpExtendedCurrent = xmpExtendedBase;
152     SkMD5 md5;
153     for (const auto& part : parts) {
154         uint32_t currentOffset = static_cast<uint32_t>(xmpExtendedCurrent - xmpExtendedBase);
155         uint32_t partOffset = std::get<0>(part);
156         const sk_sp<SkData>& partData = std::get<1>(part);
157         // Make sure the data is contiguous and doesn't overflow the buffer.
158         if (partOffset != currentOffset) {
159             SkCodecPrintf("XMP extension parts not contiguous\n");
160             return nullptr;
161         }
162         if (partData->size() > fullLength - currentOffset) {
163             SkCodecPrintf("XMP extension parts overflow\n");
164             return nullptr;
165         }
166         memcpy(xmpExtendedCurrent, partData->data(), partData->size());
167         xmpExtendedCurrent += partData->size();
168     }
169     // Make sure we wrote the full buffer.
170     if (static_cast<uint32_t>(xmpExtendedCurrent - xmpExtendedBase) != fullLength) {
171         SkCodecPrintf("XMP extension did not match full length.\n");
172         return nullptr;
173     }
174 
175     // Make sure the MD5 hash of the extended data matched the GUID.
176     md5.write(xmpExtendedData->data(), xmpExtendedData->size());
177     if (md5.finish() != guidAsDigest) {
178         SkCodecPrintf("XMP extension did not hash to GUID.\n");
179         return nullptr;
180     }
181 
182     return xmpExtendedData;
183 }
184 
185 ////////////////////////////////////////////////////////////////////////////////////////////////////
186 // XMP parsing helper functions
187 
188 const char* kXmlnsPrefix = "xmlns:";
189 const size_t kXmlnsPrefixLength = 6;
190 
get_namespace_prefix(const char * name)191 static const char* get_namespace_prefix(const char* name) {
192     if (strlen(name) <= kXmlnsPrefixLength) {
193         return nullptr;
194     }
195     return name + kXmlnsPrefixLength;
196 }
197 
198 /*
199  * Given a node, see if that node has only one child with the indicated name. If so, see if that
200  * child has only a single child of its own, and that child is text. If all of that is the case
201  * then return the text, otherwise return nullptr.
202  *
203  * In the following example, innerText will be returned.
204  *    <node><childName>innerText</childName></node>
205  *
206  * In the following examples, nullptr will be returned (because there are multiple children with
207  * childName in the first case, and because the child has children of its own in the second).
208  *    <node><childName>innerTextA</childName><childName>innerTextB</childName></node>
209  *    <node><childName>innerText<otherGrandChild/></childName></node>
210  */
get_unique_child_text(const SkDOM & dom,const SkDOM::Node * node,const std::string & childName)211 static const char* get_unique_child_text(const SkDOM& dom,
212                                          const SkDOM::Node* node,
213                                          const std::string& childName) {
214     // Fail if there are multiple children with childName.
215     if (dom.countChildren(node, childName.c_str()) != 1) {
216         return nullptr;
217     }
218     const auto* child = dom.getFirstChild(node, childName.c_str());
219     if (!child) {
220         return nullptr;
221     }
222     // Fail if the child has any children besides text.
223     if (dom.countChildren(child) != 1) {
224         return nullptr;
225     }
226     const auto* grandChild = dom.getFirstChild(child);
227     if (dom.getType(grandChild) != SkDOM::kText_Type) {
228         return nullptr;
229     }
230     // Return the text.
231     return dom.getName(grandChild);
232 }
233 
234 /*
235  * Given a node, find a child node of the specified type.
236  *
237  * If there exists a child node with name |prefix| + ":" + |type|, then return that child.
238  *
239  * If there exists a child node with name "rdf:type" that has attribute "rdf:resource" with value
240  * of |type|, then if there also exists a child node with name "rdf:value" with attribute
241  * "rdf:parseType" of "Resource", then return that child node with name "rdf:value". See Example
242  * 3 in section 7.9.2.5: RDF Typed Nodes.
243  * TODO(ccameron): This should also accept a URI for the type.
244  */
get_typed_child(const SkDOM * dom,const SkDOM::Node * node,const std::string & prefix,const std::string & type)245 static const SkDOM::Node* get_typed_child(const SkDOM* dom,
246                                           const SkDOM::Node* node,
247                                           const std::string& prefix,
248                                           const std::string& type) {
249     const auto name = prefix + std::string(":") + type;
250     const SkDOM::Node* child = dom->getFirstChild(node, name.c_str());
251     if (child) {
252         return child;
253     }
254 
255     const SkDOM::Node* typeChild = dom->getFirstChild(node, "rdf:type");
256     if (!typeChild) {
257         return nullptr;
258     }
259     const char* typeChildResource = dom->findAttr(typeChild, "rdf:resource");
260     if (!typeChildResource || typeChildResource != type) {
261         return nullptr;
262     }
263 
264     const SkDOM::Node* valueChild = dom->getFirstChild(node, "rdf:value");
265     if (!valueChild) {
266         return nullptr;
267     }
268     const char* valueChildParseType = dom->findAttr(valueChild, "rdf:parseType");
269     if (!valueChildParseType || strcmp(valueChildParseType, "Resource") != 0) {
270         return nullptr;
271     }
272     return valueChild;
273 }
274 
275 /*
276  * Given a node, return its value for the specified attribute.
277  *
278  * This will first look for an attribute with the name |prefix| + ":" + |key|, and return the value
279  * for that attribute.
280  *
281  * This will then look for a child node of name |prefix| + ":" + |key|, and return the field value
282  * for that child.
283  */
get_attr(const SkDOM * dom,const SkDOM::Node * node,const std::string & prefix,const std::string & key)284 static const char* get_attr(const SkDOM* dom,
285                             const SkDOM::Node* node,
286                             const std::string& prefix,
287                             const std::string& key) {
288     const auto name = prefix + ":" + key;
289     const char* attr = dom->findAttr(node, name.c_str());
290     if (attr) {
291         return attr;
292     }
293     return get_unique_child_text(*dom, node, name);
294 }
295 
296 // 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)297 static bool get_attr_bool(const SkDOM* dom,
298                           const SkDOM::Node* node,
299                           const std::string& prefix,
300                           const std::string& key,
301                           bool* outValue) {
302     const char* attr = get_attr(dom, node, prefix, key);
303     if (!attr) {
304         return false;
305     }
306     switch (SkParse::FindList(attr, "False,True")) {
307         case 0:
308             *outValue = false;
309             return true;
310         case 1:
311             *outValue = true;
312             return true;
313         default:
314             break;
315     }
316     return false;
317 }
318 
319 // 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)320 static bool get_attr_int32(const SkDOM* dom,
321                            const SkDOM::Node* node,
322                            const std::string& prefix,
323                            const std::string& key,
324                            int32_t* value) {
325     const char* attr = get_attr(dom, node, prefix, key);
326     if (!attr) {
327         return false;
328     }
329     if (!SkParse::FindS32(attr, value)) {
330         return false;
331     }
332     return true;
333 }
334 
335 // 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)336 static bool get_attr_float(const SkDOM* dom,
337                            const SkDOM::Node* node,
338                            const std::string& prefix,
339                            const std::string& key,
340                            float* outValue) {
341     const char* attr = get_attr(dom, node, prefix, key);
342     if (!attr) {
343         return false;
344     }
345     SkScalar value = 0.f;
346     if (SkParse::FindScalar(attr, &value)) {
347         *outValue = value;
348         return true;
349     }
350     return false;
351 }
352 
353 // Perform get_attr and parse the result as three comma-separated floats. Return the result as an
354 // 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)355 static bool get_attr_float3_as_list(const SkDOM* dom,
356                                     const SkDOM::Node* node,
357                                     const std::string& prefix,
358                                     const std::string& key,
359                                     SkColor4f* outValue) {
360     const auto name = prefix + ":" + key;
361 
362     // Fail if there are multiple children with childName.
363     if (dom->countChildren(node, name.c_str()) != 1) {
364         return false;
365     }
366     // Find the child.
367     const auto* child = dom->getFirstChild(node, name.c_str());
368     if (!child) {
369         return false;
370     }
371 
372     // Search for the rdf:Seq child.
373     const auto* seq = dom->getFirstChild(child, "rdf:Seq");
374     if (!seq) {
375         return false;
376     }
377 
378     size_t count = 0;
379     SkScalar values[3] = {0.f, 0.f, 0.f};
380     for (const auto* liNode = dom->getFirstChild(seq, "rdf:li"); liNode;
381          liNode = dom->getNextSibling(liNode, "rdf:li")) {
382         if (count > 2) {
383             SkCodecPrintf("Too many items in list.\n");
384             return false;
385         }
386         if (dom->countChildren(liNode) != 1) {
387             SkCodecPrintf("Item can only have one child.\n");
388             return false;
389         }
390         const auto* liTextNode = dom->getFirstChild(liNode);
391         if (dom->getType(liTextNode) != SkDOM::kText_Type) {
392             SkCodecPrintf("Item's only child must be text.\n");
393             return false;
394         }
395         const char* liText = dom->getName(liTextNode);
396         if (!liText) {
397             SkCodecPrintf("Failed to get item's text.\n");
398             return false;
399         }
400         if (!SkParse::FindScalar(liText, values + count)) {
401             SkCodecPrintf("Failed to parse item's text to float.\n");
402             return false;
403         }
404         count += 1;
405     }
406     if (count < 3) {
407         SkCodecPrintf("List didn't have enough items.\n");
408         return false;
409     }
410     *outValue = {values[0], values[1], values[2], 1.f};
411     return true;
412 }
413 
get_attr_float3(const SkDOM * dom,const SkDOM::Node * node,const std::string & prefix,const std::string & key,SkColor4f * outValue)414 static bool get_attr_float3(const SkDOM* dom,
415                             const SkDOM::Node* node,
416                             const std::string& prefix,
417                             const std::string& key,
418                             SkColor4f* outValue) {
419     if (get_attr_float3_as_list(dom, node, prefix, key, outValue)) {
420         return true;
421     }
422     SkScalar value = -1.0;
423     if (get_attr_float(dom, node, prefix, key, &value)) {
424         *outValue = {value, value, value, 1.f};
425         return true;
426     }
427     return false;
428 }
429 
find_uri_namespaces(const SkDOM & dom,const SkDOM::Node * node,size_t count,const char * uris[],const char * outNamespaces[])430 static void find_uri_namespaces(const SkDOM& dom,
431                                 const SkDOM::Node* node,
432                                 size_t count,
433                                 const char* uris[],
434                                 const char* outNamespaces[]) {
435     // Search all attributes for xmlns:NAMESPACEi="URIi".
436     for (const auto* attr = dom.getFirstAttr(node); attr; attr = dom.getNextAttr(node, attr)) {
437         const char* attrName = dom.getAttrName(node, attr);
438         const char* attrValue = dom.getAttrValue(node, attr);
439         if (!attrName || !attrValue) {
440             continue;
441         }
442         // Make sure the name starts with "xmlns:".
443         if (strlen(attrName) <= kXmlnsPrefixLength) {
444             continue;
445         }
446         if (memcmp(attrName, kXmlnsPrefix, kXmlnsPrefixLength) != 0) {
447             continue;
448         }
449         // Search for a requested URI that matches.
450         for (size_t i = 0; i < count; ++i) {
451             if (strcmp(attrValue, uris[i]) != 0) {
452                 continue;
453             }
454             outNamespaces[i] = attrName;
455         }
456     }
457 }
458 
459 // See SkJpegXmp::findUriNamespaces. This function has the same behavior, but only searches
460 // a single SkDOM.
find_uri_namespaces(const SkDOM & dom,size_t count,const char * uris[],const char * outNamespaces[])461 static const SkDOM::Node* find_uri_namespaces(const SkDOM& dom,
462                                               size_t count,
463                                               const char* uris[],
464                                               const char* outNamespaces[]) {
465     const SkDOM::Node* root = dom.getRootNode();
466     if (!root) {
467         return nullptr;
468     }
469 
470     // Ensure that the root node identifies itself as XMP metadata.
471     const char* rootName = dom.getName(root);
472     if (!rootName || strcmp(rootName, "x:xmpmeta") != 0) {
473         return nullptr;
474     }
475 
476     //  Iterate the children with name rdf:RDF.
477     const char* kRdf = "rdf:RDF";
478     for (const auto* rdf = dom.getFirstChild(root, kRdf); rdf;
479          rdf = dom.getNextSibling(rdf, kRdf)) {
480         std::vector<const char*> rdfNamespaces(count, nullptr);
481         find_uri_namespaces(dom, rdf, count, uris, rdfNamespaces.data());
482 
483         // Iterate the children with name rdf::Description.
484         const char* kDesc = "rdf:Description";
485         for (const auto* desc = dom.getFirstChild(rdf, kDesc); desc;
486              desc = dom.getNextSibling(desc, kDesc)) {
487             std::vector<const char*> descNamespaces = rdfNamespaces;
488             find_uri_namespaces(dom, desc, count, uris, descNamespaces.data());
489 
490             // If we have a match for all the requested URIs, return.
491             bool foundAllUris = true;
492             for (size_t i = 0; i < count; ++i) {
493                 if (!descNamespaces[i]) {
494                     foundAllUris = false;
495                     break;
496                 }
497             }
498             if (foundAllUris) {
499                 for (size_t i = 0; i < count; ++i) {
500                     outNamespaces[i] = descNamespaces[i];
501                 }
502                 return desc;
503             }
504         }
505     }
506     return nullptr;
507 }
508 
509 ////////////////////////////////////////////////////////////////////////////////////////////////////
510 // SkJpegXmp
511 
Make(const std::vector<sk_sp<SkData>> & decoderApp1Params)512 std::unique_ptr<SkJpegXmp> SkJpegXmp::Make(const std::vector<sk_sp<SkData>>& decoderApp1Params) {
513     auto xmpStandard = read_xmp_standard(decoderApp1Params);
514     if (!xmpStandard) {
515         return nullptr;
516     }
517 
518     std::unique_ptr<SkJpegXmp> xmp(new SkJpegXmp);
519     auto xmpStandardStream = SkMemoryStream::Make(xmpStandard);
520     if (!xmp->fStandardDOM.build(*xmpStandardStream)) {
521         SkCodecPrintf("Failed to parse XMP standard metadata.\n");
522         return nullptr;
523     }
524 
525     // See if there is a note indicating extended XMP. If we encounter any errors in retrieving
526     // the extended XMP, return just the standard XMP.
527     const char* namespaces[1] = {nullptr};
528     const char* uris[1] = {"http://ns.adobe.com/xmp/note/"};
529     const auto* extendedNode = find_uri_namespaces(xmp->fStandardDOM, 1, uris, namespaces);
530     if (!extendedNode) {
531         return xmp;
532     }
533     const auto xmpNotePrefix = get_namespace_prefix(namespaces[0]);
534 
535     // Extract the GUID (the MD5 hash) of the extended metadata.
536     const char* extendedGuid =
537             get_attr(&xmp->fStandardDOM, extendedNode, xmpNotePrefix, "HasExtendedXMP");
538     if (!extendedGuid) {
539         return xmp;
540     }
541 
542     // Extract and validate the extended metadata from the JPEG structure.
543     auto xmpExtended = read_xmp_extended(decoderApp1Params, extendedGuid);
544     if (!xmpExtended) {
545         SkCodecPrintf("Extended XMP was indicated but failed to read or validate.\n");
546         return xmp;
547     }
548 
549     // Parse the extended metadata.
550     auto xmpExtendedStream = SkMemoryStream::Make(xmpExtended);
551     if (xmp->fExtendedDOM.build(*xmpExtendedStream)) {
552         SkCodecPrintf("Failed to parse extended XMP metadata.\n");
553         return xmp;
554     }
555 
556     return xmp;
557 }
558 
findUriNamespaces(size_t count,const char * uris[],const char * outNamespaces[],const SkDOM ** outDom,const SkDOM::Node ** outNode) const559 bool SkJpegXmp::findUriNamespaces(size_t count,
560                                   const char* uris[],
561                                   const char* outNamespaces[],
562                                   const SkDOM** outDom,
563                                   const SkDOM::Node** outNode) const {
564     // See XMP Specification Part 3: Storage in files, Section 1.1.3.1: Extended XMP in JPEG:
565     // A JPEG reader must recompose the StandardXMP and ExtendedXMP into a single data model tree
566     // containing all of the XMP for the JPEG file, and remove the xmpNote:HasExtendedXMP property.
567     // This code does not do that. Instead, it maintains the two separate trees and searches them
568     // sequentially.
569     *outNode = find_uri_namespaces(fStandardDOM, count, uris, outNamespaces);
570     if (*outNode) {
571         *outDom = &fStandardDOM;
572         return true;
573     }
574     *outNode = find_uri_namespaces(fExtendedDOM, count, uris, outNamespaces);
575     if (*outNode) {
576         *outDom = &fExtendedDOM;
577         return true;
578     }
579     *outDom = nullptr;
580     return false;
581 }
582 
getContainerGainmapLocation(size_t * outOffset,size_t * outSize) const583 bool SkJpegXmp::getContainerGainmapLocation(size_t* outOffset, size_t* outSize) const {
584     // Find a node that matches the requested namespaces and URIs.
585     const char* namespaces[2] = {nullptr, nullptr};
586     const char* uris[2] = {"http://ns.google.com/photos/1.0/container/",
587                            "http://ns.google.com/photos/1.0/container/item/"};
588     const SkDOM* dom = nullptr;
589     const SkDOM::Node* node = nullptr;
590     if (!findUriNamespaces(2, uris, namespaces, &dom, &node)) {
591         return false;
592     }
593     const char* containerPrefix = get_namespace_prefix(namespaces[0]);
594     const char* itemPrefix = get_namespace_prefix(namespaces[1]);
595 
596     // The node must have a Container:Directory child.
597     const auto* directory = get_typed_child(dom, node, containerPrefix, "Directory");
598     if (!directory) {
599         SkCodecPrintf("Missing Container Directory");
600         return false;
601     }
602 
603     // That Container:Directory must have a sequence of  items.
604     const auto* seq = dom->getFirstChild(directory, "rdf:Seq");
605     if (!seq) {
606         SkCodecPrintf("Missing rdf:Seq");
607         return false;
608     }
609 
610     // Iterate through the items in the Container:Directory's sequence. Keep a running sum of the
611     // Item:Length of all items that appear before the GainMap.
612     bool isFirstItem = true;
613     size_t offset = 0;
614     for (const auto* li = dom->getFirstChild(seq, "rdf:li"); li;
615          li = dom->getNextSibling(li, "rdf:li")) {
616         // Each list item must contain a Container:Item.
617         const auto* item = get_typed_child(dom, li, containerPrefix, "Item");
618         if (!item) {
619             SkCodecPrintf("List item does not have container Item.\n");
620             return false;
621         }
622         // A Semantic is required for every item.
623         const char* itemSemantic = get_attr(dom, item, itemPrefix, "Semantic");
624         if (!itemSemantic) {
625             SkCodecPrintf("Item is missing Semantic.\n");
626             return false;
627         }
628         // A Mime is required for every item.
629         const char* itemMime = get_attr(dom, item, itemPrefix, "Mime");
630         if (!itemMime) {
631             SkCodecPrintf("Item is missing Mime.\n");
632             return false;
633         }
634 
635         if (isFirstItem) {
636             isFirstItem = false;
637             // The first item must be Primary.
638             if (strcmp(itemSemantic, "Primary") != 0) {
639                 SkCodecPrintf("First item is not Primary.\n");
640                 return false;
641             }
642             // The first item has mime type image/jpeg (we are decoding a jpeg).
643             if (strcmp(itemMime, "image/jpeg") != 0) {
644                 SkCodecPrintf("Primary does not report that it is image/jpeg.\n");
645                 return false;
646             }
647             // The first media item can contain a Padding attribute, which specifies additional
648             // padding between the end of the encoded primary image and the beginning of the next
649             // media item. Only the first media item can contain a Padding attribute.
650             int32_t padding = 0;
651             if (get_attr_int32(dom, item, itemPrefix, "Padding", &padding)) {
652                 if (padding < 0) {
653                     SkCodecPrintf("Item padding must be non-negative.");
654                     return false;
655                 }
656                 offset += padding;
657             }
658         } else {
659             // A Length is required for all non-Primary items.
660             int32_t length = 0;
661             if (!get_attr_int32(dom, item, itemPrefix, "Length", &length)) {
662                 SkCodecPrintf("Item length is absent.");
663                 return false;
664             }
665             if (length < 0) {
666                 SkCodecPrintf("Item length must be non-negative.");
667                 return false;
668             }
669             // If this is not the recovery map, then read past it.
670             if (strcmp(itemSemantic, "GainMap") != 0) {
671                 offset += length;
672                 continue;
673             }
674             // The recovery map must have mime type image/jpeg in this implementation.
675             if (strcmp(itemMime, "image/jpeg") != 0) {
676                 SkCodecPrintf("GainMap does not report that it is image/jpeg.\n");
677                 return false;
678             }
679 
680             // Populate the location in the file at which to find the gainmap image.
681             *outOffset = offset;
682             *outSize = length;
683             return true;
684         }
685     }
686     return false;
687 }
688 
689 // Return true if the specified XMP metadata identifies this image as an HDR gainmap.
getGainmapInfoHDRGainMap(SkGainmapInfo * info) const690 bool SkJpegXmp::getGainmapInfoHDRGainMap(SkGainmapInfo* info) const {
691     // Find a node that matches the requested namespaces and URIs.
692     const char* namespaces[2] = {nullptr, nullptr};
693     const char* uris[2] = {"http://ns.apple.com/pixeldatainfo/1.0/",
694                            "http://ns.apple.com/HDRGainMap/1.0/"};
695     const SkDOM* dom = nullptr;
696     const SkDOM::Node* node = nullptr;
697     if (!findUriNamespaces(2, uris, namespaces, &dom, &node)) {
698         return false;
699     }
700     const char* adpiPrefix = get_namespace_prefix(namespaces[0]);
701     const char* hdrGainMapPrefix = get_namespace_prefix(namespaces[1]);
702 
703     const char* auxiliaryImageType = get_attr(dom, node, adpiPrefix, "AuxiliaryImageType");
704     if (!auxiliaryImageType) {
705         SkCodecPrintf("Did not find AuxiliaryImageType.\n");
706         return false;
707     }
708     if (strcmp(auxiliaryImageType, "urn:com:apple:photo:2020:aux:hdrgainmap") != 0) {
709         SkCodecPrintf("AuxiliaryImageType was not HDR gain map.\n");
710         return false;
711     }
712 
713     int32_t version = 0;
714     if (!get_attr_int32(dom, node, hdrGainMapPrefix, "HDRGainMapVersion", &version)) {
715         SkCodecPrintf("Did not find HDRGainMapVersion.\n");
716         return false;
717     }
718     if (version != 65536) {
719         SkCodecPrintf("HDRGainMapVersion was not 65536.\n");
720         return false;
721     }
722 
723     // This node will often have StoredFormat and NativeFormat children that have inner text that
724     // specifies the integer 'L008' (also known as kCVPixelFormatType_OneComponent8).
725     const float kRatioMax = sk_float_exp(1.f);
726     info->fGainmapRatioMin = {1.f, 1.f, 1.f, 1.f};
727     info->fGainmapRatioMax = {kRatioMax, kRatioMax, kRatioMax, 1.f};
728     info->fGainmapGamma = {1.f, 1.f, 1.f, 1.f};
729     info->fEpsilonSdr = {0.f, 0.f, 0.f, 1.f};
730     info->fEpsilonHdr = {0.f, 0.f, 0.f, 1.f};
731     info->fDisplayRatioSdr = 1.f;
732     info->fDisplayRatioHdr = kRatioMax;
733     info->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
734     info->fType = SkGainmapInfo::Type::kMultiPicture;
735     return true;
736 }
737 
getGainmapInfoHDRGM(SkGainmapInfo * outGainmapInfo) const738 bool SkJpegXmp::getGainmapInfoHDRGM(SkGainmapInfo* outGainmapInfo) const {
739     // Find a node that matches the requested namespace and URI.
740     const char* namespaces[1] = {nullptr};
741     const char* uris[1] = {"http://ns.adobe.com/hdr-gain-map/1.0/"};
742     const SkDOM* dom = nullptr;
743     const SkDOM::Node* node = nullptr;
744     if (!findUriNamespaces(1, uris, namespaces, &dom, &node)) {
745         return false;
746     }
747     const char* hdrgmPrefix = get_namespace_prefix(namespaces[0]);
748 
749     // Require that hdrgm:Version="1.0" be present.
750     const char* version = get_attr(dom, node, hdrgmPrefix, "Version");
751     if (!version) {
752         SkCodecPrintf("Version attribute is absent.\n");
753         return false;
754     }
755     if (strcmp(version, "1.0") != 0) {
756         SkCodecPrintf("Version is \"%s\", not \"1.0\".\n", version);
757         return false;
758     }
759 
760     // If |outGainmapInfo| was not specified, then this function only verifies that the
761     // Version field be 1.0. It does not verify that GainMapMax or HDRCapacityMax be present.
762     if (!outGainmapInfo) {
763         return true;
764     }
765 
766     // Initialize the parameters to their defaults.
767     bool baseRenditionIsHDR = false;
768     SkColor4f gainMapMin = {1.f, 1.f, 1.f, 1.f};
769     SkColor4f gainMapMax = {2.f, 2.f, 2.f, 1.f};
770     SkColor4f gamma = {1.f, 1.f, 1.f, 1.f};
771     SkColor4f offsetSdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
772     SkColor4f offsetHdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
773     SkScalar hdrCapacityMin = 1.f;
774     SkScalar hdrCapacityMax = 2.f;
775 
776     // Read all parameters that are present.
777     get_attr_bool(dom, node, hdrgmPrefix, "BaseRenditionIsHDR", &baseRenditionIsHDR);
778     get_attr_float3(dom, node, hdrgmPrefix, "GainMapMin", &gainMapMin);
779     if (!get_attr_float3(dom, node, hdrgmPrefix, "GainMapMax", &gainMapMax)) {
780         SkCodecPrintf("GainMapMax attribute is absent.\n");
781         return false;
782     }
783     get_attr_float3(dom, node, hdrgmPrefix, "Gamma", &gamma);
784     get_attr_float3(dom, node, hdrgmPrefix, "OffsetSDR", &offsetSdr);
785     get_attr_float3(dom, node, hdrgmPrefix, "OffsetHDR", &offsetHdr);
786     get_attr_float(dom, node, hdrgmPrefix, "HDRCapacityMin", &hdrCapacityMin);
787     if (!get_attr_float(dom, node, hdrgmPrefix, "HDRCapacityMax", &hdrCapacityMax)) {
788         SkCodecPrintf("HDRCapacityMax attribute is absent.\n");
789         return false;
790     }
791 
792     // Translate all parameters to SkGainmapInfo's expected format.
793     const float kLog2 = sk_float_log(2.f);
794     outGainmapInfo->fGainmapRatioMin = {sk_float_exp(gainMapMin.fR * kLog2),
795                                         sk_float_exp(gainMapMin.fG * kLog2),
796                                         sk_float_exp(gainMapMin.fB * kLog2),
797                                         1.f};
798     outGainmapInfo->fGainmapRatioMax = {sk_float_exp(gainMapMax.fR * kLog2),
799                                         sk_float_exp(gainMapMax.fG * kLog2),
800                                         sk_float_exp(gainMapMax.fB * kLog2),
801                                         1.f};
802     outGainmapInfo->fGainmapGamma = {1.f / gamma.fR, 1.f / gamma.fG, 1.f / gamma.fB, 1.f};
803     outGainmapInfo->fEpsilonSdr = offsetSdr;
804     outGainmapInfo->fEpsilonHdr = offsetHdr;
805     outGainmapInfo->fDisplayRatioSdr = sk_float_exp(hdrCapacityMin * kLog2);
806     outGainmapInfo->fDisplayRatioHdr = sk_float_exp(hdrCapacityMax * kLog2);
807     if (baseRenditionIsHDR) {
808         outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kHDR;
809     } else {
810         outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
811     }
812     outGainmapInfo->fType = SkGainmapInfo::Type::kHDRGM;
813     return true;
814 }
815