Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/skia/skia/src/codec/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 25 kB image not shown  

Quelle  SkXmp.cpp   Sprache: C

 
/*
 * 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/utils/SkParse.h"
#include "src/codec/SkCodecPriv.h"
#include "src/xml/SkDOM.h"

#include <cmath>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include <utility>

////////////////////////////////////////////////////////////////////////////////////////////////////
// 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 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;
    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::getGainmapInfoApple(float exifHdrHeadroom, 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;
    }

    // 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");
        return false;
    }

    // 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;
    return true;
}

bool SkXmpImpl::getGainmapInfoAdobe(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 = {0.f, 0.f, 0.f, 1.f};  // log2 value
    SkColor4f gainMapMax = {1.f, 1.f, 1.f, 1.f};  // log2 value
    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 = 0.f;  // log2 value
    SkScalar hdrCapacityMax = 1.f;  // log2 value

    // 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.
    if (!outGainmapInfo) {
        return true;
    }
    const float kLog2 = std::log(2.f);
    outGainmapInfo->fGainmapRatioMin = {std::exp(gainMapMin.fR * kLog2),
                                        std::exp(gainMapMin.fG * kLog2),
                                        std::exp(gainMapMin.fB * kLog2),
                                        1.f};
    outGainmapInfo->fGainmapRatioMax = {std::exp(gainMapMax.fR * kLog2),
                                        std::exp(gainMapMax.fG * kLog2),
                                        std::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 = std::exp(hdrCapacityMin * kLog2);
    outGainmapInfo->fDisplayRatioHdr = std::exp(hdrCapacityMax * kLog2);
    if (baseRenditionIsHDR) {
        outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kHDR;
    } else {
        outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
    }
    return true;
}

bool SkXmpImpl::parseDom(sk_sp<SkData> xmpData, bool extended) {
    SkDOM* dom = extended ? &fExtendedDOM : &fStandardDOM;
    auto xmpdStream = SkMemoryStream::Make(std::move(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(std::move(xmpData), /*extended=*/false)) {
        return nullptr;
    }
    return 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(std::move(xmpStandard), /*extended=*/false)) {
        return nullptr;
    }
    // Try to parse extended xmp but ignore the return value: if parsing fails, we'll still return
    // the standard xmp.
    (void)xmp->parseDom(std::move(xmpExtended), /*extended=*/true);
    return xmp;
}

Messung V0.5
C=87 H=97 G=91

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.