[svg] Implement gradientUnits="objectBoundingBox"
Currently only works properly when filling rects, since that is the only
node that implements bounds computation with this CL.
Summary of changes:
- Scale gradient coords by object bounds when units are
kObjectBoundingBox.
- Make fGradientUnits protected instead of private.
- Change default value of fGradientUnits to kObjectBoundingBox, which
is the default according to the spec.
- Change SkSVGNode::onObjectBoundingBox to take a full render context
instead of length context.
- Implement bounds computation for SkSVGRect, SkSVGContainer and
SkSVGUse.
Bug: skia:10842
Change-Id: I2e999985e67644e50da7f681fde190bcf4823eec
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/329223
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Tyler Denniston <tdenniston@google.com>
diff --git a/modules/svg/include/SkSVGContainer.h b/modules/svg/include/SkSVGContainer.h
index 4b386ec..92ab80f 100644
--- a/modules/svg/include/SkSVGContainer.h
+++ b/modules/svg/include/SkSVGContainer.h
@@ -24,6 +24,8 @@
SkPath onAsPath(const SkSVGRenderContext&) const override;
+ SkRect onObjectBoundingBox(const SkSVGRenderContext&) const override;
+
bool hasChildren() const final;
// TODO: add some sort of child iterator, and hide the container.
diff --git a/modules/svg/include/SkSVGGradient.h b/modules/svg/include/SkSVGGradient.h
index 301a698..175d2fc 100644
--- a/modules/svg/include/SkSVGGradient.h
+++ b/modules/svg/include/SkSVGGradient.h
@@ -36,6 +36,8 @@
const SkColor*, const SkScalar*, int count,
SkTileMode, const SkMatrix& localMatrix) const = 0;
+ SkSVGGradientUnits fGradientUnits = SkSVGGradientUnits(SkSVGGradientUnits::Type::kObjectBoundingBox);
+
private:
using StopPositionArray = SkSTArray<2, SkScalar, true>;
using StopColorArray = SkSTArray<2, SkColor, true>;
@@ -45,7 +47,6 @@
SkSVGStringType fHref;
SkSVGTransformType fGradientTransform = SkSVGTransformType(SkMatrix::I());
SkSVGSpreadMethod fSpreadMethod = SkSVGSpreadMethod(SkSVGSpreadMethod::Type::kPad);
- SkSVGGradientUnits fGradientUnits = SkSVGGradientUnits(SkSVGGradientUnits::Type::kUserSpaceOnUse);
using INHERITED = SkSVGHiddenContainer;
};
diff --git a/modules/svg/include/SkSVGNode.h b/modules/svg/include/SkSVGNode.h
index 001b5ca..9017f88 100644
--- a/modules/svg/include/SkSVGNode.h
+++ b/modules/svg/include/SkSVGNode.h
@@ -73,7 +73,7 @@
void render(const SkSVGRenderContext&) const;
bool asPaint(const SkSVGRenderContext&, SkPaint*) const;
SkPath asPath(const SkSVGRenderContext&) const;
- SkRect objectBoundingBox(const SkSVGLengthContext&) const;
+ SkRect objectBoundingBox(const SkSVGRenderContext&) const;
void setAttribute(SkSVGAttribute, const SkSVGValue&);
bool setAttribute(const char* attributeName, const char* attributeValue);
@@ -121,7 +121,7 @@
virtual bool hasChildren() const { return false; }
- virtual SkRect onObjectBoundingBox(const SkSVGLengthContext&) const {
+ virtual SkRect onObjectBoundingBox(const SkSVGRenderContext&) const {
return SkRect::MakeEmpty();
}
diff --git a/modules/svg/include/SkSVGRect.h b/modules/svg/include/SkSVGRect.h
index 507d9d9..cd91187 100644
--- a/modules/svg/include/SkSVGRect.h
+++ b/modules/svg/include/SkSVGRect.h
@@ -33,6 +33,8 @@
SkPath onAsPath(const SkSVGRenderContext&) const override;
+ SkRect onObjectBoundingBox(const SkSVGRenderContext&) const override;
+
private:
SkSVGRect();
diff --git a/modules/svg/include/SkSVGTransformableNode.h b/modules/svg/include/SkSVGTransformableNode.h
index 55e0c0a..a60c347 100644
--- a/modules/svg/include/SkSVGTransformableNode.h
+++ b/modules/svg/include/SkSVGTransformableNode.h
@@ -26,6 +26,8 @@
void mapToParent(SkPath*) const;
+ void mapToParent(SkRect*) const;
+
private:
// FIXME: should be sparse
SkSVGTransformType fTransform;
diff --git a/modules/svg/include/SkSVGUse.h b/modules/svg/include/SkSVGUse.h
index 4a87f99..3b430bc 100644
--- a/modules/svg/include/SkSVGUse.h
+++ b/modules/svg/include/SkSVGUse.h
@@ -33,6 +33,7 @@
bool onPrepareToRender(SkSVGRenderContext*) const override;
void onRender(const SkSVGRenderContext&) const override;
SkPath onAsPath(const SkSVGRenderContext&) const override;
+ SkRect onObjectBoundingBox(const SkSVGRenderContext&) const override;
private:
SkSVGUse();
diff --git a/modules/svg/src/SkSVGContainer.cpp b/modules/svg/src/SkSVGContainer.cpp
index 961194c..72d2b7b 100644
--- a/modules/svg/src/SkSVGContainer.cpp
+++ b/modules/svg/src/SkSVGContainer.cpp
@@ -39,3 +39,15 @@
this->mapToParent(&path);
return path;
}
+
+SkRect SkSVGContainer::onObjectBoundingBox(const SkSVGRenderContext& ctx) const {
+ SkRect bounds = SkRect::MakeEmpty();
+
+ for (int i = 0; i < fChildren.count(); ++i) {
+ const SkRect childBounds = fChildren[i]->objectBoundingBox(ctx);
+ bounds.join(childBounds);
+ }
+
+ this->mapToParent(&bounds);
+ return bounds;
+}
diff --git a/modules/svg/src/SkSVGGradient.cpp b/modules/svg/src/SkSVGGradient.cpp
index 66d0d38..9500681 100644
--- a/modules/svg/src/SkSVGGradient.cpp
+++ b/modules/svg/src/SkSVGGradient.cpp
@@ -126,7 +126,16 @@
SkTileMode::kMirror, "SkSVGSpreadMethod::Type is out of sync");
const auto tileMode = static_cast<SkTileMode>(fSpreadMethod.type());
+ SkMatrix localMatrix = SkMatrix::I();
+ if (fGradientUnits.type() == SkSVGGradientUnits::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);
+
paint->setShader(this->onMakeShader(ctx, colors.begin(), pos.begin(), colors.count(), tileMode,
- fGradientTransform));
+ localMatrix));
return true;
}
diff --git a/modules/svg/src/SkSVGLinearGradient.cpp b/modules/svg/src/SkSVGLinearGradient.cpp
index 436428d..1579942 100644
--- a/modules/svg/src/SkSVGLinearGradient.cpp
+++ b/modules/svg/src/SkSVGLinearGradient.cpp
@@ -59,7 +59,11 @@
const SkColor* colors, const SkScalar* pos,
int count, SkTileMode tm,
const SkMatrix& localMatrix) const {
- const auto& lctx = ctx.lengthContext();
+ const SkSVGLengthContext lctx =
+ fGradientUnits.type() == SkSVGGradientUnits::Type::kObjectBoundingBox
+ ? SkSVGLengthContext({1, 1})
+ : ctx.lengthContext();
+
const auto x1 = lctx.resolve(fX1, SkSVGLengthContext::LengthType::kHorizontal);
const auto y1 = lctx.resolve(fY1, SkSVGLengthContext::LengthType::kVertical);
const auto x2 = lctx.resolve(fX2, SkSVGLengthContext::LengthType::kHorizontal);
diff --git a/modules/svg/src/SkSVGNode.cpp b/modules/svg/src/SkSVGNode.cpp
index cf12945..07f3a47 100644
--- a/modules/svg/src/SkSVGNode.cpp
+++ b/modules/svg/src/SkSVGNode.cpp
@@ -48,8 +48,8 @@
return path;
}
-SkRect SkSVGNode::objectBoundingBox(const SkSVGLengthContext& lctx) const {
- return this->onObjectBoundingBox(lctx);
+SkRect SkSVGNode::objectBoundingBox(const SkSVGRenderContext& ctx) const {
+ return this->onObjectBoundingBox(ctx);
}
bool SkSVGNode::onPrepareToRender(SkSVGRenderContext* ctx) const {
diff --git a/modules/svg/src/SkSVGRadialGradient.cpp b/modules/svg/src/SkSVGRadialGradient.cpp
index 4d311a6..0300248 100644
--- a/modules/svg/src/SkSVGRadialGradient.cpp
+++ b/modules/svg/src/SkSVGRadialGradient.cpp
@@ -68,7 +68,11 @@
const SkColor* colors, const SkScalar* pos,
int count, SkTileMode tm,
const SkMatrix& m) const {
- const auto& lctx = ctx.lengthContext();
+ const SkSVGLengthContext lctx =
+ fGradientUnits.type() == SkSVGGradientUnits::Type::kObjectBoundingBox
+ ? SkSVGLengthContext({1, 1})
+ : ctx.lengthContext();
+
const auto r = lctx.resolve(fR , SkSVGLengthContext::LengthType::kOther);
const auto center = SkPoint::Make(
lctx.resolve(fCx, SkSVGLengthContext::LengthType::kHorizontal),
@@ -79,6 +83,9 @@
fFy.isValid() ? lctx.resolve(*fFy, SkSVGLengthContext::LengthType::kVertical)
: center.y());
+ // TODO: Handle r == 0 which has a specific meaning according to the spec
+ SkASSERT(r != 0);
+
return center == focal
? SkGradientShader::MakeRadial(center, r, colors, pos, count, tm, 0, &m)
: SkGradientShader::MakeTwoPointConical(focal, 0, center, r, colors, pos, count, tm, 0, &m);
diff --git a/modules/svg/src/SkSVGRect.cpp b/modules/svg/src/SkSVGRect.cpp
index 9f5e3a9..74bce16 100644
--- a/modules/svg/src/SkSVGRect.cpp
+++ b/modules/svg/src/SkSVGRect.cpp
@@ -94,3 +94,7 @@
return path;
}
+
+SkRect SkSVGRect::onObjectBoundingBox(const SkSVGRenderContext& ctx) const {
+ return ctx.lengthContext().resolveRect(fX, fY, fWidth, fHeight);
+}
diff --git a/modules/svg/src/SkSVGTransformableNode.cpp b/modules/svg/src/SkSVGTransformableNode.cpp
index f76f2c4..55f7550 100644
--- a/modules/svg/src/SkSVGTransformableNode.cpp
+++ b/modules/svg/src/SkSVGTransformableNode.cpp
@@ -41,3 +41,7 @@
// transforms the path to parent node coordinates.
path->transform(fTransform);
}
+
+void SkSVGTransformableNode::mapToParent(SkRect* rect) const {
+ *rect = fTransform.mapRect(*rect);
+}
diff --git a/modules/svg/src/SkSVGUse.cpp b/modules/svg/src/SkSVGUse.cpp
index ea510ea..bf4457c 100644
--- a/modules/svg/src/SkSVGUse.cpp
+++ b/modules/svg/src/SkSVGUse.cpp
@@ -84,3 +84,15 @@
return ref->asPath(ctx);
}
+
+SkRect SkSVGUse::onObjectBoundingBox(const SkSVGRenderContext& ctx) const {
+ const auto ref = ctx.findNodeById(fHref);
+ if (!ref) {
+ return SkRect::MakeEmpty();
+ }
+
+ const SkRect bounds = ref->objectBoundingBox(ctx);
+ const SkScalar x = ctx.lengthContext().resolve(fX, SkSVGLengthContext::LengthType::kHorizontal);
+ const SkScalar y = ctx.lengthContext().resolve(fY, SkSVGLengthContext::LengthType::kVertical);
+ return SkRect::MakeXYWH(x, y, bounds.width(), bounds.height());
+}