[svg] Compute and use filter primitive subregion

Spec: https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion

The filter primitive subregion restricts the output of a specific <fe*>
node in a filter DAG. By default it's equal to the union of subregions
of all input filters, or the filter region if no inputs exist. If
x/y/w/h are specified on the <fe*> node, those are used to bound the
primitive subregion instead.

In this CL:
- Implement the computation of the primitive subregion in
  SkSVGFe::resolveFilterSubregion
- Add primitiveUnits to filter context
- Change result registration (by string ID) in filter context to include
  the primitive subregion of that result. This is needed because filters
  referencing previous results need access to those primitive subregions
  to compute the union.

Bug: skia:10841
Change-Id: I66fbb4979e3c65cb5e5cc61f98286ec7ad023438
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/344666
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Tyler Denniston <tdenniston@google.com>
This commit is contained in:
Tyler Denniston 2021-01-11 10:51:41 -05:00 committed by Skia Commit-Bot
parent dc8ec31ce5
commit 0a145b77f7
5 changed files with 101 additions and 12 deletions

View File

@ -46,6 +46,13 @@ protected:
bool parseAndSetAttribute(const char*, const char*) override;
private:
/**
* Resolves the rect specified by the x, y, width and height attributes (if specified) on this
* filter effect. These attributes are resolved according to the given length context and
* the value of 'primitiveUnits' on the parent <filter> element.
*/
SkRect resolveBoundaries(const SkSVGRenderContext&, const SkSVGFilterContext&) const;
using INHERITED = SkSVGHiddenContainer;
};

View File

@ -20,21 +20,33 @@ class SkSVGRenderContext;
class SkSVGFilterContext {
public:
SkSVGFilterContext(const SkRect& filterEffectsRegion)
: fFilterEffectsRegion(filterEffectsRegion) {}
SkSVGFilterContext(const SkRect& filterEffectsRegion,
const SkSVGObjectBoundingBoxUnits& primitiveUnits)
: fFilterEffectsRegion(filterEffectsRegion), fPrimitiveUnits(primitiveUnits) {}
const SkRect& filterEffectsRegion() const { return fFilterEffectsRegion; }
void registerResult(const SkSVGStringType&, const sk_sp<SkImageFilter>&);
const SkRect& filterPrimitiveSubregion(const SkSVGFeInputType&) const;
const SkSVGObjectBoundingBoxUnits& primitiveUnits() const { return fPrimitiveUnits; }
void registerResult(const SkSVGStringType&, const sk_sp<SkImageFilter>&, const SkRect&);
sk_sp<SkImageFilter> resolveInput(const SkSVGRenderContext&, const SkSVGFeInputType&) const;
private:
struct Result {
sk_sp<SkImageFilter> fImageFilter;
SkRect fFilterSubregion;
};
sk_sp<SkImageFilter> findResultById(const SkSVGStringType&) const;
SkRect fFilterEffectsRegion;
SkTHashMap<SkSVGStringType, sk_sp<SkImageFilter>> fResults;
SkSVGObjectBoundingBoxUnits fPrimitiveUnits;
SkTHashMap<SkSVGStringType, Result> fResults;
};
#endif // SkSVGFilterContext_DEFINED

View File

@ -9,16 +9,78 @@
#include "modules/svg/include/SkSVGAttributeParser.h"
#include "modules/svg/include/SkSVGFe.h"
#include "modules/svg/include/SkSVGFilterContext.h"
#include "modules/svg/include/SkSVGRenderContext.h"
sk_sp<SkImageFilter> SkSVGFe::makeImageFilter(const SkSVGRenderContext& ctx,
const SkSVGFilterContext& fctx) const {
return this->onMakeImageFilter(ctx, fctx);
}
SkRect SkSVGFe::resolveBoundaries(const SkSVGRenderContext& ctx,
const SkSVGFilterContext& fctx) const {
const auto x = fX.isValid() ? *fX : SkSVGLength(0, SkSVGLength::Unit::kPercentage);
const auto y = fY.isValid() ? *fY : SkSVGLength(0, SkSVGLength::Unit::kPercentage);
const auto w = fWidth.isValid() ? *fWidth : SkSVGLength(100, SkSVGLength::Unit::kPercentage);
const auto h = fHeight.isValid() ? *fHeight : SkSVGLength(100, SkSVGLength::Unit::kPercentage);
// Resolve the x/y/w/h boundary rect depending on primitiveUnits setting
SkRect boundaries;
switch (fctx.primitiveUnits().type()) {
case SkSVGObjectBoundingBoxUnits::Type::kUserSpaceOnUse:
boundaries = ctx.lengthContext().resolveRect(x, y, w, h);
break;
case SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox: {
SkASSERT(ctx.node());
const SkRect objBounds = ctx.node()->objectBoundingBox(ctx);
boundaries = SkSVGLengthContext({1, 1}).resolveRect(x, y, w, h);
boundaries = SkRect::MakeXYWH(objBounds.fLeft + boundaries.fLeft * objBounds.width(),
objBounds.fTop + boundaries.fTop * objBounds.height(),
boundaries.width() * objBounds.width(),
boundaries.height() * objBounds.height());
break;
}
}
return boundaries;
}
SkRect SkSVGFe::resolveFilterSubregion(const SkSVGRenderContext& ctx,
const SkSVGFilterContext& fctx) const {
// TODO: calculate primitive subregion
return fctx.filterEffectsRegion();
// From https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion,
// the default filter effect subregion is equal to the union of the subregions defined
// for all "referenced nodes" (filter effect inputs). If there are no inputs, the
// default subregion is equal to the filter effects region
// (https://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion).
const std::vector<SkSVGFeInputType> inputs = this->getInputs();
SkRect subregion;
if (inputs.empty()) {
subregion = fctx.filterEffectsRegion();
} else {
subregion = fctx.filterPrimitiveSubregion(inputs[0]);
for (size_t i = 1; i < inputs.size(); i++) {
subregion.join(fctx.filterPrimitiveSubregion(inputs[i]));
}
}
// Next resolve the rect specified by the x, y, width, height attributes on this filter effect.
// If those attributes were given, they override the corresponding attribute of the default
// filter effect subregion calculated above.
const SkRect boundaries = this->resolveBoundaries(ctx, fctx);
if (fX.isValid()) {
subregion.fLeft = boundaries.fLeft;
}
if (fY.isValid()) {
subregion.fTop = boundaries.fTop;
}
if (fWidth.isValid()) {
subregion.fRight = subregion.fLeft + boundaries.width();
}
if (fHeight.isValid()) {
subregion.fBottom = subregion.fTop + boundaries.height();
}
return subregion;
}
bool SkSVGFe::parseAndSetAttribute(const char* name, const char* value) {

View File

@ -45,7 +45,7 @@ SkRect SkSVGFilter::resolveFilterRegion(const SkSVGRenderContext& ctx) const {
sk_sp<SkImageFilter> SkSVGFilter::buildFilterDAG(const SkSVGRenderContext& ctx) const {
sk_sp<SkImageFilter> filter;
SkSVGFilterContext fctx(resolveFilterRegion(ctx));
SkSVGFilterContext fctx(resolveFilterRegion(ctx), fPrimitiveUnits);
for (const auto& child : fChildren) {
if (!SkSVGFe::IsFilterEffect(child)) {
continue;
@ -58,7 +58,7 @@ sk_sp<SkImageFilter> SkSVGFilter::buildFilterDAG(const SkSVGRenderContext& ctx)
filter = feNode.makeImageFilter(ctx, fctx);
if (!feResultType.isEmpty()) {
fctx.registerResult(feResultType, filter);
fctx.registerResult(feResultType, filter, feNode.resolveFilterSubregion(ctx, fctx));
}
}

View File

@ -12,14 +12,22 @@
#include "modules/svg/include/SkSVGTypes.h"
sk_sp<SkImageFilter> SkSVGFilterContext::findResultById(const SkSVGStringType& id) const {
const sk_sp<SkImageFilter>* res = fResults.find(id);
return res ? *res : nullptr;
const Result* res = fResults.find(id);
return res ? res->fImageFilter : nullptr;
}
const SkRect& SkSVGFilterContext::filterPrimitiveSubregion(const SkSVGFeInputType& input) const {
const Result* res = input.type() == SkSVGFeInputType::Type::kFilterPrimitiveReference
? fResults.find(input.id())
: nullptr;
return res ? res->fFilterSubregion : fFilterEffectsRegion;
}
void SkSVGFilterContext::registerResult(const SkSVGStringType& id,
const sk_sp<SkImageFilter>& result) {
const sk_sp<SkImageFilter>& result,
const SkRect& subregion) {
SkASSERT(!id.isEmpty());
fResults[id] = result;
fResults[id] = {result, subregion};
}
sk_sp<SkImageFilter> SkSVGFilterContext::resolveInput(const SkSVGRenderContext& ctx,