[skif] Update SkShaderImageFilter to use FilterResult
Bug: skia:9282
Bug: b/263134493
Bug: oss-fuzz:59022
Change-Id: I059330de6f601c44ae375e94f0370478e0352352
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/697099
Reviewed-by: Robert Phillips <robertphillips@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/gm/composecolorfilter.cpp b/gm/composecolorfilter.cpp
index dbb3658..ca5c574 100644
--- a/gm/composecolorfilter.cpp
+++ b/gm/composecolorfilter.cpp
@@ -22,7 +22,9 @@
#include "include/core/SkTileMode.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkGradientShader.h"
+#include "include/effects/SkImageFilters.h"
#include "include/effects/SkLumaColorFilter.h"
+#include "include/effects/SkPerlinNoiseShader.h"
#include "include/effects/SkRuntimeEffect.h"
#include "src/core/SkRuntimeEffectPriv.h"
#include "tools/Resources.h"
@@ -100,3 +102,46 @@
canvas->translate(0, 100);
}
}
+
+DEF_SIMPLE_GM(composeCFIF, canvas, 604, 200) {
+ // This GM draws a ::Shader image filter composed with a ::ColorFilter image filter in two
+ // ways (direct and via ::Compose). This ensures the use (or non-use in this case) of the source
+ // image is the same across both means of composition.
+ auto cf = MakeTintColorFilter(0xff300000, 0xffa00000, /*useSkSL=*/false);
+ auto shader = SkPerlinNoiseShader::MakeTurbulence(0.01f, 0.01f, 2, 0.f);
+
+ auto shaderIF = SkImageFilters::Shader(shader, SkImageFilters::Dither::kNo);
+ auto directCompose = SkImageFilters::ColorFilter(cf, shaderIF);
+ auto indirectCompose = SkImageFilters::Compose(
+ /*outer=*/SkImageFilters::ColorFilter(cf, nullptr),
+ /*inner=*/shaderIF);
+
+ { // Directly draw the shader composed with the color filter
+ canvas->save();
+ canvas->clipRect({0, 0, 200, 200});
+ SkPaint p;
+ p.setShader(shader);
+ p.setColorFilter(cf);
+ canvas->drawPaint(p);
+ canvas->restore();
+ }
+ canvas->translate(202, 0);
+ { // Draw with the directly composed image filter
+ canvas->save();
+ canvas->clipRect({0, 0, 200, 200});
+ SkPaint p;
+ p.setImageFilter(directCompose);
+ canvas->drawPaint(p);
+ canvas->restore();
+ }
+ canvas->translate(202, 0);
+ {
+ // Draw with the indirectly composed image filter
+ canvas->save();
+ canvas->clipRect({0, 0, 200, 200});
+ SkPaint p;
+ p.setImageFilter(indirectCompose);
+ canvas->drawPaint(p);
+ canvas->restore();
+ }
+}
diff --git a/include/effects/SkImageFilters.h b/include/effects/SkImageFilters.h
index e0ca7aa..a4bfad8 100644
--- a/include/effects/SkImageFilters.h
+++ b/include/effects/SkImageFilters.h
@@ -399,6 +399,9 @@
* Like Image() and Picture(), this is a leaf filter that can be used to introduce inputs to
* a complex filter graph, but should generally be combined with a filter that as at least
* one null input to use the implicit source image.
+ *
+ * Returns an image filter that evaluates to transparent black if 'shader' is null.
+ *
* @param shader The shader that fills the result image
*/
static sk_sp<SkImageFilter> Shader(sk_sp<SkShader> shader, const CropRect& cropRect = {}) {
diff --git a/relnotes/skimagefilters_shader.md b/relnotes/skimagefilters_shader.md
new file mode 100644
index 0000000..e227ba7
--- /dev/null
+++ b/relnotes/skimagefilters_shader.md
@@ -0,0 +1,4 @@
+`SkImageFilters::Shader` now returns a non-null image filter if the input `sk_sp<SkShader>` is
+null. The returned filter evaluates to transparent black, which is equivalent to a null or empty
+shader. Previously, returning a null image filter would mean that the dynamic source image could
+be surprisingly injected into the filter evaluation where it might not have been intended.
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 993c52e..a7c419b 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -147,14 +147,15 @@
}
SkImageFilter_Base::SkImageFilter_Base(sk_sp<SkImageFilter> const* inputs,
- int inputCount, const SkRect* cropRect)
- : fUsesSrcInput(false)
+ int inputCount, const SkRect* cropRect,
+ std::optional<bool> usesSrc)
+ : fUsesSrcInput(usesSrc.has_value() ? *usesSrc : false)
, fCropRect(cropRect)
, fUniqueID(next_image_filter_unique_id()) {
fInputs.reset(inputCount);
for (int i = 0; i < inputCount; ++i) {
- if (!inputs[i] || as_IFB(inputs[i])->fUsesSrcInput) {
+ if (!usesSrc.has_value() && (!inputs[i] || as_IFB(inputs[i])->usesSource())) {
fUsesSrcInput = true;
}
fInputs[i] = inputs[i];
diff --git a/src/core/SkImageFilterTypes.cpp b/src/core/SkImageFilterTypes.cpp
index 4828976..eb14478 100644
--- a/src/core/SkImageFilterTypes.cpp
+++ b/src/core/SkImageFilterTypes.cpp
@@ -745,4 +745,21 @@
return surface.snap();
}
+FilterResult FilterResult::MakeFromShader(const Context& ctx,
+ sk_sp<SkShader> shader,
+ bool dither) {
+ if (!shader) {
+ return {};
+ }
+
+ AutoSurface surface{ctx, ctx.desiredOutput(), /*renderInParameterSpace=*/true};
+ if (surface) {
+ SkPaint paint;
+ paint.setShader(shader);
+ paint.setDither(dither);
+ surface->drawPaint(paint);
+ }
+ return surface.snap();
+}
+
} // end namespace skif
diff --git a/src/core/SkImageFilterTypes.h b/src/core/SkImageFilterTypes.h
index 8b406d9..76f725e 100644
--- a/src/core/SkImageFilterTypes.h
+++ b/src/core/SkImageFilterTypes.h
@@ -656,11 +656,19 @@
// Renders the 'pic', clipped by 'cullRect', into an optimally sized surface (depending on
// picture bounds and 'ctx's desired output). The picture is transformed by the context's
- // layer matrix. Treats null pictures as full transparent.
+ // layer matrix. Treats null pictures as fully transparent.
static FilterResult MakeFromPicture(const Context& ctx,
sk_sp<SkPicture> pic,
ParameterSpace<SkRect> cullRect);
+ // Renders 'shader' into a surface that fills the context's desired output bounds. Treats null
+ // shaders as fully transparent.
+ // TODO: Update 'dither' to SkImageFilters::Dither, but that cannot be forward declared at the
+ // moment because SkImageFilters is a class and not a namespace.
+ static FilterResult MakeFromShader(const Context& ctx,
+ sk_sp<SkShader> shader,
+ bool dither);
+
// Bilinear is used as the default because it can be downgraded to nearest-neighbor when the
// final transform is pixel-aligned, and chaining multiple bilinear samples and transforms is
// assumed to be visually close enough to sampling once at highest quality and final transform.
diff --git a/src/core/SkImageFilter_Base.h b/src/core/SkImageFilter_Base.h
index d4412c1..56a5ba0 100644
--- a/src/core/SkImageFilter_Base.h
+++ b/src/core/SkImageFilter_Base.h
@@ -16,6 +16,8 @@
#include "src/core/SkImageFilterTypes.h"
+#include <optional>
+
class GrFragmentProcessor;
class GrRecordingContext;
@@ -103,6 +105,9 @@
// color other than transparent black.
bool affectsTransparentBlack() const;
+ // Returns true if this image filter graph references the Context's source image.
+ bool usesSource() const { return fUsesSrcInput; }
+
/**
* Most ImageFilters can natively handle scaling and translate components in the CTM. Only
* some of them can handle affine (or more complex) matrices. Some may only handle translation.
@@ -195,8 +200,9 @@
kYes = true
};
+ // If 'usesSrc' is not provided, this filter uses the OR of all input's usesSource() values.
SkImageFilter_Base(sk_sp<SkImageFilter> const* inputs, int inputCount,
- const SkRect* cropRect);
+ const SkRect* cropRect, std::optional<bool> usesSrc = {});
~SkImageFilter_Base() override;
diff --git a/src/effects/imagefilters/SkComposeImageFilter.cpp b/src/effects/imagefilters/SkComposeImageFilter.cpp
index e1500f0..405baab 100644
--- a/src/effects/imagefilters/SkComposeImageFilter.cpp
+++ b/src/effects/imagefilters/SkComposeImageFilter.cpp
@@ -16,6 +16,7 @@
#include "src/core/SkImageFilter_Base.h"
#include "src/core/SkSpecialImage.h"
+#include <optional>
#include <utility>
class SkMatrix;
@@ -26,7 +27,10 @@
class SkComposeImageFilter final : public SkImageFilter_Base {
public:
explicit SkComposeImageFilter(sk_sp<SkImageFilter> inputs[2])
- : INHERITED(inputs, 2, nullptr) {
+ : INHERITED(inputs, 2, nullptr,
+ // Compose only uses the source if the inner filter uses the source image.
+ // Any outer reference to source is rebound to the result of the inner.
+ inputs[1] ? as_IFB(inputs[1])->usesSource() : false) {
SkASSERT(inputs[0].get());
SkASSERT(inputs[1].get());
}
diff --git a/src/effects/imagefilters/SkShaderImageFilter.cpp b/src/effects/imagefilters/SkShaderImageFilter.cpp
index a32eca4..b7dd646 100644
--- a/src/effects/imagefilters/SkShaderImageFilter.cpp
+++ b/src/effects/imagefilters/SkShaderImageFilter.cpp
@@ -5,25 +5,22 @@
* found in the LICENSE file.
*/
-#include "include/core/SkCanvas.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkFlattenable.h"
#include "include/core/SkImageFilter.h"
-#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
-#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
-#include "include/core/SkScalar.h"
#include "include/core/SkShader.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkImageFilters.h"
+#include "src/core/SkImageFilterTypes.h"
#include "src/core/SkImageFilter_Base.h"
#include "src/core/SkPicturePriv.h"
#include "src/core/SkReadBuffer.h"
-#include "src/core/SkSpecialImage.h"
-#include "src/core/SkSpecialSurface.h"
+#include "src/core/SkRectPriv.h"
#include "src/core/SkWriteBuffer.h"
+#include "src/effects/imagefilters/SkCropImageFilter.h"
#include <utility>
@@ -31,14 +28,17 @@
class SkShaderImageFilter final : public SkImageFilter_Base {
public:
- SkShaderImageFilter(sk_sp<SkShader> shader, SkImageFilters::Dither dither, const SkRect* rect)
- : INHERITED(nullptr, 0, rect)
+ SkShaderImageFilter(sk_sp<SkShader> shader, SkImageFilters::Dither dither)
+ : SkImageFilter_Base(nullptr, 0, nullptr)
, fShader(std::move(shader))
, fDither(dither) {}
+ SkRect computeFastBounds(const SkRect& /*bounds*/) const override {
+ return fShader ? SkRectPriv::MakeLargeS32() : SkRect::MakeEmpty();
+ }
+
protected:
void flatten(SkWriteBuffer&) const override;
- sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint* offset) const override;
private:
friend void ::SkRegisterShaderImageFilterFlattenable();
@@ -46,10 +46,22 @@
bool onAffectsTransparentBlack() const override { return true; }
+ MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kComplex; }
+
+ skif::FilterResult onFilterImage(const skif::Context&) const override;
+
+ skif::LayerSpace<SkIRect> onGetInputLayerBounds(
+ const skif::Mapping&,
+ const skif::LayerSpace<SkIRect>& desiredOutput,
+ const skif::LayerSpace<SkIRect>& contentBounds,
+ VisitChildren) const override;
+
+ skif::LayerSpace<SkIRect> onGetOutputLayerBounds(
+ const skif::Mapping&,
+ const skif::LayerSpace<SkIRect>& contentBounds) const override;
+
sk_sp<SkShader> fShader;
SkImageFilters::Dither fDither;
-
- using INHERITED = SkImageFilter_Base;
};
} // end namespace
@@ -57,7 +69,11 @@
sk_sp<SkImageFilter> SkImageFilters::Shader(sk_sp<SkShader> shader,
Dither dither,
const CropRect& cropRect) {
- return sk_sp<SkImageFilter>(new SkShaderImageFilter(std::move(shader), dither, cropRect));
+ sk_sp<SkImageFilter> filter{new SkShaderImageFilter(std::move(shader), dither)};
+ if (cropRect) {
+ filter = SkMakeCropImageFilter(*cropRect, std::move(filter));
+ }
+ return filter;
}
void SkRegisterShaderImageFilterFlattenable() {
@@ -88,48 +104,36 @@
}
void SkShaderImageFilter::flatten(SkWriteBuffer& buffer) const {
- this->INHERITED::flatten(buffer);
+ this->SkImageFilter_Base::flatten(buffer);
buffer.writeFlattenable(fShader.get());
buffer.writeBool(fDither == SkImageFilters::Dither::kYes);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
-sk_sp<SkSpecialImage> SkShaderImageFilter::onFilterImage(const Context& ctx,
- SkIPoint* offset) const {
- SkIRect bounds;
- const SkIRect srcBounds = SkIRect::MakeWH(ctx.sourceImage()->width(),
- ctx.sourceImage()->height());
- if (!this->applyCropRect(ctx, srcBounds, &bounds)) {
- return nullptr;
+skif::FilterResult SkShaderImageFilter::onFilterImage(const skif::Context& ctx) const {
+ const bool dither = fDither == SkImageFilters::Dither::kYes;
+ return skif::FilterResult::MakeFromShader(ctx, fShader, dither);
+}
+
+skif::LayerSpace<SkIRect> SkShaderImageFilter::onGetInputLayerBounds(
+ const skif::Mapping&,
+ const skif::LayerSpace<SkIRect>&,
+ const skif::LayerSpace<SkIRect>&,
+ VisitChildren) const {
+ // This is a leaf filter, it requires no input and no further recursion
+ return skif::LayerSpace<SkIRect>::Empty();
+}
+
+skif::LayerSpace<SkIRect> SkShaderImageFilter::onGetOutputLayerBounds(
+ const skif::Mapping&,
+ const skif::LayerSpace<SkIRect>&) const {
+ if (fShader) {
+ // The output of a shader is infinite, unless we were to inspect the shader for a decal
+ // tile mode around a gradient or image.
+ return skif::LayerSpace<SkIRect>(SkRectPriv::MakeILarge());
+ } else {
+ // An empty shader is fully transparent
+ return skif::LayerSpace<SkIRect>::Empty();
}
-
- sk_sp<SkSpecialSurface> surf(ctx.makeSurface(bounds.size()));
- if (!surf) {
- return nullptr;
- }
-
- SkCanvas* canvas = surf->getCanvas();
- SkASSERT(canvas);
-
- canvas->clear(0x0);
-
- SkMatrix matrix(ctx.ctm());
- matrix.postTranslate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top()));
- SkRect rect = SkRect::MakeIWH(bounds.width(), bounds.height());
- SkMatrix inverse;
- if (matrix.invert(&inverse)) {
- inverse.mapRect(&rect);
- }
- canvas->setMatrix(matrix);
- if (rect.isFinite()) {
- SkPaint paint;
- paint.setShader(fShader);
- paint.setDither(fDither == SkImageFilters::Dither::kYes);
- canvas->drawRect(rect, paint);
- }
-
- offset->fX = bounds.fLeft;
- offset->fY = bounds.fTop;
- return surf->makeImageSnapshot();
}