diff --git a/modules/svg/include/SkSVGFilter.h b/modules/svg/include/SkSVGFilter.h index 2b1bf5f132..7a228dbeea 100644 --- a/modules/svg/include/SkSVGFilter.h +++ b/modules/svg/include/SkSVGFilter.h @@ -17,6 +17,8 @@ public: static sk_sp Make() { return sk_sp(new SkSVGFilter()); } + sk_sp 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; diff --git a/modules/svg/include/SkSVGRenderContext.h b/modules/svg/include/SkSVGRenderContext.h index 8b535f3e5c..4490a29eb4 100644 --- a/modules/svg/include/SkSVGRenderContext.h +++ b/modules/svg/include/SkSVGRenderContext.h @@ -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&); diff --git a/modules/svg/src/SkSVGFilter.cpp b/modules/svg/src/SkSVGFilter.cpp index 4e3ae57e65..5f22138a35 100644 --- a/modules/svg/src/SkSVGFilter.cpp +++ b/modules/svg/src/SkSVGFilter.cpp @@ -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 SkSVGFilter::buildFilterDAG(const SkSVGRenderContext& ctx) const { + sk_sp filter; + SkSVGFilterContext fctx(resolveFilterRegion(ctx)); + for (const auto& child : fChildren) { + if (!SkSVGFe::IsFilterElement(child)) { + continue; + } + + const auto& feNode = static_cast(*child); + sk_sp 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; +} diff --git a/modules/svg/src/SkSVGRenderContext.cpp b/modules/svg/src/SkSVGRenderContext.cpp index a1b7debc4b..b17cae13ef 100644 --- a/modules/svg/src/SkSVGRenderContext.cpp +++ b/modules/svg/src/SkSVGRenderContext.cpp @@ -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(node.get()); + sk_sp 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) {