[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>
This commit is contained in:
Tyler Denniston 2020-10-27 15:02:02 -04:00 committed by Skia Commit-Bot
parent 532c74936b
commit f548a028ce
14 changed files with 68 additions and 8 deletions

View File

@ -24,6 +24,8 @@ protected:
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.

View File

@ -36,6 +36,8 @@ protected:
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 @@ private:
SkSVGStringType fHref;
SkSVGTransformType fGradientTransform = SkSVGTransformType(SkMatrix::I());
SkSVGSpreadMethod fSpreadMethod = SkSVGSpreadMethod(SkSVGSpreadMethod::Type::kPad);
SkSVGGradientUnits fGradientUnits = SkSVGGradientUnits(SkSVGGradientUnits::Type::kUserSpaceOnUse);
using INHERITED = SkSVGHiddenContainer;
};

View File

@ -73,7 +73,7 @@ public:
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 @@ protected:
virtual bool hasChildren() const { return false; }
virtual SkRect onObjectBoundingBox(const SkSVGLengthContext&) const {
virtual SkRect onObjectBoundingBox(const SkSVGRenderContext&) const {
return SkRect::MakeEmpty();
}

View File

@ -33,6 +33,8 @@ protected:
SkPath onAsPath(const SkSVGRenderContext&) const override;
SkRect onObjectBoundingBox(const SkSVGRenderContext&) const override;
private:
SkSVGRect();

View File

@ -26,6 +26,8 @@ protected:
void mapToParent(SkPath*) const;
void mapToParent(SkRect*) const;
private:
// FIXME: should be sparse
SkSVGTransformType fTransform;

View File

@ -33,6 +33,7 @@ protected:
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();

View File

@ -39,3 +39,15 @@ SkPath SkSVGContainer::onAsPath(const SkSVGRenderContext& ctx) const {
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;
}

View File

@ -126,7 +126,16 @@ bool SkSVGGradient::onAsPaint(const SkSVGRenderContext& ctx, SkPaint* paint) con
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;
}

View File

@ -59,7 +59,11 @@ sk_sp<SkShader> SkSVGLinearGradient::onMakeShader(const SkSVGRenderContext& ctx,
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);

View File

@ -48,8 +48,8 @@ SkPath SkSVGNode::asPath(const SkSVGRenderContext& ctx) const {
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 {

View File

@ -68,7 +68,11 @@ sk_sp<SkShader> SkSVGRadialGradient::onMakeShader(const SkSVGRenderContext& ctx,
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 @@ sk_sp<SkShader> SkSVGRadialGradient::onMakeShader(const SkSVGRenderContext& ctx,
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);

View File

@ -94,3 +94,7 @@ SkPath SkSVGRect::onAsPath(const SkSVGRenderContext& ctx) const {
return path;
}
SkRect SkSVGRect::onObjectBoundingBox(const SkSVGRenderContext& ctx) const {
return ctx.lengthContext().resolveRect(fX, fY, fWidth, fHeight);
}

View File

@ -41,3 +41,7 @@ void SkSVGTransformableNode::mapToParent(SkPath* path) const {
// transforms the path to parent node coordinates.
path->transform(fTransform);
}
void SkSVGTransformableNode::mapToParent(SkRect* rect) const {
*rect = fTransform.mapRect(*rect);
}

View File

@ -84,3 +84,15 @@ SkPath SkSVGUse::onAsPath(const SkSVGRenderContext& ctx) const {
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());
}