[svg] Fix incorrect optimization for opacity layer

When both filter and opacity attributes are set on a leaf node, the
opacity must be applied as a separate layer so that the results of the
filter are modified by the opacity. Previously in this circumstance we
were incorrectly applying the opacity to the paints only (without a
layer).

To illustrate:

<svg viewBox="0 0 1000 500" version="1.1"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <filter id="Green" x="0%" y="0%" width="100%" height="100%">
      <feFlood flood-color="lime" flood-opacity="1" />
    </filter>
  </defs>
  <rect x="30" y="20" width="400" height="100" fill="red" opacity="0.1"
        filter="url(#Green)"/>
  <g filter="url(#Green)">
    <rect x="30" y="200" width="400" height="100" fill="red" opacity="0.1"/>
  </g>
</svg>

The two rects should render differently. In the <g> case, the filter
output (opaque green) overrides the translucent red pixels of the rect.
In the <rect> case, the filter output overrides the translucent red
pixels with opaque green, but then is modified by the opacity on the
<rect>.

Relevant W3C test is filters-blend-01-b (and possibly others).

Change-Id: I165eed36c546f1f99457865cee58ee2b3bffe6f1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/354879
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Tyler Denniston <tdenniston@google.com>
This commit is contained in:
Tyler Denniston 2021-01-15 12:12:35 -05:00 committed by Skia Commit-Bot
parent 97c476ecb7
commit a22d21e447
2 changed files with 12 additions and 9 deletions

View File

@ -141,7 +141,7 @@ private:
void* operator new(size_t, void*) = delete;
SkSVGRenderContext& operator=(const SkSVGRenderContext&) = delete;
void applyOpacity(SkScalar opacity, uint32_t flags);
void applyOpacity(SkScalar opacity, uint32_t flags, bool hasFilter);
void applyFilter(const SkSVGFuncIRI&);
void applyClip(const SkSVGFuncIRI&);
void applyMask(const SkSVGFuncIRI&);

View File

@ -352,8 +352,9 @@ void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttr
// Uninherited attributes. Only apply to the current context.
const bool hasFilter = attrs.fFilter.isValue();
if (attrs.fOpacity.isValue()) {
this->applyOpacity(*attrs.fOpacity, flags);
this->applyOpacity(*attrs.fOpacity, flags, hasFilter);
}
if (attrs.fClipPath.isValue()) {
@ -365,7 +366,7 @@ void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttr
}
// TODO: when both a filter and opacity are present, we can apply both with a single layer
if (attrs.fFilter.isValue()) {
if (hasFilter) {
this->applyFilter(*attrs.fFilter);
}
@ -378,7 +379,7 @@ void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttr
// - flood-opacity
}
void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags) {
void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags, bool hasFilter) {
if (opacity >= 1) {
return;
}
@ -386,11 +387,13 @@ void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags) {
const bool hasFill = SkToBool(this->fillPaint());
const bool hasStroke = SkToBool(this->strokePaint());
// We can apply the opacity as paint alpha iif it only affects one atomic draw.
// For now, this means a) the target node doesn't have any descendants, and
// b) it only has a stroke or a fill (but not both). Going forward, we may need
// to refine this heuristic (e.g. to accommodate markers).
if ((flags & kLeaf) && (hasFill ^ hasStroke)) {
// We can apply the opacity as paint alpha if it only affects one atomic draw.
// For now, this means all of the following must be true:
// - the target node doesn't have any descendants;
// - it only has a stroke or a fill (but not both);
// - it does not have a filter.
// Going forward, we may needto refine this heuristic (e.g. to accommodate markers).
if ((flags & kLeaf) && (hasFill ^ hasStroke) && !hasFilter) {
auto* pctx = fPresentationContext.writable();
if (hasFill) {
pctx->fFillPaint.setAlpha(