Refactor SkJpegXmp into SkXmp which is more generic.
Useful for formats other than jpeg that would use the same
gainmap xmp format for example.
Change-Id: Id1e69032a4566e96fdc4d0038ee6a6b39ea7ed95
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/699656
Reviewed-by: Christopher Cameron <ccameron@google.com>
Commit-Queue: Christopher Cameron <ccameron@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/gn/codec.gni b/gn/codec.gni
index 4afeaef..68a195f 100644
--- a/gn/codec.gni
+++ b/gn/codec.gni
@@ -28,6 +28,7 @@
"$_src/codec/SkSampler.h",
"$_src/codec/SkSwizzler.cpp",
"$_src/codec/SkSwizzler.h",
+ "$_src/codec/SkXmp.cpp",
]
# List generated by Bazel rules:
diff --git a/gn/core.gni b/gn/core.gni
index 10fb13b..a59729c 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -159,6 +159,7 @@
"$_include/private/SkPathRef.h",
"$_include/private/SkShadowFlags.h",
"$_include/private/SkWeakRefCnt.h",
+ "$_include/private/SkXmp.h",
"$_include/private/base/SingleOwner.h",
"$_include/private/base/SkAPI.h",
"$_include/private/base/SkAlign.h",
diff --git a/gn/tests.gni b/gn/tests.gni
index 7059fd3..8398ac7 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -276,6 +276,7 @@
"$_tests/SkUTFTest.cpp",
"$_tests/SkVMTest.cpp",
"$_tests/SkVxTest.cpp",
+ "$_tests/SkXmpTest.cpp",
"$_tests/Skbug12214.cpp",
"$_tests/Skbug5221.cpp",
"$_tests/Skbug6389.cpp",
@@ -472,4 +473,7 @@
tests_sources += ganesh_tests_sources
-jpeg_gainmap_tests_sources = [ "$_tests/JpegGainmapTest.cpp" ]
+jpeg_gainmap_tests_sources = [
+ "$_tests/JpegGainmapTest.cpp",
+ "$_tests/SkJpegXmpTest.cpp",
+]
diff --git a/include/private/BUILD.bazel b/include/private/BUILD.bazel
index b60a407..6bff980 100644
--- a/include/private/BUILD.bazel
+++ b/include/private/BUILD.bazel
@@ -29,6 +29,7 @@
"SkPathRef.h",
"SkShadowFlags.h",
"SkWeakRefCnt.h",
+ "SkXmp.h",
":sksl_private_hdrs",
"//include/private/base:private_hdrs",
"//include/private/chromium:private_hdrs",
diff --git a/include/private/SkXmp.h b/include/private/SkXmp.h
new file mode 100644
index 0000000..e45e5c7
--- /dev/null
+++ b/include/private/SkXmp.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkXmp_DEFINED
+#define SkXmp_DEFINED
+
+#include "include/core/SkRefCnt.h"
+#include "include/private/base/SkAPI.h"
+
+class SkData;
+struct SkGainmapInfo;
+
+#include <cstddef>
+#include <memory>
+
+/*
+ * An interface to extract information from XMP metadata.
+ */
+class SK_API SkXmp {
+public:
+ virtual ~SkXmp() = default;
+
+ // Create from XMP data.
+ static std::unique_ptr<SkXmp> Make(sk_sp<SkData> xmpData);
+ // Create from standard XMP + extended XMP data, see XMP Specification Part 3: Storage in files,
+ // Section 1.1.3.1: Extended XMP in JPEG
+ static std::unique_ptr<SkXmp> Make(sk_sp<SkData> xmpStandard, sk_sp<SkData> xmpExtended);
+
+ // Extract HDRGM gainmap parameters.
+ virtual bool getGainmapInfoHDRGM(SkGainmapInfo* info) const = 0;
+
+ // Extract HDRGainMap gainmap parameters.
+ virtual bool getGainmapInfoHDRGainMap(SkGainmapInfo* info) const = 0;
+
+ // If this includes GContainer metadata and the GContainer contains an item with semantic
+ // GainMap and Mime of image/jpeg, then return true, and populate |offset| and |size| with
+ // that item's offset (from the end of the primary JPEG image's EndOfImage), and the size of
+ // the gainmap.
+ virtual bool getContainerGainmapLocation(size_t* offset, size_t* size) const = 0;
+
+ // Return the GUID of an Extended XMP if present, or null otherwise.
+ virtual const char* getExtendedXmpGuid() const = 0;
+};
+
+#endif
diff --git a/public.bzl b/public.bzl
index 11bffe4..36c771f 100644
--- a/public.bzl
+++ b/public.bzl
@@ -244,6 +244,7 @@
"include/private/SkSLSampleUsage.h",
"include/private/SkShadowFlags.h",
"include/private/SkWeakRefCnt.h",
+ "include/private/SkXmp.h",
"include/private/base/SingleOwner.h",
"include/private/base/SkAPI.h",
"include/private/base/SkAlign.h",
diff --git a/src/BUILD.bazel b/src/BUILD.bazel
index c196107..d227e88 100644
--- a/src/BUILD.bazel
+++ b/src/BUILD.bazel
@@ -36,6 +36,7 @@
"//src/shaders:srcs",
"//src/text:srcs",
"//src/utils:srcs",
+ "//src/xml:srcs",
] + select({
"//src/gpu:has_gpu_backend": [
"//src/gpu:srcs",
@@ -48,7 +49,6 @@
}) + select({
"//src/svg:enable_svg_canvas_true": [
"//src/svg:srcs",
- "//src/xml:srcs",
],
"//conditions:default": [],
}) + select({
@@ -95,6 +95,7 @@
"//src/sksl/tracing:skopts_hdrs",
"//src/text:private_hdrs",
"//src/utils:private_hdrs",
+ "//src/xml:private_hdrs",
] + select({
"//src/gpu:has_gpu_backend": [
"//src/gpu:private_hdrs",
@@ -107,7 +108,6 @@
}) + select({
"//src/svg:enable_svg_canvas_true": [
"//src/svg:private_hdrs",
- "//src/xml:private_hdrs",
],
"//conditions:default": [],
}) + select({
@@ -132,6 +132,7 @@
"//src/encode:deps",
"//src/opts:deps",
"//src/ports:deps",
+ "//src/xml:deps",
] + select({
"//src/gpu:has_gpu_backend": ["//src/gpu:deps"],
"//conditions:default": [],
@@ -139,7 +140,6 @@
"//src/sksl:needs_sksl": ["//src/sksl:deps"],
"//conditions:default": [],
}) + select({
- "//src/svg:enable_svg_canvas_true": ["//src/xml:deps"],
"//conditions:default": [],
}) + select({
"//src/pdf:enable_pdf_backend_true": ["//src/pdf:deps"],
diff --git a/src/codec/BUILD.bazel b/src/codec/BUILD.bazel
index 8e64881..60b6c8b 100644
--- a/src/codec/BUILD.bazel
+++ b/src/codec/BUILD.bazel
@@ -30,6 +30,7 @@
"SkSampler.h",
"SkSwizzler.cpp",
"SkSwizzler.h",
+ "SkXmp.cpp",
]
split_srcs_and_hdrs(
@@ -204,9 +205,9 @@
skia_filegroup(
name = "srcs",
srcs = [
+ ":core_srcs",
":decode_android_srcs",
":decode_bmp_srcs",
- ":core_srcs",
] + select_multi(
{
":avif_decode_codec": [":decode_avif_srcs"],
@@ -224,9 +225,9 @@
skia_filegroup(
name = "private_hdrs",
srcs = [
+ ":core_hdrs",
":decode_android_hdrs",
":decode_bmp_hdrs",
- ":core_hdrs",
] + select({
":needs_jpeg_priv": ["SkJpegPriv.h"], # used by src/encode/SkJPEGWriteUtility
"//conditions:default": [],
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index a88e73b..830ab1c 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -31,6 +31,7 @@
#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
#include "include/private/SkGainmapInfo.h"
+#include "include/private/SkXmp.h"
#include "src/codec/SkJpegMultiPicture.h"
#include "src/codec/SkJpegSegmentScan.h"
#include "src/codec/SkJpegXmp.h"
@@ -1079,14 +1080,14 @@
#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
// Collect and parse the primary and extended XMP metadata.
-static std::unique_ptr<SkJpegXmp> get_xmp_metadata(const SkJpegMarkerList& markerList) {
+static std::unique_ptr<SkXmp> get_xmp_metadata(const SkJpegMarkerList& markerList) {
std::vector<sk_sp<SkData>> decoderApp1Params;
for (const auto& marker : markerList) {
if (marker.fMarker == kXMPMarker) {
decoderApp1Params.push_back(marker.fData);
}
}
- return SkJpegXmp::Make(decoderApp1Params);
+ return SkJpegMakeXmp(decoderApp1Params);
}
// Extract the SkJpegMultiPictureParameters from this image (if they exist). If |sourceMgr| and
@@ -1174,7 +1175,7 @@
}
app1Params.push_back(std::move(parameters));
}
- auto xmp = SkJpegXmp::Make(app1Params);
+ auto xmp = SkJpegMakeXmp(app1Params);
if (!xmp) {
return false;
}
@@ -1213,7 +1214,7 @@
SkGainmapInfo* info,
std::unique_ptr<SkStream>* gainmapImageStream) {
// The GContainer and APP15-based HDRGM formats require XMP metadata. Extract it now.
- std::unique_ptr<SkJpegXmp> xmp = get_xmp_metadata(markerList);
+ std::unique_ptr<SkXmp> xmp = get_xmp_metadata(markerList);
// Let |base_image_info| be the HDRGM gainmap information found in the base image (if any).
SkGainmapInfo base_image_info;
diff --git a/src/codec/SkJpegXmp.cpp b/src/codec/SkJpegXmp.cpp
index 4b9b08d..118cd62 100644
--- a/src/codec/SkJpegXmp.cpp
+++ b/src/codec/SkJpegXmp.cpp
@@ -16,15 +16,10 @@
#include <string>
-SkJpegXmp::SkJpegXmp() = default;
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// XMP JPEG extraction helper functions
-
constexpr size_t kGuidAsciiSize = 32;
/*
- * Extract standard XMP metadata.
+ * Extract standard XMP metadata. The decoderApp1Params must outlive the returned SkData.
*
* See XMP Specification Part 3: Storage in files, Section 1.1.3: JPEG.
*/
@@ -65,11 +60,11 @@
constexpr size_t kHeaderSize = kSigSize + kGuidAsciiSize + kFullLengthSize + kOffsetSize;
// Validate the provided ASCII guid.
- SkMD5::Digest guidAsDigest;
if (strlen(guidAscii) != kGuidAsciiSize) {
SkCodecPrintf("Invalid ASCII GUID size.\n");
return nullptr;
}
+ SkMD5::Digest guidAsDigest;
for (size_t i = 0; i < kGuidAsciiSize; ++i) {
uint8_t digit = 0;
if (guidAscii[i] >= '0' && guidAscii[i] <= '9') {
@@ -182,359 +177,19 @@
return xmpExtendedData;
}
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// XMP parsing helper functions
-
-const char* kXmlnsPrefix = "xmlns:";
-const size_t kXmlnsPrefixLength = 6;
-
-static const char* get_namespace_prefix(const char* name) {
- if (strlen(name) <= kXmlnsPrefixLength) {
- return nullptr;
- }
- return name + kXmlnsPrefixLength;
-}
-
-/*
- * Given a node, see if that node has only one child with the indicated name. If so, see if that
- * child has only a single child of its own, and that child is text. If all of that is the case
- * then return the text, otherwise return nullptr.
- *
- * In the following example, innerText will be returned.
- * <node><childName>innerText</childName></node>
- *
- * In the following examples, nullptr will be returned (because there are multiple children with
- * childName in the first case, and because the child has children of its own in the second).
- * <node><childName>innerTextA</childName><childName>innerTextB</childName></node>
- * <node><childName>innerText<otherGrandChild/></childName></node>
- */
-static const char* get_unique_child_text(const SkDOM& dom,
- const SkDOM::Node* node,
- const std::string& childName) {
- // Fail if there are multiple children with childName.
- if (dom.countChildren(node, childName.c_str()) != 1) {
- return nullptr;
- }
- const auto* child = dom.getFirstChild(node, childName.c_str());
- if (!child) {
- return nullptr;
- }
- // Fail if the child has any children besides text.
- if (dom.countChildren(child) != 1) {
- return nullptr;
- }
- const auto* grandChild = dom.getFirstChild(child);
- if (dom.getType(grandChild) != SkDOM::kText_Type) {
- return nullptr;
- }
- // Return the text.
- return dom.getName(grandChild);
-}
-
-/*
- * Given a node, find a child node of the specified type.
- *
- * If there exists a child node with name |prefix| + ":" + |type|, then return that child.
- *
- * If there exists a child node with name "rdf:type" that has attribute "rdf:resource" with value
- * of |type|, then if there also exists a child node with name "rdf:value" with attribute
- * "rdf:parseType" of "Resource", then return that child node with name "rdf:value". See Example
- * 3 in section 7.9.2.5: RDF Typed Nodes.
- * TODO(ccameron): This should also accept a URI for the type.
- */
-static const SkDOM::Node* get_typed_child(const SkDOM* dom,
- const SkDOM::Node* node,
- const std::string& prefix,
- const std::string& type) {
- const auto name = prefix + std::string(":") + type;
- const SkDOM::Node* child = dom->getFirstChild(node, name.c_str());
- if (child) {
- return child;
- }
-
- const SkDOM::Node* typeChild = dom->getFirstChild(node, "rdf:type");
- if (!typeChild) {
- return nullptr;
- }
- const char* typeChildResource = dom->findAttr(typeChild, "rdf:resource");
- if (!typeChildResource || typeChildResource != type) {
- return nullptr;
- }
-
- const SkDOM::Node* valueChild = dom->getFirstChild(node, "rdf:value");
- if (!valueChild) {
- return nullptr;
- }
- const char* valueChildParseType = dom->findAttr(valueChild, "rdf:parseType");
- if (!valueChildParseType || strcmp(valueChildParseType, "Resource") != 0) {
- return nullptr;
- }
- return valueChild;
-}
-
-/*
- * Given a node, return its value for the specified attribute.
- *
- * This will first look for an attribute with the name |prefix| + ":" + |key|, and return the value
- * for that attribute.
- *
- * This will then look for a child node of name |prefix| + ":" + |key|, and return the field value
- * for that child.
- */
-static const char* get_attr(const SkDOM* dom,
- const SkDOM::Node* node,
- const std::string& prefix,
- const std::string& key) {
- const auto name = prefix + ":" + key;
- const char* attr = dom->findAttr(node, name.c_str());
- if (attr) {
- return attr;
- }
- return get_unique_child_text(*dom, node, name);
-}
-
-// Perform get_attr and parse the result as a bool.
-static bool get_attr_bool(const SkDOM* dom,
- const SkDOM::Node* node,
- const std::string& prefix,
- const std::string& key,
- bool* outValue) {
- const char* attr = get_attr(dom, node, prefix, key);
- if (!attr) {
- return false;
- }
- switch (SkParse::FindList(attr, "False,True")) {
- case 0:
- *outValue = false;
- return true;
- case 1:
- *outValue = true;
- return true;
- default:
- break;
- }
- return false;
-}
-
-// Perform get_attr and parse the result as an int32_t.
-static bool get_attr_int32(const SkDOM* dom,
- const SkDOM::Node* node,
- const std::string& prefix,
- const std::string& key,
- int32_t* value) {
- const char* attr = get_attr(dom, node, prefix, key);
- if (!attr) {
- return false;
- }
- if (!SkParse::FindS32(attr, value)) {
- return false;
- }
- return true;
-}
-
-// Perform get_attr and parse the result as a float.
-static bool get_attr_float(const SkDOM* dom,
- const SkDOM::Node* node,
- const std::string& prefix,
- const std::string& key,
- float* outValue) {
- const char* attr = get_attr(dom, node, prefix, key);
- if (!attr) {
- return false;
- }
- SkScalar value = 0.f;
- if (SkParse::FindScalar(attr, &value)) {
- *outValue = value;
- return true;
- }
- return false;
-}
-
-// Perform get_attr and parse the result as three comma-separated floats. Return the result as an
-// SkColor4f with the alpha component set to 1.
-static bool get_attr_float3_as_list(const SkDOM* dom,
- const SkDOM::Node* node,
- const std::string& prefix,
- const std::string& key,
- SkColor4f* outValue) {
- const auto name = prefix + ":" + key;
-
- // Fail if there are multiple children with childName.
- if (dom->countChildren(node, name.c_str()) != 1) {
- return false;
- }
- // Find the child.
- const auto* child = dom->getFirstChild(node, name.c_str());
- if (!child) {
- return false;
- }
-
- // Search for the rdf:Seq child.
- const auto* seq = dom->getFirstChild(child, "rdf:Seq");
- if (!seq) {
- return false;
- }
-
- size_t count = 0;
- SkScalar values[3] = {0.f, 0.f, 0.f};
- for (const auto* liNode = dom->getFirstChild(seq, "rdf:li"); liNode;
- liNode = dom->getNextSibling(liNode, "rdf:li")) {
- if (count > 2) {
- SkCodecPrintf("Too many items in list.\n");
- return false;
- }
- if (dom->countChildren(liNode) != 1) {
- SkCodecPrintf("Item can only have one child.\n");
- return false;
- }
- const auto* liTextNode = dom->getFirstChild(liNode);
- if (dom->getType(liTextNode) != SkDOM::kText_Type) {
- SkCodecPrintf("Item's only child must be text.\n");
- return false;
- }
- const char* liText = dom->getName(liTextNode);
- if (!liText) {
- SkCodecPrintf("Failed to get item's text.\n");
- return false;
- }
- if (!SkParse::FindScalar(liText, values + count)) {
- SkCodecPrintf("Failed to parse item's text to float.\n");
- return false;
- }
- count += 1;
- }
- if (count < 3) {
- SkCodecPrintf("List didn't have enough items.\n");
- return false;
- }
- *outValue = {values[0], values[1], values[2], 1.f};
- return true;
-}
-
-static bool get_attr_float3(const SkDOM* dom,
- const SkDOM::Node* node,
- const std::string& prefix,
- const std::string& key,
- SkColor4f* outValue) {
- if (get_attr_float3_as_list(dom, node, prefix, key, outValue)) {
- return true;
- }
- SkScalar value = -1.0;
- if (get_attr_float(dom, node, prefix, key, &value)) {
- *outValue = {value, value, value, 1.f};
- return true;
- }
- return false;
-}
-
-static void find_uri_namespaces(const SkDOM& dom,
- const SkDOM::Node* node,
- size_t count,
- const char* uris[],
- const char* outNamespaces[]) {
- // Search all attributes for xmlns:NAMESPACEi="URIi".
- for (const auto* attr = dom.getFirstAttr(node); attr; attr = dom.getNextAttr(node, attr)) {
- const char* attrName = dom.getAttrName(node, attr);
- const char* attrValue = dom.getAttrValue(node, attr);
- if (!attrName || !attrValue) {
- continue;
- }
- // Make sure the name starts with "xmlns:".
- if (strlen(attrName) <= kXmlnsPrefixLength) {
- continue;
- }
- if (memcmp(attrName, kXmlnsPrefix, kXmlnsPrefixLength) != 0) {
- continue;
- }
- // Search for a requested URI that matches.
- for (size_t i = 0; i < count; ++i) {
- if (strcmp(attrValue, uris[i]) != 0) {
- continue;
- }
- outNamespaces[i] = attrName;
- }
- }
-}
-
-// See SkJpegXmp::findUriNamespaces. This function has the same behavior, but only searches
-// a single SkDOM.
-static const SkDOM::Node* find_uri_namespaces(const SkDOM& dom,
- size_t count,
- const char* uris[],
- const char* outNamespaces[]) {
- const SkDOM::Node* root = dom.getRootNode();
- if (!root) {
- return nullptr;
- }
-
- // Ensure that the root node identifies itself as XMP metadata.
- const char* rootName = dom.getName(root);
- if (!rootName || strcmp(rootName, "x:xmpmeta") != 0) {
- return nullptr;
- }
-
- // Iterate the children with name rdf:RDF.
- const char* kRdf = "rdf:RDF";
- for (const auto* rdf = dom.getFirstChild(root, kRdf); rdf;
- rdf = dom.getNextSibling(rdf, kRdf)) {
- std::vector<const char*> rdfNamespaces(count, nullptr);
- find_uri_namespaces(dom, rdf, count, uris, rdfNamespaces.data());
-
- // Iterate the children with name rdf::Description.
- const char* kDesc = "rdf:Description";
- for (const auto* desc = dom.getFirstChild(rdf, kDesc); desc;
- desc = dom.getNextSibling(desc, kDesc)) {
- std::vector<const char*> descNamespaces = rdfNamespaces;
- find_uri_namespaces(dom, desc, count, uris, descNamespaces.data());
-
- // If we have a match for all the requested URIs, return.
- bool foundAllUris = true;
- for (size_t i = 0; i < count; ++i) {
- if (!descNamespaces[i]) {
- foundAllUris = false;
- break;
- }
- }
- if (foundAllUris) {
- for (size_t i = 0; i < count; ++i) {
- outNamespaces[i] = descNamespaces[i];
- }
- return desc;
- }
- }
- }
- return nullptr;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// SkJpegXmp
-
-std::unique_ptr<SkJpegXmp> SkJpegXmp::Make(const std::vector<sk_sp<SkData>>& decoderApp1Params) {
+std::unique_ptr<SkXmp> SkJpegMakeXmp(const std::vector<sk_sp<SkData>>& decoderApp1Params) {
auto xmpStandard = read_xmp_standard(decoderApp1Params);
if (!xmpStandard) {
return nullptr;
}
- std::unique_ptr<SkJpegXmp> xmp(new SkJpegXmp);
- auto xmpStandardStream = SkMemoryStream::Make(xmpStandard);
- if (!xmp->fStandardDOM.build(*xmpStandardStream)) {
- SkCodecPrintf("Failed to parse XMP standard metadata.\n");
+ std::unique_ptr<SkXmp> xmp = SkXmp::Make(xmpStandard);
+ if (!xmp) {
return nullptr;
}
- // See if there is a note indicating extended XMP. If we encounter any errors in retrieving
- // the extended XMP, return just the standard XMP.
- const char* namespaces[1] = {nullptr};
- const char* uris[1] = {"http://ns.adobe.com/xmp/note/"};
- const auto* extendedNode = find_uri_namespaces(xmp->fStandardDOM, 1, uris, namespaces);
- if (!extendedNode) {
- return xmp;
- }
- const auto xmpNotePrefix = get_namespace_prefix(namespaces[0]);
-
// Extract the GUID (the MD5 hash) of the extended metadata.
- const char* extendedGuid =
- get_attr(&xmp->fStandardDOM, extendedNode, xmpNotePrefix, "HasExtendedXMP");
+ const char* extendedGuid = xmp->getExtendedXmpGuid();
if (!extendedGuid) {
return xmp;
}
@@ -546,257 +201,5 @@
return xmp;
}
- // Parse the extended metadata.
- auto xmpExtendedStream = SkMemoryStream::Make(xmpExtended);
- if (xmp->fExtendedDOM.build(*xmpExtendedStream)) {
- SkCodecPrintf("Failed to parse extended XMP metadata.\n");
- return xmp;
- }
-
- return xmp;
-}
-
-bool SkJpegXmp::findUriNamespaces(size_t count,
- const char* uris[],
- const char* outNamespaces[],
- const SkDOM** outDom,
- const SkDOM::Node** outNode) const {
- // See XMP Specification Part 3: Storage in files, Section 1.1.3.1: Extended XMP in JPEG:
- // A JPEG reader must recompose the StandardXMP and ExtendedXMP into a single data model tree
- // containing all of the XMP for the JPEG file, and remove the xmpNote:HasExtendedXMP property.
- // This code does not do that. Instead, it maintains the two separate trees and searches them
- // sequentially.
- *outNode = find_uri_namespaces(fStandardDOM, count, uris, outNamespaces);
- if (*outNode) {
- *outDom = &fStandardDOM;
- return true;
- }
- *outNode = find_uri_namespaces(fExtendedDOM, count, uris, outNamespaces);
- if (*outNode) {
- *outDom = &fExtendedDOM;
- return true;
- }
- *outDom = nullptr;
- return false;
-}
-
-bool SkJpegXmp::getContainerGainmapLocation(size_t* outOffset, size_t* outSize) const {
- // Find a node that matches the requested namespaces and URIs.
- const char* namespaces[2] = {nullptr, nullptr};
- const char* uris[2] = {"http://ns.google.com/photos/1.0/container/",
- "http://ns.google.com/photos/1.0/container/item/"};
- const SkDOM* dom = nullptr;
- const SkDOM::Node* node = nullptr;
- if (!findUriNamespaces(2, uris, namespaces, &dom, &node)) {
- return false;
- }
- const char* containerPrefix = get_namespace_prefix(namespaces[0]);
- const char* itemPrefix = get_namespace_prefix(namespaces[1]);
-
- // The node must have a Container:Directory child.
- const auto* directory = get_typed_child(dom, node, containerPrefix, "Directory");
- if (!directory) {
- SkCodecPrintf("Missing Container Directory");
- return false;
- }
-
- // That Container:Directory must have a sequence of items.
- const auto* seq = dom->getFirstChild(directory, "rdf:Seq");
- if (!seq) {
- SkCodecPrintf("Missing rdf:Seq");
- return false;
- }
-
- // Iterate through the items in the Container:Directory's sequence. Keep a running sum of the
- // Item:Length of all items that appear before the GainMap.
- bool isFirstItem = true;
- size_t offset = 0;
- for (const auto* li = dom->getFirstChild(seq, "rdf:li"); li;
- li = dom->getNextSibling(li, "rdf:li")) {
- // Each list item must contain a Container:Item.
- const auto* item = get_typed_child(dom, li, containerPrefix, "Item");
- if (!item) {
- SkCodecPrintf("List item does not have container Item.\n");
- return false;
- }
- // A Semantic is required for every item.
- const char* itemSemantic = get_attr(dom, item, itemPrefix, "Semantic");
- if (!itemSemantic) {
- SkCodecPrintf("Item is missing Semantic.\n");
- return false;
- }
- // A Mime is required for every item.
- const char* itemMime = get_attr(dom, item, itemPrefix, "Mime");
- if (!itemMime) {
- SkCodecPrintf("Item is missing Mime.\n");
- return false;
- }
-
- if (isFirstItem) {
- isFirstItem = false;
- // The first item must be Primary.
- if (strcmp(itemSemantic, "Primary") != 0) {
- SkCodecPrintf("First item is not Primary.\n");
- return false;
- }
- // The first item has mime type image/jpeg (we are decoding a jpeg).
- if (strcmp(itemMime, "image/jpeg") != 0) {
- SkCodecPrintf("Primary does not report that it is image/jpeg.\n");
- return false;
- }
- // The first media item can contain a Padding attribute, which specifies additional
- // padding between the end of the encoded primary image and the beginning of the next
- // media item. Only the first media item can contain a Padding attribute.
- int32_t padding = 0;
- if (get_attr_int32(dom, item, itemPrefix, "Padding", &padding)) {
- if (padding < 0) {
- SkCodecPrintf("Item padding must be non-negative.");
- return false;
- }
- offset += padding;
- }
- } else {
- // A Length is required for all non-Primary items.
- int32_t length = 0;
- if (!get_attr_int32(dom, item, itemPrefix, "Length", &length)) {
- SkCodecPrintf("Item length is absent.");
- return false;
- }
- if (length < 0) {
- SkCodecPrintf("Item length must be non-negative.");
- return false;
- }
- // If this is not the recovery map, then read past it.
- if (strcmp(itemSemantic, "GainMap") != 0) {
- offset += length;
- continue;
- }
- // The recovery map must have mime type image/jpeg in this implementation.
- if (strcmp(itemMime, "image/jpeg") != 0) {
- SkCodecPrintf("GainMap does not report that it is image/jpeg.\n");
- return false;
- }
-
- // Populate the location in the file at which to find the gainmap image.
- *outOffset = offset;
- *outSize = length;
- return true;
- }
- }
- return false;
-}
-
-// Return true if the specified XMP metadata identifies this image as an HDR gainmap.
-bool SkJpegXmp::getGainmapInfoHDRGainMap(SkGainmapInfo* info) const {
- // Find a node that matches the requested namespaces and URIs.
- const char* namespaces[2] = {nullptr, nullptr};
- const char* uris[2] = {"http://ns.apple.com/pixeldatainfo/1.0/",
- "http://ns.apple.com/HDRGainMap/1.0/"};
- const SkDOM* dom = nullptr;
- const SkDOM::Node* node = nullptr;
- if (!findUriNamespaces(2, uris, namespaces, &dom, &node)) {
- return false;
- }
- const char* adpiPrefix = get_namespace_prefix(namespaces[0]);
- const char* hdrGainMapPrefix = get_namespace_prefix(namespaces[1]);
-
- const char* auxiliaryImageType = get_attr(dom, node, adpiPrefix, "AuxiliaryImageType");
- if (!auxiliaryImageType) {
- SkCodecPrintf("Did not find AuxiliaryImageType.\n");
- return false;
- }
- if (strcmp(auxiliaryImageType, "urn:com:apple:photo:2020:aux:hdrgainmap") != 0) {
- SkCodecPrintf("AuxiliaryImageType was not HDR gain map.\n");
- return false;
- }
-
- int32_t version = 0;
- if (!get_attr_int32(dom, node, hdrGainMapPrefix, "HDRGainMapVersion", &version)) {
- SkCodecPrintf("Did not find HDRGainMapVersion.\n");
- return false;
- }
- if (version != 65536) {
- SkCodecPrintf("HDRGainMapVersion was not 65536.\n");
- return false;
- }
-
- // This node will often have StoredFormat and NativeFormat children that have inner text that
- // specifies the integer 'L008' (also known as kCVPixelFormatType_OneComponent8).
- const float kRatioMax = sk_float_exp(1.f);
- info->fGainmapRatioMin = {1.f, 1.f, 1.f, 1.f};
- info->fGainmapRatioMax = {kRatioMax, kRatioMax, kRatioMax, 1.f};
- info->fGainmapGamma = {1.f, 1.f, 1.f, 1.f};
- info->fEpsilonSdr = {0.f, 0.f, 0.f, 1.f};
- info->fEpsilonHdr = {0.f, 0.f, 0.f, 1.f};
- info->fDisplayRatioSdr = 1.f;
- info->fDisplayRatioHdr = kRatioMax;
- info->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
- info->fType = SkGainmapInfo::Type::kMultiPicture;
- return true;
-}
-
-bool SkJpegXmp::getGainmapInfoHDRGM(SkGainmapInfo* outGainmapInfo) const {
- // Find a node that matches the requested namespace and URI.
- const char* namespaces[1] = {nullptr};
- const char* uris[1] = {"http://ns.adobe.com/hdr-gain-map/1.0/"};
- const SkDOM* dom = nullptr;
- const SkDOM::Node* node = nullptr;
- if (!findUriNamespaces(1, uris, namespaces, &dom, &node)) {
- return false;
- }
- const char* hdrgmPrefix = get_namespace_prefix(namespaces[0]);
-
- // Require that hdrgm:Version="1.0" be present.
- const char* version = get_attr(dom, node, hdrgmPrefix, "Version");
- if (!version) {
- SkCodecPrintf("Version attribute is absent.\n");
- return false;
- }
- if (strcmp(version, "1.0") != 0) {
- SkCodecPrintf("Version is \"%s\", not \"1.0\".\n", version);
- return false;
- }
-
- // Initialize the parameters to their defaults.
- bool baseRenditionIsHDR = false;
- SkColor4f gainMapMin = {1.f, 1.f, 1.f, 1.f};
- SkColor4f gainMapMax = {2.f, 2.f, 2.f, 1.f};
- SkColor4f gamma = {1.f, 1.f, 1.f, 1.f};
- SkColor4f offsetSdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
- SkColor4f offsetHdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
- SkScalar hdrCapacityMin = 1.f;
- SkScalar hdrCapacityMax = 2.f;
-
- // Read all parameters that are present.
- get_attr_bool(dom, node, hdrgmPrefix, "BaseRenditionIsHDR", &baseRenditionIsHDR);
- get_attr_float3(dom, node, hdrgmPrefix, "GainMapMin", &gainMapMin);
- get_attr_float3(dom, node, hdrgmPrefix, "GainMapMax", &gainMapMax);
- get_attr_float3(dom, node, hdrgmPrefix, "Gamma", &gamma);
- get_attr_float3(dom, node, hdrgmPrefix, "OffsetSDR", &offsetSdr);
- get_attr_float3(dom, node, hdrgmPrefix, "OffsetHDR", &offsetHdr);
- get_attr_float(dom, node, hdrgmPrefix, "HDRCapacityMin", &hdrCapacityMin);
- get_attr_float(dom, node, hdrgmPrefix, "HDRCapacityMax", &hdrCapacityMax);
-
- // Translate all parameters to SkGainmapInfo's expected format.
- const float kLog2 = sk_float_log(2.f);
- outGainmapInfo->fGainmapRatioMin = {sk_float_exp(gainMapMin.fR * kLog2),
- sk_float_exp(gainMapMin.fG * kLog2),
- sk_float_exp(gainMapMin.fB * kLog2),
- 1.f};
- outGainmapInfo->fGainmapRatioMax = {sk_float_exp(gainMapMax.fR * kLog2),
- sk_float_exp(gainMapMax.fG * kLog2),
- sk_float_exp(gainMapMax.fB * kLog2),
- 1.f};
- outGainmapInfo->fGainmapGamma = {1.f / gamma.fR, 1.f / gamma.fG, 1.f / gamma.fB, 1.f};
- outGainmapInfo->fEpsilonSdr = offsetSdr;
- outGainmapInfo->fEpsilonHdr = offsetHdr;
- outGainmapInfo->fDisplayRatioSdr = sk_float_exp(hdrCapacityMin * kLog2);
- outGainmapInfo->fDisplayRatioHdr = sk_float_exp(hdrCapacityMax * kLog2);
- if (baseRenditionIsHDR) {
- outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kHDR;
- } else {
- outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
- }
- outGainmapInfo->fType = SkGainmapInfo::Type::kHDRGM;
- return true;
+ return SkXmp::Make(xmpStandard, xmpExtended);
}
diff --git a/src/codec/SkJpegXmp.h b/src/codec/SkJpegXmp.h
index 5c2261a..c25441a 100644
--- a/src/codec/SkJpegXmp.h
+++ b/src/codec/SkJpegXmp.h
@@ -9,62 +9,14 @@
#define SkJpegXmp_codec_DEFINED
#include "include/core/SkRefCnt.h"
-#include "src/xml/SkDOM.h"
+#include "include/private/SkXmp.h"
class SkData;
-struct SkGainmapInfo;
#include <memory>
#include <vector>
-/*
- * A structure to manage JPEG XMP metadata.
- */
-class SkJpegXmp {
-public:
- // Find and parse all XMP metadata, given a list of all APP1 segment parameters.
- static std::unique_ptr<SkJpegXmp> Make(const std::vector<sk_sp<SkData>>& decoderApp1Params);
-
- // Extract HDRGM gainmap parameters.
- bool getGainmapInfoHDRGM(SkGainmapInfo* info) const;
-
- // Extract HDRGainMap gainmap parameters.
- bool getGainmapInfoHDRGainMap(SkGainmapInfo* info) const;
-
- // If this includes GContainer metadata and the GContainer contains an item with semantic
- // GainMap and Mime of image/jpeg, then return true, and populate |offset| and |size| with
- // that item's offset (from the end of the primary JPEG image's EndOfImage), and the size of
- // the gainmap.
- bool getContainerGainmapLocation(size_t* offset, size_t* size) const;
-
-private:
- SkJpegXmp();
-
- // Find an XMP node that assigns namespaces to the specified URIs. The XMP that this will search
- // for is as follows. URIi is the input parameters in |uris|, and NAMESPACEi is the output
- // written to |outNamespaces|. The output NAMESPACEi strings will always start with the prefix
- // "xmlns:".
- //
- // <x:xmpmeta ...>
- // <rdf:RDF ...>
- // <rdf:Description NAMESPACE0="URI0" NAMESPACE1="URI1" .../>
- // </rdf:RDF>
- // </x:xmpmeta>
- //
- // This function will sequentially search the standard XMP, followed by the extended XMP (which
- // is not correct behavior -- it should merge the two XMP trees and search the merged tree).
- bool findUriNamespaces(size_t count,
- const char* uris[],
- const char* outNamespaces[],
- const SkDOM** outDom,
- const SkDOM::Node** outNode) const;
-
- // The DOM for the standard XMP.
- SkDOM fStandardDOM;
-
- // The DOM for the extended XMP. This may be invalid if there is no extended XMP, or the
- // extended XMP failed to parse.
- SkDOM fExtendedDOM;
-};
+// Find and parse all XMP metadata, given a list of all APP1 segment parameters.
+std::unique_ptr<SkXmp> SkJpegMakeXmp(const std::vector<sk_sp<SkData>>& decoderApp1Params);
#endif
diff --git a/src/codec/SkXmp.cpp b/src/codec/SkXmp.cpp
new file mode 100644
index 0000000..d78f7b7
--- /dev/null
+++ b/src/codec/SkXmp.cpp
@@ -0,0 +1,664 @@
+/*
+ * Copyright 2023 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkXmp.h"
+
+#include "include/core/SkColor.h"
+#include "include/core/SkData.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkStream.h"
+#include "include/private/SkGainmapInfo.h"
+#include "include/private/base/SkFloatingPoint.h"
+#include "include/utils/SkParse.h"
+#include "src/codec/SkCodecPriv.h"
+#include "src/xml/SkDOM.h"
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <utility>
+#include <vector>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// XMP parsing helper functions
+
+const char* kXmlnsPrefix = "xmlns:";
+const size_t kXmlnsPrefixLength = 6;
+
+static const char* get_namespace_prefix(const char* name) {
+ if (strlen(name) <= kXmlnsPrefixLength) {
+ return nullptr;
+ }
+ return name + kXmlnsPrefixLength;
+}
+
+/*
+ * Given a node, see if that node has only one child with the indicated name. If so, see if that
+ * child has only a single child of its own, and that child is text. If all of that is the case
+ * then return the text, otherwise return nullptr.
+ *
+ * In the following example, innerText will be returned.
+ * <node><childName>innerText</childName></node>
+ *
+ * In the following examples, nullptr will be returned (because there are multiple children with
+ * childName in the first case, and because the child has children of its own in the second).
+ * <node><childName>innerTextA</childName><childName>innerTextB</childName></node>
+ * <node><childName>innerText<otherGrandChild/></childName></node>
+ */
+static const char* get_unique_child_text(const SkDOM& dom,
+ const SkDOM::Node* node,
+ const std::string& childName) {
+ // Fail if there are multiple children with childName.
+ if (dom.countChildren(node, childName.c_str()) != 1) {
+ return nullptr;
+ }
+ const auto* child = dom.getFirstChild(node, childName.c_str());
+ if (!child) {
+ return nullptr;
+ }
+ // Fail if the child has any children besides text.
+ if (dom.countChildren(child) != 1) {
+ return nullptr;
+ }
+ const auto* grandChild = dom.getFirstChild(child);
+ if (dom.getType(grandChild) != SkDOM::kText_Type) {
+ return nullptr;
+ }
+ // Return the text.
+ return dom.getName(grandChild);
+}
+
+/*
+ * Given a node, find a child node of the specified type.
+ *
+ * If there exists a child node with name |prefix| + ":" + |type|, then return that child.
+ *
+ * If there exists a child node with name "rdf:type" that has attribute "rdf:resource" with value
+ * of |type|, then if there also exists a child node with name "rdf:value" with attribute
+ * "rdf:parseType" of "Resource", then return that child node with name "rdf:value". See Example
+ * 3 in section 7.9.2.5: RDF Typed Nodes.
+ * TODO(ccameron): This should also accept a URI for the type.
+ */
+static const SkDOM::Node* get_typed_child(const SkDOM* dom,
+ const SkDOM::Node* node,
+ const std::string& prefix,
+ const std::string& type) {
+ const auto name = prefix + std::string(":") + type;
+ const SkDOM::Node* child = dom->getFirstChild(node, name.c_str());
+ if (child) {
+ return child;
+ }
+
+ const SkDOM::Node* typeChild = dom->getFirstChild(node, "rdf:type");
+ if (!typeChild) {
+ return nullptr;
+ }
+ const char* typeChildResource = dom->findAttr(typeChild, "rdf:resource");
+ if (!typeChildResource || typeChildResource != type) {
+ return nullptr;
+ }
+
+ const SkDOM::Node* valueChild = dom->getFirstChild(node, "rdf:value");
+ if (!valueChild) {
+ return nullptr;
+ }
+ const char* valueChildParseType = dom->findAttr(valueChild, "rdf:parseType");
+ if (!valueChildParseType || strcmp(valueChildParseType, "Resource") != 0) {
+ return nullptr;
+ }
+ return valueChild;
+}
+
+/*
+ * Given a node, return its value for the specified attribute.
+ *
+ * This will first look for an attribute with the name |prefix| + ":" + |key|, and return the value
+ * for that attribute.
+ *
+ * This will then look for a child node of name |prefix| + ":" + |key|, and return the field value
+ * for that child.
+ */
+static const char* get_attr(const SkDOM* dom,
+ const SkDOM::Node* node,
+ const std::string& prefix,
+ const std::string& key) {
+ const auto name = prefix + ":" + key;
+ const char* attr = dom->findAttr(node, name.c_str());
+ if (attr) {
+ return attr;
+ }
+ return get_unique_child_text(*dom, node, name);
+}
+
+// Perform get_attr and parse the result as a bool.
+static bool get_attr_bool(const SkDOM* dom,
+ const SkDOM::Node* node,
+ const std::string& prefix,
+ const std::string& key,
+ bool* outValue) {
+ const char* attr = get_attr(dom, node, prefix, key);
+ if (!attr) {
+ return false;
+ }
+ switch (SkParse::FindList(attr, "False,True")) {
+ case 0:
+ *outValue = false;
+ return true;
+ case 1:
+ *outValue = true;
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+// Perform get_attr and parse the result as an int32_t.
+static bool get_attr_int32(const SkDOM* dom,
+ const SkDOM::Node* node,
+ const std::string& prefix,
+ const std::string& key,
+ int32_t* value) {
+ const char* attr = get_attr(dom, node, prefix, key);
+ if (!attr) {
+ return false;
+ }
+ if (!SkParse::FindS32(attr, value)) {
+ return false;
+ }
+ return true;
+}
+
+// Perform get_attr and parse the result as a float.
+static bool get_attr_float(const SkDOM* dom,
+ const SkDOM::Node* node,
+ const std::string& prefix,
+ const std::string& key,
+ float* outValue) {
+ const char* attr = get_attr(dom, node, prefix, key);
+ if (!attr) {
+ return false;
+ }
+ SkScalar value = 0.f;
+ if (SkParse::FindScalar(attr, &value)) {
+ *outValue = value;
+ return true;
+ }
+ return false;
+}
+
+// Perform get_attr and parse the result as three comma-separated floats. Return the result as an
+// SkColor4f with the alpha component set to 1.
+static bool get_attr_float3_as_list(const SkDOM* dom,
+ const SkDOM::Node* node,
+ const std::string& prefix,
+ const std::string& key,
+ SkColor4f* outValue) {
+ const auto name = prefix + ":" + key;
+
+ // Fail if there are multiple children with childName.
+ if (dom->countChildren(node, name.c_str()) != 1) {
+ return false;
+ }
+ // Find the child.
+ const auto* child = dom->getFirstChild(node, name.c_str());
+ if (!child) {
+ return false;
+ }
+
+ // Search for the rdf:Seq child.
+ const auto* seq = dom->getFirstChild(child, "rdf:Seq");
+ if (!seq) {
+ return false;
+ }
+
+ size_t count = 0;
+ SkScalar values[3] = {0.f, 0.f, 0.f};
+ for (const auto* liNode = dom->getFirstChild(seq, "rdf:li"); liNode;
+ liNode = dom->getNextSibling(liNode, "rdf:li")) {
+ if (count > 2) {
+ SkCodecPrintf("Too many items in list.\n");
+ return false;
+ }
+ if (dom->countChildren(liNode) != 1) {
+ SkCodecPrintf("Item can only have one child.\n");
+ return false;
+ }
+ const auto* liTextNode = dom->getFirstChild(liNode);
+ if (dom->getType(liTextNode) != SkDOM::kText_Type) {
+ SkCodecPrintf("Item's only child must be text.\n");
+ return false;
+ }
+ const char* liText = dom->getName(liTextNode);
+ if (!liText) {
+ SkCodecPrintf("Failed to get item's text.\n");
+ return false;
+ }
+ if (!SkParse::FindScalar(liText, values + count)) {
+ SkCodecPrintf("Failed to parse item's text to float.\n");
+ return false;
+ }
+ count += 1;
+ }
+ if (count < 3) {
+ SkCodecPrintf("List didn't have enough items.\n");
+ return false;
+ }
+ *outValue = {values[0], values[1], values[2], 1.f};
+ return true;
+}
+
+static bool get_attr_float3(const SkDOM* dom,
+ const SkDOM::Node* node,
+ const std::string& prefix,
+ const std::string& key,
+ SkColor4f* outValue) {
+ if (get_attr_float3_as_list(dom, node, prefix, key, outValue)) {
+ return true;
+ }
+ SkScalar value = -1.0;
+ if (get_attr_float(dom, node, prefix, key, &value)) {
+ *outValue = {value, value, value, 1.f};
+ return true;
+ }
+ return false;
+}
+
+static void find_uri_namespaces(const SkDOM& dom,
+ const SkDOM::Node* node,
+ size_t count,
+ const char* uris[],
+ const char* outNamespaces[]) {
+ // Search all attributes for xmlns:NAMESPACEi="URIi".
+ for (const auto* attr = dom.getFirstAttr(node); attr; attr = dom.getNextAttr(node, attr)) {
+ const char* attrName = dom.getAttrName(node, attr);
+ const char* attrValue = dom.getAttrValue(node, attr);
+ if (!attrName || !attrValue) {
+ continue;
+ }
+ // Make sure the name starts with "xmlns:".
+ if (strlen(attrName) <= kXmlnsPrefixLength) {
+ continue;
+ }
+ if (memcmp(attrName, kXmlnsPrefix, kXmlnsPrefixLength) != 0) {
+ continue;
+ }
+ // Search for a requested URI that matches.
+ for (size_t i = 0; i < count; ++i) {
+ if (strcmp(attrValue, uris[i]) != 0) {
+ continue;
+ }
+ outNamespaces[i] = attrName;
+ }
+ }
+}
+
+// See SkXmp::findUriNamespaces. This function has the same behavior, but only searches
+// a single SkDOM.
+static const SkDOM::Node* find_uri_namespaces(const SkDOM& dom,
+ size_t count,
+ const char* uris[],
+ const char* outNamespaces[]) {
+ const SkDOM::Node* root = dom.getRootNode();
+ if (!root) {
+ return nullptr;
+ }
+
+ // Ensure that the root node identifies itself as XMP metadata.
+ const char* rootName = dom.getName(root);
+ if (!rootName || strcmp(rootName, "x:xmpmeta") != 0) {
+ return nullptr;
+ }
+
+ // Iterate the children with name rdf:RDF.
+ const char* kRdf = "rdf:RDF";
+ for (const auto* rdf = dom.getFirstChild(root, kRdf); rdf;
+ rdf = dom.getNextSibling(rdf, kRdf)) {
+ std::vector<const char*> rdfNamespaces(count, nullptr);
+ find_uri_namespaces(dom, rdf, count, uris, rdfNamespaces.data());
+
+ // Iterate the children with name rdf::Description.
+ const char* kDesc = "rdf:Description";
+ for (const auto* desc = dom.getFirstChild(rdf, kDesc); desc;
+ desc = dom.getNextSibling(desc, kDesc)) {
+ std::vector<const char*> descNamespaces = rdfNamespaces;
+ find_uri_namespaces(dom, desc, count, uris, descNamespaces.data());
+
+ // If we have a match for all the requested URIs, return.
+ bool foundAllUris = true;
+ for (size_t i = 0; i < count; ++i) {
+ if (!descNamespaces[i]) {
+ foundAllUris = false;
+ break;
+ }
+ }
+ if (foundAllUris) {
+ for (size_t i = 0; i < count; ++i) {
+ outNamespaces[i] = descNamespaces[i];
+ }
+ return desc;
+ }
+ }
+ }
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// SkXmpImpl
+
+class SK_API SkXmpImpl final : public SkXmp {
+public:
+ SkXmpImpl() = default;
+
+ bool getGainmapInfoHDRGM(SkGainmapInfo* info) const override;
+ bool getGainmapInfoHDRGainMap(SkGainmapInfo* info) const override;
+ bool getContainerGainmapLocation(size_t* offset, size_t* size) const override;
+ const char* getExtendedXmpGuid() const override;
+ // Parse the given xmp data and store it into either the standard (main) DOM or the extended
+ // DOM. Returns true on successful parsing.
+ bool parseDom(sk_sp<SkData> xmpData, bool extended);
+
+private:
+ bool findUriNamespaces(size_t count,
+ const char* uris[],
+ const char* outNamespaces[],
+ const SkDOM** outDom,
+ const SkDOM::Node** outNode) const;
+
+ SkDOM fStandardDOM;
+ SkDOM fExtendedDOM;
+};
+
+const char* SkXmpImpl::getExtendedXmpGuid() const {
+ const char* namespaces[1] = {nullptr};
+ const char* uris[1] = {"http://ns.adobe.com/xmp/note/"};
+ const auto* extendedNode = find_uri_namespaces(fStandardDOM, 1, uris, namespaces);
+ if (!extendedNode) {
+ return nullptr;
+ }
+ const auto xmpNotePrefix = get_namespace_prefix(namespaces[0]);
+ // Extract the GUID (the MD5 hash) of the extended metadata.
+ return get_attr(&fStandardDOM, extendedNode, xmpNotePrefix, "HasExtendedXMP");
+}
+
+bool SkXmpImpl::findUriNamespaces(size_t count,
+ const char* uris[],
+ const char* outNamespaces[],
+ const SkDOM** outDom,
+ const SkDOM::Node** outNode) const {
+ // See XMP Specification Part 3: Storage in files, Section 1.1.3.1: Extended XMP in JPEG:
+ // A JPEG reader must recompose the StandardXMP and ExtendedXMP into a single data model tree
+ // containing all of the XMP for the JPEG file, and remove the xmpNote:HasExtendedXMP property.
+ // This code does not do that. Instead, it maintains the two separate trees and searches them
+ // sequentially.
+ *outNode = find_uri_namespaces(fStandardDOM, count, uris, outNamespaces);
+ if (*outNode) {
+ *outDom = &fStandardDOM;
+ return true;
+ }
+ *outNode = find_uri_namespaces(fExtendedDOM, count, uris, outNamespaces);
+ if (*outNode) {
+ *outDom = &fExtendedDOM;
+ return true;
+ }
+ *outDom = nullptr;
+ return false;
+}
+
+bool SkXmpImpl::getContainerGainmapLocation(size_t* outOffset, size_t* outSize) const {
+ // Find a node that matches the requested namespaces and URIs.
+ const char* namespaces[2] = {nullptr, nullptr};
+ const char* uris[2] = {"http://ns.google.com/photos/1.0/container/",
+ "http://ns.google.com/photos/1.0/container/item/"};
+ const SkDOM* dom = nullptr;
+ const SkDOM::Node* node = nullptr;
+ if (!findUriNamespaces(2, uris, namespaces, &dom, &node)) {
+ return false;
+ }
+ const char* containerPrefix = get_namespace_prefix(namespaces[0]);
+ const char* itemPrefix = get_namespace_prefix(namespaces[1]);
+
+ // The node must have a Container:Directory child.
+ const auto* directory = get_typed_child(dom, node, containerPrefix, "Directory");
+ if (!directory) {
+ SkCodecPrintf("Missing Container Directory");
+ return false;
+ }
+
+ // That Container:Directory must have a sequence of items.
+ const auto* seq = dom->getFirstChild(directory, "rdf:Seq");
+ if (!seq) {
+ SkCodecPrintf("Missing rdf:Seq");
+ return false;
+ }
+
+ // Iterate through the items in the Container:Directory's sequence. Keep a running sum of the
+ // Item:Length of all items that appear before the GainMap.
+ bool isFirstItem = true;
+ size_t offset = 0;
+ for (const auto* li = dom->getFirstChild(seq, "rdf:li"); li;
+ li = dom->getNextSibling(li, "rdf:li")) {
+ // Each list item must contain a Container:Item.
+ const auto* item = get_typed_child(dom, li, containerPrefix, "Item");
+ if (!item) {
+ SkCodecPrintf("List item does not have container Item.\n");
+ return false;
+ }
+ // A Semantic is required for every item.
+ const char* itemSemantic = get_attr(dom, item, itemPrefix, "Semantic");
+ if (!itemSemantic) {
+ SkCodecPrintf("Item is missing Semantic.\n");
+ return false;
+ }
+ // A Mime is required for every item.
+ const char* itemMime = get_attr(dom, item, itemPrefix, "Mime");
+ if (!itemMime) {
+ SkCodecPrintf("Item is missing Mime.\n");
+ return false;
+ }
+
+ if (isFirstItem) {
+ isFirstItem = false;
+ // The first item must be Primary.
+ if (strcmp(itemSemantic, "Primary") != 0) {
+ SkCodecPrintf("First item is not Primary.\n");
+ return false;
+ }
+ // The first item has mime type image/jpeg (we are decoding a jpeg).
+ if (strcmp(itemMime, "image/jpeg") != 0) {
+ SkCodecPrintf("Primary does not report that it is image/jpeg.\n");
+ return false;
+ }
+ // The first media item can contain a Padding attribute, which specifies additional
+ // padding between the end of the encoded primary image and the beginning of the next
+ // media item. Only the first media item can contain a Padding attribute.
+ int32_t padding = 0;
+ if (get_attr_int32(dom, item, itemPrefix, "Padding", &padding)) {
+ if (padding < 0) {
+ SkCodecPrintf("Item padding must be non-negative.");
+ return false;
+ }
+ offset += padding;
+ }
+ } else {
+ // A Length is required for all non-Primary items.
+ int32_t length = 0;
+ if (!get_attr_int32(dom, item, itemPrefix, "Length", &length)) {
+ SkCodecPrintf("Item length is absent.");
+ return false;
+ }
+ if (length < 0) {
+ SkCodecPrintf("Item length must be non-negative.");
+ return false;
+ }
+ // If this is not the recovery map, then read past it.
+ if (strcmp(itemSemantic, "GainMap") != 0) {
+ offset += length;
+ continue;
+ }
+ // The recovery map must have mime type image/jpeg in this implementation.
+ if (strcmp(itemMime, "image/jpeg") != 0) {
+ SkCodecPrintf("GainMap does not report that it is image/jpeg.\n");
+ return false;
+ }
+
+ // Populate the location in the file at which to find the gainmap image.
+ *outOffset = offset;
+ *outSize = length;
+ return true;
+ }
+ }
+ return false;
+}
+
+// Return true if the specified XMP metadata identifies this image as an HDR gainmap.
+bool SkXmpImpl::getGainmapInfoHDRGainMap(SkGainmapInfo* info) const {
+ // Find a node that matches the requested namespaces and URIs.
+ const char* namespaces[2] = {nullptr, nullptr};
+ const char* uris[2] = {"http://ns.apple.com/pixeldatainfo/1.0/",
+ "http://ns.apple.com/HDRGainMap/1.0/"};
+ const SkDOM* dom = nullptr;
+ const SkDOM::Node* node = nullptr;
+ if (!findUriNamespaces(2, uris, namespaces, &dom, &node)) {
+ return false;
+ }
+ const char* adpiPrefix = get_namespace_prefix(namespaces[0]);
+ const char* hdrGainMapPrefix = get_namespace_prefix(namespaces[1]);
+
+ const char* auxiliaryImageType = get_attr(dom, node, adpiPrefix, "AuxiliaryImageType");
+ if (!auxiliaryImageType) {
+ SkCodecPrintf("Did not find AuxiliaryImageType.\n");
+ return false;
+ }
+ if (strcmp(auxiliaryImageType, "urn:com:apple:photo:2020:aux:hdrgainmap") != 0) {
+ SkCodecPrintf("AuxiliaryImageType was not HDR gain map.\n");
+ return false;
+ }
+
+ int32_t version = 0;
+ if (!get_attr_int32(dom, node, hdrGainMapPrefix, "HDRGainMapVersion", &version)) {
+ SkCodecPrintf("Did not find HDRGainMapVersion.\n");
+ return false;
+ }
+ if (version != 65536) {
+ SkCodecPrintf("HDRGainMapVersion was not 65536.\n");
+ return false;
+ }
+
+ // This node will often have StoredFormat and NativeFormat children that have inner text that
+ // specifies the integer 'L008' (also known as kCVPixelFormatType_OneComponent8).
+ const float kRatioMax = sk_float_exp(1.f);
+ info->fGainmapRatioMin = {1.f, 1.f, 1.f, 1.f};
+ info->fGainmapRatioMax = {kRatioMax, kRatioMax, kRatioMax, 1.f};
+ info->fGainmapGamma = {1.f, 1.f, 1.f, 1.f};
+ info->fEpsilonSdr = {0.f, 0.f, 0.f, 1.f};
+ info->fEpsilonHdr = {0.f, 0.f, 0.f, 1.f};
+ info->fDisplayRatioSdr = 1.f;
+ info->fDisplayRatioHdr = kRatioMax;
+ info->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
+ info->fType = SkGainmapInfo::Type::kMultiPicture;
+ return true;
+}
+
+bool SkXmpImpl::getGainmapInfoHDRGM(SkGainmapInfo* outGainmapInfo) const {
+ // Find a node that matches the requested namespace and URI.
+ const char* namespaces[1] = {nullptr};
+ const char* uris[1] = {"http://ns.adobe.com/hdr-gain-map/1.0/"};
+ const SkDOM* dom = nullptr;
+ const SkDOM::Node* node = nullptr;
+ if (!findUriNamespaces(1, uris, namespaces, &dom, &node)) {
+ return false;
+ }
+ const char* hdrgmPrefix = get_namespace_prefix(namespaces[0]);
+
+ // Require that hdrgm:Version="1.0" be present.
+ const char* version = get_attr(dom, node, hdrgmPrefix, "Version");
+ if (!version) {
+ SkCodecPrintf("Version attribute is absent.\n");
+ return false;
+ }
+ if (strcmp(version, "1.0") != 0) {
+ SkCodecPrintf("Version is \"%s\", not \"1.0\".\n", version);
+ return false;
+ }
+
+ // Initialize the parameters to their defaults.
+ bool baseRenditionIsHDR = false;
+ SkColor4f gainMapMin = {1.f, 1.f, 1.f, 1.f};
+ SkColor4f gainMapMax = {2.f, 2.f, 2.f, 1.f};
+ SkColor4f gamma = {1.f, 1.f, 1.f, 1.f};
+ SkColor4f offsetSdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
+ SkColor4f offsetHdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
+ SkScalar hdrCapacityMin = 1.f;
+ SkScalar hdrCapacityMax = 2.f;
+
+ // Read all parameters that are present.
+ get_attr_bool(dom, node, hdrgmPrefix, "BaseRenditionIsHDR", &baseRenditionIsHDR);
+ get_attr_float3(dom, node, hdrgmPrefix, "GainMapMin", &gainMapMin);
+ get_attr_float3(dom, node, hdrgmPrefix, "GainMapMax", &gainMapMax);
+ get_attr_float3(dom, node, hdrgmPrefix, "Gamma", &gamma);
+ get_attr_float3(dom, node, hdrgmPrefix, "OffsetSDR", &offsetSdr);
+ get_attr_float3(dom, node, hdrgmPrefix, "OffsetHDR", &offsetHdr);
+ get_attr_float(dom, node, hdrgmPrefix, "HDRCapacityMin", &hdrCapacityMin);
+ get_attr_float(dom, node, hdrgmPrefix, "HDRCapacityMax", &hdrCapacityMax);
+
+ // Translate all parameters to SkGainmapInfo's expected format.
+ const float kLog2 = sk_float_log(2.f);
+ outGainmapInfo->fGainmapRatioMin = {sk_float_exp(gainMapMin.fR * kLog2),
+ sk_float_exp(gainMapMin.fG * kLog2),
+ sk_float_exp(gainMapMin.fB * kLog2),
+ 1.f};
+ outGainmapInfo->fGainmapRatioMax = {sk_float_exp(gainMapMax.fR * kLog2),
+ sk_float_exp(gainMapMax.fG * kLog2),
+ sk_float_exp(gainMapMax.fB * kLog2),
+ 1.f};
+ outGainmapInfo->fGainmapGamma = {1.f / gamma.fR, 1.f / gamma.fG, 1.f / gamma.fB, 1.f};
+ outGainmapInfo->fEpsilonSdr = offsetSdr;
+ outGainmapInfo->fEpsilonHdr = offsetHdr;
+ outGainmapInfo->fDisplayRatioSdr = sk_float_exp(hdrCapacityMin * kLog2);
+ outGainmapInfo->fDisplayRatioHdr = sk_float_exp(hdrCapacityMax * kLog2);
+ if (baseRenditionIsHDR) {
+ outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kHDR;
+ } else {
+ outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
+ }
+ outGainmapInfo->fType = SkGainmapInfo::Type::kHDRGM;
+ return true;
+}
+
+bool SkXmpImpl::parseDom(sk_sp<SkData> xmpData, bool extended) {
+ SkDOM* dom = extended ? &fExtendedDOM : &fStandardDOM;
+ auto xmpdStream = SkMemoryStream::Make(xmpData);
+ if (!dom->build(*xmpdStream)) {
+ SkCodecPrintf("Failed to parse XMP %s metadata.\n", extended ? "extended" : "standard");
+ return false;
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// SkXmp
+
+std::unique_ptr<SkXmp> SkXmp::Make(sk_sp<SkData> xmpData) {
+ std::unique_ptr<SkXmpImpl> xmp(new SkXmpImpl);
+ if (!xmp->parseDom(xmpData, /*extended=*/false)) {
+ return nullptr;
+ }
+ return std::move(xmp);
+}
+
+std::unique_ptr<SkXmp> SkXmp::Make(sk_sp<SkData> xmpStandard, sk_sp<SkData> xmpExtended) {
+ std::unique_ptr<SkXmpImpl> xmp(new SkXmpImpl);
+ if (!xmp->parseDom(xmpStandard, /*extended=*/false)) {
+ return nullptr;
+ }
+ if (!xmp->parseDom(xmpExtended, /*extended=*/true)) {
+ // Still return xmp with just standard metadata.
+ return std::move(xmp);
+ }
+ return std::move(xmp);
+}
diff --git a/tests/JpegGainmapTest.cpp b/tests/JpegGainmapTest.cpp
index 86fbce7..00676c1 100644
--- a/tests/JpegGainmapTest.cpp
+++ b/tests/JpegGainmapTest.cpp
@@ -23,7 +23,6 @@
#include "src/codec/SkJpegMultiPicture.h"
#include "src/codec/SkJpegSegmentScan.h"
#include "src/codec/SkJpegSourceMgr.h"
-#include "src/codec/SkJpegXmp.h"
#include "tests/Test.h"
#include "tools/Resources.h"
@@ -389,7 +388,7 @@
}
// Render an applied gainmap.
-SkBitmap render_gainmap(const SkImageInfo& renderInfo,
+static SkBitmap render_gainmap(const SkImageInfo& renderInfo,
float renderHdrRatio,
const SkBitmap& baseBitmap,
const SkBitmap& gainmapBitmap,
@@ -432,234 +431,8 @@
return result;
}
-DEF_TEST(AndroidCodec_xmpHdrgmAsFieldValue, r) {
- // Expose HDRM values as fields. Also place the HDRGM namespace in the rdf:RDF node.
- const char xmpData[] =
- "http://ns.adobe.com/xap/1.0/\0"
- "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 6.0.0\">\n"
- " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
- " xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\">\n"
- " <rdf:Description rdf:about=\"\">\n"
- " <hdrgm:Version>1.0</hdrgm:Version>\n"
- " <hdrgm:GainMapMax>3</hdrgm:GainMapMax>\n"
- " <hdrgm:HDRCapacityMax>4</hdrgm:HDRCapacityMax>\n"
- " </rdf:Description>\n"
- " </rdf:RDF>\n"
- "</x:xmpmeta>\n";
-
- std::vector<sk_sp<SkData>> app1Params;
- app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
-
- auto xmp = SkJpegXmp::Make(app1Params);
- REPORTER_ASSERT(r, xmp);
-
- SkGainmapInfo info;
- REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
- REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
- REPORTER_ASSERT(r, info.fDisplayRatioHdr == 16.f);
-}
-
-DEF_TEST(AndroidCodec_xmpHdrgmRequiresVersion, r) {
- // Same as the above, except with Version being absent.
- const char xmpData[] =
- "http://ns.adobe.com/xap/1.0/\0"
- "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 6.0.0\">\n"
- " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
- " xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\">\n"
- " <rdf:Description rdf:about=\"\">\n"
- " <hdrgm:GainMapMax>3</hdrgm:GainMapMax>\n"
- " <hdrgm:HDRCapacityMax>4</hdrgm:HDRCapacityMax>\n"
- " </rdf:Description>\n"
- " </rdf:RDF>\n"
- "</x:xmpmeta>\n";
-
- std::vector<sk_sp<SkData>> app1Params;
- app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
-
- auto xmp = SkJpegXmp::Make(app1Params);
- REPORTER_ASSERT(r, xmp);
-
- SkGainmapInfo info;
- REPORTER_ASSERT(r, !xmp->getGainmapInfoHDRGM(&info));
-}
-
-DEF_TEST(AndroidCodec_xmpHdrgmAsDescriptionPropertyAttributes, r) {
- // Expose HDRGM values as attributes on an rdf:Description node.
- const char xmpData[] =
- "http://ns.adobe.com/xap/1.0/\0"
- "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 6.0.0\">\n"
- " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
- " <rdf:Description rdf:about=\"\"\n"
- " xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\"\n"
- " hdrgm:Version=\"1.0\"\n"
- " hdrgm:GainMapMax=\"3\"\n"
- " hdrgm:HDRCapacityMax=\"4\"/>\n"
- " </rdf:RDF>\n"
- "</x:xmpmeta>\n";
-
- std::vector<sk_sp<SkData>> app1Params;
- app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
-
- auto xmp = SkJpegXmp::Make(app1Params);
- REPORTER_ASSERT(r, xmp);
-
- SkGainmapInfo info;
- REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
- REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
- REPORTER_ASSERT(r, info.fDisplayRatioHdr == 16.f);
-}
-
-// Test mixed list and non-list entries.
-DEF_TEST(AndroidCodec_xmpHdrgmList, r) {
- const char xmpData[] =
- "http://ns.adobe.com/xap/1.0/\0"
- "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 6.0.0\">\n"
- " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
- " xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\">\n"
- " <rdf:Description rdf:about=\"\"\n"
- " hdrgm:Version=\"1.0\"\n"
- " hdrgm:GainMapMin=\"2.0\"\n"
- " hdrgm:OffsetSDR=\"0.1\">\n"
- " <hdrgm:GainMapMax>\n"
- " <rdf:Seq>\n"
- " <rdf:li>3</rdf:li>\n"
- " <rdf:li>4</rdf:li>\n"
- " <rdf:li>5</rdf:li>\n"
- " </rdf:Seq>\n"
- " </hdrgm:GainMapMax>\n"
- " <hdrgm:Gamma>\n"
- " 1.2\n"
- " </hdrgm:Gamma>\n"
- " <hdrgm:OffsetHDR>\n"
- " <rdf:Seq>\n"
- " <rdf:li>\n"
- " 0.2\n"
- " </rdf:li>\n"
- " <rdf:li>\n"
- " 0.3\n"
- " </rdf:li>\n"
- " <rdf:li>\n"
- " 0.4\n"
- " </rdf:li>\n"
- " </rdf:Seq>\n"
- " </hdrgm:OffsetHDR>\n"
- " </rdf:Description>\n"
- " </rdf:RDF>\n"
- "</x:xmpmeta>\n";
-
- std::vector<sk_sp<SkData>> app1Params;
- app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
-
- auto xmp = SkJpegXmp::Make(app1Params);
- REPORTER_ASSERT(r, xmp);
-
- SkGainmapInfo info;
- REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
- REPORTER_ASSERT(r, info.fGainmapRatioMin.fR == 4.f);
- REPORTER_ASSERT(r, info.fGainmapRatioMin.fG == 4.f);
- REPORTER_ASSERT(r, info.fGainmapRatioMin.fB == 4.f);
- REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
- REPORTER_ASSERT(r, info.fGainmapRatioMax.fG == 16.f);
- REPORTER_ASSERT(r, info.fGainmapRatioMax.fB == 32.f);
-
- REPORTER_ASSERT(r, info.fGainmapGamma.fR == 1.f/1.2f);
- REPORTER_ASSERT(r, info.fGainmapGamma.fG == 1.f/1.2f);
- REPORTER_ASSERT(r, info.fGainmapGamma.fB == 1.f/1.2f);
-
- REPORTER_ASSERT(r, info.fEpsilonSdr.fR == 0.1f);
- REPORTER_ASSERT(r, info.fEpsilonSdr.fG == 0.1f);
- REPORTER_ASSERT(r, info.fEpsilonSdr.fB == 0.1f);
-
- REPORTER_ASSERT(r, info.fEpsilonHdr.fR == 0.2f);
- REPORTER_ASSERT(r, info.fEpsilonHdr.fG == 0.3f);
- REPORTER_ASSERT(r, info.fEpsilonHdr.fB == 0.4f);
-}
-
-DEF_TEST(AndroidCodec_xmpContainerTypedNode, r) {
- // Container and Item using a node of type Container:Item.
- const char xmpData[] =
- "http://ns.adobe.com/xap/1.0/\0"
- "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 5.5.0\">\n"
- " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
- " <rdf:Description rdf:about=\"\"\n"
- " xmlns:Container=\"http://ns.google.com/photos/1.0/container/\"\n"
- " xmlns:Item=\"http://ns.google.com/photos/1.0/container/item/\">\n"
- " <Container:Directory>\n"
- " <rdf:Seq>\n"
- " <rdf:li rdf:parseType=\"Resource\">\n"
- " <Container:Item>\n"
- " <Item:Mime>image/jpeg</Item:Mime>\n"
- " <Item:Semantic>Primary</Item:Semantic>\n"
- " </Container:Item>\n"
- " </rdf:li>\n"
- " <rdf:li rdf:parseType=\"Resource\">\n"
- " <Container:Item\n"
- " Item:Semantic=\"GainMap\"\n"
- " Item:Mime=\"image/jpeg\"\n"
- " Item:Length=\"49035\"/>\n"
- " </rdf:li>\n"
- " </rdf:Seq>\n"
- " </Container:Directory>\n"
- " </rdf:Description>\n"
- " </rdf:RDF>\n"
- "</x:xmpmeta>\n";
- std::vector<sk_sp<SkData>> app1Params;
- app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
-
- auto xmp = SkJpegXmp::Make(app1Params);
- REPORTER_ASSERT(r, xmp);
-
- size_t offset = 999;
- size_t size = 999;
- REPORTER_ASSERT(r, xmp->getContainerGainmapLocation(&offset, &size));
- REPORTER_ASSERT(r, size == 49035);
-}
-
-DEF_TEST(AndroidCodec_xmpContainerTypedNodeRdfEquivalent, r) {
- // Container and Item using rdf:value and rdf:type pairs.
- const char xmpData[] =
- "http://ns.adobe.com/xap/1.0/\0"
- "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 5.5.0\">\n"
- " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
- " <rdf:Description rdf:about=\"\"\n"
- " xmlns:Container=\"http://ns.google.com/photos/1.0/container/\"\n"
- " xmlns:Item=\"http://ns.google.com/photos/1.0/container/item/\">\n"
- " <Container:Directory>\n"
- " <rdf:Seq>\n"
- " <rdf:li rdf:parseType=\"Resource\">\n"
- " <rdf:value rdf:parseType=\"Resource\">\n"
- " <Item:Mime>image/jpeg</Item:Mime>\n"
- " <Item:Semantic>Primary</Item:Semantic>\n"
- " </rdf:value>\n"
- " <rdf:type rdf:resource=\"Item\"/>\n"
- " </rdf:li>\n"
- " <rdf:li rdf:parseType=\"Resource\">\n"
- " <rdf:value rdf:parseType=\"Resource\">\n"
- " <Item:Semantic>GainMap</Item:Semantic>\n"
- " <Item:Mime>image/jpeg</Item:Mime>\n"
- " <Item:Length>49035</Item:Length>\n"
- " </rdf:value>\n"
- " <rdf:type rdf:resource=\"Item\"/>\n"
- " </rdf:li>\n"
- " </rdf:Seq>\n"
- " </Container:Directory>\n"
- " </rdf:Description>\n"
- " </rdf:RDF>\n"
- "</x:xmpmeta>\n";
- std::vector<sk_sp<SkData>> app1Params;
- app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
-
- auto xmp = SkJpegXmp::Make(app1Params);
- REPORTER_ASSERT(r, xmp);
-
- size_t offset = 999;
- size_t size = 999;
- REPORTER_ASSERT(r, xmp->getContainerGainmapLocation(&offset, &size));
- REPORTER_ASSERT(r, size == 49035);
-}
-
// Render a single pixel of an applied gainmap and return it.
-SkColor4f render_gainmap_pixel(float renderHdrRatio,
+static SkColor4f render_gainmap_pixel(float renderHdrRatio,
const SkBitmap& baseBitmap,
const SkBitmap& gainmapBitmap,
const SkGainmapInfo& gainmapInfo,
diff --git a/tests/SkJpegXmpTest.cpp b/tests/SkJpegXmpTest.cpp
new file mode 100644
index 0000000..6c8bdbd
--- /dev/null
+++ b/tests/SkJpegXmpTest.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2023 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkData.h"
+#include "include/private/SkGainmapInfo.h"
+#include "src/codec/SkJpegXmp.h"
+#include "src/core/SkMD5.h"
+#include "tests/Test.h"
+
+#include <iomanip>
+#include <iostream>
+#include <regex>
+#include <sstream>
+
+DEF_TEST(SkJpegXmp_standardXmp, r) {
+ const char xmpData[] =
+ "http://ns.adobe.com/xap/1.0/\0"
+ R"(
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/">
+ <rdf:Description rdf:about="">
+ <hdrgm:Version>1.0</hdrgm:Version>
+ <hdrgm:GainMapMax>3</hdrgm:GainMapMax>
+ <hdrgm:HDRCapacityMax>4</hdrgm:HDRCapacityMax>
+ </rdf:Description>
+ </rdf:RDF>
+ </x:xmpmeta>)";
+
+ std::vector<sk_sp<SkData>> app1Params;
+ app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
+
+ auto xmp = SkJpegMakeXmp(app1Params);
+ REPORTER_ASSERT(r, xmp);
+
+ SkGainmapInfo info;
+ REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
+ REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
+ REPORTER_ASSERT(r, info.fDisplayRatioHdr == 16.f);
+}
+
+static void append_uint32(uint32_t v, std::vector<char>* c) {
+ for (int i = 0; i < 4; ++i) {
+ c->push_back(static_cast<char>((v >> ((3 - i) * 8)) & 0xff));
+ }
+}
+
+static std::string digest_to_hex_string(const SkMD5::Digest& digest) {
+ std::stringstream ss;
+ for (int i = 0; i < 16; ++i) {
+ ss << std::uppercase << std::setfill('0') << std::setw(2) << std::right << std::hex
+ << (int)digest.data[i];
+ }
+ return ss.str();
+}
+
+static std::string standard_xmp_with_header(const SkMD5::Digest& digest, const std::string& data) {
+ const std::string sig = "http://ns.adobe.com/xap/1.0/";
+ std::vector<char> c(sig.begin(), sig.end());
+ c.push_back('\0');
+ const std::string guid = digest_to_hex_string(digest);
+ const std::string dataWithGuid = std::regex_replace(data, std::regex("\\$GUID"), guid);
+ c.insert(c.end(), dataWithGuid.begin(), dataWithGuid.end());
+ return std::string(c.data(), c.size());
+}
+
+static std::string extended_xmp_with_header(const SkMD5::Digest& digest,
+ uint32_t size,
+ uint32_t offset,
+ const std::string& data) {
+ const std::string sig = "http://ns.adobe.com/xmp/extension/";
+ std::vector<char> c(sig.begin(), sig.end());
+ c.push_back('\0');
+ const std::string guid = digest_to_hex_string(digest);
+ c.insert(c.end(), guid.begin(), guid.end());
+ append_uint32(size, &c);
+ append_uint32(offset, &c);
+ c.insert(c.end(), data.begin(), data.end());
+ return std::string(c.data(), c.size());
+}
+
+DEF_TEST(SkJpegXmp_readExtendedXmp, r) {
+ const std::string standardXmpData = R"(
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
+ <rdf:RDF xmlns:xmpNote="http://ns.adobe.com/xmp/note/">
+ <rdf:Description rdf:about="">
+ <xmpNote:HasExtendedXMP>$GUID</xmpNote:HasExtendedXMP>
+ </rdf:Description>
+ </rdf:RDF>
+ </x:xmpmeta>)";
+
+ const std::string extendedXmpData1 = R"(
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/">
+ <rdf:Description rdf:about="">
+ <hdrgm:Version>1.0</hdrgm:Version>
+ <hdrgm:GainMapMax>3</hdrgm:GainMapMax>
+ <hdrgm:HDRCapacityMax>4</hdrgm:HDRCapacityMax>)";
+ const std::string extendedXmpData2 = R"(
+ </rdf:Description>
+ </rdf:RDF>
+ </x:xmpmeta>)";
+
+ const uint32_t totalExtendedXmpSize = extendedXmpData1.size() + extendedXmpData2.size();
+ SkMD5 md5;
+ md5.write(extendedXmpData1.data(), extendedXmpData1.length());
+ md5.write(extendedXmpData2.data(), extendedXmpData2.length());
+ const SkMD5::Digest digest = md5.finish();
+
+ const std::string standardXmpDataWithHeader = standard_xmp_with_header(digest, standardXmpData);
+
+ const uint32_t offset1 = 0;
+ const std::string extendedXmpData1WithHeader =
+ extended_xmp_with_header(digest, totalExtendedXmpSize, offset1, extendedXmpData1);
+
+ const uint32_t offset2 = extendedXmpData1.size();
+ const std::string extendedXmpData2WithHeader =
+ extended_xmp_with_header(digest, totalExtendedXmpSize, offset2, extendedXmpData2);
+
+ std::vector<sk_sp<SkData>> app1Params;
+ app1Params.push_back(SkData::MakeWithoutCopy(standardXmpDataWithHeader.data(),
+ standardXmpDataWithHeader.length()));
+ app1Params.push_back(SkData::MakeWithoutCopy(extendedXmpData1WithHeader.data(),
+ extendedXmpData1WithHeader.length()));
+ app1Params.push_back(SkData::MakeWithoutCopy(extendedXmpData2WithHeader.data(),
+ extendedXmpData2WithHeader.length()));
+
+ auto xmp = SkJpegMakeXmp(app1Params);
+ REPORTER_ASSERT(r, xmp);
+
+ SkGainmapInfo info;
+ REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
+ REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
+ REPORTER_ASSERT(r, info.fDisplayRatioHdr == 16.f);
+}
diff --git a/tests/SkXmpTest.cpp b/tests/SkXmpTest.cpp
new file mode 100644
index 0000000..78b893a
--- /dev/null
+++ b/tests/SkXmpTest.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2023 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkColor.h"
+#include "include/core/SkData.h"
+#include "include/core/SkRefCnt.h"
+#include "include/private/SkGainmapInfo.h"
+#include "include/private/SkXmp.h"
+#include "tests/Test.h"
+
+#include <cstddef>
+#include <memory>
+
+DEF_TEST(SkXmp_invalidXml, r) {
+ // Invalid truncated xml.
+ const char xmpData[] = R"(
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:)";
+
+ sk_sp<SkData> app1Param = SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1);
+
+ auto xmp = SkXmp::Make(app1Param);
+ REPORTER_ASSERT(r, xmp == nullptr);
+}
+
+DEF_TEST(SkXmp_xmpHdrgmAsFieldValue, r) {
+ // Expose HDRM values as fields. Also place the HDRGM namespace in the rdf:RDF node.
+ const char xmpData[] = R"(
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/">
+ <rdf:Description rdf:about="">
+ <hdrgm:Version>1.0</hdrgm:Version>
+ <hdrgm:GainMapMax>3</hdrgm:GainMapMax>
+ <hdrgm:HDRCapacityMax>4</hdrgm:HDRCapacityMax>
+ </rdf:Description>
+ </rdf:RDF>
+ </x:xmpmeta>)";
+
+ sk_sp<SkData> app1Param = SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1);
+
+ auto xmp = SkXmp::Make(app1Param);
+ REPORTER_ASSERT(r, xmp);
+
+ SkGainmapInfo info;
+ REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
+ REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
+ REPORTER_ASSERT(r, info.fDisplayRatioHdr == 16.f);
+}
+
+DEF_TEST(SkXmp_xmpHdrgmRequiresVersion, r) {
+ // Same as the above, except with Version being absent.
+ const char xmpData[] = R"(
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/">
+ <rdf:Description rdf:about="">
+ <hdrgm:GainMapMax>3</hdrgm:GainMapMax>
+ <hdrgm:HDRCapacityMax>4</hdrgm:HDRCapacityMax>
+ </rdf:Description>
+ </rdf:RDF>
+ </x:xmpmeta>)";
+
+ sk_sp<SkData> app1Param = SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1);
+
+ auto xmp = SkXmp::Make(app1Param);
+ REPORTER_ASSERT(r, xmp);
+
+ SkGainmapInfo info;
+ REPORTER_ASSERT(r, !xmp->getGainmapInfoHDRGM(&info));
+}
+
+DEF_TEST(SkXmp_xmpHdrgmAsDescriptionPropertyAttributes, r) {
+ // Expose HDRGM values as attributes on an rdf:Description node.
+ const char xmpData[] = R"(
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <rdf:Description rdf:about=""
+ xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
+ hdrgm:Version="1.0"
+ hdrgm:GainMapMax="3"
+ hdrgm:HDRCapacityMax="4"/>
+ </rdf:RDF>
+ </x:xmpmeta>)";
+
+ sk_sp<SkData> app1Param = SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1);
+
+ auto xmp = SkXmp::Make(app1Param);
+ REPORTER_ASSERT(r, xmp);
+
+ SkGainmapInfo info;
+ REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
+ REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
+ REPORTER_ASSERT(r, info.fDisplayRatioHdr == 16.f);
+}
+
+// Test mixed list and non-list entries.
+DEF_TEST(SkXmp_xmpHdrgmList, r) {
+ const char xmpData[] = R"(
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/">
+ <rdf:Description rdf:about=""
+ hdrgm:Version="1.0"
+ hdrgm:GainMapMin="2.0"
+ hdrgm:OffsetSDR="0.1">
+ <hdrgm:GainMapMax>
+ <rdf:Seq>
+ <rdf:li>3</rdf:li>
+ <rdf:li>4</rdf:li>
+ <rdf:li>5</rdf:li>
+ </rdf:Seq>
+ </hdrgm:GainMapMax>
+ <hdrgm:Gamma>
+ 1.2
+ </hdrgm:Gamma>
+ <hdrgm:OffsetHDR>
+ <rdf:Seq>
+ <rdf:li>
+ 0.2
+ </rdf:li>
+ <rdf:li>
+ 0.3
+ </rdf:li>
+ <rdf:li>
+ 0.4
+ </rdf:li>
+ </rdf:Seq>
+ </hdrgm:OffsetHDR>
+ </rdf:Description>
+ </rdf:RDF>
+ </x:xmpmeta>)";
+
+ sk_sp<SkData> app1Param = SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1);
+
+ auto xmp = SkXmp::Make(app1Param);
+ REPORTER_ASSERT(r, xmp);
+
+ SkGainmapInfo info;
+ REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
+ REPORTER_ASSERT(r, info.fGainmapRatioMin.fR == 4.f);
+ REPORTER_ASSERT(r, info.fGainmapRatioMin.fG == 4.f);
+ REPORTER_ASSERT(r, info.fGainmapRatioMin.fB == 4.f);
+ REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
+ REPORTER_ASSERT(r, info.fGainmapRatioMax.fG == 16.f);
+ REPORTER_ASSERT(r, info.fGainmapRatioMax.fB == 32.f);
+
+ REPORTER_ASSERT(r, info.fGainmapGamma.fR == 1.f/1.2f);
+ REPORTER_ASSERT(r, info.fGainmapGamma.fG == 1.f/1.2f);
+ REPORTER_ASSERT(r, info.fGainmapGamma.fB == 1.f/1.2f);
+
+ REPORTER_ASSERT(r, info.fEpsilonSdr.fR == 0.1f);
+ REPORTER_ASSERT(r, info.fEpsilonSdr.fG == 0.1f);
+ REPORTER_ASSERT(r, info.fEpsilonSdr.fB == 0.1f);
+
+ REPORTER_ASSERT(r, info.fEpsilonHdr.fR == 0.2f);
+ REPORTER_ASSERT(r, info.fEpsilonHdr.fG == 0.3f);
+ REPORTER_ASSERT(r, info.fEpsilonHdr.fB == 0.4f);
+}
+
+DEF_TEST(SkXmp_xmpContainerTypedNode, r) {
+ // Container and Item using a node of type Container:Item.
+ const char xmpData[] = R"(
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.5.0">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <rdf:Description rdf:about=""
+ xmlns:Container="http://ns.google.com/photos/1.0/container/"
+ xmlns:Item="http://ns.google.com/photos/1.0/container/item/">
+ <Container:Directory>
+ <rdf:Seq>
+ <rdf:li rdf:parseType="Resource">
+ <Container:Item>
+ <Item:Mime>image/jpeg</Item:Mime>
+ <Item:Semantic>Primary</Item:Semantic>
+ </Container:Item>
+ </rdf:li>
+ <rdf:li rdf:parseType="Resource">
+ <Container:Item
+ Item:Semantic="GainMap"
+ Item:Mime="image/jpeg"
+ Item:Length="49035"/>
+ </rdf:li>
+ </rdf:Seq>
+ </Container:Directory>
+ </rdf:Description>
+ </rdf:RDF>
+ </x:xmpmeta>)";
+ sk_sp<SkData> app1Param = SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1);
+
+ auto xmp = SkXmp::Make(app1Param);
+ REPORTER_ASSERT(r, xmp);
+
+ size_t offset = 999;
+ size_t size = 999;
+ REPORTER_ASSERT(r, xmp->getContainerGainmapLocation(&offset, &size));
+ REPORTER_ASSERT(r, size == 49035);
+}
+
+DEF_TEST(SkXmp_xmpContainerTypedNodeRdfEquivalent, r) {
+ // Container and Item using rdf:value and rdf:type pairs.
+ const char xmpData[] = R"(
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.5.0">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <rdf:Description rdf:about=""
+ xmlns:Container="http://ns.google.com/photos/1.0/container/"
+ xmlns:Item="http://ns.google.com/photos/1.0/container/item/">
+ <Container:Directory>
+ <rdf:Seq>
+ <rdf:li rdf:parseType="Resource">
+ <rdf:value rdf:parseType="Resource">
+ <Item:Mime>image/jpeg</Item:Mime>
+ <Item:Semantic>Primary</Item:Semantic>
+ </rdf:value>
+ <rdf:type rdf:resource="Item"/>
+ </rdf:li>
+ <rdf:li rdf:parseType="Resource">
+ <rdf:value rdf:parseType="Resource">
+ <Item:Semantic>GainMap</Item:Semantic>
+ <Item:Mime>image/jpeg</Item:Mime>
+ <Item:Length>49035</Item:Length>
+ </rdf:value>
+ <rdf:type rdf:resource="Item"/>
+ </rdf:li>
+ </rdf:Seq>
+ </Container:Directory>
+ </rdf:Description>
+ </rdf:RDF>
+ </x:xmpmeta>)";
+ sk_sp<SkData> app1Param = SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1);
+
+ auto xmp = SkXmp::Make(app1Param);
+ REPORTER_ASSERT(r, xmp);
+
+ size_t offset = 999;
+ size_t size = 999;
+ REPORTER_ASSERT(r, xmp->getContainerGainmapLocation(&offset, &size));
+ REPORTER_ASSERT(r, size == 49035);
+}
diff --git a/tests/testgroups.bzl b/tests/testgroups.bzl
index f0188aa..1249688 100644
--- a/tests/testgroups.bzl
+++ b/tests/testgroups.bzl
@@ -169,6 +169,7 @@
"SkSLDebugTraceTest.cpp",
"SkVMTest.cpp",
"SkVxTest.cpp",
+ "SkXmpTest.cpp",
"Skbug6389.cpp",
"SortTest.cpp",
"SrcOverTest.cpp",