[svg] Add plumbing for color-interpolation-filters property

The default colorspace for filter effects is linear RGB, as specified in
https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationProperties.
Currently we perform all filtering in the destination colorspace. This
CL adds the new presentation attribute with the default setting
(according to the spec) of linear RGB.

This CL does not actually implement any colorspace transformations for
filters.

Bug: skia:10841
Change-Id: Id778ad3fa5cb6e0aed756584a50880edd9d82e2b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/352738
Commit-Queue: Tyler Denniston <tdenniston@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Tyler Denniston 2021-01-13 12:08:04 -05:00 committed by Skia Commit-Bot
parent bd747cb1e7
commit 7bb85dbb5e
10 changed files with 111 additions and 56 deletions

View File

@ -16,6 +16,7 @@ class SkSVGRenderContext;
enum class SkSVGAttribute {
kClipRule,
kColor,
kColorInterpolationFilters,
kCx, // <circle>, <ellipse>, <radialGradient>: center x position
kCy, // <circle>, <ellipse>, <radialGradient>: center y position
kD,
@ -89,6 +90,7 @@ struct SkSVGPresentationAttributes {
SkSVGProperty<SkSVGVisibility, true> fVisibility;
SkSVGProperty<SkSVGColorType , true> fColor;
SkSVGProperty<SkSVGColorspace, true> fColorInterpolationFilters;
SkSVGProperty<SkSVGFontFamily, true> fFontFamily;
SkSVGProperty<SkSVGFontStyle , true> fFontStyle;

View File

@ -29,6 +29,16 @@ public:
// https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion
SkRect resolveFilterSubregion(const SkSVGRenderContext&, const SkSVGFilterContext&) const;
/**
* Resolves the colorspace within which this filter effect should be applied.
* Spec: https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationProperties
* 'color-interpolation-filters' property.
*/
SkSVGColorspace resolveColorspace(const SkSVGRenderContext&) const;
/** Propagates any inherited presentation attributes in the given context. */
void applyProperties(SkSVGRenderContext*) const;
SVG_ATTR(In, SkSVGFeInputType, SkSVGFeInputType(SkSVGFeInputType::Type::kSourceGraphic))
SVG_ATTR(Result, SkSVGStringType, SkSVGStringType())
SVG_OPTIONAL_ATTR(X, SkSVGLength)

View File

@ -101,35 +101,36 @@ public:
virtual bool parseAndSetAttribute(const char* name, const char* value);
// inherited
SVG_PRES_ATTR(ClipRule , SkSVGFillRule , true)
SVG_PRES_ATTR(Color , SkSVGColorType , true)
SVG_PRES_ATTR(FillRule , SkSVGFillRule , true)
SVG_PRES_ATTR(Fill , SkSVGPaint , true)
SVG_PRES_ATTR(FillOpacity , SkSVGNumberType, true)
SVG_PRES_ATTR(FontFamily , SkSVGFontFamily, true)
SVG_PRES_ATTR(FontSize , SkSVGFontSize , true)
SVG_PRES_ATTR(FontStyle , SkSVGFontStyle , true)
SVG_PRES_ATTR(FontWeight , SkSVGFontWeight, true)
SVG_PRES_ATTR(Stroke , SkSVGPaint , true)
SVG_PRES_ATTR(StrokeDashArray , SkSVGDashArray , true)
SVG_PRES_ATTR(StrokeDashOffset, SkSVGLength , true)
SVG_PRES_ATTR(StrokeLineCap , SkSVGLineCap , true)
SVG_PRES_ATTR(StrokeLineJoin , SkSVGLineJoin , true)
SVG_PRES_ATTR(StrokeMiterLimit, SkSVGNumberType, true)
SVG_PRES_ATTR(StrokeOpacity , SkSVGNumberType, true)
SVG_PRES_ATTR(StrokeWidth , SkSVGLength , true)
SVG_PRES_ATTR(TextAnchor , SkSVGTextAnchor, true)
SVG_PRES_ATTR(Visibility , SkSVGVisibility, true)
SVG_PRES_ATTR(ClipRule , SkSVGFillRule , true)
SVG_PRES_ATTR(Color , SkSVGColorType , true)
SVG_PRES_ATTR(ColorInterpolationFilters, SkSVGColorspace, true)
SVG_PRES_ATTR(FillRule , SkSVGFillRule , true)
SVG_PRES_ATTR(Fill , SkSVGPaint , true)
SVG_PRES_ATTR(FillOpacity , SkSVGNumberType, true)
SVG_PRES_ATTR(FontFamily , SkSVGFontFamily, true)
SVG_PRES_ATTR(FontSize , SkSVGFontSize , true)
SVG_PRES_ATTR(FontStyle , SkSVGFontStyle , true)
SVG_PRES_ATTR(FontWeight , SkSVGFontWeight, true)
SVG_PRES_ATTR(Stroke , SkSVGPaint , true)
SVG_PRES_ATTR(StrokeDashArray , SkSVGDashArray , true)
SVG_PRES_ATTR(StrokeDashOffset , SkSVGLength , true)
SVG_PRES_ATTR(StrokeLineCap , SkSVGLineCap , true)
SVG_PRES_ATTR(StrokeLineJoin , SkSVGLineJoin , true)
SVG_PRES_ATTR(StrokeMiterLimit , SkSVGNumberType, true)
SVG_PRES_ATTR(StrokeOpacity , SkSVGNumberType, true)
SVG_PRES_ATTR(StrokeWidth , SkSVGLength , true)
SVG_PRES_ATTR(TextAnchor , SkSVGTextAnchor, true)
SVG_PRES_ATTR(Visibility , SkSVGVisibility, true)
// not inherited
SVG_PRES_ATTR(ClipPath , SkSVGFuncIRI , false)
SVG_PRES_ATTR(Mask , SkSVGFuncIRI , false)
SVG_PRES_ATTR(Filter , SkSVGFuncIRI , false)
SVG_PRES_ATTR(Opacity , SkSVGNumberType, false)
SVG_PRES_ATTR(StopColor , SkSVGColor , false)
SVG_PRES_ATTR(StopOpacity , SkSVGNumberType, false)
SVG_PRES_ATTR(FloodColor , SkSVGColor , false)
SVG_PRES_ATTR(FloodOpacity , SkSVGNumberType, false)
SVG_PRES_ATTR(ClipPath , SkSVGFuncIRI , false)
SVG_PRES_ATTR(Mask , SkSVGFuncIRI , false)
SVG_PRES_ATTR(Filter , SkSVGFuncIRI , false)
SVG_PRES_ATTR(Opacity , SkSVGNumberType, false)
SVG_PRES_ATTR(StopColor , SkSVGColor , false)
SVG_PRES_ATTR(StopOpacity , SkSVGNumberType, false)
SVG_PRES_ATTR(FloodColor , SkSVGColor , false)
SVG_PRES_ATTR(FloodOpacity , SkSVGNumberType, false)
protected:
SkSVGNode(SkSVGTag);

View File

@ -650,4 +650,10 @@ enum class SkSVGXmlSpace {
kPreserve,
};
enum class SkSVGColorspace {
kAuto,
kSRGB,
kLinearRGB,
};
#endif // SkSVGTypes_DEFINED

View File

@ -27,6 +27,7 @@ SkSVGPresentationAttributes SkSVGPresentationAttributes::MakeInitial() {
result.fVisibility.set(SkSVGVisibility(SkSVGVisibility::Type::kVisible));
result.fColor.set(SkSVGColorType(SK_ColorBLACK));
result.fColorInterpolationFilters.set(SkSVGColorspace::kLinearRGB);
result.fFontFamily.init("Sans");
result.fFontStyle.init(SkSVGFontStyle::Type::kNormal);

View File

@ -927,3 +927,14 @@ template <>
bool SkSVGAttributeParser::parse(std::vector<SkSVGNumberType>* numbers) {
return this->parseList(numbers);
}
template <>
bool SkSVGAttributeParser::parse(SkSVGColorspace* colorspace) {
static constexpr std::tuple<const char*, SkSVGColorspace> gColorspaceMap[] = {
{ "auto" , SkSVGColorspace::kAuto },
{ "sRGB" , SkSVGColorspace::kSRGB },
{ "linearRGB", SkSVGColorspace::kLinearRGB },
};
return this->parseEnumMap(gColorspaceMap, colorspace) && this->parseEOSToken();
}

View File

@ -83,6 +83,12 @@ SkRect SkSVGFe::resolveFilterSubregion(const SkSVGRenderContext& ctx,
return subregion;
}
SkSVGColorspace SkSVGFe::resolveColorspace(const SkSVGRenderContext& ctx) const {
return *ctx.presentationContext().fInherited.fColorInterpolationFilters;
}
void SkSVGFe::applyProperties(SkSVGRenderContext* ctx) const { this->onPrepareToRender(ctx); }
bool SkSVGFe::parseAndSetAttribute(const char* name, const char* value) {
return INHERITED::parseAndSetAttribute(name, value) ||
this->setIn(SkSVGAttributeParser::parse<SkSVGFeInputType>("in", name, value)) ||

View File

@ -54,11 +54,20 @@ sk_sp<SkImageFilter> SkSVGFilter::buildFilterDAG(const SkSVGRenderContext& ctx)
const auto& feNode = static_cast<const SkSVGFe&>(*child);
const auto& feResultType = feNode.getResult();
// Propagate any inherited properties that may impact filter effect behavior (e.g.
// color-interpolation-filters). We call this explicitly here because the SkSVGFe
// nodes do not participate in the normal onRender path, which is when property
// propagation currently occurs.
SkSVGRenderContext localCtx(ctx);
feNode.applyProperties(&localCtx);
// TODO: there are specific composition rules that need to be followed
filter = feNode.makeImageFilter(ctx, fctx);
// TODO: perform colorspace conversions depending on 'color-interpolation-filters' setting
// of the current node and its inputs.
filter = feNode.makeImageFilter(localCtx, fctx);
if (!feResultType.isEmpty()) {
fctx.registerResult(feResultType, filter, feNode.resolveFilterSubregion(ctx, fctx));
fctx.registerResult(feResultType, filter, feNode.resolveFilterSubregion(localCtx, fctx));
}
}

View File

@ -88,33 +88,34 @@ bool SkSVGNode::parseAndSetAttribute(const char* n, const char* v) {
SkSVGAttributeParser::parseProperty<decltype(fPresentationAttributes.f##attrName)>( \
svgName, n, v))
return PARSE_AND_SET("clip-path" , ClipPath)
|| PARSE_AND_SET("clip-rule" , ClipRule)
|| PARSE_AND_SET("color" , Color)
|| PARSE_AND_SET("fill" , Fill)
|| PARSE_AND_SET("fill-opacity" , FillOpacity)
|| PARSE_AND_SET("fill-rule" , FillRule)
|| PARSE_AND_SET("filter" , Filter)
|| PARSE_AND_SET("flood-color" , FloodColor)
|| PARSE_AND_SET("flood-opacity" , FloodOpacity)
|| PARSE_AND_SET("font-family" , FontFamily)
|| PARSE_AND_SET("font-size" , FontSize)
|| PARSE_AND_SET("font-style" , FontStyle)
|| PARSE_AND_SET("font-weight" , FontWeight)
|| PARSE_AND_SET("mask" , Mask)
|| PARSE_AND_SET("opacity" , Opacity)
|| PARSE_AND_SET("stop-color" , StopColor)
|| PARSE_AND_SET("stop-opacity" , StopOpacity)
|| PARSE_AND_SET("stroke" , Stroke)
|| PARSE_AND_SET("stroke-dasharray" , StrokeDashArray)
|| PARSE_AND_SET("stroke-dashoffset", StrokeDashOffset)
|| PARSE_AND_SET("stroke-linecap" , StrokeLineCap)
|| PARSE_AND_SET("stroke-linejoin" , StrokeLineJoin)
|| PARSE_AND_SET("stroke-miterlimit", StrokeMiterLimit)
|| PARSE_AND_SET("stroke-opacity" , StrokeOpacity)
|| PARSE_AND_SET("stroke-width" , StrokeWidth)
|| PARSE_AND_SET("text-anchor" , TextAnchor)
|| PARSE_AND_SET("visibility" , Visibility);
return PARSE_AND_SET( "clip-path" , ClipPath)
|| PARSE_AND_SET("clip-rule" , ClipRule)
|| PARSE_AND_SET("color" , Color)
|| PARSE_AND_SET("color-interpolation-filters", ColorInterpolationFilters)
|| PARSE_AND_SET("fill" , Fill)
|| PARSE_AND_SET("fill-opacity" , FillOpacity)
|| PARSE_AND_SET("fill-rule" , FillRule)
|| PARSE_AND_SET("filter" , Filter)
|| PARSE_AND_SET("flood-color" , FloodColor)
|| PARSE_AND_SET("flood-opacity" , FloodOpacity)
|| PARSE_AND_SET("font-family" , FontFamily)
|| PARSE_AND_SET("font-size" , FontSize)
|| PARSE_AND_SET("font-style" , FontStyle)
|| PARSE_AND_SET("font-weight" , FontWeight)
|| PARSE_AND_SET("mask" , Mask)
|| PARSE_AND_SET("opacity" , Opacity)
|| PARSE_AND_SET("stop-color" , StopColor)
|| PARSE_AND_SET("stop-opacity" , StopOpacity)
|| PARSE_AND_SET("stroke" , Stroke)
|| PARSE_AND_SET("stroke-dasharray" , StrokeDashArray)
|| PARSE_AND_SET("stroke-dashoffset" , StrokeDashOffset)
|| PARSE_AND_SET("stroke-linecap" , StrokeLineCap)
|| PARSE_AND_SET("stroke-linejoin" , StrokeLineJoin)
|| PARSE_AND_SET("stroke-miterlimit" , StrokeMiterLimit)
|| PARSE_AND_SET("stroke-opacity" , StrokeOpacity)
|| PARSE_AND_SET("stroke-width" , StrokeWidth)
|| PARSE_AND_SET("text-anchor" , TextAnchor)
|| PARSE_AND_SET("visibility" , Visibility);
#undef PARSE_AND_SET
}

View File

@ -261,6 +261,13 @@ void commitToPaint<SkSVGAttribute::kColor>(const SkSVGPresentationAttributes&,
// Not part of the SkPaint state; applied via 'currentColor' color value
}
template <>
void commitToPaint<SkSVGAttribute::kColorInterpolationFilters>(const SkSVGPresentationAttributes&,
const SkSVGRenderContext&,
SkSVGPresentationContext*) {
// Not part of the SkPaint state; applied at render time.
}
template <>
void commitToPaint<SkSVGAttribute::kFontFamily>(const SkSVGPresentationAttributes&,
const SkSVGRenderContext&,
@ -406,6 +413,7 @@ void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttr
ApplyLazyInheritedAttribute(TextAnchor);
ApplyLazyInheritedAttribute(Visibility);
ApplyLazyInheritedAttribute(Color);
ApplyLazyInheritedAttribute(ColorInterpolationFilters);
// Local 'color' attribute: update paints for attributes that are set to 'currentColor'.
if (attrs.fColor.isValue()) {