[skif] Take dst bounds into account for layer fills in FilterResult
When the image fully covers what needs to be drawn, pixels that would
be non-transparent due to a color filter or non-decal tile mode don't
matter. Previously, the fills_layer_bounds function was just checking
state and would always block compacting operations, unless the layer
bounds also hid the CF/tiling effects.
Bug: skia:9296
Change-Id: I02b8b585dc723de6d9ad99fce11a21e5348601e4
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/717297
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/core/SkImageFilterTypes.cpp b/src/core/SkImageFilterTypes.cpp
index 9443c3e..d1575aa 100644
--- a/src/core/SkImageFilterTypes.cpp
+++ b/src/core/SkImageFilterTypes.cpp
@@ -207,10 +207,6 @@
#endif
-bool fills_layer_bounds(const SkColorFilter* colorFilter) {
- return colorFilter && as_CFB(colorFilter)->affectsTransparentBlack();
-}
-
// AutoSurface manages an SkSpecialSurface and canvas state to draw to a layer-space bounding box,
// and then snap it into a FilterResult. It provides operators to be used directly as a canvas,
// assuming surface creation succeeded. Usage:
@@ -545,11 +541,33 @@
return image;
}
+
+bool FilterResult::modifiesPixelsBeyondImage(const LayerSpace<SkIRect>& dstBounds) const {
+ // If there is no transparency-affecting color filter and it's just decal tiling, it doesn't
+ // matter how the image geometry overlaps with the dst bounds.
+ if (!(fColorFilter && as_CFB(fColorFilter)->affectsTransparentBlack())) {
+ // TODO: add "&& fTileMode == SkTileMode::kDecal) {""
+ return false;
+ }
+
+ // If the base image completely covers the render bounds then the effects of tiling won't be
+ // visible and it doesn't matter if any color filter affects transparent black.
+ if (SkRectPriv::QuadContainsRect(SkMatrix(fTransform),
+ SkIRect::MakeSize(fImage->dimensions()),
+ SkIRect(dstBounds))) {
+ return false;
+ }
+
+ // Otherwise tiling or transparency-affecting color filters will modify the pixels beyond
+ // the image bounds that are still within render bounds.
+ return true;
+}
+
bool FilterResult::isCropped(const LayerSpace<SkMatrix>& xtraTransform,
const LayerSpace<SkIRect>& dstBounds) const {
// Tiling and color-filtering can completely fill 'fLayerBounds' in which case its edge is
// a transition from possibly non-transparent to definitely transparent color.
- bool fillsLayerBounds = fills_layer_bounds(fColorFilter.get());
+ bool fillsLayerBounds = this->modifiesPixelsBeyondImage(dstBounds);
if (!fillsLayerBounds) {
// When that's not the case, 'fLayerBounds' may still be important if it crops the
// edges of the original transformed image itself.
@@ -589,7 +607,7 @@
}
LayerSpace<SkIPoint> origin;
- if (!fills_layer_bounds(fColorFilter.get()) &&
+ if (!this->modifiesPixelsBeyondImage(tightBounds) &&
is_nearly_integer_translation(fTransform, &origin)) {
// We can lift the crop to earlier in the order of operations and apply it to the image
// subset directly. This does not rely on resolve() to call extract_subset() because it
@@ -808,12 +826,12 @@
SkSurfaceProps props = {};
AutoSurface surface{ctx, dstBounds, /*renderInParameterSpace=*/false, &props};
if (surface) {
- this->draw(surface.canvas());
+ this->draw(surface.canvas(), dstBounds);
}
return surface.snap();
}
-void FilterResult::draw(SkCanvas* canvas) const {
+void FilterResult::draw(SkCanvas* canvas, const LayerSpace<SkIRect>& dstBounds) const {
if (!fImage) {
return;
}
@@ -838,7 +856,7 @@
sampling = {};
}
- if (fills_layer_bounds(fColorFilter.get())) {
+ if (this->modifiesPixelsBeyondImage(dstBounds)) {
#ifdef SK_ENABLE_SKSL
// apply_decal consumes the transform, so we don't modify the canvas
paint.setShader(apply_decal(fTransform, fImage, fLayerBounds, sampling));
@@ -1096,7 +1114,7 @@
input.fSampling == kDefaultSampling &&
input.fFlags == ShaderFlags::kNone);
surface->save();
- input.fImage.draw(surface.canvas());
+ input.fImage.draw(surface.canvas(), outputBounds);
surface->restore();
}
}
diff --git a/src/core/SkImageFilterTypes.h b/src/core/SkImageFilterTypes.h
index 52367fd..d2d4b58 100644
--- a/src/core/SkImageFilterTypes.h
+++ b/src/core/SkImageFilterTypes.h
@@ -767,6 +767,11 @@
std::pair<sk_sp<SkSpecialImage>, LayerSpace<SkIPoint>>
resolve(const Context& ctx, LayerSpace<SkIRect> dstBounds) const;
+ // Returns true if tiling and color filtering affect pixels outside of the image's bounds that
+ // are within the layer bounds (limited to 'dstBounds'). This does not consider the layer bounds
+ // which are considered separately in isCropped().
+ bool modifiesPixelsBeyondImage(const LayerSpace<SkIRect>& dstBounds) const;
+
// Returns true if the effects of the fLayerBounds crop are visible when this image is drawn
// with 'xtraTransform' restricted to 'dstBounds'.
bool isCropped(const LayerSpace<SkMatrix>& xtraTransform,
@@ -774,7 +779,7 @@
// Draw directly to the canvas, which draws the same image as produced by resolve() but can be
// useful if multiple operations need to be performed on the canvas.
- void draw(SkCanvas* canvas) const;
+ void draw(SkCanvas* canvas, const LayerSpace<SkIRect>& dstBounds) const;
// Returns the FilterResult as a shader, ideally without resolving to an axis-aligned image.
// 'xtraSampling' is the sampling that any parent shader applies to the FilterResult.
diff --git a/tests/FilterResultTest.cpp b/tests/FilterResultTest.cpp
index ec4254a..4fb3e40 100644
--- a/tests/FilterResultTest.cpp
+++ b/tests/FilterResultTest.cpp
@@ -126,7 +126,7 @@
canvas->drawPaint(paint);
} else {
SkASSERT(fMethod == Method::kDrawToCanvas);
- image.draw(canvas);
+ image.draw(canvas, ctx.desiredOutput());
}
return {surface->makeImageSnapshot(), SkIPoint(ctx.desiredOutput().topLeft())};