Support complex matrices in Image image filter

Instead of using drawImageRect from fSrcRect to a dst rect already
mapped by the CTM, this concats the CTM to the intermediate canvas and
then does a drawImageRect from fSrcRect to fDstRect.

It also updates the passthrough optimization to require positive scale
factors so that mirrors aren't accidentally ignored.

Bug: skia:11994, skia:12015
Change-Id: I6f3ee7afd842818dc9b8146beb866ac8b8f9a990
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/410098
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Michael Ludwig 2021-05-18 21:16:50 -04:00 committed by Skia Commit-Bot
parent c6260f9742
commit ee8f277198

View File

@ -38,6 +38,8 @@ protected:
SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
MapDirection, const SkIRect* inputRect) const override; MapDirection, const SkIRect* inputRect) const override;
bool onCanHandleComplexCTM() const override { return true; }
private: private:
friend void ::SkRegisterImageImageFilterFlattenable(); friend void ::SkRegisterImageImageFilterFlattenable();
SK_FLATTENABLE_HOOKS(SkImageImageFilter) SK_FLATTENABLE_HOOKS(SkImageImageFilter)
@ -101,57 +103,56 @@ void SkImageImageFilter::flatten(SkWriteBuffer& buffer) const {
sk_sp<SkSpecialImage> SkImageImageFilter::onFilterImage(const Context& ctx, sk_sp<SkSpecialImage> SkImageImageFilter::onFilterImage(const Context& ctx,
SkIPoint* offset) const { SkIPoint* offset) const {
SkRect dstRect; const SkRect dstBounds = ctx.ctm().mapRect(fDstRect);
ctx.ctm().mapRect(&dstRect, fDstRect); const SkIRect dstIBounds = dstBounds.roundOut();
SkRect bounds = SkRect::MakeIWH(fImage->width(), fImage->height()); // Quick check to see if we can return the image directly, which can be done if the transform
if (fSrcRect == bounds) { // ends up being an integer translate and sampling would have no effect on the output.
int iLeft = dstRect.fLeft; // TODO: This currently means cubic sampling can be skipped, even though it would change results
int iTop = dstRect.fTop; // for integer translation draws.
// TODO: this seems to be a very noise-prone way to determine this (esp. the floating-point // TODO: This is prone to false negatives due to the floating point math; we could probably
// widths & heights). // get away with dimensions and translates being epsilon close to integers.
if (dstRect.width() == bounds.width() && dstRect.height() == bounds.height() && const bool passthroughTransform = ctx.ctm().isScaleTranslate() &&
iLeft == dstRect.fLeft && iTop == dstRect.fTop) { ctx.ctm().getScaleX() > 0.f &&
// The dest is just an un-scaled integer translation of the entire image; return it ctx.ctm().getScaleY() > 0.f;
offset->fX = iLeft; const bool passthroughSrcOffsets = SkScalarIsInt(fSrcRect.fLeft) &&
offset->fY = iTop; SkScalarIsInt(fSrcRect.fTop);
const bool passthroughDstOffsets = SkScalarIsInt(dstBounds.fLeft) &&
SkScalarIsInt(dstBounds.fTop);
const bool passthroughDims =
SkScalarIsInt(fSrcRect.width()) && fSrcRect.width() == dstBounds.width() &&
SkScalarIsInt(fSrcRect.height()) && fSrcRect.height() == dstBounds.height();
return SkSpecialImage::MakeFromImage(ctx.getContext(), if (passthroughTransform && passthroughSrcOffsets && passthroughDstOffsets && passthroughDims) {
SkIRect::MakeWH(fImage->width(), fImage->height()), // Can pass through fImage directly, applying the dst's location to 'offset'. If fSrcRect
fImage, ctx.surfaceProps()); // extends outside of the image, we adjust dst to match since those areas would have been
// transparent black anyways.
SkIRect srcIBounds = fSrcRect.roundOut();
SkIPoint srcOffset = srcIBounds.topLeft();
if (!srcIBounds.intersect(SkIRect::MakeWH(fImage->width(), fImage->height()))) {
return nullptr;
} }
*offset = dstIBounds.topLeft() + srcIBounds.topLeft() - srcOffset;
return SkSpecialImage::MakeFromImage(ctx.getContext(), srcIBounds, fImage,
ctx.surfaceProps());
} }
const SkIRect dstIRect = dstRect.roundOut(); sk_sp<SkSpecialSurface> surf(ctx.makeSurface(dstIBounds.size()));
sk_sp<SkSpecialSurface> surf(ctx.makeSurface(dstIRect.size()));
if (!surf) { if (!surf) {
return nullptr; return nullptr;
} }
SkCanvas* canvas = surf->getCanvas(); SkCanvas* canvas = surf->getCanvas();
SkASSERT(canvas);
// TODO: it seems like this clear shouldn't be necessary (see skbug.com/5075)
canvas->clear(0x0);
SkPaint paint;
// Subtract off the integer component of the translation (will be applied in offset, below). // Subtract off the integer component of the translation (will be applied in offset, below).
dstRect.offset(-SkIntToScalar(dstIRect.fLeft), -SkIntToScalar(dstIRect.fTop)); canvas->translate(-dstIBounds.fLeft, -dstIBounds.fTop);
paint.setBlendMode(SkBlendMode::kSrc); canvas->concat(ctx.ctm());
// TODO(skbug.com/5075): Canvases from GPU special surfaces come with unitialized content
// FIXME: this probably shouldn't be necessary, but drawImageRect asserts canvas->clear(SK_ColorTRANSPARENT);
SkSamplingOptions sampling = fSampling; canvas->drawImageRect(fImage.get(), fSrcRect, fDstRect, fSampling, nullptr,
// None filtering when it's translate-only (even for cubicresampling? <reed>)
if (fSrcRect.width() == dstRect.width() && fSrcRect.height() == dstRect.height()) {
sampling = SkSamplingOptions();
}
canvas->drawImageRect(fImage.get(), fSrcRect, dstRect, sampling, &paint,
SkCanvas::kStrict_SrcRectConstraint); SkCanvas::kStrict_SrcRectConstraint);
offset->fX = dstIRect.fLeft; *offset = dstIBounds.topLeft();
offset->fY = dstIRect.fTop;
return surf->makeImageSnapshot(); return surf->makeImageSnapshot();
} }