two pass convexity

This separates the existing convexity logic into
two passes. The first pass detects concavity by
counting the changes in direction.
The second pass computes the cross product to
see that all angles bend in the same direction, and
computes the dot product to see if the angle
doubles back on itself.

The second pass treats axis-aligned vectors
separately, and computes the dot and cross products
by comparing point values; it does not use arithmetic
to determine convexity, so it works with all finite
values.

A compile time switch enables returning concave
for co-linear diagonal points:
If successive points are not axis-aligned, and
those points are co-linear along a diagonal;
the path is treated as concave. This is conservative
but avoids paths that change convexity when the
are translated or scaled, since transforming the
path may cause the midpoint to shift to either
side of a line formed by the endpoints.

The compile time switch is set so that co-linear
diagonal points do not affect convexity. Note that
this permits shapes formerly considered concave, such
as stroked lines with round caps, to become convex;
this accounts for many of the GM differences.

A path may double back on itself and be convex;
for instance, a path containing a single line.

Path may have multiple initial moveTo verbs, or
trailing moveTo verbs, and still evaluate as convex.

A separate entry point, SkPathPriv::IsConvex()
allows passing an array of points instead of a path.

A legacy define has been checked into Chrome to
use the old code until layout tests have been
rebaselined.

R=reed@google.com,bsalomon@google.com
Bug:899689
Change-Id: I392bbe04836ffb19666ad92ab2a2404c56543019
Reviewed-on: https://skia-review.googlesource.com/c/173427
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Cary Clark <caryclark@google.com>
Commit-Queue: Cary Clark <caryclark@skia.org>
diff --git a/gm/convex_all_line_paths.cpp b/gm/convex_all_line_paths.cpp
index 6d7155d..e4a959c 100644
--- a/gm/convex_all_line_paths.cpp
+++ b/gm/convex_all_line_paths.cpp
@@ -56,9 +56,13 @@
 const SkPoint gPoints4[] = {
     { -6.0f, -50.0f },
     { 4.0f, -50.0f },
-    { 5.0f, -25.0f },
+#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE == 0
+    { 5.0f, -25.0f },  // remove if collinear diagonal points are not concave
+#endif
     { 6.0f,   0.0f },
-    { 5.0f,  25.0f },
+#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE == 0
+    { 5.0f,  25.0f },  // remove if collinear diagonal points are not concave
+#endif
     { 4.0f,  50.0f },
     { -4.0f,  50.0f }
 };
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 4eafc1b..1fc75d2 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -2295,6 +2295,8 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+#ifdef SK_LEGACY_PATH_CONVEXITY  // for rebaselining Chrome
+
 static int sign(SkScalar x) { return x < 0; }
 #define kValueNeverReturnedBySign   2
 
@@ -2578,6 +2580,331 @@
     return this->getConvexityOrUnknown();
 }
 
+#else
+
+static int sign(SkScalar x1, SkScalar x2) { SkASSERT(x1 != x2); return x2 < x1; }
+static int sign(SkScalar x) { return x < 0; }
+#define kValueNeverReturnedBySign   2
+
+enum DirChange {
+    kUnknown_DirChange,
+    kLeft_DirChange,
+    kRight_DirChange,
+    kStraight_DirChange,
+    kConcave_DirChange,   // if cross on diagonal is too small, assume concave
+    kBackwards_DirChange, // if double back, allow simple lines to be convex
+    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
+    // of 12.
+    const int epsilon = 16;
+    if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
+        return false;
+    }
+    // no need to check for small numbers because SkPath::Iter has removed degenerate values
+    int aBits = SkFloatAs2sCompliment(compA);
+    int bBits = SkFloatAs2sCompliment(compB);
+    return aBits < bBits + epsilon && bBits < aBits + epsilon;
+}
+
+static DirChange same_sign(SkScalar curr, SkScalar last, SkScalar prior) {
+    return sign(curr, last) == sign(last, prior) ? kStraight_DirChange : kBackwards_DirChange;
+}
+
+// only valid for a single contour
+struct Convexicator {
+
+    /** The direction returned is only valid if the path is determined convex */
+    SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
+
+    void setMovePt(const SkPoint& pt) {
+        fPriorPt = fLastPt = fCurrPt = pt;
+    }
+
+    bool addPt(const SkPoint& pt) {
+        if (fCurrPt == pt) {
+            return true;
+        }
+        fCurrPt = pt;
+        if (fPriorPt == fLastPt) {  // should only be true for first non-zero vector
+            fFirstPt = pt;
+            fCurrAligned = pt.fX == fLastPt.fX || pt.fY == fLastPt.fY;
+        } else if (!this->addVec()) {
+            return false;
+        }
+        fPriorPt = fLastPt;
+        fLastPt = fCurrPt;
+        fLastAligned = fCurrAligned;
+        return true;
+    }
+
+    static SkPath::Convexity BySign(const SkPoint points[], int count) {
+        const SkPoint* last = points + count;
+        SkPoint currPt = *points++;
+        SkPoint firstPt = currPt;
+        int dxes = 0;
+        int dyes = 0;
+        int lastSx = kValueNeverReturnedBySign;
+        int lastSy = kValueNeverReturnedBySign;
+        for (int outerLoop = 0; outerLoop < 2; ++outerLoop ) {
+            while (points != last) {
+                SkVector vec = *points - currPt;
+                if (!vec.isZero()) {
+                    // give up if vector construction failed
+                    if (!vec.isFinite()) {
+                        return SkPath::kUnknown_Convexity;
+                    }
+                    int sx = sign(vec.fX);
+                    int sy = sign(vec.fY);
+                    dxes += (sx != lastSx);
+                    dyes += (sy != lastSy);
+                    if (dxes > 3 || dyes > 3) {
+                        return SkPath::kConcave_Convexity;
+                    }
+                    lastSx = sx;
+                    lastSy = sy;
+                }
+                currPt = *points++;
+                if (outerLoop) {
+                    break;
+                }
+            }
+            points = &firstPt;
+        }
+        return SkPath::kConvex_Convexity;  // that is, it may be convex, don't know yet
+    }
+
+    bool close() {
+        return this->addPt(fFirstPt);
+    }
+
+    bool isFinite() const {
+        return fIsFinite;
+    }
+
+    int reversals() const {
+        return fReversals;
+    }
+
+private:
+    DirChange directionChange() {
+        // if both vectors are axis-aligned, don't do cross product
+        fCurrAligned = fCurrPt.fX == fLastPt.fX || fCurrPt.fY == fLastPt.fY;
+        if (fLastAligned && fCurrAligned) {
+            bool noYChange = fCurrPt.fY == fLastPt.fY && fLastPt.fY == fPriorPt.fY;
+            if (fCurrPt.fX == fLastPt.fX && fLastPt.fX == fPriorPt.fX) {
+                if (noYChange) {
+                    return kStraight_DirChange;
+                }
+                return same_sign(fCurrPt.fY, fLastPt.fY, fPriorPt.fY);
+            }
+            if (!noYChange) { // must be turn to left or right
+                bool flip = fCurrPt.fX != fLastPt.fX;
+                SkASSERT(flip ? fCurrPt.fY == fLastPt.fY &&
+                        fLastPt.fY != fPriorPt.fY && fLastPt.fX == fPriorPt.fX :
+                        fCurrPt.fY != fLastPt.fY &&
+                        fLastPt.fY == fPriorPt.fY && fLastPt.fX != fPriorPt.fX);
+                bool product = flip ? (fCurrPt.fX > fLastPt.fX) != (fLastPt.fY > fPriorPt.fY) :
+                        (fCurrPt.fY > fLastPt.fY) == (fLastPt.fX > fPriorPt.fX);
+                SkDEBUGCODE(SkVector lastV = fLastPt - fPriorPt);
+                SkDEBUGCODE(SkVector curV = fCurrPt - fLastPt);
+                SkDEBUGCODE(SkScalar crossV = SkPoint::CrossProduct(lastV, curV));
+                SkDEBUGCODE(int signV = SkScalarSignAsInt(crossV));
+                SkASSERT(signV == (product ? 1 : -1));
+                return product ? kRight_DirChange : kLeft_DirChange;
+            }
+            return same_sign(fCurrPt.fX, fLastPt.fX, fPriorPt.fX);
+        }
+        // there are no subtractions above this line; axis aligned paths
+        // are robust and can handle arbitrary values
+        SkVector lastVec = fLastPt - fPriorPt;
+        SkVector curVec = fCurrPt - fLastPt;
+        SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
+        if (!SkScalarIsFinite(cross)) {
+                return kUnknown_DirChange;
+        }
+        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)) {
+#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE
+    // colinear diagonals are not allowed; they aren't numerically stable
+    #define COLINEAR_POINT_DIR_CHANGE kConcave_DirChange
+#else
+    // colinear diagonals are allowed; we can survive dealing with 'close enough'
+    #define COLINEAR_POINT_DIR_CHANGE kStraight_DirChange
+#endif
+
+            SkScalar dot = lastVec.dot(curVec);
+            return dot < 0 ? kBackwards_DirChange : COLINEAR_POINT_DIR_CHANGE;
+        }
+        return 1 == SkScalarSignAsInt(cross) ? kRight_DirChange : kLeft_DirChange;
+    }
+
+    bool addVec() {
+        DirChange dir = this->directionChange();
+        switch (dir) {
+            case kLeft_DirChange:       // fall through
+            case kRight_DirChange:
+                if (kInvalid_DirChange == fExpectedDir) {
+                    fExpectedDir = dir;
+                    fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
+                                                                : SkPathPriv::kCCW_FirstDirection;
+                } else if (dir != fExpectedDir) {
+                    fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
+                    return false;
+                }
+                break;
+            case kStraight_DirChange:
+                break;
+            case kConcave_DirChange:
+                fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
+                return false;
+            case kBackwards_DirChange:
+                //  allow path to reverse direction twice
+                //    Given path.moveTo(0, 0); path.lineTo(1, 1);
+                //    - 1st reversal: direction change formed by line (0,0 1,1), line (1,1 0,0)
+                //    - 2nd reversal: direction change formed by line (1,1 0,0), line (0,0 1,1)
+                return ++fReversals < 3;
+            case kUnknown_DirChange:
+                return (fIsFinite = false);
+            case kInvalid_DirChange:
+                SK_ABORT("Use of invalid direction change flag");
+                break;
+        }
+        return true;
+    }
+
+    SkPoint             fFirstPt {0, 0};
+    SkPoint             fPriorPt {0, 0};
+    SkPoint             fLastPt {0, 0};
+    SkPoint             fCurrPt {0, 0};
+    DirChange           fExpectedDir { kInvalid_DirChange };
+    SkPathPriv::FirstDirection   fFirstDirection { SkPathPriv::kUnknown_FirstDirection };
+    int                 fReversals { 0 };
+    bool                fIsFinite { true };
+    bool                fLastAligned { true };
+    bool                fCurrAligned { true };
+};
+
+SkPath::Convexity SkPath::internalGetConvexity() const {
+    SkPoint         pts[4];
+    SkPath::Verb    verb;
+    SkPath::Iter    iter(*this, true);
+    auto setComputedConvexity = [=](Convexity convexity){
+        SkASSERT(kUnknown_Convexity != convexity);
+        this->setConvexity(convexity);
+        return convexity;
+    };
+
+    // Check to see if path changes direction more than three times as quick concave test
+    int pointCount = fLastMoveToIndex > 0 ? fLastMoveToIndex : this->countPoints();
+    if (pointCount > 3) {
+        const SkPoint* points = fPathRef->points();
+        const SkPoint* last = &points[pointCount];
+        // only consider the last of the initial move tos
+        while (SkPath::kMove_Verb == iter.next(pts, false, false)) {
+            ++points;
+        }
+        --points;
+        SkPath::Convexity convexity = Convexicator::BySign(points, (int) (last - points));
+        if (SkPath::kConcave_Convexity == convexity) {
+            return setComputedConvexity(SkPath::kConcave_Convexity);
+        } else if (SkPath::kUnknown_Convexity == convexity) {
+            return SkPath::kUnknown_Convexity;
+        }
+        iter.setPath(*this, true);
+    } else if (!this->isFinite()) {
+        return kUnknown_Convexity;
+    }
+
+    int             contourCount = 0;
+    int             count;
+    Convexicator    state;
+    auto setFail = [=](){
+        if (!state.isFinite()) {
+            return SkPath::kUnknown_Convexity;
+        }
+        return setComputedConvexity(SkPath::kConcave_Convexity);
+    };
+
+    while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
+        switch (verb) {
+            case kMove_Verb:
+                if (++contourCount > 1) {
+                    return setComputedConvexity(kConcave_Convexity);
+                }
+                state.setMovePt(pts[0]);
+                count = 0;
+                break;
+            case kLine_Verb:
+                count = 1;
+                break;
+            case kQuad_Verb:
+                // fall through
+            case kConic_Verb:
+                count = 2;
+                break;
+            case kCubic_Verb:
+                count = 3;
+                break;
+            case kClose_Verb:
+                if (!state.close()) {
+                    return setFail();
+                }
+                count = 0;
+                break;
+            default:
+                SkDEBUGFAIL("bad verb");
+                return setComputedConvexity(kConcave_Convexity);
+        }
+        for (int i = 1; i <= count; i++) {
+            if (!state.addPt(pts[i])) {
+                return setFail();
+            }
+        }
+    }
+
+    if (this->getFirstDirection() == SkPathPriv::kUnknown_FirstDirection) {
+        if (state.getFirstDirection() == SkPathPriv::kUnknown_FirstDirection
+                && !this->getBounds().isEmpty()) {
+            return setComputedConvexity(state.reversals() < 3 ?
+                    kConvex_Convexity : kConcave_Convexity);
+        }
+        this->setFirstDirection(state.getFirstDirection());
+    }
+    return setComputedConvexity(kConvex_Convexity);
+}
+
+bool SkPathPriv::IsConvex(const SkPoint points[], int count) {
+    SkPath::Convexity convexity = Convexicator::BySign(points, count);
+    if (SkPath::kConvex_Convexity != convexity) {
+        return false;
+    }
+    Convexicator state;
+    state.setMovePt(points[0]);
+    for (int i = 1; i < count; i++) {
+        if (!state.addPt(points[i])) {
+            return false;
+        }
+    }
+    if (!state.addPt(points[0])) {
+        return false;
+    }
+    if (!state.close()) {
+        return false;
+    }
+    return state.getFirstDirection() != SkPathPriv::kUnknown_FirstDirection
+            || state.reversals() < 3;
+}
+
+#endif
+
 ///////////////////////////////////////////////////////////////////////////////
 
 class ContourIter {
diff --git a/src/core/SkPathPriv.h b/src/core/SkPathPriv.h
index 8962d0e..311bd43 100644
--- a/src/core/SkPathPriv.h
+++ b/src/core/SkPathPriv.h
@@ -10,6 +10,14 @@
 
 #include "SkPath.h"
 
+#define SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE 0
+
+#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE
+    #define COLINEAR_DIAGONAL_CONVEXITY kConcave_Convexity
+#else
+    #define COLINEAR_DIAGONAL_CONVEXITY kConvex_Convexity
+#endif
+
 class SkPathPriv {
 public:
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
@@ -159,6 +167,15 @@
         return path.fPathRef->conicWeights();
     }
 
+    /** Returns true if path formed by pts is convex.
+
+        @param pts    SkPoint array of path
+        @param count  number of entries in array
+
+        @return       true if pts represent a convex geometry
+    */
+    static bool IsConvex(const SkPoint pts[], int count);
+
     /** Returns true if the underlying SkPathRef has one single owner. */
     static bool TestingOnly_unique(const SkPath& path) {
         return path.fPathRef->unique();
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index 7bd33d6..f6d1e5e 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -27,6 +27,7 @@
 
 #include <cmath>
 #include <utility>
+#include <vector>
 
 static void set_radii(SkVector radii[4], int index, float rad) {
     sk_bzero(radii, sizeof(SkVector) * 4);
@@ -1319,6 +1320,21 @@
     SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
     SkPath::Convexity c = copy.getConvexity();
     REPORTER_ASSERT(reporter, c == expected);
+    // test points-by-array interface
+    SkPath::Iter iter(path, true);
+    int initialMoves = 0;
+    SkPoint pts[4];
+    while (SkPath::kMove_Verb == iter.next(pts, false, false)) {
+        ++initialMoves;
+    }
+    if (initialMoves > 0) {
+        std::vector<SkPoint> points;
+        points.resize(path.getPoints(nullptr, 0));
+        (void) path.getPoints(&points.front(), points.size());
+        int skip = initialMoves - 1;
+        bool isConvex = SkPathPriv::IsConvex(&points.front() + skip, points.size() - skip);
+        REPORTER_ASSERT(reporter, isConvex == (SkPath::kConvex_Convexity == expected));
+    }
 }
 
 static void test_path_crbug389050(skiatest::Reporter* reporter) {
@@ -1329,8 +1345,16 @@
     tinyConvexPolygon.lineTo(600.134891f, 800.137724f);
     tinyConvexPolygon.close();
     tinyConvexPolygon.getConvexity();
-    check_convexity(reporter, tinyConvexPolygon, SkPath::kConvex_Convexity);
+    check_convexity(reporter, tinyConvexPolygon, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
+#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE
+    // colinear diagonal points cause convexicator to give up, so CheapComputeFirstDirection
+    // makes its best guess
     check_direction(reporter, tinyConvexPolygon, SkPathPriv::kCW_FirstDirection);
+#else
+    // lines are close enough to straight that polygon collapses to line that does not
+    // enclose area, so has unknown first direction
+    check_direction(reporter, tinyConvexPolygon, SkPathPriv::kUnknown_FirstDirection);
+#endif
 
     SkPath  platTriangle;
     platTriangle.moveTo(0, 0);
@@ -1458,7 +1482,7 @@
     SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
     stroke.setStrokeStyle(2 * SK_Scalar1);
     stroke.applyToPath(&strokedSin, strokedSin);
-    check_convexity(reporter, strokedSin, SkPath::kConcave_Convexity);
+    check_convexity(reporter, strokedSin, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
     check_direction(reporter, strokedSin, kDontCheckDir);
 
     // http://crbug.com/412640
@@ -1487,6 +1511,29 @@
     check_convexity(reporter, badFirstVector, SkPath::kConcave_Convexity);
 }
 
+static void test_convexity_doubleback(skiatest::Reporter* reporter) {
+    SkPath doubleback;
+    doubleback.lineTo(1, 1);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+    doubleback.lineTo(2, 2);
+    check_convexity(reporter, doubleback, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
+    doubleback.reset();
+    doubleback.lineTo(1, 0);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+    doubleback.lineTo(2, 0);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+    doubleback.lineTo(1, 0);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+    doubleback.reset();
+    doubleback.quadTo(1, 1, 2, 2);
+    check_convexity(reporter, doubleback, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
+    doubleback.reset();
+    doubleback.quadTo(1, 0, 2, 0);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+    doubleback.quadTo(1, 0, 0, 0);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+}
+
 static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p,
                                 const SkRect& bounds) {
     REPORTER_ASSERT(reporter, p.isConvex());
@@ -1541,8 +1588,8 @@
     REPORTER_ASSERT(reporter, SkPathPriv::CheapIsFirstDirection(path, SkPathPriv::kCW_FirstDirection));
 
     path.reset();
-    path.quadTo(100, 100, 50, 50); // This is a convex path from GM:convexpaths
-    check_convexity(reporter, path, SkPath::kConvex_Convexity);
+    path.quadTo(100, 100, 50, 50); // This from GM:convexpaths
+    check_convexity(reporter, path, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
 
     static const struct {
         const char*                 fPathStr;
@@ -1560,7 +1607,7 @@
     };
 
     for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
-        SkPath path;
+        path.reset();
         setFromString(&path, gRec[i].fPathStr);
         check_convexity(reporter, path, gRec[i].fExpectedConvexity);
         check_direction(reporter, path, gRec[i].fExpectedDirection);
@@ -1594,62 +1641,102 @@
 
     const size_t nonFinitePtsCount = sizeof(nonFinitePts) / sizeof(nonFinitePts[0]);
 
-    static const SkPoint finitePts[] = {
+    static const SkPoint axisAlignedPts[] = {
         { SK_ScalarMax, 0 },
         { 0, SK_ScalarMax },
-        { SK_ScalarMax, SK_ScalarMax },
         { SK_ScalarMin, 0 },
         { 0, SK_ScalarMin },
-        { SK_ScalarMin, SK_ScalarMin },
     };
 
-    const size_t finitePtsCount = sizeof(finitePts) / sizeof(finitePts[0]);
+    const size_t axisAlignedPtsCount = sizeof(axisAlignedPts) / sizeof(axisAlignedPts[0]);
 
-    for (int index = 0; index < (int) (13 * nonFinitePtsCount * finitePtsCount); ++index) {
+    for (int index = 0; index < (int) (13 * nonFinitePtsCount * axisAlignedPtsCount); ++index) {
         int i = (int) (index % nonFinitePtsCount);
-        int f = (int) (index % finitePtsCount);
-        int g = (int) ((f + 1) % finitePtsCount);
+        int f = (int) (index % axisAlignedPtsCount);
+        int g = (int) ((f + 1) % axisAlignedPtsCount);
         path.reset();
         switch (index % 13) {
             case 0: path.lineTo(nonFinitePts[i]); break;
             case 1: path.quadTo(nonFinitePts[i], nonFinitePts[i]); break;
-            case 2: path.quadTo(nonFinitePts[i], finitePts[f]); break;
-            case 3: path.quadTo(finitePts[f], nonFinitePts[i]); break;
-            case 4: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[f]); break;
-            case 5: path.cubicTo(finitePts[f], nonFinitePts[i], finitePts[f]); break;
-            case 6: path.cubicTo(finitePts[f], finitePts[f], nonFinitePts[i]); break;
-            case 7: path.cubicTo(nonFinitePts[i], nonFinitePts[i], finitePts[f]); break;
-            case 8: path.cubicTo(nonFinitePts[i], finitePts[f], nonFinitePts[i]); break;
-            case 9: path.cubicTo(finitePts[f], nonFinitePts[i], nonFinitePts[i]); break;
+            case 2: path.quadTo(nonFinitePts[i], axisAlignedPts[f]); break;
+            case 3: path.quadTo(axisAlignedPts[f], nonFinitePts[i]); break;
+            case 4: path.cubicTo(nonFinitePts[i], axisAlignedPts[f], axisAlignedPts[f]); break;
+            case 5: path.cubicTo(axisAlignedPts[f], nonFinitePts[i], axisAlignedPts[f]); break;
+            case 6: path.cubicTo(axisAlignedPts[f], axisAlignedPts[f], nonFinitePts[i]); break;
+            case 7: path.cubicTo(nonFinitePts[i], nonFinitePts[i], axisAlignedPts[f]); break;
+            case 8: path.cubicTo(nonFinitePts[i], axisAlignedPts[f], nonFinitePts[i]); break;
+            case 9: path.cubicTo(axisAlignedPts[f], nonFinitePts[i], nonFinitePts[i]); break;
             case 10: path.cubicTo(nonFinitePts[i], nonFinitePts[i], nonFinitePts[i]); break;
-            case 11: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[g]); break;
+            case 11: path.cubicTo(nonFinitePts[i], axisAlignedPts[f], axisAlignedPts[g]); break;
             case 12: path.moveTo(nonFinitePts[i]); break;
         }
         check_convexity(reporter, path, SkPath::kUnknown_Convexity);
     }
 
-    for (int index = 0; index < (int) (11 * finitePtsCount); ++index) {
-        int f = (int) (index % finitePtsCount);
-        int g = (int) ((f + 1) % finitePtsCount);
+    for (int index = 0; index < (int) (11 * axisAlignedPtsCount); ++index) {
+        int f = (int) (index % axisAlignedPtsCount);
+        int g = (int) ((f + 1) % axisAlignedPtsCount);
         path.reset();
         int curveSelect = index % 11;
         switch (curveSelect) {
-            case 0: path.moveTo(finitePts[f]); break;
-            case 1: path.lineTo(finitePts[f]); break;
-            case 2: path.quadTo(finitePts[f], finitePts[f]); break;
-            case 3: path.quadTo(finitePts[f], finitePts[g]); break;
-            case 4: path.quadTo(finitePts[g], finitePts[f]); break;
-            case 5: path.cubicTo(finitePts[f], finitePts[f], finitePts[f]); break;
-            case 6: path.cubicTo(finitePts[f], finitePts[f], finitePts[g]); break;
-            case 7: path.cubicTo(finitePts[f], finitePts[g], finitePts[f]); break;
-            case 8: path.cubicTo(finitePts[f], finitePts[g], finitePts[g]); break;
-            case 9: path.cubicTo(finitePts[g], finitePts[f], finitePts[f]); break;
-            case 10: path.cubicTo(finitePts[g], finitePts[f], finitePts[g]); break;
+            case 0: path.moveTo(axisAlignedPts[f]); break;
+            case 1: path.lineTo(axisAlignedPts[f]); break;
+            case 2: path.quadTo(axisAlignedPts[f], axisAlignedPts[f]); break;
+            case 3: path.quadTo(axisAlignedPts[f], axisAlignedPts[g]); break;
+            case 4: path.quadTo(axisAlignedPts[g], axisAlignedPts[f]); break;
+            case 5: path.cubicTo(axisAlignedPts[f], axisAlignedPts[f], axisAlignedPts[f]); break;
+            case 6: path.cubicTo(axisAlignedPts[f], axisAlignedPts[f], axisAlignedPts[g]); break;
+            case 7: path.cubicTo(axisAlignedPts[f], axisAlignedPts[g], axisAlignedPts[f]); break;
+            case 8: path.cubicTo(axisAlignedPts[f], axisAlignedPts[g], axisAlignedPts[g]); break;
+            case 9: path.cubicTo(axisAlignedPts[g], axisAlignedPts[f], axisAlignedPts[f]); break;
+            case 10: path.cubicTo(axisAlignedPts[g], axisAlignedPts[f], axisAlignedPts[g]); break;
         }
-        check_convexity(reporter, path, curveSelect == 0 ? SkPath::kConvex_Convexity
-                : SkPath::kUnknown_Convexity);
+        if (curveSelect == 0 || curveSelect == 1 || curveSelect == 2 || curveSelect == 5) {
+            check_convexity(reporter, path, SkPath::kConvex_Convexity);
+        } else {
+            SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
+            SkPath::Convexity c = copy.getConvexity();
+            REPORTER_ASSERT(reporter, SkPath::kUnknown_Convexity == c
+                    || SkPath::kConcave_Convexity == c);
+        }
     }
 
+    static const SkPoint diagonalPts[] = {
+        { SK_ScalarMax, SK_ScalarMax },
+        { SK_ScalarMin, SK_ScalarMin },
+    };
+
+    const size_t diagonalPtsCount = sizeof(diagonalPts) / sizeof(diagonalPts[0]);
+
+    for (int index = 0; index < (int) (7 * diagonalPtsCount); ++index) {
+        int f = (int) (index % diagonalPtsCount);
+        int g = (int) ((f + 1) % diagonalPtsCount);
+        path.reset();
+        int curveSelect = index % 11;
+        switch (curveSelect) {
+            case 0: path.moveTo(diagonalPts[f]); break;
+            case 1: path.lineTo(diagonalPts[f]); break;
+            case 2: path.quadTo(diagonalPts[f], diagonalPts[f]); break;
+            case 3: path.quadTo(axisAlignedPts[f], diagonalPts[g]); break;
+            case 4: path.quadTo(diagonalPts[g], axisAlignedPts[f]); break;
+            case 5: path.cubicTo(diagonalPts[f], diagonalPts[f], diagonalPts[f]); break;
+            case 6: path.cubicTo(diagonalPts[f], diagonalPts[f], axisAlignedPts[g]); break;
+            case 7: path.cubicTo(diagonalPts[f], axisAlignedPts[g], diagonalPts[f]); break;
+            case 8: path.cubicTo(axisAlignedPts[f], diagonalPts[g], diagonalPts[g]); break;
+            case 9: path.cubicTo(diagonalPts[g], diagonalPts[f], axisAlignedPts[f]); break;
+            case 10: path.cubicTo(diagonalPts[g], axisAlignedPts[f], diagonalPts[g]); break;
+        }
+        if (curveSelect == 0) {
+            check_convexity(reporter, path, SkPath::kConvex_Convexity);
+        } else {
+            SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
+            SkPath::Convexity c = copy.getConvexity();
+            REPORTER_ASSERT(reporter, SkPath::kUnknown_Convexity == c
+                    || SkPath::kConcave_Convexity == c);
+        }
+    }
+
+
     path.reset();
     path.moveTo(SkBits2Float(0xbe9171db), SkBits2Float(0xbd7eeb5d));  // -0.284072f, -0.0622362f
     path.lineTo(SkBits2Float(0xbe9171db), SkBits2Float(0xbd7eea38));  // -0.284072f, -0.0622351f
@@ -3523,7 +3610,7 @@
     REPORTER_ASSERT(reporter, path->isConvex());
     REPORTER_ASSERT(reporter, SkPathPriv::CheapIsFirstDirection(*path, SkPathPriv::AsFirstDirection(dir)));
     path->setConvexity(SkPath::kUnknown_Convexity);
-    REPORTER_ASSERT(reporter, path->getConvexity() == SkPath::kUnknown_Convexity);
+    REPORTER_ASSERT(reporter, path->getConvexity() == SkPath::kConvex_Convexity);
     path->reset();
 }
 
@@ -3619,10 +3706,13 @@
     REPORTER_ASSERT(reporter, p == ccwOval);
     p.reset();
     p.addArc(oval, 1, 180);
-    REPORTER_ASSERT(reporter, p.isConvex());
+    // diagonal colinear points make arc convex
+    // TODO: one way to keep it concave would be to introduce interpolated on curve points
+    // between control points and computing the on curve point at scan conversion time
+    REPORTER_ASSERT(reporter, p.getConvexity() == SkPath::COLINEAR_DIAGONAL_CONVEXITY);
     REPORTER_ASSERT(reporter, SkPathPriv::CheapIsFirstDirection(p, SkPathPriv::kCW_FirstDirection));
     p.setConvexity(SkPath::kUnknown_Convexity);
-    REPORTER_ASSERT(reporter, p.isConvex());
+    REPORTER_ASSERT(reporter, p.getConvexity() == SkPath::COLINEAR_DIAGONAL_CONVEXITY);
 }
 
 static inline SkScalar oval_start_index_to_angle(unsigned start) {
@@ -4623,6 +4713,7 @@
     test_direction(reporter);
     test_convexity(reporter);
     test_convexity2(reporter);
+    test_convexity_doubleback(reporter);
     test_conservativelyContains(reporter);
     test_close(reporter);
     test_segment_masks(reporter);
@@ -5304,4 +5395,3 @@
     REPORTER_ASSERT(r, path.getConvexity() == SkPath::kConvex_Convexity);
     survive(&path, x, false, r, [](const SkPath& p) { return true; });
 }
-
diff --git a/tests/TessellatingPathRendererTests.cpp b/tests/TessellatingPathRendererTests.cpp
index db8665d..58e0d5d 100644
--- a/tests/TessellatingPathRendererTests.cpp
+++ b/tests/TessellatingPathRendererTests.cpp
@@ -340,14 +340,6 @@
     return path;
 }
 
-// A quad which becomes NaN when interpolated.
-static SkPath create_path_22() {
-    SkPath path;
-    path.moveTo(-5.71889e+13f, 1.36759e+09f);
-    path.quadTo(2.45472e+19f, -3.12406e+15f, -2.19589e+18f, 2.79462e+14f);
-    return path;
-}
-
 // A path which contains out-of-range colinear intersections.
 static SkPath create_path_23() {
     SkPath path;
@@ -713,7 +705,6 @@
     test_path(ctx, rtc.get(), create_path_19());
     test_path(ctx, rtc.get(), create_path_20(), SkMatrix(), GrAAType::kCoverage);
     test_path(ctx, rtc.get(), create_path_21(), SkMatrix(), GrAAType::kCoverage);
-    test_path(ctx, rtc.get(), create_path_22());
     test_path(ctx, rtc.get(), create_path_23());
     test_path(ctx, rtc.get(), create_path_24());
     test_path(ctx, rtc.get(), create_path_25(), SkMatrix(), GrAAType::kCoverage);