Fix reverse bounds calculation for SkImageFilters::Compose
Since reverse bounds calculation is more-or-less the inverse operation
of forward bounds calculation, it needs to be computed from the outer
filter and then the inner filter. Previously bounds were always
computed from inner and then outer, which is only valid for forward
bounds calculations.
Bug: skia:10888
Change-Id: I94a2170617ed01c8ec3066f3518c6baa06da952d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/337401
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/gm/imagefilterstransformed.cpp b/gm/imagefilterstransformed.cpp
index 4a4dda9..c9d51f5 100644
--- a/gm/imagefilterstransformed.cpp
+++ b/gm/imagefilterstransformed.cpp
@@ -216,3 +216,101 @@
};
DEF_GM(return new ImageFilterMatrixWLocalMatrix();)
+
+class ImageFilterComposedTransform : public skiagm::GM {
+public:
+
+ // Start at 70 degrees since that highlighted the issue in skbug.com/10888
+ ImageFilterComposedTransform() : fDegrees(70.f) {}
+
+protected:
+ SkString onShortName() override {
+ return SkString("imagefilter_composed_transform");
+ }
+
+ SkISize onISize() override {
+ return SkISize::Make(512, 512);
+ }
+
+ bool onAnimate(double nanos) override {
+ // Animate the rotation angle to test a variety of transformations
+ fDegrees = TimeUtils::Scaled(1e-9f * nanos, 360.f);
+ return true;
+ }
+
+ void onOnceBeforeDraw() override {
+ fImage = GetResourceAsImage("images/mandrill_256.png");
+ }
+
+ void onDraw(SkCanvas* canvas) override {
+ SkMatrix matrix = SkMatrix::RotateDeg(fDegrees);
+ // All four quadrants should render the same
+ this->drawFilter(canvas, 0.f, 0.f, this->makeDirectFilter(matrix));
+ this->drawFilter(canvas, 256.f, 0.f, this->makeEarlyComposeFilter(matrix));
+ this->drawFilter(canvas, 0.f, 256.f, this->makeLateComposeFilter(matrix));
+ this->drawFilter(canvas, 256.f, 256.f, this->makeFullComposeFilter(matrix));
+ }
+
+private:
+ SkScalar fDegrees;
+ sk_sp<SkImage> fImage;
+
+ void drawFilter(SkCanvas* canvas, SkScalar tx, SkScalar ty, sk_sp<SkImageFilter> filter) const {
+ SkPaint p;
+ p.setImageFilter(std::move(filter));
+
+ canvas->save();
+ canvas->translate(tx, ty);
+ canvas->clipRect(SkRect::MakeIWH(256, 256));
+ canvas->scale(0.5f, 0.5f);
+ canvas->translate(128, 128);
+ canvas->drawImage(fImage, 0, 0, &p);
+ canvas->restore();
+ }
+
+ // offset(matrix(offset))
+ sk_sp<SkImageFilter> makeDirectFilter(const SkMatrix& matrix) const {
+ SkPoint v = {fImage->width() / 2.f, fImage->height() / 2.f};
+ sk_sp<SkImageFilter> filter = SkImageFilters::Offset(-v.fX, -v.fY, nullptr);
+ filter = SkImageFilters::MatrixTransform(matrix, SkFilterQuality::kLow_SkFilterQuality,
+ std::move(filter));
+ filter = SkImageFilters::Offset(v.fX, v.fY, std::move(filter));
+ return filter;
+ }
+
+ // offset(compose(matrix, offset))
+ sk_sp<SkImageFilter> makeEarlyComposeFilter(const SkMatrix& matrix) const {
+ SkPoint v = {fImage->width() / 2.f, fImage->height() / 2.f};
+ sk_sp<SkImageFilter> offset = SkImageFilters::Offset(-v.fX, -v.fY, nullptr);
+ sk_sp<SkImageFilter> filter = SkImageFilters::MatrixTransform(
+ matrix, SkFilterQuality::kLow_SkFilterQuality, nullptr);
+ filter = SkImageFilters::Compose(std::move(filter), std::move(offset));
+ filter = SkImageFilters::Offset(v.fX, v.fY, std::move(filter));
+ return filter;
+ }
+
+ // compose(offset, matrix(offset))
+ sk_sp<SkImageFilter> makeLateComposeFilter(const SkMatrix& matrix) const {
+ SkPoint v = {fImage->width() / 2.f, fImage->height() / 2.f};
+ sk_sp<SkImageFilter> filter = SkImageFilters::Offset(-v.fX, -v.fY, nullptr);
+ filter = SkImageFilters::MatrixTransform(matrix, SkFilterQuality::kLow_SkFilterQuality,
+ std::move(filter));
+ sk_sp<SkImageFilter> offset = SkImageFilters::Offset(v.fX, v.fY, nullptr);
+ filter = SkImageFilters::Compose(std::move(offset), std::move(filter));
+ return filter;
+ }
+
+ // compose(offset, compose(matrix, offset))
+ sk_sp<SkImageFilter> makeFullComposeFilter(const SkMatrix& matrix) const {
+ SkPoint v = {fImage->width() / 2.f, fImage->height() / 2.f};
+ sk_sp<SkImageFilter> offset = SkImageFilters::Offset(-v.fX, -v.fY, nullptr);
+ sk_sp<SkImageFilter> filter = SkImageFilters::MatrixTransform(
+ matrix, SkFilterQuality::kLow_SkFilterQuality, nullptr);
+ filter = SkImageFilters::Compose(std::move(filter), std::move(offset));
+ offset = SkImageFilters::Offset(v.fX, v.fY, nullptr);
+ filter = SkImageFilters::Compose(std::move(offset), std::move(filter));
+ return filter;
+ }
+};
+
+DEF_GM(return new ImageFilterComposedTransform();)
diff --git a/src/effects/imagefilters/SkComposeImageFilter.cpp b/src/effects/imagefilters/SkComposeImageFilter.cpp
index 7a9f02a..3f98a26 100644
--- a/src/effects/imagefilters/SkComposeImageFilter.cpp
+++ b/src/effects/imagefilters/SkComposeImageFilter.cpp
@@ -116,7 +116,16 @@
const SkImageFilter* outer = this->getInput(0);
const SkImageFilter* inner = this->getInput(1);
- const SkIRect innerRect = inner->filterBounds(src, ctm, dir, inputRect);
- return outer->filterBounds(innerRect, ctm, dir,
- kReverse_MapDirection == dir ? &innerRect : nullptr);
+ if (dir == kReverse_MapDirection) {
+ // The output 'src' is processed by the outer filter, producing its required input bounds,
+ // which is then the output bounds required of the inner filter. We pass the inputRect to
+ // outer and not inner to match the default recursion logic of onGetInputLayerBounds
+ const SkIRect outerRect = outer->filterBounds(src, ctm, dir, inputRect);
+ return inner->filterBounds(outerRect, ctm, dir);
+ } else {
+ // The input 'src' is processed by the inner filter, producing the input bounds for the
+ // outer filter of the composition, which then produces the final forward output bounds
+ const SkIRect innerRect = inner->filterBounds(src, ctm, dir);
+ return outer->filterBounds(innerRect, ctm, dir);
+ }
}