[svg] Implement feComposite, basic filter result storage
- Plumbing to store filter results by id and resolve them as inputs
when referenced
- Added implementation of feComposite filter
- Added call to resolve input in feColorMatrix
- Bugfix to SkSVGFilterType operator==
The tests filters-color-01-b and filters-composite-03-b should now be
passing.
Bug: skia:10841
Change-Id: I2cd099c60ac21710f25184806c5cc537656b42af
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/332723
Commit-Queue: Tyler Denniston <tdenniston@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/modules/svg/include/SkSVGFe.h b/modules/svg/include/SkSVGFe.h
index 7965a39..1031b15 100644
--- a/modules/svg/include/SkSVGFe.h
+++ b/modules/svg/include/SkSVGFe.h
@@ -18,11 +18,15 @@
~SkSVGFe() override = default;
static bool IsFilterEffect(const sk_sp<SkSVGNode>& node) {
- return node->tag() == SkSVGTag::kFeTurbulence || node->tag() == SkSVGTag::kFeColorMatrix;
+ return node->tag() == SkSVGTag::kFeTurbulence || node->tag() == SkSVGTag::kFeColorMatrix ||
+ node->tag() == SkSVGTag::kFeComposite;
}
sk_sp<SkImageFilter> makeImageFilter(const SkSVGRenderContext& ctx,
- SkSVGFilterContext* fctx) const;
+ const SkSVGFilterContext& fctx) const;
+
+ SVG_ATTR(In, SkSVGFeInputType, SkSVGFeInputType(SkSVGFeInputType::Type::kSourceGraphic))
+ SVG_ATTR(Result, SkSVGStringType, SkSVGStringType())
protected:
explicit SkSVGFe(SkSVGTag t) : INHERITED(t) {}
@@ -30,6 +34,8 @@
virtual sk_sp<SkImageFilter> onMakeImageFilter(const SkSVGRenderContext&,
const SkSVGFilterContext&) const = 0;
+ bool parseAndSetAttribute(const char*, const char*) override;
+
private:
using INHERITED = SkSVGHiddenContainer;
};
diff --git a/modules/svg/include/SkSVGFeComposite.h b/modules/svg/include/SkSVGFeComposite.h
new file mode 100644
index 0000000..9a6570a
--- /dev/null
+++ b/modules/svg/include/SkSVGFeComposite.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSVGFeComposite_DEFINED
+#define SkSVGFeComposite_DEFINED
+
+#include "include/core/SkBlendMode.h"
+#include "modules/svg/include/SkSVGFe.h"
+#include "modules/svg/include/SkSVGTypes.h"
+
+class SkSVGFeComposite final : public SkSVGFe {
+public:
+ ~SkSVGFeComposite() override = default;
+ static sk_sp<SkSVGFeComposite> Make() {
+ return sk_sp<SkSVGFeComposite>(new SkSVGFeComposite());
+ }
+
+ SVG_ATTR(In2, SkSVGFeInputType, SkSVGFeInputType())
+ SVG_ATTR(K1, SkSVGNumberType, SkSVGNumberType(0))
+ SVG_ATTR(K2, SkSVGNumberType, SkSVGNumberType(0))
+ SVG_ATTR(K3, SkSVGNumberType, SkSVGNumberType(0))
+ SVG_ATTR(K4, SkSVGNumberType, SkSVGNumberType(0))
+ SVG_ATTR(Operator, SkSVGFeCompositeOperator, SkSVGFeCompositeOperator::kOver)
+
+protected:
+ sk_sp<SkImageFilter> onMakeImageFilter(const SkSVGRenderContext&,
+ const SkSVGFilterContext&) const override;
+
+ bool parseAndSetAttribute(const char*, const char*) override;
+
+private:
+ SkSVGFeComposite() : INHERITED(SkSVGTag::kFeComposite) {}
+
+ static SkBlendMode BlendModeForOperator(SkSVGFeCompositeOperator);
+
+ using INHERITED = SkSVGFe;
+};
+
+#endif // SkSVGFeComposite_DEFINED
diff --git a/modules/svg/include/SkSVGFilterContext.h b/modules/svg/include/SkSVGFilterContext.h
index 9077c63..01a138b 100644
--- a/modules/svg/include/SkSVGFilterContext.h
+++ b/modules/svg/include/SkSVGFilterContext.h
@@ -10,23 +10,31 @@
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
+#include "include/core/SkString.h"
#include "include/private/SkTHash.h"
+#include "modules/svg/include/SkSVGTypes.h"
class SkImageFilter;
-class SkPicture;
-class SkString;
+class SkSVGFeInputType;
+class SkSVGRenderContext;
class SkSVGFilterContext {
public:
SkSVGFilterContext(const SkRect& filterEffectsRegion)
: fFilterEffectsRegion(filterEffectsRegion) {}
- sk_sp<SkImageFilter> findResultById(const SkString& id) const;
-
const SkRect& filterEffectsRegion() const { return fFilterEffectsRegion; }
+ void registerResult(const SkSVGStringType&, const sk_sp<SkImageFilter>&);
+
+ sk_sp<SkImageFilter> resolveInput(const SkSVGRenderContext&, const SkSVGFeInputType&) const;
+
private:
+ sk_sp<SkImageFilter> findResultById(const SkSVGStringType&) const;
+
SkRect fFilterEffectsRegion;
+
+ SkTHashMap<SkSVGStringType, sk_sp<SkImageFilter>> fResults;
};
#endif // SkSVGFilterContext_DEFINED
diff --git a/modules/svg/include/SkSVGNode.h b/modules/svg/include/SkSVGNode.h
index 26a9dd8..8b731a5 100644
--- a/modules/svg/include/SkSVGNode.h
+++ b/modules/svg/include/SkSVGNode.h
@@ -26,6 +26,7 @@
kDefs,
kEllipse,
kFeColorMatrix,
+ kFeComposite,
kFeTurbulence,
kFilter,
kG,
diff --git a/modules/svg/include/SkSVGTypes.h b/modules/svg/include/SkSVGTypes.h
index 3985aa1..e4f0fb7 100644
--- a/modules/svg/include/SkSVGTypes.h
+++ b/modules/svg/include/SkSVGTypes.h
@@ -501,7 +501,9 @@
explicit SkSVGFilterType(Type t) : fType(t) {}
explicit SkSVGFilterType(const SkString& iri) : fType(Type::kIRI), fIRI(iri) {}
- bool operator==(const SkSVGFilterType& other) const { return fType == other.fType; }
+ bool operator==(const SkSVGFilterType& other) const {
+ return fType == other.fType && fIRI == other.fIRI;
+ }
bool operator!=(const SkSVGFilterType& other) const { return !(*this == other); }
const SkString& iri() const {
@@ -516,6 +518,40 @@
SkString fIRI;
};
+class SkSVGFeInputType {
+public:
+ enum class Type {
+ kSourceGraphic,
+ kSourceAlpha,
+ kBackgroundImage,
+ kBackgroundAlpha,
+ kFillPaint,
+ kStrokePaint,
+ kFilterPrimitiveReference,
+ };
+
+ SkSVGFeInputType() : fType(Type::kSourceGraphic) {}
+ explicit SkSVGFeInputType(Type t) : fType(t) {}
+ explicit SkSVGFeInputType(const SkSVGStringType& id)
+ : fType(Type::kFilterPrimitiveReference), fId(id) {}
+
+ bool operator==(const SkSVGFeInputType& other) const {
+ return fType == other.fType && fId == other.fId;
+ }
+ bool operator!=(const SkSVGFeInputType& other) const { return !(*this == other); }
+
+ const SkString& id() const {
+ SkASSERT(fType == Type::kFilterPrimitiveReference);
+ return fId;
+ }
+
+ Type type() const { return fType; }
+
+private:
+ Type fType;
+ SkString fId;
+};
+
enum class SkSVGFeColorMatrixType {
kMatrix,
kSaturate,
@@ -525,6 +561,15 @@
using SkSVGFeColorMatrixValues = SkTDArray<SkSVGNumberType>;
+enum class SkSVGFeCompositeOperator {
+ kOver,
+ kIn,
+ kOut,
+ kAtop,
+ kXor,
+ kArithmetic,
+};
+
class SkSVGFeTurbulenceBaseFrequency {
public:
SkSVGFeTurbulenceBaseFrequency() : fFreqX(0), fFreqY(0) {}
diff --git a/modules/svg/src/SkSVGAttributeParser.cpp b/modules/svg/src/SkSVGAttributeParser.cpp
index 0f74906..361dcae 100644
--- a/modules/svg/src/SkSVGAttributeParser.cpp
+++ b/modules/svg/src/SkSVGAttributeParser.cpp
@@ -269,6 +269,16 @@
}, iri);
}
+template <>
+bool SkSVGAttributeParser::parse(SkSVGStringType* result) {
+ if (this->parseEOSToken()) {
+ return false;
+ }
+ *result = SkSVGStringType(fCurPos);
+ fCurPos += result->size();
+ return this->parseEOSToken();
+}
+
// https://www.w3.org/TR/SVG11/types.html#DataTypeNumber
bool SkSVGAttributeParser::parseNumber(SkSVGNumberType* number) {
// consume WS
diff --git a/modules/svg/src/SkSVGDOM.cpp b/modules/svg/src/SkSVGDOM.cpp
index 342b861..790d2f4 100644
--- a/modules/svg/src/SkSVGDOM.cpp
+++ b/modules/svg/src/SkSVGDOM.cpp
@@ -16,6 +16,7 @@
#include "modules/svg/include/SkSVGDefs.h"
#include "modules/svg/include/SkSVGEllipse.h"
#include "modules/svg/include/SkSVGFeColorMatrix.h"
+#include "modules/svg/include/SkSVGFeComposite.h"
#include "modules/svg/include/SkSVGFeTurbulence.h"
#include "modules/svg/include/SkSVGFilter.h"
#include "modules/svg/include/SkSVGG.h"
@@ -333,6 +334,7 @@
{ "defs" , []() -> sk_sp<SkSVGNode> { return SkSVGDefs::Make(); }},
{ "ellipse" , []() -> sk_sp<SkSVGNode> { return SkSVGEllipse::Make(); }},
{ "feColorMatrix" , []() -> sk_sp<SkSVGNode> { return SkSVGFeColorMatrix::Make(); }},
+ { "feComposite" , []() -> sk_sp<SkSVGNode> { return SkSVGFeComposite::Make(); }},
{ "feTurbulence" , []() -> sk_sp<SkSVGNode> { return SkSVGFeTurbulence::Make(); }},
{ "filter" , []() -> sk_sp<SkSVGNode> { return SkSVGFilter::Make(); }},
{ "g" , []() -> sk_sp<SkSVGNode> { return SkSVGG::Make(); }},
diff --git a/modules/svg/src/SkSVGFe.cpp b/modules/svg/src/SkSVGFe.cpp
index 058c05c..78ed49f 100644
--- a/modules/svg/src/SkSVGFe.cpp
+++ b/modules/svg/src/SkSVGFe.cpp
@@ -6,10 +6,40 @@
*/
#include "include/effects/SkImageFilters.h"
+#include "modules/svg/include/SkSVGAttributeParser.h"
#include "modules/svg/include/SkSVGFe.h"
-#include "modules/svg/include/SkSVGFilterContext.h"
sk_sp<SkImageFilter> SkSVGFe::makeImageFilter(const SkSVGRenderContext& ctx,
- SkSVGFilterContext* fctx) const {
- return this->onMakeImageFilter(ctx, *fctx);
+ const SkSVGFilterContext& fctx) const {
+ return this->onMakeImageFilter(ctx, fctx);
+}
+
+bool SkSVGFe::parseAndSetAttribute(const char* name, const char* value) {
+ return INHERITED::parseAndSetAttribute(name, value) ||
+ this->setIn(SkSVGAttributeParser::parse<SkSVGFeInputType>("in", name, value)) ||
+ this->setResult(SkSVGAttributeParser::parse<SkSVGStringType>("result", name, value));
+}
+
+template <> bool SkSVGAttributeParser::parse(SkSVGFeInputType* type) {
+ static constexpr std::tuple<const char*, SkSVGFeInputType::Type> gTypeMap[] = {
+ {"SourceGraphic", SkSVGFeInputType::Type::kSourceGraphic},
+ {"SourceAlpha", SkSVGFeInputType::Type::kSourceAlpha},
+ {"BackgroundImage", SkSVGFeInputType::Type::kBackgroundImage},
+ {"BackgroundAlpha", SkSVGFeInputType::Type::kBackgroundAlpha},
+ {"FillPaint", SkSVGFeInputType::Type::kFillPaint},
+ {"StrokePaint", SkSVGFeInputType::Type::kStrokePaint},
+ };
+
+ SkSVGStringType resultId;
+ SkSVGFeInputType::Type t;
+ bool parsedValue = false;
+ if (this->parseEnumMap(gTypeMap, &t)) {
+ *type = SkSVGFeInputType(t);
+ parsedValue = true;
+ } else if (parse(&resultId)) {
+ *type = SkSVGFeInputType(resultId);
+ parsedValue = true;
+ }
+
+ return parsedValue && this->parseEOSToken();
}
diff --git a/modules/svg/src/SkSVGFeColorMatrix.cpp b/modules/svg/src/SkSVGFeColorMatrix.cpp
index 4458f38..db1e3d5 100644
--- a/modules/svg/src/SkSVGFeColorMatrix.cpp
+++ b/modules/svg/src/SkSVGFeColorMatrix.cpp
@@ -91,10 +91,9 @@
sk_sp<SkImageFilter> SkSVGFeColorMatrix::onMakeImageFilter(const SkSVGRenderContext& ctx,
const SkSVGFilterContext& fctx) const {
- // TODO: "in" param should supply filter source
- const sk_sp<SkImageFilter> input = nullptr;
- return SkImageFilters::ColorFilter(
- SkColorFilters::Matrix(makeMatrixForType()), input, fctx.filterEffectsRegion());
+ return SkImageFilters::ColorFilter(SkColorFilters::Matrix(makeMatrixForType()),
+ fctx.resolveInput(ctx, this->getIn()),
+ fctx.filterEffectsRegion());
}
template <> bool SkSVGAttributeParser::parse(SkSVGFeColorMatrixValues* values) {
diff --git a/modules/svg/src/SkSVGFeComposite.cpp b/modules/svg/src/SkSVGFeComposite.cpp
new file mode 100644
index 0000000..488574f
--- /dev/null
+++ b/modules/svg/src/SkSVGFeComposite.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/effects/SkImageFilters.h"
+#include "modules/svg/include/SkSVGAttributeParser.h"
+#include "modules/svg/include/SkSVGFeComposite.h"
+#include "modules/svg/include/SkSVGFilterContext.h"
+#include "modules/svg/include/SkSVGRenderContext.h"
+
+bool SkSVGFeComposite::parseAndSetAttribute(const char* name, const char* value) {
+ return INHERITED::parseAndSetAttribute(name, value) ||
+ // SkSVGFeInputType parsing defined in SkSVGFe.cpp:
+ this->setIn2(SkSVGAttributeParser::parse<SkSVGFeInputType>("in2", name, value)) ||
+ this->setK1(SkSVGAttributeParser::parse<SkSVGNumberType>("k1", name, value)) ||
+ this->setK2(SkSVGAttributeParser::parse<SkSVGNumberType>("k2", name, value)) ||
+ this->setK3(SkSVGAttributeParser::parse<SkSVGNumberType>("k3", name, value)) ||
+ this->setK4(SkSVGAttributeParser::parse<SkSVGNumberType>("k4", name, value)) ||
+ this->setOperator(
+ SkSVGAttributeParser::parse<SkSVGFeCompositeOperator>("operator", name, value));
+}
+
+SkBlendMode SkSVGFeComposite::BlendModeForOperator(SkSVGFeCompositeOperator op) {
+ switch (op) {
+ case SkSVGFeCompositeOperator::kOver:
+ return SkBlendMode::kSrcOver;
+ case SkSVGFeCompositeOperator::kIn:
+ return SkBlendMode::kSrcIn;
+ case SkSVGFeCompositeOperator::kOut:
+ return SkBlendMode::kSrcOut;
+ case SkSVGFeCompositeOperator::kAtop:
+ return SkBlendMode::kSrcATop;
+ case SkSVGFeCompositeOperator::kXor:
+ return SkBlendMode::kXor;
+ case SkSVGFeCompositeOperator::kArithmetic:
+ // Arithmetic is not handled with a blend
+ SkASSERT(false);
+ return SkBlendMode::kSrcOver;
+ }
+
+ SkUNREACHABLE;
+}
+
+sk_sp<SkImageFilter> SkSVGFeComposite::onMakeImageFilter(const SkSVGRenderContext& ctx,
+ const SkSVGFilterContext& fctx) const {
+ const SkRect cropRect = fctx.filterEffectsRegion();
+ const sk_sp<SkImageFilter> background = fctx.resolveInput(ctx, fIn2);
+ const sk_sp<SkImageFilter> foreground = fctx.resolveInput(ctx, this->getIn());
+ if (fOperator == SkSVGFeCompositeOperator::kArithmetic) {
+ constexpr bool enforcePMColor = true;
+ return SkImageFilters::Arithmetic(
+ fK1, fK2, fK3, fK4, enforcePMColor, background, foreground, cropRect);
+ } else {
+ return SkImageFilters::Blend(
+ BlendModeForOperator(fOperator), background, foreground, cropRect);
+ }
+}
+
+template <> bool SkSVGAttributeParser::parse(SkSVGFeCompositeOperator* op) {
+ static constexpr std::tuple<const char*, SkSVGFeCompositeOperator> gOpMap[] = {
+ {"over", SkSVGFeCompositeOperator::kOver},
+ {"in", SkSVGFeCompositeOperator::kIn},
+ {"out", SkSVGFeCompositeOperator::kOut},
+ {"atop", SkSVGFeCompositeOperator::kAtop},
+ {"xor", SkSVGFeCompositeOperator::kXor},
+ {"arithmetic", SkSVGFeCompositeOperator::kArithmetic},
+ };
+
+ return this->parseEnumMap(gOpMap, op) && this->parseEOSToken();
+}
diff --git a/modules/svg/src/SkSVGFilter.cpp b/modules/svg/src/SkSVGFilter.cpp
index 5dc97520..f1f57ee 100644
--- a/modules/svg/src/SkSVGFilter.cpp
+++ b/modules/svg/src/SkSVGFilter.cpp
@@ -72,10 +72,13 @@
}
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);
+ const auto& feResultType = feNode.getResult();
+
+ // TODO: there are specific composition rules that need to be followed
+ filter = feNode.makeImageFilter(ctx, fctx);
+
+ if (!feResultType.isEmpty()) {
+ fctx.registerResult(feResultType, filter);
}
}
diff --git a/modules/svg/src/SkSVGFilterContext.cpp b/modules/svg/src/SkSVGFilterContext.cpp
index 743272a..ea9683b 100644
--- a/modules/svg/src/SkSVGFilterContext.cpp
+++ b/modules/svg/src/SkSVGFilterContext.cpp
@@ -7,7 +7,41 @@
#include "include/effects/SkImageFilters.h"
#include "modules/svg/include/SkSVGFilterContext.h"
+#include "modules/svg/include/SkSVGNode.h"
+#include "modules/svg/include/SkSVGRenderContext.h"
+#include "modules/svg/include/SkSVGTypes.h"
-sk_sp<SkImageFilter> SkSVGFilterContext::findResultById(const SkString& id) const {
- return nullptr;
+sk_sp<SkImageFilter> SkSVGFilterContext::findResultById(const SkSVGStringType& id) const {
+ const sk_sp<SkImageFilter>* res = fResults.find(id);
+ return res ? *res : nullptr;
+}
+
+void SkSVGFilterContext::registerResult(const SkSVGStringType& id,
+ const sk_sp<SkImageFilter>& result) {
+ SkASSERT(!id.isEmpty());
+ fResults[id] = result;
+}
+
+sk_sp<SkImageFilter> SkSVGFilterContext::resolveInput(const SkSVGRenderContext& ctx,
+ const SkSVGFeInputType& inputType) const {
+ switch (inputType.type()) {
+ case SkSVGFeInputType::Type::kSourceGraphic:
+ return nullptr;
+ case SkSVGFeInputType::Type::kFillPaint:
+ return SkImageFilters::Paint(*ctx.fillPaint());
+ case SkSVGFeInputType::Type::kStrokePaint: {
+ // The paint filter doesn't handle stroke paints properly, so convert to fill for
+ // simplicity.
+ // TODO: Paint filter is deprecated, but the replacement (SkShaders::*())
+ // requires some extra work to handle all paint features (gradients, etc).
+ SkPaint p = *ctx.strokePaint();
+ p.setStyle(SkPaint::kFill_Style);
+ return SkImageFilters::Paint(p);
+ }
+ case SkSVGFeInputType::Type::kFilterPrimitiveReference:
+ return findResultById(inputType.id());
+ default:
+ SkDebugf("unhandled filter input type %d\n", inputType.type());
+ return nullptr;
+ }
}
diff --git a/modules/svg/svg.gni b/modules/svg/svg.gni
index 14d7622..bd4a305 100644
--- a/modules/svg/svg.gni
+++ b/modules/svg/svg.gni
@@ -18,6 +18,7 @@
"$_include/SkSVGEllipse.h",
"$_include/SkSVGFe.h",
"$_include/SkSVGFeColorMatrix.h",
+ "$_include/SkSVGFeComposite.h",
"$_include/SkSVGFeTurbulence.h",
"$_include/SkSVGFilter.h",
"$_include/SkSVGFilterContext.h",
@@ -54,6 +55,7 @@
"$_src/SkSVGEllipse.cpp",
"$_src/SkSVGFe.cpp",
"$_src/SkSVGFeColorMatrix.cpp",
+ "$_src/SkSVGFeComposite.cpp",
"$_src/SkSVGFeTurbulence.cpp",
"$_src/SkSVGFilter.cpp",
"$_src/SkSVGFilterContext.cpp",