[skottie] Allow extra-normal Bezier interpolation

AE/Lottie allow Bezier control Ys to be outside the [0..1] range.

Update SkCubicMap to do the same.

Bug: skia:8931
Change-Id: I54ee8dbb4e6e0a33a917500523c82fe56c854d4a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/205002
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/include/core/SkCubicMap.h b/include/core/SkCubicMap.h
index 85ed7dd..1375c78 100644
--- a/include/core/SkCubicMap.h
+++ b/include/core/SkCubicMap.h
@@ -16,7 +16,7 @@
  *
  *  pt[0] is implicitly { 0, 0 }
  *  pt[3] is implicitly { 1, 1 }
- *  pts[1,2] are inside the unit square
+ *  pts[1,2].X are inside the unit [0..1]
  */
 class SK_API SkCubicMap {
 public:
diff --git a/modules/skottie/src/SkottieAnimator.cpp b/modules/skottie/src/SkottieAnimator.cpp
index a5323b6..189f421 100644
--- a/modules/skottie/src/SkottieAnimator.cpp
+++ b/modules/skottie/src/SkottieAnimator.cpp
@@ -57,7 +57,7 @@
 
         return rec.cmidx < 0
             ? lt
-            : SkTPin(fCubicMaps[rec.cmidx].computeYFromX(lt), 0.0f, 1.0f);
+            : fCubicMaps[rec.cmidx].computeYFromX(lt);
     }
 
     virtual int parseValue(const skjson::Value&, const AnimationBuilder* abuilder) = 0;
diff --git a/modules/skottie/src/SkottieValue.cpp b/modules/skottie/src/SkottieValue.cpp
index eed53ae..5393849 100644
--- a/modules/skottie/src/SkottieValue.cpp
+++ b/modules/skottie/src/SkottieValue.cpp
@@ -30,7 +30,6 @@
 template <>
 void ValueTraits<ScalarValue>::Lerp(const ScalarValue& v0, const ScalarValue& v1, float t,
                                     ScalarValue* result) {
-    SkASSERT(t >= 0 && t <= 1);
     *result = v0 + (v1 - v0) * t;
 }
 
@@ -183,7 +182,6 @@
 template <>
 void ValueTraits<ShapeValue>::Lerp(const ShapeValue& v0, const ShapeValue& v1, float t,
                                    ShapeValue* result) {
-    SkASSERT(t >= 0 && t <= 1);
     SkASSERT(v0.fVertices.size() == v1.fVertices.size());
     SkASSERT(v0.fClosed == v1.fClosed);
 
diff --git a/modules/skottie/tests/bezier-extranormal.json b/modules/skottie/tests/bezier-extranormal.json
new file mode 100644
index 0000000..cc7f818
--- /dev/null
+++ b/modules/skottie/tests/bezier-extranormal.json
@@ -0,0 +1,254 @@
+{
+  "assets": [],
+  "ddd": 0,
+  "fr": 60,
+  "h": 500,
+  "ip": 0,
+  "layers": [
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 1,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            0,
+            0,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "s": true,
+          "x": {
+            "a": 0,
+            "ix": 3,
+            "k": 250
+          },
+          "y": {
+            "a": 1,
+            "ix": 4,
+            "k": [
+              {
+                "i": {
+                  "x": [
+                    0.978
+                  ],
+                  "y": [
+                    2.189
+                  ]
+                },
+                "o": {
+                  "x": [
+                    0.29
+                  ],
+                  "y": [
+                    -1.57
+                  ]
+                },
+                "s": [
+                  400
+                ],
+                "t": 0
+              },
+              {
+                "s": [
+                  100
+                ],
+                "t": 238
+              }
+            ]
+          }
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            100,
+            100,
+            100
+          ]
+        }
+      },
+      "nm": "Shape Layer 1",
+      "op": 300,
+      "shapes": [
+        {
+          "d": 1,
+          "hd": false,
+          "mn": "ADBE Vector Shape - Ellipse",
+          "nm": "Ellipse Path 1",
+          "p": {
+            "a": 0,
+            "ix": 3,
+            "k": [
+              0,
+              0
+            ]
+          },
+          "s": {
+            "a": 0,
+            "ix": 2,
+            "k": [
+              50,
+              50
+            ]
+          },
+          "ty": "el"
+        },
+        {
+          "bm": 0,
+          "c": {
+            "a": 0,
+            "ix": 4,
+            "k": [
+              1,
+              0,
+              0,
+              1
+            ]
+          },
+          "hd": false,
+          "mn": "ADBE Vector Graphic - Fill",
+          "nm": "Fill 1",
+          "o": {
+            "a": 0,
+            "ix": 5,
+            "k": 100
+          },
+          "r": 1,
+          "ty": "fl"
+        }
+      ],
+      "sr": 1,
+      "st": 0,
+      "ty": 4
+    },
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 2,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            250,
+            1,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "a": 0,
+          "ix": 2,
+          "k": [
+            250,
+            400,
+            0
+          ]
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            100,
+            100,
+            100
+          ]
+        }
+      },
+      "nm": "Green Solid 1",
+      "op": 300,
+      "sc": "#00ff00",
+      "sh": 2,
+      "sr": 1,
+      "st": 0,
+      "sw": 500,
+      "ty": 1
+    },
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 3,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            250,
+            1,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "a": 0,
+          "ix": 2,
+          "k": [
+            250,
+            100,
+            0
+          ]
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            100,
+            100,
+            100
+          ]
+        }
+      },
+      "nm": "Green Solid 1",
+      "op": 300,
+      "sc": "#00ff00",
+      "sh": 2,
+      "sr": 1,
+      "st": 0,
+      "sw": 500,
+      "ty": 1
+    }
+  ],
+  "markers": [],
+  "nm": "negative bezier",
+  "op": 300,
+  "v": "5.5.0",
+  "w": 500
+}
\ No newline at end of file
diff --git a/src/core/SkCubicMap.cpp b/src/core/SkCubicMap.cpp
index 56aef11..7656009 100644
--- a/src/core/SkCubicMap.cpp
+++ b/src/core/SkCubicMap.cpp
@@ -181,8 +181,8 @@
     float b = fCoeff[1].fY;
     float c = fCoeff[2].fY;
     float y = ((a * t + b) * t + c) * t;
-    SkASSERT(y >= 0);
-    return std::min(y, 1.0f);
+
+    return y;
 }
 
 static inline bool coeff_nearly_zero(float delta) {
@@ -190,12 +190,13 @@
 }
 
 SkCubicMap::SkCubicMap(SkPoint p1, SkPoint p2) {
+    // Clamp X values only (we allow Ys outside [0..1]).
+    p1.fX = SkTMin(SkTMax(p1.fX, 0.0f), 1.0f);
+    p2.fX = SkTMin(SkTMax(p2.fX, 0.0f), 1.0f);
+
     Sk2s s1 = Sk2s::Load(&p1) * 3;
     Sk2s s2 = Sk2s::Load(&p2) * 3;
 
-    s1 = Sk2s::Min(Sk2s::Max(s1, 0), 3);
-    s2 = Sk2s::Min(Sk2s::Max(s2, 0), 3);
-
     (Sk2s(1) + s1 - s2).store(&fCoeff[0]);
     (s2 - s1 - s1).store(&fCoeff[1]);
     s1.store(&fCoeff[2]);
diff --git a/tests/CubicMapTest.cpp b/tests/CubicMapTest.cpp
index ff4aae4..db44dc0 100644
--- a/tests/CubicMapTest.cpp
+++ b/tests/CubicMapTest.cpp
@@ -43,7 +43,7 @@
     for (SkScalar x = dx; x < 1; x += dx) {
         SkScalar y = cmap.computeYFromX(x);
         // are we valid and (mostly) monotonic?
-        if (y < 0 || y > 1 || !nearly_le(prev_y, y)) {
+        if (!nearly_le(prev_y, y)) {
             cmap.computeYFromX(x);
             REPORTER_ASSERT(reporter, false);
         }