[svg] New layer for image filter if one is set

Prior to rendering a node, check the filter presentation attribute and
create a new canvas layer with a corresponding SkImageFilter set.

Also added the computation of the filter effect region and added a naive
(and incomplete) construction of the image filter DAG for a
<filter> element.

Bug: skia:10841
Change-Id: Ie94299757e059c39540ad316cddf438df5726d97
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/330619
Commit-Queue: Tyler Denniston <tdenniston@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Tyler Denniston 2020-11-02 16:16:57 -05:00 committed by Skia Commit-Bot
parent 2882e70ef3
commit b5e29be9c4
4 changed files with 70 additions and 0 deletions

View File

@ -17,6 +17,8 @@ public:
static sk_sp<SkSVGFilter> Make() { return sk_sp<SkSVGFilter>(new SkSVGFilter()); }
sk_sp<SkImageFilter> buildFilterDAG(const SkSVGRenderContext&) const;
SVG_ATTR(X, SkSVGLength, SkSVGLength(-10, SkSVGLength::Unit::kPercentage))
SVG_ATTR(Y, SkSVGLength, SkSVGLength(-10, SkSVGLength::Unit::kPercentage))
SVG_ATTR(Width, SkSVGLength, SkSVGLength(120, SkSVGLength::Unit::kPercentage))
@ -28,6 +30,8 @@ public:
private:
SkSVGFilter() : INHERITED(SkSVGTag::kFilter) {}
SkRect resolveFilterRegion(const SkSVGRenderContext&) const;
void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
using INHERITED = SkSVGHiddenContainer;

View File

@ -130,6 +130,7 @@ private:
SkSVGRenderContext& operator=(const SkSVGRenderContext&) = delete;
void applyOpacity(SkScalar opacity, uint32_t flags);
void applyFilter(const SkSVGFilterType&);
void applyClip(const SkSVGClip&);
void updatePaintsWithCurrentColor(const SkSVGPresentationAttributes&);

View File

@ -43,3 +43,41 @@ void SkSVGFilter::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
this->INHERITED::onSetAttribute(attr, v);
}
}
SkRect SkSVGFilter::resolveFilterRegion(const SkSVGRenderContext& ctx) const {
const SkSVGLengthContext lctx =
fFilterUnits.type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox
? SkSVGLengthContext({1, 1})
: ctx.lengthContext();
SkRect filterRegion = lctx.resolveRect(fX, fY, fWidth, fHeight);
if (fFilterUnits.type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
SkASSERT(ctx.node());
const SkRect objBounds = ctx.node()->objectBoundingBox(ctx);
filterRegion = SkRect::MakeXYWH(objBounds.fLeft + filterRegion.fLeft * objBounds.fLeft,
objBounds.fTop + filterRegion.fTop * objBounds.fTop,
filterRegion.width() * objBounds.width(),
filterRegion.height() * objBounds.height());
}
return filterRegion;
}
sk_sp<SkImageFilter> SkSVGFilter::buildFilterDAG(const SkSVGRenderContext& ctx) const {
sk_sp<SkImageFilter> filter;
SkSVGFilterContext fctx(resolveFilterRegion(ctx));
for (const auto& child : fChildren) {
if (!SkSVGFe::IsFilterElement(child)) {
continue;
}
const auto& feNode = static_cast<const SkSVGFe&>(*child);
sk_sp<SkImageFilter> imageFilter = feNode.makeImageFilter(ctx, &fctx);
if (imageFilter) {
// TODO: there are specific composition rules that need to be followed
filter = SkImageFilters::Compose(imageFilter, filter);
}
}
return filter;
}

View File

@ -8,10 +8,12 @@
#include "modules/svg/include/SkSVGRenderContext.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkPath.h"
#include "include/effects/SkDashPathEffect.h"
#include "include/private/SkTo.h"
#include "modules/svg/include/SkSVGAttribute.h"
#include "modules/svg/include/SkSVGFilter.h"
#include "modules/svg/include/SkSVGNode.h"
#include "modules/svg/include/SkSVGTypes.h"
@ -430,6 +432,11 @@ void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttr
if (auto* clip = attrs.fClipPath.getMaybeNull()) {
this->applyClip(*clip);
}
// TODO: when both a filter and opacity are present, we can apply both with a single layer
if (auto* filter = attrs.fFilter.getMaybeNull()) {
this->applyFilter(*filter);
}
}
void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags) {
@ -462,6 +469,26 @@ void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags) {
}
}
void SkSVGRenderContext::applyFilter(const SkSVGFilterType& filter) {
if (filter.type() != SkSVGFilterType::Type::kIRI) {
return;
}
const auto node = this->findNodeById(filter.iri());
if (!node || node->tag() != SkSVGTag::kFilter) {
return;
}
const SkSVGFilter* filterNode = reinterpret_cast<const SkSVGFilter*>(node.get());
sk_sp<SkImageFilter> imageFilter = filterNode->buildFilterDAG(*this);
if (imageFilter) {
SkPaint filterPaint;
filterPaint.setImageFilter(imageFilter);
// Balanced in the destructor, via restoreToCount().
fCanvas->saveLayer(nullptr, &filterPaint);
}
}
void SkSVGRenderContext::saveOnce() {
// The canvas only needs to be saved once, per local SkSVGRenderContext.
if (fCanvas->getSaveCount() == fCanvasSaveCount) {