don't trust convexity with affine transforms

In theory, a convex shape transformed by an affine matrix should still
be convex. However, due to numerical nastiness of floats, when we try
to determine if something is convex, we can get different answers pre
and post a transformation (think of two line segments nearly colinear).

Convex paths take a faster scan converter, but it is only well behaved
if the path is, in fact, convex. Thus we have to be conservative about
which paths we mark as convex.

This bug found a case where a "convex" path, after going through a transform,
became (according to our measure) non-convex. The bug was that we *thought*
that once convex always convex, but in reality it was not. The fix (hack) is
to notice when we transform by an affine matrix (we're still assuming/hoping
that scaling and translate keep things convex (1)...) and mark the convexity
as "unknown", forcing us to re-compute it.

This will slow down these paths, since it costs something to compute convexity.
Hopefully non-scale-translate transforms are rare, so we won't notice the
speed loss too much.

(1) This is not proven. If we find scaling/translation to break our notion of
convexity, we'll need to get more aggressive/clever to find a fix.


Bug: 899689
Change-Id: I5921eca247428bf89380bc2395fe373fa70deb1d
Reviewed-on: https://skia-review.googlesource.com/c/173080
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Cary Clark <caryclark@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index af8cf9e..257a0bd 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -1841,6 +1841,13 @@
             dst->fIsVolatile = fIsVolatile;
         }
 
+        if (!matrix.isScaleTranslate()) {
+            // If we're rotated/skewed/etc. we can't trust that our convexity stays put.
+            // ... just because convexity is fragle with segments are *nearly* colinear...
+            // See Path_crbug_899689 test, which asserts w/o this check.
+            dst->fConvexity = kUnknown_Convexity;
+        }
+
         if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
             dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
         } else {
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index febfd4f..5e7f218 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -5204,3 +5204,66 @@
     shallowPath.incReserve(0xffffffff);
 }
 
+DEF_TEST(Path_crbug_899689, r) {
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(24, 24);
+    SkCanvas canvas(bitmap);
+
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kFill_Style);
+
+    // This is monotone in both x and y, but has a tiny concavity
+    SkPath path;
+    path.moveTo(-1,-1);
+    path.lineTo(0, 0);
+    path.lineTo(0, 0.5e-10f);
+    path.lineTo(0.1e-10f, 1.1e-10f);
+    path.lineTo(1.5e-10f, 1.1e-10f);
+    path.lineTo(1.5e-10f, 2.5e-10f);
+    path.lineTo(0.9f, 1);
+    path.lineTo(-1, 1);
+    path.close();
+
+#if 0
+    // If asked, Skia is going to declare it convex
+    if(path.isConvex()) {
+        printf("convex\n");
+    } else {
+        printf("not convex\n");
+    }
+#endif
+
+    SkMatrix m;
+    m.setRotate(-45);
+    m.postScale(10e10f, 10e10f);
+    m.postSkew(-1, 0);
+    m.postTranslate(1, 10);
+    path.transform(m);
+
+#if 0
+    // The convexity flag is going to survive all affine transforms
+    // Even those that will enlarge the concavity and make the path
+    // non-monotone.
+    // As demonstrated here
+    if(path.isConvex()) {
+        printf("convex\n");
+    } else {
+        printf("not convex\n");
+    }
+
+    // This used to print "convex" (and then assert) before the fix.
+#endif
+
+    // We'll use the path as a clip region
+    canvas.clipPath(path);
+
+    // And now we'll just draw a simple triangle.
+    SkPath path2;
+    path2.moveTo(15.5f, 15);
+    path2.lineTo(50.5f, 50);
+    path2.lineTo(-19.5f, 50);
+    path2.close();
+    canvas.drawPath(path2, paint);
+}
+