[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:
parent
dc8ec31ce5
commit
0a145b77f7
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user