[svg] Implement <image> element

https://www.w3.org/TR/SVG11/struct.html#ImageElement

Supported reference types are data and non-local URIs. Resource loading
requires a ResourceProvider to have been set on the render context.

Not handled in this CL:
- preserveAspectRatio support
- SVG reference types (i.e. '<image xlink:href="file.svg" ...')

Bug: skia:10842
Change-Id: Ieec7569f60516b01fc847f4160d0733b1e3a1cf5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/364576
Commit-Queue: Tyler Denniston <tdenniston@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/modules/svg/src/SkSVGImage.cpp b/modules/svg/src/SkSVGImage.cpp
new file mode 100644
index 0000000..d872378
--- /dev/null
+++ b/modules/svg/src/SkSVGImage.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2021 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/SkCanvas.h"
+#include "include/core/SkImage.h"
+#include "modules/svg/include/SkSVGAttributeParser.h"
+#include "modules/svg/include/SkSVGImage.h"
+#include "modules/svg/include/SkSVGRenderContext.h"
+#include "modules/svg/include/SkSVGValue.h"
+#include "src/utils/SkOSPath.h"
+
+bool SkSVGImage::parseAndSetAttribute(const char* n, const char* v) {
+    return INHERITED::parseAndSetAttribute(n, v) ||
+           this->setX(SkSVGAttributeParser::parse<SkSVGLength>("x", n, v)) ||
+           this->setY(SkSVGAttributeParser::parse<SkSVGLength>("y", n, v)) ||
+           this->setWidth(SkSVGAttributeParser::parse<SkSVGLength>("width", n, v)) ||
+           this->setHeight(SkSVGAttributeParser::parse<SkSVGLength>("height", n, v)) ||
+           this->setHref(SkSVGAttributeParser::parse<SkSVGIRI>("xlink:href", n, v));
+}
+
+bool SkSVGImage::onPrepareToRender(SkSVGRenderContext* ctx) const {
+    // Width or height of 0 disables rendering per spec:
+    // https://www.w3.org/TR/SVG11/struct.html#ImageElement
+    return !fHref.iri().isEmpty() && fWidth.value() > 0 && fHeight.value() > 0 &&
+           INHERITED::onPrepareToRender(ctx);
+}
+
+static sk_sp<SkImage> LoadImage(const sk_sp<skresources::ResourceProvider>& rp,
+                                const SkSVGIRI& href) {
+    // TODO: It may be better to use the SVG 'id' attribute as the asset id, to allow
+    // clients to perform asset substitution based on element id.
+    sk_sp<skresources::ImageAsset> imageAsset;
+    switch (href.type()) {
+        case SkSVGIRI::Type::kDataURI:
+            imageAsset = rp->loadImageAsset("", href.iri().c_str(), "");
+            break;
+        case SkSVGIRI::Type::kNonlocal: {
+            const auto path = SkOSPath::Dirname(href.iri().c_str());
+            const auto name = SkOSPath::Basename(href.iri().c_str());
+            imageAsset = rp->loadImageAsset(path.c_str(), name.c_str(), /* id */ name.c_str());
+            break;
+        }
+        default:
+            SkDebugf("error loading image: unhandled iri type %d\n", href.type());
+            return nullptr;
+    }
+
+    return imageAsset ? imageAsset->getFrameData(0).image : nullptr;
+}
+
+void SkSVGImage::onRender(const SkSVGRenderContext& ctx) const {
+    const auto& rp = ctx.resourceProvider();
+    SkASSERT(rp);
+
+    // TODO: svg sources
+    sk_sp<SkImage> image = LoadImage(rp, fHref);
+    if (!image) {
+        SkDebugf("can't render image: load image failed\n");
+        return;
+    }
+
+    // TODO: preserveAspectRatio support
+    const SkSVGLengthContext& lctx = ctx.lengthContext();
+    const SkRect rect = lctx.resolveRect(fX, fY, fWidth, fHeight);
+    ctx.canvas()->drawImageRect(image, rect, SkSamplingOptions(SkFilterMode::kLinear));
+}
+
+SkPath SkSVGImage::onAsPath(const SkSVGRenderContext&) const { return {}; }
+
+SkRect SkSVGImage::onObjectBoundingBox(const SkSVGRenderContext& ctx) const {
+    const SkSVGLengthContext& lctx = ctx.lengthContext();
+    return lctx.resolveRect(fX, fY, fWidth, fHeight);
+}