Update SkMatrixImageFilter to new API

Switches to use new coord-space tagging and separates definitions of
input vs. output bounds. For now, the onFilter logic still handles
rendering but a follow-up CL will fold transforms into FilterResult to
allow more general passthrough and combining the transform with the
draw on the next stage (or final layer restore).

Bug: skia:9282
Change-Id: I4060244283c039d28074c1070397ca10f49872d7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/570396
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/core/SkImageFilterTypes.cpp b/src/core/SkImageFilterTypes.cpp
index 18974df..59240fc 100644
--- a/src/core/SkImageFilterTypes.cpp
+++ b/src/core/SkImageFilterTypes.cpp
@@ -148,6 +148,19 @@
     return SkSize::Make(v.fX, v.fY);
 }
 
+template<>
+SkMatrix Mapping::map<SkMatrix>(const SkMatrix& m, const SkMatrix& matrix) {
+    // If 'matrix' maps from the C1 coord space to the C2 coord space, and 'm' is a transform that
+    // operates on, and outputs to, the C1 coord space, we want to return a new matrix that is
+    // equivalent to 'm' that operates on and outputs to C2. This is the same as mapping the input
+    // from C2 to C1 (matrix^-1), then transforming by 'm', and then mapping from C1 to C2 (matrix).
+    SkMatrix inv;
+    SkAssertResult(matrix.invert(&inv));
+    inv.postConcat(m);
+    inv.postConcat(matrix);
+    return inv;
+}
+
 FilterResult FilterResult::resolveToBounds(const LayerSpace<SkIRect>& newBounds) const {
     // NOTE(michaelludwig) - This implementation is based on the assumption that an image resolved
     // to 'newBounds' will be decal tiled and that the current image is decal tiled. Because of this
diff --git a/src/core/SkImageFilterTypes.h b/src/core/SkImageFilterTypes.h
index 9fc0456..839fa73 100644
--- a/src/core/SkImageFilterTypes.h
+++ b/src/core/SkImageFilterTypes.h
@@ -10,6 +10,8 @@
 
 #include "include/core/SkColorSpace.h"
 #include "include/core/SkMatrix.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkTypes.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
 
@@ -383,6 +385,7 @@
     LayerSpace() = default;
     explicit LayerSpace(const SkRect& geometry) : fData(geometry) {}
     explicit LayerSpace(SkRect&& geometry) : fData(std::move(geometry)) {}
+    explicit LayerSpace(const LayerSpace<SkIRect>& rect) : fData(SkRect::Make(SkIRect(rect))) {}
     explicit operator const SkRect&() const { return fData; }
 
     static LayerSpace<SkRect> Empty() { return LayerSpace<SkRect>(SkRect::MakeEmpty()); }
@@ -418,6 +421,50 @@
     SkRect fData;
 };
 
+// A transformation that manipulates geometry in the layer-space coordinate system. Mathematically
+// there's little difference from these matrices compared to what's stored in a skif::Mapping, but
+// the intent differs. skif::Mapping's matrices map geometry from one coordinate space to another
+// while these transforms move geometry w/o changing the coordinate space semantics.
+// TODO(michaelludwig): Will be replaced with an SkM44 version when skif::Mapping works with SkM44.
+template<>
+class LayerSpace<SkMatrix> {
+public:
+    LayerSpace() = default;
+    explicit LayerSpace(const SkMatrix& m) : fData(m) {}
+    explicit LayerSpace(SkMatrix&& m) : fData(std::move(m)) {}
+    explicit operator const SkMatrix&() const { return fData; }
+
+    // Parrot a limited selection of the SkMatrix API while preserving coordinate space.
+    LayerSpace<SkRect> mapRect(const LayerSpace<SkRect>& r) {
+        return LayerSpace<SkRect>(fData.mapRect(SkRect(r)));
+    }
+
+    LayerSpace<SkPoint> mapPoint(const LayerSpace<SkPoint>& p) {
+        return LayerSpace<SkPoint>(fData.mapPoint(SkPoint(p)));
+    }
+
+    LayerSpace<Vector> mapVector(const LayerSpace<Vector>& v) {
+        return LayerSpace<Vector>(Vector(fData.mapVector(v.x(), v.y())));
+    }
+
+    LayerSpace<SkMatrix>& preConcat(const LayerSpace<SkMatrix>& m) {
+        fData = SkMatrix::Concat(fData, m.fData);
+        return *this;
+    }
+
+    LayerSpace<SkMatrix>& postConcat(const LayerSpace<SkMatrix>& m) {
+        fData = SkMatrix::Concat(m.fData, fData);
+        return *this;
+    }
+
+    bool invert(LayerSpace<SkMatrix>* inverse) {
+        return fData.invert(&inverse->fData);
+    }
+
+private:
+    SkMatrix fData;
+};
+
 // Mapping is the primary definition of the shared layer space used when evaluating an image filter
 // DAG. It encapsulates any needed decomposition of the total CTM into the parameter-to-layer matrix
 // (that filters use to map their parameters to the layer space), and the layer-to-device matrix
@@ -533,6 +580,8 @@
             : fImage(std::move(image))
             , fOrigin{{0, 0}} {}
 
+    explicit operator bool() const { return SkToBool(fImage); }
+
     const SkSpecialImage* image() const { return fImage.get(); }
     sk_sp<SkSpecialImage> refImage() const { return fImage; }
 
diff --git a/src/effects/imagefilters/SkMatrixTransformImageFilter.cpp b/src/effects/imagefilters/SkMatrixTransformImageFilter.cpp
index 0efc089..4e5b3ba 100644
--- a/src/effects/imagefilters/SkMatrixTransformImageFilter.cpp
+++ b/src/effects/imagefilters/SkMatrixTransformImageFilter.cpp
@@ -5,12 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "include/core/SkFlattenable.h"
 #include "src/core/SkImageFilter_Base.h"
 
 #include "include/core/SkCanvas.h"
+#include "include/core/SkFlattenable.h"
 #include "include/core/SkRect.h"
 #include "include/effects/SkImageFilters.h"
+#include "src/core/SkImageFilterTypes.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSamplingPriv.h"
 #include "src/core/SkSpecialImage.h"
@@ -29,25 +30,32 @@
             , fTransform(transform)
             , fSampling(sampling) {
         // Pre-cache so future calls to fTransform.getType() are threadsafe.
-        (void)fTransform.getType();
+        (void) static_cast<const SkMatrix&>(fTransform).getType();
     }
 
     SkRect computeFastBounds(const SkRect&) const override;
 
 protected:
-
     void flatten(SkWriteBuffer&) const override;
 
-    sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint* offset) const override;
-    SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
-                               MapDirection, const SkIRect* inputRect) const override;
-
 private:
     friend void ::SkRegisterMatrixTransformImageFilterFlattenable();
     SK_FLATTENABLE_HOOKS(SkMatrixTransformImageFilter)
 
-    SkMatrix            fTransform;
-    SkSamplingOptions   fSampling;
+    skif::FilterResult onFilterImage(const skif::Context& context) const override;
+
+    skif::LayerSpace<SkIRect> onGetInputLayerBounds(
+            const skif::Mapping& mapping,
+            const skif::LayerSpace<SkIRect>& desiredOutput,
+            const skif::LayerSpace<SkIRect>& contentBounds,
+            VisitChildren recurse) const override;
+
+    skif::LayerSpace<SkIRect> onGetOutputLayerBounds(
+            const skif::Mapping& mapping,
+            const skif::LayerSpace<SkIRect>& contentBounds) const override;
+
+    skif::ParameterSpace<SkMatrix> fTransform;
+    SkSamplingOptions fSampling;
 };
 
 } // namespace
@@ -83,40 +91,36 @@
 
 void SkMatrixTransformImageFilter::flatten(SkWriteBuffer& buffer) const {
     this->SkImageFilter_Base::flatten(buffer);
-    buffer.writeMatrix(fTransform);
+    buffer.writeMatrix(SkMatrix(fTransform));
     buffer.writeSampling(fSampling);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-sk_sp<SkSpecialImage> SkMatrixTransformImageFilter::onFilterImage(const Context& ctx,
-                                                                  SkIPoint* offset) const {
-
-    SkIPoint inputOffset = SkIPoint::Make(0, 0);
-    sk_sp<SkSpecialImage> input(this->filterInput(0, ctx, &inputOffset));
-    if (!input) {
-        return nullptr;
+skif::FilterResult SkMatrixTransformImageFilter::onFilterImage(const skif::Context& context) const {
+    skif::FilterResult childOutput = this->filterInput(0, context);
+    if (!childOutput) {
+        return {};
     }
 
-    SkMatrix matrix;
-    if (!ctx.ctm().invert(&matrix)) {
-        return nullptr;
+    skif::LayerSpace<SkRect> srcBoundsF{childOutput.layerBounds()};
+    skif::LayerSpace<SkMatrix> matrix = context.mapping().paramToLayer(fTransform);
+    skif::LayerSpace<SkIRect> dstBounds = matrix.mapRect(srcBoundsF).roundOut();
+
+    // TODO(michaelludwig): Generalize this logic for image filters that fall into the pattern of
+    // draw into canvas, or fill canvas with X. Ideally it would let us avoid building a full
+    // canvas and device for the very constrained operations that they actually perform.
+
+    // Unless a child filter optimized its result, srcBounds should be large enough that dstBounds
+    // will cover the desired output. However, we only need to produce the intersection between
+    // what's desired and what's defined.
+    if (!dstBounds.intersect(context.desiredOutput())) {
+        return {};
     }
-    matrix.postConcat(fTransform);
-    matrix.postConcat(ctx.ctm());
 
-    const SkIRect srcBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(),
-                                                input->width(), input->height());
-    const SkRect srcRect = SkRect::Make(srcBounds);
-
-    SkRect dstRect;
-    matrix.mapRect(&dstRect, srcRect);
-    SkIRect dstBounds;
-    dstRect.roundOut(&dstBounds);
-
-    sk_sp<SkSpecialSurface> surf(ctx.makeSurface(dstBounds.size()));
+    sk_sp<SkSpecialSurface> surf(context.makeSurface(SkISize(dstBounds.size())));
     if (!surf) {
-        return nullptr;
+        return {};
     }
 
     SkCanvas* canvas = surf->getCanvas();
@@ -124,54 +128,55 @@
 
     canvas->clear(0x0);
 
-    canvas->translate(-SkIntToScalar(dstBounds.x()), -SkIntToScalar(dstBounds.y()));
-    canvas->concat(matrix);
+    canvas->translate(-dstBounds.left(), -dstBounds.top());
+    canvas->concat(SkMatrix(matrix));
 
     SkPaint paint;
     paint.setAntiAlias(true);
     paint.setBlendMode(SkBlendMode::kSrc);
 
-    input->draw(canvas, srcRect.x(), srcRect.y(), fSampling, &paint);
+    childOutput.image()->draw(canvas, srcBoundsF.left(), srcBoundsF.top(), fSampling, &paint);
 
-    offset->fX = dstBounds.fLeft;
-    offset->fY = dstBounds.fTop;
-    return surf->makeImageSnapshot();
+    return {surf->makeImageSnapshot(), dstBounds.topLeft()};
 }
 
 SkRect SkMatrixTransformImageFilter::computeFastBounds(const SkRect& src) const {
     SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
-    SkRect dst;
-    fTransform.mapRect(&dst, bounds);
-    return dst;
+    return static_cast<const SkMatrix&>(fTransform).mapRect(bounds);
 }
 
-SkIRect SkMatrixTransformImageFilter::onFilterNodeBounds(const SkIRect& src,
-                                                         const SkMatrix& ctm,
-                                                         MapDirection dir,
-                                                         const SkIRect* inputRect) const {
-    SkMatrix matrix;
-    if (!ctm.invert(&matrix)) {
-        return src;
+skif::LayerSpace<SkIRect> SkMatrixTransformImageFilter::onGetInputLayerBounds(
+        const skif::Mapping& mapping,
+        const skif::LayerSpace<SkIRect>& desiredOutput,
+        const skif::LayerSpace<SkIRect>& contentBounds,
+        VisitChildren recurse) const {
+    // The required input for this filter to cover 'desiredOutput' is the smallest rectangle such
+    // that after being transformed by the layer-space adjusted 'fTransform', it contains the output
+    skif::LayerSpace<SkMatrix> inverse;
+    if (!mapping.paramToLayer(fTransform).invert(&inverse)) {
+        return skif::LayerSpace<SkIRect>::Empty();
     }
-    if (kForward_MapDirection == dir) {
-        matrix.postConcat(fTransform);
+    skif::LayerSpace<SkIRect> requiredInput =
+            inverse.mapRect(skif::LayerSpace<SkRect>(desiredOutput)).roundOut();
+
+    // Additionally if there is any filtering beyond nearest neighbor, we request an extra buffer of
+    // pixels so that the content is available to the bilerp/bicubic kernel.
+    if (fSampling != SkSamplingOptions()) {
+        requiredInput.outset(skif::LayerSpace<SkISize>({1, 1}));
+    }
+
+    if (recurse == VisitChildren::kNo) {
+        return requiredInput;
     } else {
-        SkMatrix transformInverse;
-        if (!fTransform.invert(&transformInverse)) {
-            return src;
-        }
-        matrix.postConcat(transformInverse);
+        // Our required input is the desired output for our child image filter.
+        return this->visitInputLayerBounds(mapping, requiredInput, contentBounds);
     }
-    matrix.postConcat(ctm);
-    SkRect floatBounds;
-    matrix.mapRect(&floatBounds, SkRect::Make(src));
-    SkIRect result = floatBounds.roundOut();
+}
 
-    if (kReverse_MapDirection == dir && SkSamplingOptions() != fSampling) {
-        // When filtering we might need some pixels in the source that might be otherwise
-        // clipped off.
-        result.outset(1, 1);
-    }
-
-    return result;
+skif::LayerSpace<SkIRect> SkMatrixTransformImageFilter::onGetOutputLayerBounds(
+        const skif::Mapping& mapping,
+        const skif::LayerSpace<SkIRect>& contentBounds) const {
+    // The output of this filter is the transformed bounds of its child's output.
+    skif::LayerSpace<SkRect> childOutput{this->visitOutputLayerBounds(mapping, contentBounds)};
+    return mapping.paramToLayer(fTransform).mapRect(childOutput).roundOut();
 }