Fix convexicator bug

BUG=crbug.com/412640
R=caryclark@google.com, bsalomon@google.com

Author: robertphillips@google.com

Review URL: https://codereview.chromium.org/573763002

Review URL: https://codereview.chromium.org/597963002
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index aa7943c..457ad9d 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -2132,6 +2132,16 @@
 static int sign(SkScalar x) { return x < 0; }
 #define kValueNeverReturnedBySign   2
 
+enum DirChange {
+    kLeft_DirChange,
+    kRight_DirChange,
+    kStraight_DirChange,
+    kBackwards_DirChange,
+
+    kInvalid_DirChange
+};
+
+
 static bool almost_equal(SkScalar compA, SkScalar compB) {
     // The error epsilon was empirically derived; worse case round rects
     // with a mid point outset by 2x float epsilon in tests had an error
@@ -2146,13 +2156,37 @@
     return aBits < bBits + epsilon && bBits < aBits + epsilon;
 }
 
+static DirChange direction_change(const SkPoint& lastPt, const SkVector& curPt,
+                                  const SkVector& lastVec, const SkVector& curVec) {
+    SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
+
+    SkScalar smallest = SkTMin(curPt.fX, SkTMin(curPt.fY, SkTMin(lastPt.fX, lastPt.fY)));
+    SkScalar largest = SkTMax(curPt.fX, SkTMax(curPt.fY, SkTMax(lastPt.fX, lastPt.fY)));
+    largest = SkTMax(largest, -smallest);
+
+    if (!almost_equal(largest, largest + cross)) {
+        int sign = SkScalarSignAsInt(cross);
+        if (sign) {
+            return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
+        }
+    }
+
+    if (!SkScalarNearlyZero(lastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
+        !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
+        lastVec.dot(curVec) < 0.0f) {
+        return kBackwards_DirChange;
+    }
+
+    return kStraight_DirChange;
+}
+
 // only valid for a single contour
 struct Convexicator {
     Convexicator()
     : fPtCount(0)
     , fConvexity(SkPath::kConvex_Convexity)
     , fDirection(SkPath::kUnknown_Direction) {
-        fSign = 0;
+        fExpectedDir = kInvalid_DirChange;
         // warnings
         fLastPt.set(0, 0);
         fCurrPt.set(0, 0);
@@ -2211,20 +2245,28 @@
 private:
     void addVec(const SkVector& vec) {
         SkASSERT(vec.fX || vec.fY);
-        SkScalar cross = SkPoint::CrossProduct(fLastVec, vec);
-        SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
-        SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
-        largest = SkTMax(largest, -smallest);
-        if (!almost_equal(largest, largest + cross)) {
-            int sign = SkScalarSignAsInt(cross);
-            if (0 == fSign) {
-                fSign = sign;
-                fDirection = (1 == sign) ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
-            } else if (sign && fSign != sign) {
-                fConvexity = SkPath::kConcave_Convexity;
-                fDirection = SkPath::kUnknown_Direction;
-            }
-            fLastVec = vec;
+        DirChange dir = direction_change(fLastPt, fCurrPt, fLastVec, vec);
+        switch (dir) {
+            case kLeft_DirChange:       // fall through
+            case kRight_DirChange:
+                if (kInvalid_DirChange == fExpectedDir) {
+                    fExpectedDir = dir;
+                    fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
+                                                           : SkPath::kCCW_Direction;
+                } else if (dir != fExpectedDir) {
+                    fConvexity = SkPath::kConcave_Convexity;
+                    fDirection = SkPath::kUnknown_Direction;
+                }
+                fLastVec = vec;
+                break;
+            case kStraight_DirChange:
+                break;
+            case kBackwards_DirChange:
+                fLastVec = vec;
+                break;
+            case kInvalid_DirChange:
+                SkFAIL("Use of invalid direction change flag");
+                break;
         }
     }
 
@@ -2234,7 +2276,7 @@
     // value with the current vec is deemed to be of a significant value.
     SkVector            fLastVec, fFirstVec;
     int                 fPtCount;   // non-degenerate points
-    int                 fSign;
+    DirChange           fExpectedDir;
     SkPath::Convexity   fConvexity;
     SkPath::Direction   fDirection;
     int                 fDx, fDy, fSx, fSy;
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index 0de64b0..798185c 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -1195,6 +1195,17 @@
     stroke.applyToPath(&strokedSin, strokedSin);
     check_convexity(reporter, strokedSin, SkPath::kConcave_Convexity);
     check_direction(reporter, strokedSin, kDontCheckDir);
+
+    // http://crbug.com/412640
+    SkPath degenerateConcave;
+    degenerateConcave.moveTo(148.67912f, 191.875f);
+    degenerateConcave.lineTo(470.37695f, 7.5f);
+    degenerateConcave.lineTo(148.67912f, 191.875f);
+    degenerateConcave.lineTo(41.446522f, 376.25f);
+    degenerateConcave.lineTo(-55.971577f, 460.0f);
+    degenerateConcave.lineTo(41.446522f, 376.25f);
+    check_convexity(reporter, degenerateConcave, SkPath::kConcave_Convexity);
+    check_direction(reporter, degenerateConcave, SkPath::kUnknown_Direction);
 }
 
 static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p,