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>
diff --git a/src/effects/imagefilters/SkImageImageFilter.cpp b/src/effects/imagefilters/SkImageImageFilter.cpp
index a6aa38a..fb52be2 100644
--- a/src/effects/imagefilters/SkImageImageFilter.cpp
+++ b/src/effects/imagefilters/SkImageImageFilter.cpp
@@ -38,6 +38,8 @@
     SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
                                MapDirection, const SkIRect* inputRect) const override;
 
+    bool onCanHandleComplexCTM() const override { return true; }
+
 private:
     friend void ::SkRegisterImageImageFilterFlattenable();
     SK_FLATTENABLE_HOOKS(SkImageImageFilter)
@@ -101,57 +103,56 @@
 
 sk_sp<SkSpecialImage> SkImageImageFilter::onFilterImage(const Context& ctx,
                                                         SkIPoint* offset) const {
-    SkRect dstRect;
-    ctx.ctm().mapRect(&dstRect, fDstRect);
+    const SkRect dstBounds = ctx.ctm().mapRect(fDstRect);
+    const SkIRect dstIBounds = dstBounds.roundOut();
 
-    SkRect bounds = SkRect::MakeIWH(fImage->width(), fImage->height());
-    if (fSrcRect == bounds) {
-        int iLeft = dstRect.fLeft;
-        int iTop = dstRect.fTop;
-        // TODO: this seems to be a very noise-prone way to determine this (esp. the floating-point
-        // widths & heights).
-        if (dstRect.width() == bounds.width() && dstRect.height() == bounds.height() &&
-            iLeft == dstRect.fLeft && iTop == dstRect.fTop) {
-            // The dest is just an un-scaled integer translation of the entire image; return it
-            offset->fX = iLeft;
-            offset->fY = iTop;
+    // Quick check to see if we can return the image directly, which can be done if the transform
+    // ends up being an integer translate and sampling would have no effect on the output.
+    // TODO: This currently means cubic sampling can be skipped, even though it would change results
+    // for integer translation draws.
+    // TODO: This is prone to false negatives due to the floating point math; we could probably
+    // get away with dimensions and translates being epsilon close to integers.
+    const bool passthroughTransform = ctx.ctm().isScaleTranslate() &&
+                                      ctx.ctm().getScaleX() > 0.f &&
+                                      ctx.ctm().getScaleY() > 0.f;
+    const bool passthroughSrcOffsets = SkScalarIsInt(fSrcRect.fLeft) &&
+                                       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(),
-                                                 SkIRect::MakeWH(fImage->width(), fImage->height()),
-                                                 fImage, ctx.surfaceProps());
+    if (passthroughTransform && passthroughSrcOffsets && passthroughDstOffsets && passthroughDims) {
+        // Can pass through fImage directly, applying the dst's location to 'offset'. If fSrcRect
+        // 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(dstIRect.size()));
+    sk_sp<SkSpecialSurface> surf(ctx.makeSurface(dstIBounds.size()));
     if (!surf) {
         return nullptr;
     }
 
     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).
-    dstRect.offset(-SkIntToScalar(dstIRect.fLeft), -SkIntToScalar(dstIRect.fTop));
-    paint.setBlendMode(SkBlendMode::kSrc);
-
-    // FIXME: this probably shouldn't be necessary, but drawImageRect asserts
-    SkSamplingOptions sampling = fSampling;
-    // 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,
+    canvas->translate(-dstIBounds.fLeft, -dstIBounds.fTop);
+    canvas->concat(ctx.ctm());
+    // TODO(skbug.com/5075): Canvases from GPU special surfaces come with unitialized content
+    canvas->clear(SK_ColorTRANSPARENT);
+    canvas->drawImageRect(fImage.get(), fSrcRect, fDstRect, fSampling, nullptr,
                           SkCanvas::kStrict_SrcRectConstraint);
 
-    offset->fX = dstIRect.fLeft;
-    offset->fY = dstIRect.fTop;
+    *offset = dstIBounds.topLeft();
     return surf->makeImageSnapshot();
 }