[svg] Refactor object bounding box access

Introduce a helper (SkSVGRenderContext::obbTransform) and refactor all
obb clients to funnel their calls through it.

This will facilitate some follow up obb changes.

Bug: skia:11936
Change-Id: If0d81b0fc9148389c2cb4bff597057a2afa2fb1b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/402956
Reviewed-by: Tyler Denniston <tdenniston@google.com>
Commit-Queue: Florin Malita <fmalita@google.com>
diff --git a/modules/svg/src/SkSVGClipPath.cpp b/modules/svg/src/SkSVGClipPath.cpp
index 065a024..06ef8ae 100644
--- a/modules/svg/src/SkSVGClipPath.cpp
+++ b/modules/svg/src/SkSVGClipPath.cpp
@@ -20,12 +20,10 @@
 SkPath SkSVGClipPath::resolveClip(const SkSVGRenderContext& ctx) const {
     auto clip = this->asPath(ctx);
 
-    if (fClipPathUnits.type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
-        const auto obb = ctx.node()->objectBoundingBox(ctx);
-        const auto obb_transform = SkMatrix::Translate(obb.x(), obb.y()) *
-                                   SkMatrix::Scale(obb.width(), obb.height());
-        clip.transform(obb_transform);
-    }
+    const auto obbt = ctx.transformForCurrentOBB(fClipPathUnits);
+    const auto m = SkMatrix::Translate(obbt.offset.x, obbt.offset.y)
+                 * SkMatrix::Scale(obbt.scale.x, obbt.scale.y);
+    clip.transform(m);
 
     return clip;
 }
diff --git a/modules/svg/src/SkSVGFe.cpp b/modules/svg/src/SkSVGFe.cpp
index 4aebb57..37834bc 100644
--- a/modules/svg/src/SkSVGFe.cpp
+++ b/modules/svg/src/SkSVGFe.cpp
@@ -23,26 +23,7 @@
     const auto w = fWidth.isValid() ? *fWidth : SkSVGLength(100, SkSVGLength::Unit::kPercentage);
     const auto h = fHeight.isValid() ? *fHeight : SkSVGLength(100, SkSVGLength::Unit::kPercentage);
 
-    // Resolve the x/y/w/h boundary rect depending on primitiveUnits setting
-    SkRect boundaries;
-    switch (fctx.primitiveUnits().type()) {
-        case SkSVGObjectBoundingBoxUnits::Type::kUserSpaceOnUse:
-            boundaries = ctx.lengthContext().resolveRect(x, y, w, h);
-            break;
-        case SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox: {
-            SkASSERT(ctx.node());
-            const SkRect objBounds = ctx.node()->objectBoundingBox(ctx);
-            boundaries = SkSVGLengthContext({1, 1}).resolveRect(x, y, w, h);
-            boundaries = SkRect::MakeXYWH(objBounds.fLeft + boundaries.fLeft * objBounds.width(),
-                                          objBounds.fTop + boundaries.fTop * objBounds.height(),
-                                          boundaries.width() * objBounds.width(),
-                                          boundaries.height() * objBounds.height());
-
-            break;
-        }
-    }
-
-    return boundaries;
+    return ctx.resolveOBBRect(x, y, w, h, fctx.primitiveUnits());
 }
 
 static bool AnyIsStandardInput(const SkSVGFilterContext& fctx,
diff --git a/modules/svg/src/SkSVGFeDisplacementMap.cpp b/modules/svg/src/SkSVGFeDisplacementMap.cpp
index 966faa5..9698a55 100644
--- a/modules/svg/src/SkSVGFeDisplacementMap.cpp
+++ b/modules/svg/src/SkSVGFeDisplacementMap.cpp
@@ -36,10 +36,9 @@
 
     SkScalar scale = fScale;
     if (fctx.primitiveUnits().type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
-        SkASSERT(ctx.node());
-        const SkRect objBounds = ctx.node()->objectBoundingBox(ctx);
-        const SkSVGLengthContext lctx({objBounds.width(), objBounds.height()});
-        scale = lctx.resolve(SkSVGLength(scale, SkSVGLength::Unit::kPercentage),
+        const auto obbt = ctx.transformForCurrentOBB(fctx.primitiveUnits());
+        scale = SkSVGLengthContext({obbt.scale.x, obbt.scale.y})
+                    .resolve(SkSVGLength(scale, SkSVGLength::Unit::kPercentage),
                              SkSVGLengthContext::LengthType::kOther);
     }
 
diff --git a/modules/svg/src/SkSVGFeGaussianBlur.cpp b/modules/svg/src/SkSVGFeGaussianBlur.cpp
index 54ea011..1443845 100644
--- a/modules/svg/src/SkSVGFeGaussianBlur.cpp
+++ b/modules/svg/src/SkSVGFeGaussianBlur.cpp
@@ -20,17 +20,11 @@
 
 sk_sp<SkImageFilter> SkSVGFeGaussianBlur::onMakeImageFilter(const SkSVGRenderContext& ctx,
                                                             const SkSVGFilterContext& fctx) const {
-    SkScalar sigmaX = fStdDeviation.fX;
-    SkScalar sigmaY = fStdDeviation.fY;
-    if (fctx.primitiveUnits().type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
-        SkASSERT(ctx.node());
-        const SkRect objBounds = ctx.node()->objectBoundingBox(ctx);
-        sigmaX *= objBounds.width();
-        sigmaY *= objBounds.height();
-    }
+    const auto sigma = SkV2{fStdDeviation.fX, fStdDeviation.fY}
+                     * ctx.transformForCurrentOBB(fctx.primitiveUnits()).scale;
 
     return SkImageFilters::Blur(
-            sigmaX, sigmaY,
+            sigma.x, sigma.y,
             fctx.resolveInput(ctx, this->getIn(), this->resolveColorspace(ctx, fctx)),
             this->resolveFilterSubregion(ctx, fctx));
 }
diff --git a/modules/svg/src/SkSVGFeLighting.cpp b/modules/svg/src/SkSVGFeLighting.cpp
index 0882b00..31d50e2 100644
--- a/modules/svg/src/SkSVGFeLighting.cpp
+++ b/modules/svg/src/SkSVGFeLighting.cpp
@@ -71,16 +71,12 @@
                                      SkSVGNumberType x,
                                      SkSVGNumberType y,
                                      SkSVGNumberType z) const {
-    if (fctx.primitiveUnits().type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
-        SkASSERT(ctx.node());
-        const SkRect objBounds = ctx.node()->objectBoundingBox(ctx);
-        const SkSVGLengthContext lctx({objBounds.width(), objBounds.height()});
-        x = objBounds.left() + x * objBounds.width();
-        y = objBounds.top() + y * objBounds.height();
-        z = lctx.resolve(SkSVGLength(z * 100.f, SkSVGLength::Unit::kPercentage),
-                         SkSVGLengthContext::LengthType::kOther);
-    }
-    return SkPoint3::Make(x, y, z);
+    const auto obbt = ctx.transformForCurrentOBB(fctx.primitiveUnits());
+    const auto xy = SkV2{x,y} * obbt.scale + obbt.offset;
+    z = SkSVGLengthContext({obbt.scale.x, obbt.scale.y})
+            .resolve(SkSVGLength(z * 100.f, SkSVGLength::Unit::kPercentage),
+                     SkSVGLengthContext::LengthType::kOther);
+    return SkPoint3::Make(xy.x, xy.y, z);
 }
 
 bool SkSVGFeSpecularLighting::parseAndSetAttribute(const char* n, const char* v) {
diff --git a/modules/svg/src/SkSVGFeMorphology.cpp b/modules/svg/src/SkSVGFeMorphology.cpp
index 67310c8..77b834b 100644
--- a/modules/svg/src/SkSVGFeMorphology.cpp
+++ b/modules/svg/src/SkSVGFeMorphology.cpp
@@ -26,20 +26,13 @@
     const SkSVGColorspace colorspace = this->resolveColorspace(ctx, fctx);
     sk_sp<SkImageFilter> input = fctx.resolveInput(ctx, this->getIn(), colorspace);
 
-    SkScalar rx = fRadius.fX;
-    SkScalar ry = fRadius.fY;
-    if (fctx.primitiveUnits().type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
-        SkASSERT(ctx.node());
-        const SkRect objBounds = ctx.node()->objectBoundingBox(ctx);
-        rx *= objBounds.width();
-        ry *= objBounds.height();
-    }
-
+    const auto r = SkV2{fRadius.fX, fRadius.fY}
+                 * ctx.transformForCurrentOBB(fctx.primitiveUnits()).scale;
     switch (fOperator) {
         case Operator::kErode:
-            return SkImageFilters::Erode(rx, ry, input, cropRect);
+            return SkImageFilters::Erode(r.x, r.y, input, cropRect);
         case Operator::kDilate:
-            return SkImageFilters::Dilate(rx, ry, input, cropRect);
+            return SkImageFilters::Dilate(r.x, r.y, input, cropRect);
     }
 
     SkUNREACHABLE;
diff --git a/modules/svg/src/SkSVGFeOffset.cpp b/modules/svg/src/SkSVGFeOffset.cpp
index df29c83..057a404 100644
--- a/modules/svg/src/SkSVGFeOffset.cpp
+++ b/modules/svg/src/SkSVGFeOffset.cpp
@@ -20,15 +20,10 @@
 
 sk_sp<SkImageFilter> SkSVGFeOffset::onMakeImageFilter(const SkSVGRenderContext& ctx,
                                                       const SkSVGFilterContext& fctx) const {
-    SkScalar dx = this->getDx(), dy = this->getDy();
-    if (fctx.primitiveUnits().type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
-        SkASSERT(ctx.node());
-        const SkRect objBounds = ctx.node()->objectBoundingBox(ctx);
-        dx *= objBounds.width();
-        dy *= objBounds.height();
-    }
+    const auto d = SkV2{this->getDx(), this->getDy()}
+                 * ctx.transformForCurrentOBB(fctx.primitiveUnits()).scale;
 
     sk_sp<SkImageFilter> in =
             fctx.resolveInput(ctx, this->getIn(), this->resolveColorspace(ctx, fctx));
-    return SkImageFilters::Offset(dx, dy, std::move(in), this->resolveFilterSubregion(ctx, fctx));
+    return SkImageFilters::Offset(d.x, d.y, std::move(in), this->resolveFilterSubregion(ctx, fctx));
 }
diff --git a/modules/svg/src/SkSVGGradient.cpp b/modules/svg/src/SkSVGGradient.cpp
index 8a5ea8c..cf09ee5 100644
--- a/modules/svg/src/SkSVGGradient.cpp
+++ b/modules/svg/src/SkSVGGradient.cpp
@@ -86,14 +86,10 @@
                   SkTileMode::kMirror, "SkSVGSpreadMethod::Type is out of sync");
     const auto tileMode = static_cast<SkTileMode>(fSpreadMethod.type());
 
-    SkMatrix localMatrix = SkMatrix::I();
-    if (fGradientUnits.type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
-        SkASSERT(ctx.node());
-        const SkRect objBounds = ctx.node()->objectBoundingBox(ctx);
-        localMatrix.preTranslate(objBounds.fLeft, objBounds.fTop);
-        localMatrix.preScale(objBounds.width(), objBounds.height());
-    }
-    localMatrix.preConcat(fGradientTransform);
+    const auto obbt = ctx.transformForCurrentOBB(fGradientUnits);
+    const auto localMatrix = SkMatrix::Translate(obbt.offset.x, obbt.offset.y)
+                           * SkMatrix::Scale(obbt.scale.x, obbt.scale.y)
+                           * fGradientTransform;
 
     paint->setShader(this->onMakeShader(ctx, colors.begin(), pos.begin(), colors.count(), tileMode,
                                         localMatrix));
diff --git a/modules/svg/src/SkSVGMask.cpp b/modules/svg/src/SkSVGMask.cpp
index 9e32daf..a19246b 100644
--- a/modules/svg/src/SkSVGMask.cpp
+++ b/modules/svg/src/SkSVGMask.cpp
@@ -55,13 +55,9 @@
     // Something to consider if masking performance ever becomes an issue.
     lctx.canvas()->saveLayer(nullptr, &mask_filter);
 
-    if (fMaskContentUnits.type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
-        // Fot maskContentUnits == OBB the mask content is rendered in a normalized coordinate
-        // system, which maps to the node OBB.
-        const auto obb = lctx.node()->objectBoundingBox(ctx);
-        lctx.canvas()->translate(obb.x(), obb.y());
-        lctx.canvas()->scale(obb.width(), obb.height());
-    }
+    const auto obbt = ctx.transformForCurrentOBB(fMaskContentUnits);
+    lctx.canvas()->translate(obbt.offset.x, obbt.offset.y);
+    lctx.canvas()->scale(obbt.scale.x, obbt.scale.y);
 
     for (const auto& child : fChildren) {
         child->render(lctx);
diff --git a/modules/svg/src/SkSVGRenderContext.cpp b/modules/svg/src/SkSVGRenderContext.cpp
index 8d2094c..2358f15 100644
--- a/modules/svg/src/SkSVGRenderContext.cpp
+++ b/modules/svg/src/SkSVGRenderContext.cpp
@@ -467,6 +467,16 @@
     SkUNREACHABLE;
 }
 
+SkSVGRenderContext::OBBTransform
+SkSVGRenderContext::transformForCurrentOBB(SkSVGObjectBoundingBoxUnits u) const {
+    if (!fNode || u.type() == SkSVGObjectBoundingBoxUnits::Type::kUserSpaceOnUse) {
+        return {{0,0},{1,1}};
+    }
+
+    const auto obb = fNode->objectBoundingBox(*this);
+    return {{obb.x(), obb.y()}, {obb.width(), obb.height()}};
+}
+
 SkRect SkSVGRenderContext::resolveOBBRect(const SkSVGLength& x, const SkSVGLength& y,
                                           const SkSVGLength& w, const SkSVGLength& h,
                                           SkSVGObjectBoundingBoxUnits obbu) const {
@@ -477,13 +487,10 @@
     }
 
     auto r = lctx->resolveRect(x, y, w, h);
-    if (obbu.type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
-        const auto obb = fNode->objectBoundingBox(*this);
-        r = SkRect::MakeXYWH(obb.x() + r.x() * obb.width(),
-                             obb.y() + r.y() * obb.height(),
-                             r.width()  * obb.width(),
-                             r.height() * obb.height());
-    }
+    const auto obbt = this->transformForCurrentOBB(obbu);
 
-    return r;
+    return SkRect::MakeXYWH(obbt.scale.x * r.x() + obbt.offset.x,
+                            obbt.scale.y * r.y() + obbt.offset.y,
+                            obbt.scale.x * r.width(),
+                            obbt.scale.y * r.height());
 }