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>
This commit is contained in:
Michael Ludwig 2020-11-23 10:08:12 -05:00 committed by Skia Commit-Bot
parent b432754c00
commit cbbe0b0df6
2 changed files with 110 additions and 3 deletions

View File

@ -216,3 +216,101 @@ private:
};
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();)

View File

@ -116,7 +116,16 @@ SkIRect SkComposeImageFilterImpl::onFilterBounds(const SkIRect& src, const SkMat
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);
}
}