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