staticconstchar* get_namespace_prefix(constchar* 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>
*/ staticconstchar* 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;
} constauto* 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;
} constauto* 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.
*/ staticconst SkDOM::Node* get_typed_child(const SkDOM* dom, const SkDOM::Node* node, const std::string& prefix, const std::string& type) { constauto name = prefix + std::string(":") + type; const SkDOM::Node* child = dom->getFirstChild(node, name.c_str()); if (child) { return child;
}
/* * 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.
*/ staticconstchar* get_attr(const SkDOM* dom, const SkDOM::Node* node, const std::string& prefix, const std::string& key) { constauto name = prefix + ":" + key; constchar* 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. staticbool get_attr_bool(const SkDOM* dom, const SkDOM::Node* node, const std::string& prefix, const std::string& key, bool* outValue) { constchar* attr = get_attr(dom, node, prefix, key); if (!attr) { returnfalse;
} switch (SkParse::FindList(attr, "False,True")) { case 0:
*outValue = false; returntrue; case 1:
*outValue = true; returntrue; default: break;
} returnfalse;
}
// Perform get_attr and parse the result as an int32_t. staticbool get_attr_int32(const SkDOM* dom, const SkDOM::Node* node, const std::string& prefix, const std::string& key,
int32_t* value) { constchar* attr = get_attr(dom, node, prefix, key); if (!attr) { returnfalse;
} if (!SkParse::FindS32(attr, value)) { returnfalse;
} returntrue;
}
// Perform get_attr and parse the result as a float. staticbool get_attr_float(const SkDOM* dom, const SkDOM::Node* node, const std::string& prefix, const std::string& key, float* outValue) { constchar* attr = get_attr(dom, node, prefix, key); if (!attr) { returnfalse;
}
SkScalar value = 0.f; if (SkParse::FindScalar(attr, &value)) {
*outValue = value; returntrue;
} returnfalse;
}
// 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. staticbool get_attr_float3_as_list(const SkDOM* dom, const SkDOM::Node* node, const std::string& prefix, const std::string& key,
SkColor4f* outValue) { constauto name = prefix + ":" + key;
// Fail if there are multiple children with childName. if (dom->countChildren(node, name.c_str()) != 1) { returnfalse;
} // Find the child. constauto* child = dom->getFirstChild(node, name.c_str()); if (!child) { returnfalse;
}
// Search for the rdf:Seq child. constauto* seq = dom->getFirstChild(child, "rdf:Seq"); if (!seq) { returnfalse;
}
size_t count = 0;
SkScalar values[3] = {0.f, 0.f, 0.f}; for (constauto* liNode = dom->getFirstChild(seq, "rdf:li"); liNode;
liNode = dom->getNextSibling(liNode, "rdf:li")) { if (count > 2) {
SkCodecPrintf("Too many items in list.\n"); returnfalse;
} if (dom->countChildren(liNode) != 1) {
SkCodecPrintf("Item can only have one child.\n"); returnfalse;
} constauto* liTextNode = dom->getFirstChild(liNode); if (dom->getType(liTextNode) != SkDOM::kText_Type) {
SkCodecPrintf("Item's only child must be text.\n"); returnfalse;
} constchar* liText = dom->getName(liTextNode); if (!liText) {
SkCodecPrintf("Failed to get item's text.\n"); returnfalse;
} if (!SkParse::FindScalar(liText, values + count)) {
SkCodecPrintf("Failed to parse item's text to float.\n"); returnfalse;
}
count += 1;
} if (count < 3) {
SkCodecPrintf("List didn't have enough items.\n"); returnfalse;
}
*outValue = {values[0], values[1], values[2], 1.f}; returntrue;
}
staticvoid find_uri_namespaces(const SkDOM& dom, const SkDOM::Node* node,
size_t count, constchar* uris[], constchar* outNamespaces[]) { // Search all attributes for xmlns:NAMESPACEi="URIi". for (constauto* attr = dom.getFirstAttr(node); attr; attr = dom.getNextAttr(node, attr)) { constchar* attrName = dom.getAttrName(node, attr); constchar* 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. staticconst SkDOM::Node* find_uri_namespaces(const SkDOM& dom,
size_t count, constchar* uris[], constchar* outNamespaces[]) { const SkDOM::Node* root = dom.getRootNode(); if (!root) { return nullptr;
}
// Ensure that the root node identifies itself as XMP metadata. constchar* rootName = dom.getName(root); if (!rootName || strcmp(rootName, "x:xmpmeta") != 0) { return nullptr;
}
// Iterate the children with name rdf:RDF. constchar* kRdf = "rdf:RDF"; for (constauto* rdf = dom.getFirstChild(root, kRdf); rdf;
rdf = dom.getNextSibling(rdf, kRdf)) {
std::vector<constchar*> rdfNamespaces(count, nullptr);
find_uri_namespaces(dom, rdf, count, uris, rdfNamespaces.data());
// Iterate the children with name rdf::Description. constchar* kDesc = "rdf:Description"; for (constauto* desc = dom.getFirstChild(rdf, kDesc); desc;
desc = dom.getNextSibling(desc, kDesc)) {
std::vector<constchar*> 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;
}
class SkXmpImpl final : public SkXmp { public:
SkXmpImpl() = default;
bool getGainmapInfoAdobe(SkGainmapInfo* info) const override; bool getGainmapInfoApple(float exifHdrHeadroom, SkGainmapInfo* info) const override; bool getContainerGainmapLocation(size_t* offset, size_t* size) const override; constchar* 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);
constchar* SkXmpImpl::getExtendedXmpGuid() const { constchar* namespaces[1] = {nullptr}; constchar* uris[1] = {"http://ns.adobe.com/xmp/note/"}; constauto* extendedNode = find_uri_namespaces(fStandardDOM, 1, uris, namespaces); if (!extendedNode) { return nullptr;
} constauto 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, constchar* uris[], constchar* 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; returntrue;
}
*outNode = find_uri_namespaces(fExtendedDOM, count, uris, outNamespaces); if (*outNode) {
*outDom = &fExtendedDOM; returntrue;
}
*outDom = nullptr; returnfalse;
}
bool SkXmpImpl::getContainerGainmapLocation(size_t* outOffset, size_t* outSize) const{ // Find a node that matches the requested namespaces and URIs. constchar* namespaces[2] = {nullptr, nullptr}; constchar* 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)) { returnfalse;
} constchar* containerPrefix = get_namespace_prefix(namespaces[0]); constchar* itemPrefix = get_namespace_prefix(namespaces[1]);
// The node must have a Container:Directory child. constauto* directory = get_typed_child(dom, node, containerPrefix, "Directory"); if (!directory) {
SkCodecPrintf("Missing Container Directory"); returnfalse;
}
// That Container:Directory must have a sequence of items. constauto* seq = dom->getFirstChild(directory, "rdf:Seq"); if (!seq) {
SkCodecPrintf("Missing rdf:Seq"); returnfalse;
}
// 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 (constauto* li = dom->getFirstChild(seq, "rdf:li"); li;
li = dom->getNextSibling(li, "rdf:li")) { // Each list item must contain a Container:Item. constauto* item = get_typed_child(dom, li, containerPrefix, "Item"); if (!item) {
SkCodecPrintf("List item does not have container Item.\n"); returnfalse;
} // A Semantic is required for every item. constchar* itemSemantic = get_attr(dom, item, itemPrefix, "Semantic"); if (!itemSemantic) {
SkCodecPrintf("Item is missing Semantic.\n"); returnfalse;
} // A Mime is required for every item. constchar* itemMime = get_attr(dom, item, itemPrefix, "Mime"); if (!itemMime) {
SkCodecPrintf("Item is missing Mime.\n"); returnfalse;
}
if (isFirstItem) {
isFirstItem = false; // The first item must be Primary. if (strcmp(itemSemantic, "Primary") != 0) {
SkCodecPrintf("First item is not Primary.\n"); returnfalse;
} // 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"); returnfalse;
} // 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."); returnfalse;
}
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."); returnfalse;
} if (length < 0) {
SkCodecPrintf("Item length must be non-negative."); returnfalse;
} // 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"); returnfalse;
}
// Populate the location in the file at which to find the gainmap image.
*outOffset = offset;
*outSize = length; returntrue;
}
} returnfalse;
}
// Return true if the specified XMP metadata identifies this image as an HDR gainmap. bool SkXmpImpl::getGainmapInfoApple(float exifHdrHeadroom, SkGainmapInfo* info) const{ // Find a node that matches the requested namespaces and URIs. constchar* namespaces[2] = {nullptr, nullptr}; constchar* 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)) { returnfalse;
} constchar* adpiPrefix = get_namespace_prefix(namespaces[0]); constchar* hdrGainMapPrefix = get_namespace_prefix(namespaces[1]);
constchar* auxiliaryImageType = get_attr(dom, node, adpiPrefix, "AuxiliaryImageType"); if (!auxiliaryImageType) {
SkCodecPrintf("Did not find AuxiliaryImageType.\n"); returnfalse;
} if (strcmp(auxiliaryImageType, "urn:com:apple:photo:2020:aux:hdrgainmap") != 0) {
SkCodecPrintf("AuxiliaryImageType was not HDR gain map.\n"); returnfalse;
}
// Require that the gainmap version be present, but do not require a specific version.
int32_t version = 0; if (!get_attr_int32(dom, node, hdrGainMapPrefix, "HDRGainMapVersion", &version)) {
SkCodecPrintf("Did not find HDRGainMapVersion.\n"); returnfalse;
}
// If the XMP also specifies a HDRGainMapHeadroom parameter, then prefer that parameter to the // parameter specified in the base image Exif. float hdrHeadroom = exifHdrHeadroom; float xmpHdrHeadroom = 0.f; if (get_attr_float(dom, node, hdrGainMapPrefix, "HDRGainMapHeadroom", &xmpHdrHeadroom)) {
hdrHeadroom = xmpHdrHeadroom;
}
// This node will often have StoredFormat and NativeFormat children that have inner text that // specifies the integer 'L008' (also known as kCVPixelFormatType_OneComponent8).
info->fGainmapRatioMin = {1.f, 1.f, 1.f, 1.f};
info->fGainmapRatioMax = {hdrHeadroom, hdrHeadroom, hdrHeadroom, 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 = hdrHeadroom;
info->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
info->fType = SkGainmapInfo::Type::kApple; returntrue;
}
bool SkXmpImpl::getGainmapInfoAdobe(SkGainmapInfo* outGainmapInfo) const { // Find a node that matches the requested namespace and URI. constchar* namespaces[1] = {nullptr}; constchar* 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)) { returnfalse;
} constchar* hdrgmPrefix = get_namespace_prefix(namespaces[0]);
// Require that hdrgm:Version="1.0" be present. constchar* version = get_attr(dom, node, hdrgmPrefix, "Version"); if (!version) {
SkCodecPrintf("Version attribute is absent.\n"); returnfalse;
} if (strcmp(version, "1.0") != 0) {
SkCodecPrintf("Version is \"%s\", not \"1.0\".\n", version); returnfalse;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.