[svg] Support preserveAspectRatio for images

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

We already had a function to compute the appropriate matrix, and since
we can share the functionality with other elements that establish a new
viewport (including svg, symbol, and a few others), this CL moves the
function to the SVG node base class.

Relevant test for images is struct-image-06.

Bug: skia:10842
Change-Id: I5d6261210d03959e28d0bd7189da7f4ea53abc03
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/366398
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
index d872378..6dec0e8 100644
--- a/modules/svg/src/SkSVGImage.cpp
+++ b/modules/svg/src/SkSVGImage.cpp
@@ -19,7 +19,9 @@
            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));
+           this->setHref(SkSVGAttributeParser::parse<SkSVGIRI>("xlink:href", n, v)) ||
+           this->setPreserveAspectRatio(SkSVGAttributeParser::parse<SkSVGPreserveAspectRatio>(
+                   "preserveAspectRatio", n, v));
 }
 
 bool SkSVGImage::onPrepareToRender(SkSVGRenderContext* ctx) const {
@@ -52,6 +54,12 @@
     return imageAsset ? imageAsset->getFrameData(0).image : nullptr;
 }
 
+SkRect SkSVGImage::resolveImageRect(const SkRect& viewBox, const SkRect& viewPort) const {
+    const SkMatrix m = ComputeViewboxMatrix(viewBox, viewPort, fPreserveAspectRatio);
+    // Map and place at x, y specified by image attributes
+    return m.mapRect(viewBox).makeOffset(viewPort.fLeft, viewPort.fTop);
+}
+
 void SkSVGImage::onRender(const SkSVGRenderContext& ctx) const {
     const auto& rp = ctx.resourceProvider();
     SkASSERT(rp);
@@ -63,10 +71,15 @@
         return;
     }
 
-    // TODO: preserveAspectRatio support
+    // Per spec: x, w, width, height attributes establish the new viewport.
     const SkSVGLengthContext& lctx = ctx.lengthContext();
-    const SkRect rect = lctx.resolveRect(fX, fY, fWidth, fHeight);
-    ctx.canvas()->drawImageRect(image, rect, SkSamplingOptions(SkFilterMode::kLinear));
+    const SkRect viewPort = lctx.resolveRect(fX, fY, fWidth, fHeight);
+    // Per spec: raster content has implicit viewbox of '0 0 width height'.
+    const SkRect viewBox = SkRect::Make(image->bounds());
+
+    ctx.canvas()->drawImageRect(image,
+                                this->resolveImageRect(viewBox, viewPort),
+                                SkSamplingOptions(SkFilterMode::kLinear));
 }
 
 SkPath SkSVGImage::onAsPath(const SkSVGRenderContext&) const { return {}; }