[skif] Consolidate mapping/layerspace code

Removes the static functions that were used by both Mapping and
LayerSpace<T>, and instead just has LayerSpace<T> use Mapping's
functionality.

Adjusts some of the ceil/floor functions to match how rectangles are
rounded (i.e. include the rounding epsilon).

Improves mapSize to be more correct when the transform is not just a
scale/translate matrix.

Bug: skia:9282
Change-Id: I1116cae9d7d3a486d601f985c2c779259bb2acda
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/706438
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 5d0970b..81c37ca 100644
--- a/src/core/SkImageFilterTypes.cpp
+++ b/src/core/SkImageFilterTypes.cpp
@@ -28,87 +28,6 @@
 // input image when using a strict roundOut.
 static constexpr float kRoundEpsilon = 1e-3f;
 
-// Both [I]Vectors and Sk[I]Sizes are transformed as non-positioned values, i.e. go through
-// mapVectors() not mapPoints().
-SkIVector map_as_vector(int32_t x, int32_t y, const SkMatrix& matrix) {
-    SkVector v = SkVector::Make(SkIntToScalar(x), SkIntToScalar(y));
-    matrix.mapVectors(&v, 1);
-    return SkIVector::Make(SkScalarRoundToInt(v.fX), SkScalarRoundToInt(v.fY));
-}
-
-SkVector map_as_vector(SkScalar x, SkScalar y, const SkMatrix& matrix) {
-    SkVector v = SkVector::Make(x, y);
-    matrix.mapVectors(&v, 1);
-    return v;
-}
-
-SkRect map_rect(const SkMatrix& matrix, const SkRect& rect) {
-    return rect.isEmpty() ? SkRect::MakeEmpty() : matrix.mapRect(rect);
-}
-
-SkIRect map_rect(const SkMatrix& matrix, const SkIRect& rect) {
-    if (rect.isEmpty()) {
-        return SkIRect::MakeEmpty();
-    }
-    // Unfortunately, there is a range of integer values such that we have 1px precision as an int,
-    // but less precision as a float. This can lead to non-empty SkIRects becoming empty simply
-    // because of float casting. If we're already dealing with a float rect or having a float
-    // output, that's what we're stuck with; but if we are starting form an irect and desiring an
-    // SkIRect output, we go through efforts to preserve the 1px precision for simple transforms.
-    if (matrix.isScaleTranslate()) {
-        double l = (double)matrix.getScaleX()*rect.fLeft   + (double)matrix.getTranslateX();
-        double r = (double)matrix.getScaleX()*rect.fRight  + (double)matrix.getTranslateX();
-        double t = (double)matrix.getScaleY()*rect.fTop    + (double)matrix.getTranslateY();
-        double b = (double)matrix.getScaleY()*rect.fBottom + (double)matrix.getTranslateY();
-        return {sk_double_saturate2int(sk_double_floor(std::min(l, r) + kRoundEpsilon)),
-                sk_double_saturate2int(sk_double_floor(std::min(t, b) + kRoundEpsilon)),
-                sk_double_saturate2int(sk_double_ceil(std::max(l, r)  - kRoundEpsilon)),
-                sk_double_saturate2int(sk_double_ceil(std::max(t, b)  - kRoundEpsilon))};
-    } else {
-        return skif::RoundOut(matrix.mapRect(SkRect::Make(rect)));
-    }
-}
-
-bool inverse_map_rect(const SkMatrix& matrix, const SkRect& rect, SkRect* out) {
-    if (rect.isEmpty()) {
-        *out = SkRect::MakeEmpty();
-        return true;
-    }
-    return SkMatrixPriv::InverseMapRect(matrix, out, rect);
-}
-
-bool inverse_map_rect(const SkMatrix& matrix, const SkIRect& rect, SkIRect* out) {
-    if (rect.isEmpty()) {
-        *out = SkIRect::MakeEmpty();
-        return true;
-    }
-    // This is a specialized inverse equivalent to the 1px precision preserving map_rect above.
-    if (matrix.isScaleTranslate()) {
-        // A scale-translate matrix with a 0 scale factor is not invertible.
-        if (matrix.getScaleX() == 0.f || matrix.getScaleY() == 0.f) {
-            return false;
-        }
-        double l = (rect.fLeft   - (double)matrix.getTranslateX()) / (double)matrix.getScaleX();
-        double r = (rect.fRight  - (double)matrix.getTranslateX()) / (double)matrix.getScaleX();
-        double t = (rect.fTop    - (double)matrix.getTranslateY()) / (double)matrix.getScaleY();
-        double b = (rect.fBottom - (double)matrix.getTranslateY()) / (double)matrix.getScaleY();
-
-        *out = {sk_double_saturate2int(sk_double_floor(std::min(l, r) + kRoundEpsilon)),
-                sk_double_saturate2int(sk_double_floor(std::min(t, b) + kRoundEpsilon)),
-                sk_double_saturate2int(sk_double_ceil(std::max(l, r)  - kRoundEpsilon)),
-                sk_double_saturate2int(sk_double_ceil(std::max(t, b)  - kRoundEpsilon))};
-        return true;
-    } else {
-        SkRect mapped;
-        if (inverse_map_rect(matrix, SkRect::Make(rect), &mapped)) {
-            *out = skif::RoundOut(mapped);
-            return true;
-        } else {
-            return false;
-        }
-    }
-}
-
 // If m is epsilon within the form [1 0 tx], this returns true and sets out to [tx, ty]
 //                                 [0 1 ty]
 //                                 [0 0 1 ]
@@ -223,10 +142,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-SkIRect RoundOut(SkRect r) { return r.makeInset(kRoundEpsilon, kRoundEpsilon).roundOut(); }
-
-SkIRect RoundIn(SkRect r) { return r.makeOutset(kRoundEpsilon, kRoundEpsilon).roundIn(); }
-
 sk_sp<SkSpecialSurface> Context::makeSurface(const SkISize& size,
                                              const SkSurfaceProps* props) const {
     if (!props) {
@@ -260,6 +175,10 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Mapping
 
+SkIRect RoundOut(SkRect r) { return r.makeInset(kRoundEpsilon, kRoundEpsilon).roundOut(); }
+
+SkIRect RoundIn(SkRect r) { return r.makeOutset(kRoundEpsilon, kRoundEpsilon).roundIn(); }
+
 bool Mapping::decomposeCTM(const SkMatrix& ctm, const SkImageFilter* filter,
                            const skif::ParameterSpace<SkPoint>& representativePt) {
     SkMatrix remainder, layer;
@@ -328,12 +247,31 @@
 // Instantiate map specializations for the 6 geometric types used during filtering
 template<>
 SkRect Mapping::map<SkRect>(const SkRect& geom, const SkMatrix& matrix) {
-    return map_rect(matrix, geom);
+    return geom.isEmpty() ? SkRect::MakeEmpty() : matrix.mapRect(geom);
 }
 
 template<>
 SkIRect Mapping::map<SkIRect>(const SkIRect& geom, const SkMatrix& matrix) {
-    return map_rect(matrix, geom);
+    if (geom.isEmpty()) {
+        return SkIRect::MakeEmpty();
+    }
+    // Unfortunately, there is a range of integer values such that we have 1px precision as an int,
+    // but less precision as a float. This can lead to non-empty SkIRects becoming empty simply
+    // because of float casting. If we're already dealing with a float rect or having a float
+    // output, that's what we're stuck with; but if we are starting form an irect and desiring an
+    // SkIRect output, we go through efforts to preserve the 1px precision for simple transforms.
+    if (matrix.isScaleTranslate()) {
+        double l = (double)matrix.getScaleX()*geom.fLeft   + (double)matrix.getTranslateX();
+        double r = (double)matrix.getScaleX()*geom.fRight  + (double)matrix.getTranslateX();
+        double t = (double)matrix.getScaleY()*geom.fTop    + (double)matrix.getTranslateY();
+        double b = (double)matrix.getScaleY()*geom.fBottom + (double)matrix.getTranslateY();
+        return {sk_double_saturate2int(sk_double_floor(std::min(l, r) + kRoundEpsilon)),
+                sk_double_saturate2int(sk_double_floor(std::min(t, b) + kRoundEpsilon)),
+                sk_double_saturate2int(sk_double_ceil(std::max(l, r)  - kRoundEpsilon)),
+                sk_double_saturate2int(sk_double_ceil(std::max(t, b)  - kRoundEpsilon))};
+    } else {
+        return RoundOut(matrix.mapRect(SkRect::Make(geom)));
+    }
 }
 
 template<>
@@ -351,25 +289,40 @@
 }
 
 template<>
-IVector Mapping::map<IVector>(const IVector& geom, const SkMatrix& matrix) {
-    return IVector(map_as_vector(geom.fX, geom.fY, matrix));
+Vector Mapping::map<Vector>(const Vector& geom, const SkMatrix& matrix) {
+    SkVector v = SkVector::Make(geom.fX, geom.fY);
+    matrix.mapVectors(&v, 1);
+    return Vector{v};
 }
 
 template<>
-Vector Mapping::map<Vector>(const Vector& geom, const SkMatrix& matrix) {
-    return Vector(map_as_vector(geom.fX, geom.fY, matrix));
+IVector Mapping::map<IVector>(const IVector& geom, const SkMatrix& matrix) {
+    SkVector v = SkVector::Make(SkIntToScalar(geom.fX), SkIntToScalar(geom.fY));
+    matrix.mapVectors(&v, 1);
+    return IVector(SkScalarRoundToInt(v.fX), SkScalarRoundToInt(v.fY));
+}
+
+// Sizes are also treated as non-positioned values (although this assumption breaks down if there's
+// perspective). Unlike vectors, we treat input sizes as specifying lengths of the local X and Y
+// axes and return the lengths of those mapped axes.
+template<>
+SkSize Mapping::map<SkSize>(const SkSize& geom, const SkMatrix& matrix) {
+    if (matrix.isScaleTranslate()) {
+        // This is equivalent to mapping the two basis vectors and calculating their lengths.
+        SkVector sizes = matrix.mapVector(geom.fWidth, geom.fHeight);
+        return {SkScalarAbs(sizes.fX), SkScalarAbs(sizes.fY)};
+    }
+
+    SkVector xAxis = matrix.mapVector(geom.fWidth, 0.f);
+    SkVector yAxis = matrix.mapVector(0.f, geom.fHeight);
+    return {xAxis.length(), yAxis.length()};
 }
 
 template<>
 SkISize Mapping::map<SkISize>(const SkISize& geom, const SkMatrix& matrix) {
-    SkIVector v = map_as_vector(geom.fWidth, geom.fHeight, matrix);
-    return SkISize::Make(v.fX, v.fY);
-}
-
-template<>
-SkSize Mapping::map<SkSize>(const SkSize& geom, const SkMatrix& matrix) {
-    SkVector v = map_as_vector(geom.fWidth, geom.fHeight, matrix);
-    return SkSize::Make(v.fX, v.fY);
+    SkSize size = map(SkSize::Make(geom), matrix);
+    return SkISize::Make(SkScalarCeilToInt(size.fWidth - kRoundEpsilon),
+                         SkScalarCeilToInt(size.fHeight - kRoundEpsilon));
 }
 
 template<>
@@ -388,18 +341,49 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // LayerSpace<T>
 
-LayerSpace<SkRect> LayerSpace<SkMatrix>::mapRect(const LayerSpace<SkRect>& r) const {
-    return LayerSpace<SkRect>(map_rect(fData, SkRect(r)));
+// Match rounding tolerances of SkRects to SkIRects
+LayerSpace<SkISize> LayerSpace<SkSize>::round() const {
+    return LayerSpace<SkISize>(fData.toRound());
+}
+LayerSpace<SkISize> LayerSpace<SkSize>::ceil() const {
+    return LayerSpace<SkISize>({SkScalarCeilToInt(fData.fWidth - kRoundEpsilon),
+                                SkScalarCeilToInt(fData.fHeight - kRoundEpsilon)});
+}
+LayerSpace<SkISize> LayerSpace<SkSize>::floor() const {
+    return LayerSpace<SkISize>({SkScalarFloorToInt(fData.fWidth + kRoundEpsilon),
+                                SkScalarFloorToInt(fData.fHeight + kRoundEpsilon)});
 }
 
+LayerSpace<SkRect> LayerSpace<SkMatrix>::mapRect(const LayerSpace<SkRect>& r) const {
+    return LayerSpace<SkRect>(Mapping::map(SkRect(r), fData));
+}
+
+// Effectively mapRect(SkRect).roundOut() but more accurate when the underlying matrix or
+// SkIRect has large floating point values.
 LayerSpace<SkIRect> LayerSpace<SkMatrix>::mapRect(const LayerSpace<SkIRect>& r) const {
-    return LayerSpace<SkIRect>(map_rect(fData, SkIRect(r)));
+    return LayerSpace<SkIRect>(Mapping::map(SkIRect(r), fData));
+}
+
+LayerSpace<SkPoint> LayerSpace<SkMatrix>::mapPoint(const LayerSpace<SkPoint>& p) const {
+    return LayerSpace<SkPoint>(Mapping::map(SkPoint(p), fData));
+}
+
+LayerSpace<Vector> LayerSpace<SkMatrix>::mapVector(const LayerSpace<Vector>& v) const {
+    return LayerSpace<Vector>(Mapping::map(Vector(v), fData));
+}
+
+LayerSpace<SkSize> LayerSpace<SkMatrix>::mapSize(const LayerSpace<SkSize>& s) const {
+    return LayerSpace<SkSize>(Mapping::map(SkSize(s), fData));
 }
 
 bool LayerSpace<SkMatrix>::inverseMapRect(const LayerSpace<SkRect>& r,
                                           LayerSpace<SkRect>* out) const {
     SkRect mapped;
-    if (inverse_map_rect(fData, SkRect(r), &mapped)) {
+    if (r.isEmpty()) {
+        // An empty input always inverse maps to an empty rect "successfully"
+        *out = LayerSpace<SkRect>::Empty();
+        return true;
+    } else if (SkMatrixPriv::InverseMapRect(fData, &mapped, SkRect(r))) {
         *out = LayerSpace<SkRect>(mapped);
         return true;
     } else {
@@ -407,15 +391,37 @@
     }
 }
 
-bool LayerSpace<SkMatrix>::inverseMapRect(const LayerSpace<SkIRect>& r,
+bool LayerSpace<SkMatrix>::inverseMapRect(const LayerSpace<SkIRect>& rect,
                                           LayerSpace<SkIRect>* out) const {
-    SkIRect mapped;
-    if (inverse_map_rect(fData, SkIRect(r), &mapped)) {
+    if (rect.isEmpty()) {
+        // An empty input always inverse maps to an empty rect "successfully"
+        *out = LayerSpace<SkIRect>::Empty();
+        return true;
+    } else if (fData.isScaleTranslate()) { // Specialized inverse of 1px-preserving map<SkIRect>
+        // A scale-translate matrix with a 0 scale factor is not invertible.
+        if (fData.getScaleX() == 0.f || fData.getScaleY() == 0.f) {
+            return false;
+        }
+        double l = (rect.left()   - (double)fData.getTranslateX()) / (double)fData.getScaleX();
+        double r = (rect.right()  - (double)fData.getTranslateX()) / (double)fData.getScaleX();
+        double t = (rect.top()    - (double)fData.getTranslateY()) / (double)fData.getScaleY();
+        double b = (rect.bottom() - (double)fData.getTranslateY()) / (double)fData.getScaleY();
+
+        SkIRect mapped{sk_double_saturate2int(sk_double_floor(std::min(l, r) + kRoundEpsilon)),
+                       sk_double_saturate2int(sk_double_floor(std::min(t, b) + kRoundEpsilon)),
+                       sk_double_saturate2int(sk_double_ceil(std::max(l, r)  - kRoundEpsilon)),
+                       sk_double_saturate2int(sk_double_ceil(std::max(t, b)  - kRoundEpsilon))};
         *out = LayerSpace<SkIRect>(mapped);
         return true;
     } else {
-        return false;
+        SkRect mapped;
+        if (SkMatrixPriv::InverseMapRect(fData, &mapped, SkRect::Make(SkIRect(rect)))) {
+            *out = LayerSpace<SkRect>(mapped).roundOut();
+            return true;
+        }
     }
+
+    return false;
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkImageFilterTypes.h b/src/core/SkImageFilterTypes.h
index f0bd759..b343808 100644
--- a/src/core/SkImageFilterTypes.h
+++ b/src/core/SkImageFilterTypes.h
@@ -349,9 +349,9 @@
     bool isEmpty() const { return fData.isEmpty(); }
     bool isZero() const { return fData.isZero(); }
 
-    LayerSpace<SkISize> round() const { return LayerSpace<SkISize>(fData.toRound()); }
-    LayerSpace<SkISize> ceil() const { return LayerSpace<SkISize>(fData.toCeil()); }
-    LayerSpace<SkISize> floor() const { return LayerSpace<SkISize>(fData.toFloor()); }
+    LayerSpace<SkISize> round() const;
+    LayerSpace<SkISize> ceil() const;
+    LayerSpace<SkISize> floor() const;
 
 private:
     SkSize fData;
@@ -470,13 +470,11 @@
     // SkIRect has large floating point values.
     LayerSpace<SkIRect> mapRect(const LayerSpace<SkIRect>& r) const;
 
-    LayerSpace<SkPoint> mapPoint(const LayerSpace<SkPoint>& p) const {
-        return LayerSpace<SkPoint>(fData.mapPoint(SkPoint(p)));
-    }
+    LayerSpace<SkPoint> mapPoint(const LayerSpace<SkPoint>& p) const;
 
-    LayerSpace<Vector> mapVector(const LayerSpace<Vector>& v) const {
-        return LayerSpace<Vector>(Vector(fData.mapVector(v.x(), v.y())));
-    }
+    LayerSpace<Vector> mapVector(const LayerSpace<Vector>& v) const;
+
+    LayerSpace<SkSize> mapSize(const LayerSpace<SkSize>& s) const;
 
     LayerSpace<SkMatrix>& preConcat(const LayerSpace<SkMatrix>& m) {
         fData = SkMatrix::Concat(fData, m.fData);
@@ -577,6 +575,8 @@
     }
 
 private:
+    friend class LayerSpace<SkMatrix>; // for map()
+
     // The image filter process decomposes the total CTM into layerToDev * paramToLayer and uses the
     // param-to-layer matrix to define the layer-space coordinate system. Depending on how it's
     // decomposed, either the layer matrix or the device matrix could be the identity matrix (but