[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:
parent
2882e70ef3
commit
b5e29be9c4
@ -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;
|
||||
|
@ -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&);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user