pathops work in progress

BUG=

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

git-svn-id: http://skia.googlecode.com/svn/trunk@12089 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/Makefile b/Makefile
index aa54c1f..9b98edc 100644
--- a/Makefile
+++ b/Makefile
@@ -48,6 +48,7 @@
                  SampleApp_APK \
                  skhello \
                  skia_lib \
+                 skpskgr_test \
                  tests \
                  tools \
                  skpdiff
diff --git a/gyp/most.gyp b/gyp/most.gyp
index cfe528e..8a8b85d 100644
--- a/gyp/most.gyp
+++ b/gyp/most.gyp
@@ -18,6 +18,7 @@
         'tests.gyp:tests',
         'tools.gyp:tools',
         'pathops_unittest.gyp:*',
+        'skpskgr_test.gyp:*',
 #       'pdfviewer.gyp:pdfviewer',
       ],
       'conditions': [
diff --git a/gyp/skpskgr_test.gyp b/gyp/skpskgr_test.gyp
new file mode 100755
index 0000000..3a5e4d1
--- /dev/null
+++ b/gyp/skpskgr_test.gyp
@@ -0,0 +1,46 @@
+# GYP file to build unit tests.
+{
+  'includes': [
+    'apptype_console.gypi',
+  ],
+  'targets': [
+    {
+      'target_name': 'skpskgr_test',
+      'type': 'executable',
+      'suppress_wildcard': '1',
+      'include_dirs' : [
+        '../src/core',
+        '../src/effects',
+        '../src/lazy',
+        '../src/pathops',
+        '../src/pdf',
+        '../src/pipe/utils',
+        '../src/utils',
+        '../tools/',
+      ],
+      'sources': [
+        '../tests/SkpSkGrTest.cpp',
+        '../tests/Test.cpp',
+        '../tests/skia_test.cpp',
+        '../tests/Test.h',
+      ],
+      'dependencies': [
+        'skia_lib.gyp:skia_lib',
+        'flags.gyp:flags',
+      ],
+      'conditions': [
+        [ 'skia_gpu == 1', {
+          'include_dirs': [
+            '../src/gpu',
+          ],
+        }],
+      ],
+    },
+  ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/src/pathops/SkAddIntersections.cpp b/src/pathops/SkAddIntersections.cpp
index 5fa80ec..7d5fc0d 100644
--- a/src/pathops/SkAddIntersections.cpp
+++ b/src/pathops/SkAddIntersections.cpp
@@ -383,8 +383,8 @@
                 for (int pt = 0; pt < pts - 1; ++pt) {
                     const SkDPoint& point = ts.pt(pt);
                     const SkDPoint& next = ts.pt(pt + 1);
-                    if (wt.isNear(ts[swap][pt], ts[swap][pt + 1], point, next)
-                            && wn.isNear(ts[!swap][pt], ts[!swap][pt + 1], point, next)) {
+                    if (wt.isPartial(ts[swap][pt], ts[swap][pt + 1], point, next)
+                            && wn.isPartial(ts[!swap][pt], ts[!swap][pt + 1], point, next)) {
                         if (!wt.addPartialCoincident(wn, ts, pt, swap)) {
                             // remove extra point if two map to same float values
                             ts.cleanUpCoincidence();  // prefer (t == 0 or t == 1)
diff --git a/src/pathops/SkDCubicIntersection.cpp b/src/pathops/SkDCubicIntersection.cpp
index ce23448..27b1634 100644
--- a/src/pathops/SkDCubicIntersection.cpp
+++ b/src/pathops/SkDCubicIntersection.cpp
@@ -292,6 +292,7 @@
     tmpLine[1].fX += cubic2[2 - start].fY - cubic2[t1Index].fY;
     tmpLine[1].fY -= cubic2[2 - start].fX - cubic2[t1Index].fX;
     SkIntersections impTs;
+    impTs.allowNear(false);
     impTs.intersectRay(cubic1, tmpLine);
     for (int index = 0; index < impTs.used(); ++index) {
         SkDPoint realPt = impTs.pt(index);
@@ -446,6 +447,7 @@
             return fUsed;
         }
     } else {
+        // OPTIMIZATION: set exact end bits here to avoid cubic exact end later
         for (int i1 = 0; i1 < 4; i1 += 3) {
             for (int i2 = 0; i2 < 4; i2 += 3) {
                 if (c1[i1].approximatelyEqual(c2[i2])) {
@@ -483,21 +485,22 @@
         return fUsed;
     }
     // FIXME: pass in cached bounds from caller
-    SkDRect c1Bounds, c2Bounds;
-    c1Bounds.setBounds(c1);  // OPTIMIZE use setRawBounds ?
+    SkDRect c2Bounds;
     c2Bounds.setBounds(c2);
-    if (!(exactEndBits & 1)) {
+    if (!(exactEndBits & 4)) {
         cubicNearEnd(c1, false, c2, c2Bounds);
     }
-    if (!(exactEndBits & 2)) {
+    if (!(exactEndBits & 8)) {
         cubicNearEnd(c1, true, c2, c2Bounds);
     }
     if (!selfIntersect) {
+        SkDRect c1Bounds;
+        c1Bounds.setBounds(c1);  // OPTIMIZE use setRawBounds ?
         swap();
-        if (!(exactEndBits & 4)) {
+        if (!(exactEndBits & 1)) {
             cubicNearEnd(c2, false, c1, c1Bounds);
         }
-        if (!(exactEndBits & 8)) {
+        if (!(exactEndBits & 2)) {
             cubicNearEnd(c2, true, c1, c1Bounds);
         }
         swap();
@@ -506,7 +509,61 @@
         SkASSERT(!selfIntersect);
         return fUsed;
     }
-    ::intersect(c1, 0, 1, c2, 0, 1, 1, *this);
+    SkIntersections i;
+    i.fAllowNear = false;
+    i.fMax = 9;
+    ::intersect(c1, 0, 1, c2, 0, 1, 1, i);
+    int compCount = i.used();
+    if (compCount) {
+        int exactCount = used();
+        if (exactCount == 0) {
+            set(i);
+        } else {
+            // at least one is exact or near, and at least one was computed. Eliminate duplicates
+            for (int exIdx = 0; exIdx < exactCount; ++exIdx) {
+                for (int cpIdx = 0; cpIdx < compCount; ) {
+                    if (fT[0][0] == i[0][0] && fT[1][0] == i[1][0]) {
+                        i.removeOne(cpIdx);
+                        --compCount;
+                        continue;
+                    }
+                    double tAvg = (fT[0][exIdx] + i[0][cpIdx]) / 2;
+                    SkDPoint pt = c1.ptAtT(tAvg);
+                    if (!pt.approximatelyEqual(fPt[exIdx])) {
+                        ++cpIdx;
+                        continue;
+                    }
+                    tAvg = (fT[1][exIdx] + i[1][cpIdx]) / 2;
+                    pt = c2.ptAtT(tAvg);
+                    if (!pt.approximatelyEqual(fPt[exIdx])) {
+                        ++cpIdx;
+                        continue;
+                    }
+                    i.removeOne(cpIdx);
+                    --compCount;
+                }
+            }
+            // if mid t evaluates to nearly the same point, skip the t
+            for (int cpIdx = 0; cpIdx < compCount - 1; ) {
+                double tAvg = (fT[0][cpIdx] + i[0][cpIdx + 1]) / 2;
+                SkDPoint pt = c1.ptAtT(tAvg);
+                if (!pt.approximatelyEqual(fPt[cpIdx])) {
+                    ++cpIdx;
+                    continue;
+                }
+                tAvg = (fT[1][cpIdx] + i[1][cpIdx + 1]) / 2;
+                pt = c2.ptAtT(tAvg);
+                if (!pt.approximatelyEqual(fPt[cpIdx])) {
+                    ++cpIdx;
+                    continue;
+                }
+                i.removeOne(cpIdx);
+                --compCount;
+            }
+            // in addition to adding below missing function, think about how to say
+            append(i);
+        }
+    }
     // If an end point and a second point very close to the end is returned, the second
     // point may have been detected because the approximate quads
     // intersected at the end and close to it. Verify that the second point is valid.
diff --git a/src/pathops/SkDCubicLineIntersection.cpp b/src/pathops/SkDCubicLineIntersection.cpp
index e9997e4..abbc4e3 100644
--- a/src/pathops/SkDCubicLineIntersection.cpp
+++ b/src/pathops/SkDCubicLineIntersection.cpp
@@ -215,6 +215,8 @@
         }
     }
 
+    /* Note that this does not look for endpoints of the line that are near the cubic.
+       These points are found later when check ends looks for missing points */
     void addNearEndPoints() {
         for (int cIndex = 0; cIndex < 4; cIndex += 3) {
             double cubicT = (double) (cIndex >> 1);
diff --git a/src/pathops/SkDLineIntersection.cpp b/src/pathops/SkDLineIntersection.cpp
index fca0a04..33c8480 100644
--- a/src/pathops/SkDLineIntersection.cpp
+++ b/src/pathops/SkDLineIntersection.cpp
@@ -181,7 +181,7 @@
 }
 
 static double horizontal_intercept(const SkDLine& line, double y) {
-     return (y - line[0].fY) / (line[1].fY - line[0].fY);
+     return SkPinT((y - line[0].fY) / (line[1].fY - line[0].fY));
 }
 
 int SkIntersections::horizontal(const SkDLine& line, double y) {
@@ -267,7 +267,7 @@
 }
 
 static double vertical_intercept(const SkDLine& line, double x) {
-    return (x - line[0].fX) / (line[1].fX - line[0].fX);
+    return SkPinT((x - line[0].fX) / (line[1].fX - line[0].fX));
 }
 
 int SkIntersections::vertical(const SkDLine& line, double x) {
diff --git a/src/pathops/SkDQuadIntersection.cpp b/src/pathops/SkDQuadIntersection.cpp
index 6e5f3e6..4872508 100644
--- a/src/pathops/SkDQuadIntersection.cpp
+++ b/src/pathops/SkDQuadIntersection.cpp
@@ -301,7 +301,7 @@
             *pt = t1[1];
     #if ONE_OFF_DEBUG
             SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) == (%1.9g,%1.9g)\n", __FUNCTION__,
-                    t1Seed, t2Seed, t1[1].fX, t1[1].fY, t1[2].fX, t1[2].fY);
+                    t1Seed, t2Seed, t1[1].fX, t1[1].fY, t2[1].fX, t2[1].fY);
     #endif
             return true;
         }
@@ -490,15 +490,11 @@
         pts2[index] = q2.ptAtT(roots2Copy[index]);
     }
     if (r1Count == r2Count && r1Count <= 1) {
-        if (r1Count == 1) {
+        if (r1Count == 1 && used() == 0) {
             if (pts1[0].approximatelyEqual(pts2[0])) {
                 insert(roots1Copy[0], roots2Copy[0], pts1[0]);
             } else if (pts1[0].moreRoughlyEqual(pts2[0])) {
                 // experiment: try to find intersection by chasing t
-                rootCount = findRoots(i2, q1, roots1, useCubic, flip1, 0);
-                (void) addValidRoots(roots1, rootCount, roots1Copy);
-                rootCount2 = findRoots(i1, q2, roots2, useCubic, flip2, 0);
-                (void) addValidRoots(roots2, rootCount2, roots2Copy);
                 if (binary_search(q1, q2, roots1Copy, roots2Copy, pts1)) {
                     insert(roots1Copy[0], roots2Copy[0], pts1[0]);
                 }
diff --git a/src/pathops/SkDQuadLineIntersection.cpp b/src/pathops/SkDQuadLineIntersection.cpp
index 14d7d9c..8fce7a0 100644
--- a/src/pathops/SkDQuadLineIntersection.cpp
+++ b/src/pathops/SkDQuadLineIntersection.cpp
@@ -141,14 +141,18 @@
         if (fAllowNear) {
             addNearEndPoints();
         }
-        double rootVals[2];
-        int roots = intersectRay(rootVals);
-        for (int index = 0; index < roots; ++index) {
-            double quadT = rootVals[index];
-            double lineT = findLineT(quadT);
-            SkDPoint pt;
-            if (pinTs(&quadT, &lineT, &pt, kPointUninitialized)) {
-                fIntersections->insert(quadT, lineT, pt);
+        if (fIntersections->used() == 2) {
+            // FIXME : need sharable code that turns spans into coincident if middle point is on
+        } else {
+            double rootVals[2];
+            int roots = intersectRay(rootVals);
+            for (int index = 0; index < roots; ++index) {
+                double quadT = rootVals[index];
+                double lineT = findLineT(quadT);
+                SkDPoint pt;
+                if (pinTs(&quadT, &lineT, &pt, kPointUninitialized)) {
+                    fIntersections->insert(quadT, lineT, pt);
+                }
             }
         }
         return fIntersections->used();
diff --git a/src/pathops/SkIntersectionHelper.h b/src/pathops/SkIntersectionHelper.h
index 1a4b1f0..f5eeaf8 100644
--- a/src/pathops/SkIntersectionHelper.h
+++ b/src/pathops/SkIntersectionHelper.h
@@ -7,6 +7,10 @@
 #include "SkOpContour.h"
 #include "SkPath.h"
 
+#if SK_DEBUG
+#include "SkPathOpsPoint.h"
+#endif
+
 class SkIntersectionHelper {
 public:
     enum SegmentType {
@@ -81,6 +85,14 @@
         return midPtByT.approximatelyEqual(midPtByAvg);
     }
 
+    bool isPartial(double t1, double t2, const SkDPoint& pt1, const SkDPoint& pt2) const {
+        const SkOpSegment& segment = fContour->segments()[fIndex];
+        double mid = (t1 + t2) / 2;
+        SkDPoint midPtByT = segment.dPtAtT(mid);
+        SkDPoint midPtByAvg = SkDPoint::Mid(pt1, pt2);
+        return midPtByT.approximatelyPEqual(midPtByAvg);
+    }
+
     SkScalar left() const {
         return bounds().fLeft;
     }
@@ -137,6 +149,19 @@
         return y() != pts()[0].fY;
     }
 
+#ifdef SK_DEBUG
+    void dump() {
+        SkDPoint::dump(pts()[0]);
+        SkDPoint::dump(pts()[1]);
+        if (verb() >= SkPath::kQuad_Verb) {
+            SkDPoint::dump(pts()[2]);
+        }
+        if (verb() >= SkPath::kCubic_Verb) {
+            SkDPoint::dump(pts()[3]);
+        }
+    }
+#endif
+
 private:
     SkOpContour* fContour;
     int fIndex;
diff --git a/src/pathops/SkIntersections.cpp b/src/pathops/SkIntersections.cpp
index 608ffe3..35846f6 100644
--- a/src/pathops/SkIntersections.cpp
+++ b/src/pathops/SkIntersections.cpp
@@ -7,6 +7,12 @@
 
 #include "SkIntersections.h"
 
+void SkIntersections::append(const SkIntersections& i) {
+    for (int index = 0; index < i.fUsed; ++index) {
+        insert(i[0][index], i[1][index], i.pt(index));
+    }
+}
+
 int (SkIntersections::*CurveVertical[])(const SkPoint[], SkScalar, SkScalar, SkScalar, bool) = {
     NULL,
     &SkIntersections::verticalLine,
@@ -16,7 +22,7 @@
 
 int (SkIntersections::*CurveRay[])(const SkPoint[], const SkDLine&) = {
     NULL,
-    NULL,
+    &SkIntersections::lineRay,
     &SkIntersections::quadRay,
     &SkIntersections::cubicRay
 };
@@ -126,6 +132,13 @@
     fIsCoincident[1] |= bit;
 }
 
+int SkIntersections::lineRay(const SkPoint pts[2], const SkDLine& line) {
+    SkDLine l;
+    l.set(pts);
+    fMax = 2;
+    return intersectRay(l, line);
+}
+
 void SkIntersections::offset(int base, double start, double end) {
     for (int index = base; index < fUsed; ++index) {
         double val = fT[fSwap][index];
diff --git a/src/pathops/SkIntersections.h b/src/pathops/SkIntersections.h
index f63a023..a3e8332 100644
--- a/src/pathops/SkIntersections.h
+++ b/src/pathops/SkIntersections.h
@@ -183,9 +183,6 @@
         return intersect(aQuad, bQuad);
     }
 
-    int quadRay(const SkPoint pts[3], const SkDLine& line);
-    void removeOne(int index);
-
     // leaves flip, swap, max alone
     void reset() {
         fAllowNear = true;
@@ -218,6 +215,7 @@
         SkASSERT(++fDepth < 16);
     }
 
+    void append(const SkIntersections& );
     static double Axial(const SkDQuad& , const SkDPoint& , bool vertical);
     void cleanUpCoincidence();
     int coincidentUsed() const;
@@ -246,8 +244,11 @@
     int intersectRay(const SkDQuad&, const SkDLine&);
     int intersectRay(const SkDCubic&, const SkDLine&);
     static SkDPoint Line(const SkDLine&, const SkDLine&);
+    int lineRay(const SkPoint pts[2], const SkDLine& line);
     void offset(int base, double start, double end);
     void quickRemoveOne(int index, int replace);
+    int quadRay(const SkPoint pts[3], const SkDLine& line);
+    void removeOne(int index);
     static bool Test(const SkDLine& , const SkDLine&);
     int vertical(const SkDLine&, double x);
     int vertical(const SkDLine&, double top, double bottom, double x, bool flipped);
diff --git a/src/pathops/SkOpAngle.cpp b/src/pathops/SkOpAngle.cpp
index 5e1d9e7..4144add 100644
--- a/src/pathops/SkOpAngle.cpp
+++ b/src/pathops/SkOpAngle.cpp
@@ -207,7 +207,10 @@
         return COMPARE_RESULT("roots == 0 || rroots == 0", this < &rh);
     }
     SkASSERT(fSide != 0 && rh.fSide != 0);
-    SkASSERT(fSide * rh.fSide > 0); // both are the same sign
+    if (fSide * rh.fSide < 0) {
+        fUnsortable = true;
+        return COMPARE_RESULT("14 fSide * rh.fSide < 0", this < &rh);
+    }
     SkDPoint lLoc;
     double best = SK_ScalarInfinity;
 #if DEBUG_SORT
@@ -246,7 +249,7 @@
     if (flip) {
         leftLessThanRight = !leftLessThanRight;
     }
-    return COMPARE_RESULT("14 leftLessThanRight", leftLessThanRight);
+    return COMPARE_RESULT("15 leftLessThanRight", leftLessThanRight);
 }
 
 bool SkOpAngle::isHorizontal() const {
diff --git a/src/pathops/SkOpContour.cpp b/src/pathops/SkOpContour.cpp
index 4aa12cd..5feef79 100644
--- a/src/pathops/SkOpContour.cpp
+++ b/src/pathops/SkOpContour.cpp
@@ -174,6 +174,63 @@
     }
 }
 
+void SkOpContour::joinCoincidence(const SkTArray<SkCoincidence, true>& coincidences, bool partial) {
+    int count = coincidences.count();
+#if DEBUG_CONCIDENT
+    if (count > 0) {
+        SkDebugf("%s count=%d\n", __FUNCTION__, count);
+    }
+#endif
+    // look for a lineup where the partial implies another adjoining coincidence
+    for (int index = 0; index < count; ++index) {
+        const SkCoincidence& coincidence = coincidences[index];
+        int thisIndex = coincidence.fSegments[0];
+        SkOpSegment& thisOne = fSegments[thisIndex];
+        SkOpContour* otherContour = coincidence.fOther;
+        int otherIndex = coincidence.fSegments[1];
+        SkOpSegment& other = otherContour->fSegments[otherIndex];
+        double startT = coincidence.fTs[0][0];
+        double endT = coincidence.fTs[0][1];
+        if (startT == endT) {  // this can happen in very large compares
+            continue;
+        }
+        double oStartT = coincidence.fTs[1][0];
+        double oEndT = coincidence.fTs[1][1];
+        if (oStartT == oEndT) {
+            continue;
+        }
+        bool swapStart = startT > endT;
+        bool swapOther = oStartT > oEndT;
+        if (swapStart) {
+            SkTSwap<double>(startT, endT);
+            SkTSwap<double>(oStartT, oEndT);
+        }
+        bool cancel = swapOther != swapStart;
+        int step = swapStart ? -1 : 1;
+        int oStep = swapOther ? -1 : 1;
+        double oMatchStart = cancel ? oEndT : oStartT;
+        if (partial ? startT != 0 || oMatchStart != 0 : (startT == 0) != (oMatchStart == 0)) {
+            bool added = false;
+            if (oMatchStart != 0) {
+                added = thisOne.joinCoincidence(false, &other, oMatchStart, oStep, cancel);
+            }
+            if (startT != 0 && !added) {
+                (void) other.joinCoincidence(cancel, &thisOne, startT, step, cancel);
+            }
+        }
+        double oMatchEnd = cancel ? oStartT : oEndT;
+        if (partial ? endT != 1 || oMatchEnd != 1 : (endT == 1) != (oMatchEnd == 1)) {
+            bool added = false;
+            if (oMatchEnd != 1) {
+                added = thisOne.joinCoincidence(true, &other, oMatchEnd, -oStep, cancel);
+            }
+            if (endT != 1 && !added) {
+                (void) other.joinCoincidence(!cancel, &thisOne, endT, -step, cancel);
+            }
+        }
+    }
+}
+
 void SkOpContour::calcCommonCoincidentWinding(const SkCoincidence& coincidence) {
     int thisIndex = coincidence.fSegments[0];
     SkOpSegment& thisOne = fSegments[thisIndex];
diff --git a/src/pathops/SkOpContour.h b/src/pathops/SkOpContour.h
index a4ec6d3..6b412e5 100644
--- a/src/pathops/SkOpContour.h
+++ b/src/pathops/SkOpContour.h
@@ -152,6 +152,11 @@
         }
     }
 
+    void joinCoincidence() {
+        joinCoincidence(fCoincidences, false);
+        joinCoincidence(fPartialCoincidences, true);
+    }
+
     SkOpSegment* nonVerticalSegment(int* start, int* end);
 
     bool operand() const {
@@ -239,7 +244,8 @@
 #endif
 
 private:
-    void calcCommonCoincidentWinding(const SkCoincidence& coincidence);
+    void calcCommonCoincidentWinding(const SkCoincidence& );
+    void joinCoincidence(const SkTArray<SkCoincidence, true>& , bool partial);
     void setBounds();
 
     SkTArray<SkOpSegment> fSegments;
diff --git a/src/pathops/SkOpEdgeBuilder.cpp b/src/pathops/SkOpEdgeBuilder.cpp
index 676c34f..ae72e29 100644
--- a/src/pathops/SkOpEdgeBuilder.cpp
+++ b/src/pathops/SkOpEdgeBuilder.cpp
@@ -42,16 +42,11 @@
 }
 
 void SkOpEdgeBuilder::closeContour(const SkPoint& curveEnd, const SkPoint& curveStart) {
-    if ((!AlmostEqualUlps(curveEnd.fX, curveStart.fX)
-            || !AlmostEqualUlps(curveEnd.fY, curveStart.fY))) {
+    if (!SkDPoint::ApproximatelyEqual(curveEnd, curveStart)) {
         fPathVerbs.push_back(SkPath::kLine_Verb);
         fPathPts.push_back_n(1, &curveStart);
     } else {
-        if (curveEnd.fX != curveStart.fX || curveEnd.fY != curveStart.fY) {
-            fPathPts[fPathPts.count() - 1] = curveStart;
-        } else {
-            fPathPts[fPathPts.count() - 1] = curveStart;
-        }
+        fPathPts[fPathPts.count() - 1] = curveStart;
     }
     fPathVerbs.push_back(SkPath::kClose_Verb);
 }
@@ -82,9 +77,9 @@
                 lastCurve = false;
                 continue;
             case SkPath::kLine_Verb:
-                if (AlmostEqualUlps(curve[0].fX, pts[1].fX)
-                        && AlmostEqualUlps(curve[0].fY, pts[1].fY)) {
-                    if (fPathVerbs.back() != SkPath::kLine_Verb) {
+                if (SkDPoint::ApproximatelyEqual(curve[0], pts[1])) {
+                    uint8_t lastVerb = fPathVerbs.back();
+                    if (lastVerb != SkPath::kLine_Verb && lastVerb != SkPath::kMove_Verb) {
                         fPathPts.back() = pts[1];
                     }
                     continue;  // skip degenerate points
diff --git a/src/pathops/SkOpSegment.cpp b/src/pathops/SkOpSegment.cpp
index 4d11eb3..6fe1fbb 100644
--- a/src/pathops/SkOpSegment.cpp
+++ b/src/pathops/SkOpSegment.cpp
@@ -1298,6 +1298,7 @@
     SkIntersections intersections;
     // OPTIMIZE: use specialty function that intersects ray with curve,
     // returning t values only for curve (we don't care about t on ray)
+    intersections.allowNear(false);
     int pts = (intersections.*CurveVertical[SkPathOpsVerbToPoints(fVerb)])
             (fPts, top, bottom, basePt.fX, false);
     if (pts == 0 || (current && pts == 1)) {
@@ -1420,15 +1421,29 @@
         }
         // t start/last describe the range of spans that match the t of this span
         double t = span.fT;
-        int tStart = index;
-        while (--tStart >= 0 && (t == fTs[tStart].fT || fTs[tStart].fTiny))
-            ;
-        int tLast = index;
-        while (fTs[tLast].fTiny) {
-            ++tLast;
+        double tBottom = -1;
+        int tStart = -1;
+        int tLast = count;
+        bool lastSmall = false;
+        double afterT = t;
+        for (int inner = 0; inner < count; ++inner) {
+            double innerT = fTs[inner].fT;
+            if (innerT <= t && innerT > tBottom) {
+                if (innerT < t || !lastSmall) {
+                    tStart = inner - 1;
+                }
+                tBottom = innerT;
+            }
+            if (innerT > afterT) {
+                if (t == afterT && lastSmall) {
+                    afterT = innerT;
+                } else {
+                    tLast = inner;
+                    break;
+                }
+            }
+            lastSmall = innerT <= t ? fTs[inner].fSmall : false;
         }
-        while (++tLast < count && t == fTs[tLast].fT)
-            ;
         for (int peekIndex = peekStart; peekIndex <= peekLast; ++peekIndex) {
             if (peekIndex == span.fOtherIndex) {  // skip the other span pointed to by this span
                 continue;
@@ -1696,6 +1711,70 @@
     }
 }
 
+bool SkOpSegment::findCoincidentMatch(const SkOpSpan* span, const SkOpSegment* other, int oStart,
+        int oEnd, int step, SkPoint* startPt, SkPoint* endPt, double* endT) const {
+    SkASSERT(span->fT == 0 || span->fT == 1);
+    SkASSERT(span->fOtherT == 0 || span->fOtherT == 1);
+    const SkOpSpan* otherSpan = &other->span(oEnd);
+    double refT = otherSpan->fT;
+    const SkPoint& refPt = otherSpan->fPt;
+    const SkOpSpan* lastSpan = &other->span(step > 0 ? other->count() - 1 : 0);
+    do {
+        const SkOpSegment* match = span->fOther;
+        if (match == otherSpan->fOther) {
+            // find start of respective spans and see if both have winding
+            int startIndex, endIndex;
+            if (span->fOtherT == 1) {
+                endIndex = span->fOtherIndex;
+                startIndex = match->nextExactSpan(endIndex, -1);
+            } else {
+                startIndex = span->fOtherIndex;
+                endIndex = match->nextExactSpan(startIndex, 1);
+            }
+            const SkOpSpan& startSpan = match->span(startIndex);
+            if (startSpan.fWindValue != 0) {
+                // draw ray from endSpan.fPt perpendicular to end tangent and measure distance
+                // to other segment.
+                const SkOpSpan& endSpan = match->span(endIndex);
+                SkDLine ray;
+                SkVector dxdy;
+                if (span->fOtherT == 1) {
+                    ray.fPts[0].set(startSpan.fPt);
+                    dxdy = match->dxdy(startIndex);
+                } else {
+                    ray.fPts[0].set(endSpan.fPt);
+                    dxdy = match->dxdy(endIndex);
+                }
+                ray.fPts[1].fX = ray.fPts[0].fX + dxdy.fY;
+                ray.fPts[1].fY = ray.fPts[0].fY - dxdy.fX;
+                SkIntersections i;
+                int roots = (i.*CurveRay[SkPathOpsVerbToPoints(other->verb())])(other->pts(), ray);
+                for (int index = 0; index < roots; ++index) {
+                    if (ray.fPts[0].approximatelyEqual(i.pt(index))) {
+                        double matchMidT = (match->span(startIndex).fT
+                                + match->span(endIndex).fT) / 2;
+                        SkPoint matchMidPt = match->ptAtT(matchMidT);
+                        double otherMidT = (i[0][index] + other->span(oStart).fT) / 2;
+                        SkPoint otherMidPt = other->ptAtT(otherMidT);
+                        if (SkDPoint::ApproximatelyEqual(matchMidPt, otherMidPt)) {
+                            *startPt = startSpan.fPt;
+                            *endPt = endSpan.fPt;
+                            *endT = endSpan.fT;
+                            return true;
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+        if (otherSpan == lastSpan) {
+            break;
+        }
+        otherSpan += step;
+    } while (otherSpan->fT == refT || otherSpan->fPt == refPt);
+    return false;
+}
+
 /*
  The M and S variable name parts stand for the operators.
    Mi stands for Minuend (see wiki subtraction, analogous to difference)
@@ -2076,6 +2155,18 @@
     return firstIndex;
 }
 
+int SkOpSegment::findT(double t, const SkOpSegment* match) const {
+    int count = this->count();
+    for (int index = 0; index < count; ++index) {
+        const SkOpSpan& span = fTs[index];
+        if (span.fT == t && span.fOther == match) {
+            return index;
+        }
+    }
+    SkASSERT(0);
+    return -1;
+}
+
 // FIXME: either:
 // a) mark spans with either end unsortable as done, or
 // b) rewrite findTop / findTopSegment / findTopContour to iterate further
@@ -2299,6 +2390,76 @@
     return false;
 }
 
+bool SkOpSegment::isTiny(const SkOpAngle* angle) const {
+    int start = angle->start();
+    int end = angle->end();
+    const SkOpSpan& mSpan = fTs[SkMin32(start, end)];
+    return mSpan.fTiny;
+}
+
+bool SkOpSegment::isTiny(int index) const {
+    return fTs[index].fTiny;
+}
+
+// look pair of active edges going away from coincident edge
+// one of them should be the continuation of other
+// if both are active, look to see if they both the connect to another coincident pair
+// if one at least one is a line, then make the pair coincident
+// if neither is a line, test for coincidence
+bool SkOpSegment::joinCoincidence(bool end, SkOpSegment* other, double otherT, int step,
+        bool cancel) {
+    int otherTIndex = other->findT(otherT, this);
+    int next = other->nextExactSpan(otherTIndex, step);
+    int otherMin = SkTMin(otherTIndex, next);
+    int otherWind = other->span(otherMin).fWindValue;
+    if (otherWind == 0) {
+        return false;
+    }
+    SkASSERT(next >= 0);
+    if (end) {
+        int tIndex = count() - 1;
+        do {
+            SkOpSpan* test = &fTs[tIndex];
+            SkASSERT(test->fT == 1);
+            if (test->fOther == other || test->fOtherT != 0) {
+                continue;
+            }
+            SkPoint startPt, endPt;
+            double endT;
+            if (findCoincidentMatch(test, other, otherTIndex, next, step, &startPt, &endPt, &endT)) {
+                SkOpSegment* match = test->fOther;
+                if (cancel) {
+                    match->addTCancel(startPt, endPt, other);
+                } else {
+                    match->addTCoincident(startPt, endPt, endT, other);
+                }
+                return true;
+            }
+        } while (fTs[--tIndex].fT == 1);
+    } else {
+        int tIndex = 0;
+        do {
+            SkOpSpan* test = &fTs[tIndex];
+            SkASSERT(test->fT == 0);
+            if (test->fOther == other || test->fOtherT != 1) {
+                continue;
+            }
+            SkPoint startPt, endPt;
+            double endT;
+            if (findCoincidentMatch(test, other, otherTIndex, next, step, &startPt, &endPt, &endT)) {
+                SkOpSegment* match = test->fOther;
+                if (cancel) {
+                    match->addTCancel(startPt, endPt, other);
+                } else {
+                    match->addTCoincident(startPt, endPt, endT, other);
+                }
+                return true;
+            }
+        } while (fTs[++tIndex].fT == 0);
+    }
+    return false;
+}
+
 // this span is excluded by the winding rule -- chase the ends
 // as long as they are unambiguous to mark connections as done
 // and give them the same winding value
@@ -3018,17 +3179,6 @@
     (bounds->*SetCurveBounds[SkPathOpsVerbToPoints(fVerb)])(edge);
 }
 
-bool SkOpSegment::isTiny(const SkOpAngle* angle) const {
-    int start = angle->start();
-    int end = angle->end();
-    const SkOpSpan& mSpan = fTs[SkMin32(start, end)];
-    return mSpan.fTiny;
-}
-
-bool SkOpSegment::isTiny(int index) const {
-    return fTs[index].fTiny;
-}
-
 void SkOpSegment::TrackOutsidePair(SkTArray<SkPoint, true>* outsidePts, const SkPoint& endPt,
         const SkPoint& startPt) {
     int outCount = outsidePts->count();
@@ -3558,10 +3708,10 @@
     SkDebugf("{{");
     int index = 0;
     do {
-        SkDPoint::DumpSkPoint(fPts[index]);
+        SkDPoint::dump(fPts[index]);
         SkDebugf(", ");
     } while (++index < last);
-    SkDPoint::DumpSkPoint(fPts[index]);
+    SkDPoint::dump(fPts[index]);
     SkDebugf("}}\n");
 }
 
diff --git a/src/pathops/SkOpSegment.h b/src/pathops/SkOpSegment.h
index 85531f5..d56ce8e 100644
--- a/src/pathops/SkOpSegment.h
+++ b/src/pathops/SkOpSegment.h
@@ -259,12 +259,15 @@
                     SkTArray<SkOpAngle, true>* angles, SkTArray<SkOpAngle*, true>* sorted);
     int crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT, bool* hitSomething,
                      double mid, bool opp, bool current) const;
+    bool findCoincidentMatch(const SkOpSpan* span, const SkOpSegment* other, int oStart, int oEnd,
+                             int step, SkPoint* startPt, SkPoint* endPt, double* endT) const;
     SkOpSegment* findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
                             bool* unsortable, SkPathOp op, const int xorMiMask,
                             const int xorSuMask);
     SkOpSegment* findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
                                  bool* unsortable);
     SkOpSegment* findNextXor(int* nextStart, int* nextEnd, bool* unsortable);
+    int findT(double t, const SkOpSegment* ) const;
     SkOpSegment* findTop(int* tIndex, int* endIndex, bool* unsortable, bool onlySortable);
     void fixOtherTIndex();
     void initWinding(int start, int end);
@@ -272,6 +275,7 @@
                      SkScalar hitOppDx);
     bool isMissing(double startT, const SkPoint& pt) const;
     bool isTiny(const SkOpAngle* angle) const;
+    bool joinCoincidence(bool end, SkOpSegment* other, double otherT, int step, bool cancel);
     SkOpSpan* markAndChaseDoneBinary(int index, int endIndex);
     SkOpSpan* markAndChaseDoneUnary(int index, int endIndex);
     SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding);
diff --git a/src/pathops/SkPathOpsCommon.cpp b/src/pathops/SkPathOpsCommon.cpp
index 4db6079..c48a7ee 100644
--- a/src/pathops/SkPathOpsCommon.cpp
+++ b/src/pathops/SkPathOpsCommon.cpp
@@ -4,6 +4,7 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+#include "SkAddIntersections.h"
 #include "SkOpEdgeBuilder.h"
 #include "SkPathOpsCommon.h"
 #include "SkPathWriter.h"
@@ -350,7 +351,7 @@
     return current;
 }
 
-void CheckEnds(SkTArray<SkOpContour*, true>* contourList) {
+static void checkEnds(SkTArray<SkOpContour*, true>* contourList) {
     // it's hard to determine if the end of a cubic or conic nearly intersects another curve.
     // instead, look to see if the connecting curve intersected at that same end.
     int contourCount = (*contourList).count();
@@ -361,7 +362,7 @@
 }
 
 // A tiny interval may indicate an undiscovered coincidence. Find and fix.
-void CheckTiny(SkTArray<SkOpContour*, true>* contourList) {
+static void checkTiny(SkTArray<SkOpContour*, true>* contourList) {
     int contourCount = (*contourList).count();
     for (int cTest = 0; cTest < contourCount; ++cTest) {
         SkOpContour* contour = (*contourList)[cTest];
@@ -369,7 +370,7 @@
     }
 }
 
-void FixOtherTIndex(SkTArray<SkOpContour*, true>* contourList) {
+static void fixOtherTIndex(SkTArray<SkOpContour*, true>* contourList) {
     int contourCount = (*contourList).count();
     for (int cTest = 0; cTest < contourCount; ++cTest) {
         SkOpContour* contour = (*contourList)[cTest];
@@ -377,7 +378,15 @@
     }
 }
 
-void SortSegments(SkTArray<SkOpContour*, true>* contourList) {
+static void joinCoincidence(SkTArray<SkOpContour*, true>* contourList) {
+    int contourCount = (*contourList).count();
+    for (int cTest = 0; cTest < contourCount; ++cTest) {
+        SkOpContour* contour = (*contourList)[cTest];
+        contour->joinCoincidence();
+    }
+}
+
+static void sortSegments(SkTArray<SkOpContour*, true>* contourList) {
     int contourCount = (*contourList).count();
     for (int cTest = 0; cTest < contourCount; ++cTest) {
         SkOpContour* contour = (*contourList)[cTest];
@@ -603,3 +612,21 @@
     }
 #endif
 }
+
+void HandleCoincidence(SkTArray<SkOpContour*, true>* contourList, int total) {
+#if DEBUG_SHOW_WINDING
+    SkOpContour::debugShowWindingValues(contourList);
+#endif
+    CoincidenceCheck(contourList, total);
+#if DEBUG_SHOW_WINDING
+    SkOpContour::debugShowWindingValues(contourList);
+#endif
+    fixOtherTIndex(contourList);
+    checkEnds(contourList);
+    checkTiny(contourList);
+    joinCoincidence(contourList);
+    sortSegments(contourList);
+#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
+    DebugShowActiveSpans(*contourList);
+#endif
+}
diff --git a/src/pathops/SkPathOpsCommon.h b/src/pathops/SkPathOpsCommon.h
index e1ae998..afc751d 100644
--- a/src/pathops/SkPathOpsCommon.h
+++ b/src/pathops/SkPathOpsCommon.h
@@ -14,18 +14,15 @@
 class SkPathWriter;
 
 void Assemble(const SkPathWriter& path, SkPathWriter* simple);
-void CheckEnds(SkTArray<SkOpContour*, true>* contourList);
-void CheckTiny(SkTArray<SkOpContour*, true>* contourList);
 // FIXME: find chase uses insert, so it can't be converted to SkTArray yet
 SkOpSegment* FindChase(SkTDArray<SkOpSpan*>& chase, int& tIndex, int& endIndex);
 SkOpSegment* FindSortableTop(const SkTArray<SkOpContour*, true>& , SkOpAngle::IncludeType ,
                              bool* firstContour, int* index, int* endIndex, SkPoint* topLeft,
                              bool* unsortable, bool* done);
 SkOpSegment* FindUndone(SkTArray<SkOpContour*, true>& contourList, int* start, int* end);
-void FixOtherTIndex(SkTArray<SkOpContour*, true>* contourList);
 void MakeContourList(SkTArray<SkOpContour>& contours, SkTArray<SkOpContour*, true>& list,
                      bool evenOdd, bool oppEvenOdd);
-void SortSegments(SkTArray<SkOpContour*, true>* contourList);
+void HandleCoincidence(SkTArray<SkOpContour*, true>* , int );
 
 #if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
 void DebugShowActiveSpans(SkTArray<SkOpContour*, true>& contourList);
diff --git a/src/pathops/SkPathOpsDebug.cpp b/src/pathops/SkPathOpsDebug.cpp
index 1b19fe5..95e2204 100644
--- a/src/pathops/SkPathOpsDebug.cpp
+++ b/src/pathops/SkPathOpsDebug.cpp
@@ -103,7 +103,7 @@
     SkDebugf("t=");
     DebugDumpDouble(fT);
     SkDebugf(" pt=");
-    SkDPoint::DumpSkPoint(fPt);
+    SkDPoint::dump(fPt);
     SkDebugf(" other.fID=%d", fOther->debugID());
     SkDebugf(" [%d] otherT=", fOtherIndex);
     DebugDumpDouble(fOtherT);
@@ -157,3 +157,8 @@
 }
 
 #endif
+
+#if !FORCE_RELEASE && 0  // enable when building without extended test
+void SkPathOpsDebug::ShowPath(const SkPath& one, const SkPath& two, SkPathOp op, const char* name) {
+}
+#endif
diff --git a/src/pathops/SkPathOpsOp.cpp b/src/pathops/SkPathOpsOp.cpp
index 71ebef0..9d6cd51 100644
--- a/src/pathops/SkPathOpsOp.cpp
+++ b/src/pathops/SkPathOpsOp.cpp
@@ -304,20 +304,7 @@
     for (index = 0; index < contourList.count(); ++index) {
         total += contourList[index]->segments().count();
     }
-#if DEBUG_SHOW_WINDING
-    SkOpContour::debugShowWindingValues(contourList);
-#endif
-    CoincidenceCheck(&contourList, total);
-#if DEBUG_SHOW_WINDING
-    SkOpContour::debugShowWindingValues(contourList);
-#endif
-    FixOtherTIndex(&contourList);
-    CheckEnds(&contourList);
-    CheckTiny(&contourList);
-    SortSegments(&contourList);
-#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
-    DebugShowActiveSpans(contourList);
-#endif
+    HandleCoincidence(&contourList, total);
     // construct closed contours
     SkPathWriter wrapper(*result);
     bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper);
diff --git a/src/pathops/SkPathOpsPoint.h b/src/pathops/SkPathOpsPoint.h
index 40688d8..c3e0b40 100644
--- a/src/pathops/SkPathOpsPoint.h
+++ b/src/pathops/SkPathOpsPoint.h
@@ -98,7 +98,7 @@
 
     // note: this can not be implemented with
     // return approximately_equal(a.fY, fY) && approximately_equal(a.fX, fX);
-    // because that will not take the magnitude of the values
+    // because that will not take the magnitude of the values into account
     bool approximatelyEqual(const SkDPoint& a) const {
         if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) {
             return true;
@@ -136,6 +136,20 @@
         return AlmostBequalUlps((double) largest, largest + dist); // is dist within ULPS tolerance?
     }
 
+    bool approximatelyPEqual(const SkDPoint& a) const {
+        if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) {
+            return true;
+        }
+        if (!RoughlyEqualUlps(fX, a.fX) || !RoughlyEqualUlps(fY, a.fY)) {
+            return false;
+        }
+        double dist = distance(a);  // OPTIMIZATION: can we compare against distSq instead ?
+        double tiniest = SkTMin(SkTMin(SkTMin(fX, a.fX), fY), a.fY);
+        double largest = SkTMax(SkTMax(SkTMax(fX, a.fX), fY), a.fY);
+        largest = SkTMax(largest, -tiniest);
+        return AlmostPequalUlps(largest, largest + dist); // is the dist within ULPS tolerance?
+    }
+
     bool approximatelyZero() const {
         return approximately_zero(fX) && approximately_zero(fY);
     }
@@ -186,7 +200,7 @@
         SkDebugf("}");
     }
 
-    static void DumpSkPoint(const SkPoint& pt) {
+    static void dump(const SkPoint& pt) {
         SkDebugf("{");
         DebugDumpFloat(pt.fX);
         SkDebugf(", ");
diff --git a/src/pathops/SkPathOpsSimplify.cpp b/src/pathops/SkPathOpsSimplify.cpp
index 76e3413..548f83e 100644
--- a/src/pathops/SkPathOpsSimplify.cpp
+++ b/src/pathops/SkPathOpsSimplify.cpp
@@ -182,15 +182,7 @@
             next = *nextPtr++;
         } while (AddIntersectTs(current, next) && nextPtr != listEnd);
     } while (currentPtr != listEnd);
-    // eat through coincident edges
-    CoincidenceCheck(&contourList, 0);
-    FixOtherTIndex(&contourList);
-    CheckEnds(&contourList);
-    CheckTiny(&contourList);
-    SortSegments(&contourList);
-#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
-    DebugShowActiveSpans(contourList);
-#endif
+    HandleCoincidence(&contourList, 0);
     // construct closed contours
     SkPathWriter simple(*result);
     if (builder.xorMask() == kWinding_PathOpsMask ? bridgeWinding(contourList, &simple)
diff --git a/src/pathops/SkPathOpsTriangle.cpp b/src/pathops/SkPathOpsTriangle.cpp
index 4939166..003968d 100644
--- a/src/pathops/SkPathOpsTriangle.cpp
+++ b/src/pathops/SkPathOpsTriangle.cpp
@@ -8,6 +8,7 @@
 #include "SkPathOpsTriangle.h"
 
 // http://www.blackpawn.com/texts/pointinpoly/default.html
+// return true if pt is inside triangle; false if outside or on the line
 bool SkDTriangle::contains(const SkDPoint& pt) const {
 // Compute vectors
     SkDVector v0 = fPts[2] - fPts[0];
@@ -21,11 +22,30 @@
     double dot11 = v1.dot(v1);
     double dot12 = v1.dot(v2);
 
+// original code doesn't handle degenerate input; isn't symmetric with inclusion of corner pts;
+// introduces necessary error with divide; doesn't short circuit on early answer
+#if 0
 // Compute barycentric coordinates
     double invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
     double u = (dot11 * dot02 - dot01 * dot12) * invDenom;
     double v = (dot00 * dot12 - dot01 * dot02) * invDenom;
 
 // Check if point is in triangle
-    return (u >= 0) && (v >= 0) && (u + v < 1);
+    return (u >= 0) && (v >= 0) && (u + v <= 1);
+#else
+    double w = dot00 * dot11 - dot01 * dot01;
+    if (w == 0) {
+        return false;
+    }
+    double wSign = w < 0 ? -1 : 1;
+    double u = (dot11 * dot02 - dot01 * dot12) * wSign;
+    if (u <= 0) {
+        return false;
+    }
+    double v = (dot00 * dot12 - dot01 * dot02) * wSign;
+    if (v <= 0) {
+        return false;
+    }
+    return u + v < w * wSign; 
+#endif
 }
diff --git a/src/pathops/SkPathOpsTypes.cpp b/src/pathops/SkPathOpsTypes.cpp
index df73d11..dbed086 100644
--- a/src/pathops/SkPathOpsTypes.cpp
+++ b/src/pathops/SkPathOpsTypes.cpp
@@ -8,17 +8,17 @@
 #include "SkPathOpsTypes.h"
 
 static bool arguments_denormalized(float a, float b, int epsilon) {
-    float denomalizedCheck = FLT_EPSILON * epsilon / 2;
-    return fabsf(a) <= denomalizedCheck && fabsf(b) <= denomalizedCheck;
+    float denormalizedCheck = FLT_EPSILON * epsilon / 2;
+    return fabsf(a) <= denormalizedCheck && fabsf(b) <= denormalizedCheck;
 }
 
 // from http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
 // FIXME: move to SkFloatBits.h
-static bool equal_ulps(float a, float b, int epsilon) {
+static bool equal_ulps(float a, float b, int epsilon, int depsilon) {
     if (!SkScalarIsFinite(a) || !SkScalarIsFinite(b)) {
         return false;
     }
-    if (arguments_denormalized(a, b, epsilon)) {
+    if (arguments_denormalized(a, b, depsilon)) {
         return true;
     }
     int aBits = SkFloatAs2sCompliment(a);
@@ -89,7 +89,12 @@
 // equality using the same error term as between
 bool AlmostBequalUlps(float a, float b) {
     const int UlpsEpsilon = 2;
-    return equal_ulps(a, b, UlpsEpsilon);
+    return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon);
+}
+
+bool AlmostPequalUlps(float a, float b) {
+    const int UlpsEpsilon = 8;
+    return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon);
 }
 
 bool AlmostDequalUlps(float a, float b) {
@@ -99,7 +104,7 @@
 
 bool AlmostEqualUlps(float a, float b) {
     const int UlpsEpsilon = 16;
-    return equal_ulps(a, b, UlpsEpsilon);
+    return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon);
 }
 
 bool NotAlmostEqualUlps(float a, float b) {
@@ -114,7 +119,8 @@
 
 bool RoughlyEqualUlps(float a, float b) {
     const int UlpsEpsilon = 256;
-    return equal_ulps(a, b, UlpsEpsilon);
+    const int DUlpsEpsilon = 1024;
+    return equal_ulps(a, b, UlpsEpsilon, DUlpsEpsilon);
 }
 
 bool AlmostBetweenUlps(float a, float b, float c) {
diff --git a/src/pathops/SkPathOpsTypes.h b/src/pathops/SkPathOpsTypes.h
index 0ad10c2..4fa86ab 100644
--- a/src/pathops/SkPathOpsTypes.h
+++ b/src/pathops/SkPathOpsTypes.h
@@ -50,6 +50,11 @@
     return AlmostBequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b));
 }
 
+bool AlmostPequalUlps(float a, float b);
+inline bool AlmostPequalUlps(double a, double b) {
+    return AlmostPequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b));
+}
+
 bool RoughlyEqualUlps(float a, float b);
 inline bool RoughlyEqualUlps(double a, double b) {
     return RoughlyEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b));
diff --git a/src/pathops/SkQuarticRoot.cpp b/src/pathops/SkQuarticRoot.cpp
index dca96de..e5b486c 100644
--- a/src/pathops/SkQuarticRoot.cpp
+++ b/src/pathops/SkQuarticRoot.cpp
@@ -71,7 +71,7 @@
         return num;
     }
     if (oneHint) {
-        SkASSERT(approximately_zero(t4 + t3 + t2 + t1 + t0));  // 1 is one root
+        SkASSERT(approximately_zero_double(t4 + t3 + t2 + t1 + t0));  // 1 is one root
         // note that -C == A + B + D + E
         int num = SkDCubic::RootsReal(t4, t4 + t3, -(t1 + t0), -t0, roots);
         for (int i = 0; i < num; ++i) {
diff --git a/tests/PathOpsCubicIntersectionTest.cpp b/tests/PathOpsCubicIntersectionTest.cpp
index 04797b4..c8a2e05 100644
--- a/tests/PathOpsCubicIntersectionTest.cpp
+++ b/tests/PathOpsCubicIntersectionTest.cpp
@@ -164,11 +164,23 @@
 const size_t testSetCount = SK_ARRAY_COUNT(testSet);
 
 static const SkDCubic newTestSet[] = {
+{{{275,532}, {277.209137,532}, {279,530.209106}, {279,528}}},
+{{{278,529}, {278,530.65686}, {276.65686,532}, {275,532}}},
+
 #if 0  // FIXME: asserts coincidence, not working yet
 {{{195, 785}, {124.30755615234375, 785}, {67, 841.85986328125}, {67, 912}}},
 {{{67, 913}, {67, 842.30755615234375}, {123.85984039306641, 785}, {194, 785}}},
 #endif
 
+{{{149,710.001465}, {149.000809,712.209961}, {150.791367,714}, {153,714}}},
+{{{154,715}, {151.238571,715}, {149,712.761414}, {149,710}}},
+
+{{{1,2}, {1,2}, {2,0}, {6,0}}},
+{{{0,2}, {0,6}, {2,1}, {2,1}}},
+
+{{{0,1}, {2,3}, {5,1}, {4,3}}},
+{{{1,5}, {3,4}, {1,0}, {3,2}}},
+
 {{{399,657}, {399,661.970581}, {403.029449,666}, {408,666}}},
 {{{406,666}, {402.686279,666}, {400,663.313721}, {400,660}}},
 
diff --git a/tests/PathOpsCubicLineIntersectionTest.cpp b/tests/PathOpsCubicLineIntersectionTest.cpp
index 49219fb..53e9d60 100644
--- a/tests/PathOpsCubicLineIntersectionTest.cpp
+++ b/tests/PathOpsCubicLineIntersectionTest.cpp
@@ -15,6 +15,9 @@
     SkDCubic cubic;
     SkDLine line;
 } lineCubicTests[] = {
+    {{{{154,715}, {151.238571,715}, {149,712.761414}, {149,710}}},
+            {{{149,675}, {149,710.001465}}}},
+
     {{{{0,1}, {1,6}, {4,1}, {4,3}}},
             {{{6,1}, {1,4}}}},
 
diff --git a/tests/PathOpsCubicQuadIntersectionTest.cpp b/tests/PathOpsCubicQuadIntersectionTest.cpp
index c9da2c7..35b49d2 100644
--- a/tests/PathOpsCubicQuadIntersectionTest.cpp
+++ b/tests/PathOpsCubicQuadIntersectionTest.cpp
@@ -17,14 +17,32 @@
     int answerCount;
     SkDPoint answers[2];
 } quadCubicTests[] = {
+    {{{{778, 14089}, {778, 14091.208984375}, {776.20916748046875, 14093}, {774, 14093}}},

+     {{{778, 14089}, {777.99957275390625, 14090.65625}, {776.82843017578125, 14091.828125}}}, 2,

+     {{778, 14089}, {776.82855609581270,14091.828250841330}}},

+
+    {{{{1110, 817}, {1110.55225f, 817}, {1111, 817.447693f}, {1111, 818}}},
+     {{{1110.70715f, 817.292908f}, {1110.41406f, 817.000122f}, {1110, 817}}}, 2,
+      {{1110, 817}, {1110.70715f, 817.292908f}}},
+
+    {{{{1110, 817}, {1110.55225f, 817}, {1111, 817.447693f}, {1111, 818}}},
+     {{{1111, 818}, {1110.99988f, 817.585876f}, {1110.70715f, 817.292908f}}}, 2,
+      {{1110.70715f, 817.292908f}, {1111, 818}}},
+
+    {{{{55, 207}, {52.238574981689453, 207}, {50, 204.76142883300781}, {50, 202}}},
+     {{{55, 207}, {52.929431915283203, 206.99949645996094},
+       {51.464466094970703, 205.53553771972656}}}, 2,
+      {{55, 207}, {51.464466094970703, 205.53553771972656}}},
+
     {{{{49, 47}, {49, 74.614250183105469}, {26.614250183105469, 97}, {-1, 97}}},
      {{{-8.659739592076221e-015, 96.991401672363281}, {20.065492630004883, 96.645187377929688},
        {34.355339050292969, 82.355339050292969}}}, 2,
-      {{34.355339050292969,82.355339050292969}, {34.306797674910243,82.403823585863449}}},
+      {{34.355339050292969,82.355339050292969}, {34.28654835573549, 82.424006509351585}}},
 
     {{{{10,234}, {10,229.58172607421875}, {13.581720352172852,226}, {18,226}}},
      {{{18,226}, {14.686291694641113,226}, {12.342399597167969,228.3424072265625}}}, 1,
       {{18,226}, {0,0}}},
+
     {{{{10,234}, {10,229.58172607421875}, {13.581720352172852,226}, {18,226}}},
      {{{12.342399597167969,228.3424072265625}, {10,230.68629455566406}, {10,234}}}, 1,
       {{10,234}, {0,0}}},
@@ -69,6 +87,10 @@
             for (int idx2 = 0; idx2 < quadCubicTests[index].answerCount; ++idx2) {
                 found |= quadCubicTests[index].answers[idx2].approximatelyEqual(xy1);
             }
+            if (!found) {
+                SkDebugf("%s [%d,%d] xy1=(%g,%g) != \n",
+                    __FUNCTION__, iIndex, pt, xy1.fX, xy1.fY);
+            }
             REPORTER_ASSERT(reporter, found);
         }
         reporter->bumpTestCount();
diff --git a/tests/PathOpsDQuadTest.cpp b/tests/PathOpsDQuadTest.cpp
index 5921b69..e6f1deb 100644
--- a/tests/PathOpsDQuadTest.cpp
+++ b/tests/PathOpsDQuadTest.cpp
@@ -5,7 +5,9 @@
  * found in the LICENSE file.
  */
 #include "PathOpsTestCommon.h"
+#include "SkPath.h"
 #include "SkPathOpsQuad.h"
+#include "SkRRect.h"
 #include "Test.h"
 
 static const SkDQuad tests[] = {
@@ -21,7 +23,7 @@
     {1,   0.8},
     {1.8, 1},
     {1.5, 1},
-    {0.5, 0.5},
+    {0.4999, 0.5},  // was 0.5, 0.5; points on the hull are considered outside
 };
 
 static const SkDPoint outPoint[]= {
@@ -51,5 +53,16 @@
     }
 }
 
+static void PathOpsRRectTest(skiatest::Reporter* reporter) {
+    SkPath path;
+    SkRRect rRect;
+    SkRect rect = {135, 143, 250, 177};
+    SkVector radii[4] = {{8, 8}, {8, 8}, {0, 0}, {0, 0}};
+    rRect.setRectRadii(rect, radii);
+    path.addRRect(rRect);
+}
+
 #include "TestClassDef.h"
 DEFINE_TESTCLASS_SHORT(PathOpsDQuadTest)
+
+DEFINE_TESTCLASS_SHORT(PathOpsRRectTest)
diff --git a/tests/PathOpsDTriangleTest.cpp b/tests/PathOpsDTriangleTest.cpp
index 35bfe06..6aec308 100644
--- a/tests/PathOpsDTriangleTest.cpp
+++ b/tests/PathOpsDTriangleTest.cpp
@@ -45,5 +45,31 @@
     }
 }
 
-#include "TestClassDef.h"
-DEFINE_TESTCLASS_SHORT(PathOpsTriangleUtilitiesTest)
+static const SkDTriangle oneOff[] = {

+    {{{271.03291625750461, 5.0402503630087025e-05}, {275.21652430019037, 3.6997300650817753},

+      {279.25839233398438, 7.7416000366210938}}},

+

+    {{{271.03291625750461, 5.0402503617874572e-05}, {275.21652430019037, 3.6997300650817877},

+      {279.25839233398438, 7.7416000366210938}}}

+};

+

+static const size_t oneOff_count = SK_ARRAY_COUNT(oneOff);

+

+static void PathOpsTriangleOneOffTest(skiatest::Reporter* reporter) {

+    for (size_t index = 0; index < oneOff_count; ++index) {

+        const SkDTriangle& triangle = oneOff[index];

+        SkASSERT(ValidTriangle(triangle));

+        for (int inner = 0; inner < 3; ++inner) {

+            bool result = triangle.contains(triangle.fPts[inner]);

+            if (result) {

+                SkDebugf("%s [%d][%d] point on triangle is not in\n", __FUNCTION__, index, inner);

+                REPORTER_ASSERT(reporter, 0);

+            }

+        }

+    }

+}

+

+#include "TestClassDef.h"

+DEFINE_TESTCLASS_SHORT(PathOpsTriangleUtilitiesTest)

+

+DEFINE_TESTCLASS_SHORT(PathOpsTriangleOneOffTest)

diff --git a/tests/PathOpsExtendedTest.cpp b/tests/PathOpsExtendedTest.cpp
index 28830ed..ca6f865 100644
--- a/tests/PathOpsExtendedTest.cpp
+++ b/tests/PathOpsExtendedTest.cpp
@@ -12,6 +12,7 @@
 #include "SkForceLinking.h"
 #include "SkMatrix.h"
 #include "SkPaint.h"
+#include "SkRTConf.h"
 #include "SkStream.h"
 #include "SkThreadPool.h"
 
@@ -634,6 +635,10 @@
 SK_DECLARE_STATIC_MUTEX(gMutex);
 
 int initializeTests(skiatest::Reporter* reporter, const char* test) {
+#if 0  // doesn't work yet
+    SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true);
+    SK_CONF_SET("images.png.suppressDecoderWarnings", true);
+#endif
 #ifdef SK_DEBUG
     SkPathOpsDebug::gMaxWindSum = 4;
     SkPathOpsDebug::gMaxWindValue = 4;
diff --git a/tests/PathOpsOpTest.cpp b/tests/PathOpsOpTest.cpp
index 08ae1b9..d192e0b 100644
--- a/tests/PathOpsOpTest.cpp
+++ b/tests/PathOpsOpTest.cpp
@@ -2718,8 +2718,6 @@
     testPathOp(reporter, path, pathB, kIntersect_PathOp);
 }
 
-#define SKPS_WORKING 0
-#if SKPS_WORKING
 static void skpcarpetplanet_ru22(skiatest::Reporter* reporter) {
     SkPath path;
     path.setFillType(SkPath::kEvenOdd_FillType);
@@ -2744,6 +2742,8 @@
     testPathOp(reporter, path, pathB, kIntersect_PathOp);
 }
 
+#define SKPS_WORKING 0
+#if SKPS_WORKING
 static void skpcarrot_is24(skiatest::Reporter* reporter) {
     SkPath path;
     path.setFillType(SkPath::kEvenOdd_FillType);
@@ -3010,7 +3010,33 @@
     testPathOp(reporter, path, pathB, kDifference_PathOp);
 }
 
-static void (*firstTest)(skiatest::Reporter* ) = 0;
+static void cubicOp97x(skiatest::Reporter* reporter) {

+    SkPath path, pathB;

+    path.setFillType(SkPath::kEvenOdd_FillType);

+    path.moveTo(0, 2);

+    path.cubicTo(0, 6, 2, 1, 2, 1);

+    path.close();

+    pathB.setFillType(SkPath::kEvenOdd_FillType);

+    pathB.moveTo(1, 2);

+    pathB.cubicTo(1, 2, 2, 0, 6, 0);

+    pathB.close();

+    testPathOp(reporter, path, pathB, kXOR_PathOp);

+}
+
+static void cubicOp98x(skiatest::Reporter* reporter) {

+    SkPath path, pathB;

+    path.setFillType(SkPath::kEvenOdd_FillType);

+    path.moveTo(0, 3);

+    path.cubicTo(3, 6, 4, 1, 6, 3);

+    path.close();

+    pathB.setFillType(SkPath::kEvenOdd_FillType);

+    pathB.moveTo(1, 4);

+    pathB.cubicTo(3, 6, 3, 0, 6, 3);

+    pathB.close();

+    testPathOp(reporter, path, pathB, kXOR_PathOp);

+}
+
+static void (*firstTest)(skiatest::Reporter* ) = bufferOverflow;
 
 static struct TestDesc tests[] = {
 #if ISSUE_1435_WORKING
@@ -3018,11 +3044,13 @@
 #endif
 #if SKPS_WORKING
     TEST(skpcarrot_is24),
-    TEST(skpcarpetplanet_ru22),  // cubic/cubic intersect detects unwanted coincidence
 #endif
 #if ISSUE_1417_WORKING_ON_LINUX_32
     TEST(issue1417),
 #endif
+    TEST(cubicOp98x),
+    TEST(cubicOp97x),
+    TEST(skpcarpetplanet_ru22),  // cubic/cubic intersect detects unwanted coincidence
     TEST(cubicOp96d),
     TEST(cubicOp95u),
     TEST(skpadbox_lt15),
diff --git a/tests/PathOpsQuadIntersectionTest.cpp b/tests/PathOpsQuadIntersectionTest.cpp
index 07d2dac..900123b 100644
--- a/tests/PathOpsQuadIntersectionTest.cpp
+++ b/tests/PathOpsQuadIntersectionTest.cpp
@@ -53,6 +53,9 @@
 }
 
 static const SkDQuad testSet[] = {
+{{{164, -40}, {231.51681518554687, -40}, {279.25839233398438, 7.7416000366210938}}},

+{{{279.25839233398438, 7.7416000366210938}, {275.2164306640625, 3.6996400356292725}, {271.03286743164062, -5.3290705182007514e-015}}},

+
 {{{2.9999997378517067, 1.9737872594345709}, {2.9999997432230918, 1.9739647181863822}, {1.2414155459263587e-163, 5.2957833941332142e-315}}},
 {{{2.9999047485265304, 1.9739164225694723}, {3.0000947268526112, 1.9738379076623633}, {0.61149411077591886, 0.0028382324376270418}}},
 
diff --git a/tests/PathOpsQuadLineIntersectionTest.cpp b/tests/PathOpsQuadLineIntersectionTest.cpp
index a871417..4793a13 100644
--- a/tests/PathOpsQuadLineIntersectionTest.cpp
+++ b/tests/PathOpsQuadLineIntersectionTest.cpp
@@ -59,6 +59,9 @@
     SkDQuad quad;
     SkDLine line;
 } oneOffs[] = {
+    {{{{447.96701049804687, 894.4381103515625}, {448.007080078125, 894.4239501953125},

+       {448.0140380859375, 894.4215087890625}}},

+     {{{490.43548583984375, 879.40740966796875}, {405.59262084960937, 909.435546875}}}},
     {{{{142.589081, 102.283646}, {149.821579, 100}, {158, 100}}},
         {{{90, 230}, {160, 60}}}},
     {{{{1101, 10}, {1101, 8.3431453704833984}, {1099.828857421875, 7.1711997985839844}}},
@@ -94,7 +97,7 @@
     }
 }
 
-static void PathOpsQuadLineIntersectionTestOne(skiatest::Reporter* reporter) {
+static void PathOpsQuadLineIntersectionOneOffTest(skiatest::Reporter* reporter) {
     testOneOffs(reporter);
 }
 
@@ -148,4 +151,4 @@
 #include "TestClassDef.h"
 DEFINE_TESTCLASS_SHORT(PathOpsQuadLineIntersectionTest)
 
-DEFINE_TESTCLASS_SHORT(PathOpsQuadLineIntersectionTestOne)
+DEFINE_TESTCLASS_SHORT(PathOpsQuadLineIntersectionOneOffTest)
diff --git a/tests/PathOpsSkpClipTest.cpp b/tests/PathOpsSkpClipTest.cpp
index d2fa988..7905faa 100755
--- a/tests/PathOpsSkpClipTest.cpp
+++ b/tests/PathOpsSkpClipTest.cpp
@@ -1,207 +1,573 @@
-#include "PathOpsExtendedTest.h"
-#include "PathOpsThreadedCommon.h"
+
 #include "SkBitmap.h"
-#include "SkColor.h"
-#include "SkDevice.h"
 #include "SkCanvas.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkDevice.h"
+#include "SkGraphics.h"
 #include "SkImageDecoder.h"
 #include "SkImageEncoder.h"
-#include "SkStream.h"
 #include "SkOSFile.h"
+#include "SkPathOpsDebug.h"
 #include "SkPicture.h"
+#include "SkRTConf.h"
+#include "SkStream.h"
 #include "SkString.h"
+#include "SkTArray.h"
+#include "SkTDArray.h"
+#include "SkThreadPool.h"
+#include "SkTime.h"
+#include "Test.h"
 
 #ifdef SK_BUILD_FOR_WIN
     #define PATH_SLASH "\\"
-    #define IN_DIR "D:" PATH_SLASH "skp"
-    #define OUT_DIR "D:" PATH_SLASH
+    #define IN_DIR "D:\\9-30-13\\"
+    #define OUT_DIR "D:\\opSkpClip\\1\\"
 #else
     #define PATH_SLASH "/"
-    #if 1
-        #define IN_DIR "/usr/local/google/home/caryclark/new10k" PATH_SLASH
-        #define OUT_DIR "/usr/local/google/home/caryclark/out10k" PATH_SLASH
+    #ifdef SK_BUILD_FOR_MAC
+        #define IN_DIR "/Volumes/tera/9-30-13/skp"
+        #define OUT_DIR "/Volumes/tera/out/9-30-13/1/"
     #else
-        #define IN_DIR "/usr/local/google/home/caryclark/6-18-13" PATH_SLASH
-        #define OUT_DIR "/usr/local/google/home/caryclark" PATH_SLASH
+        #define IN_DIR "/usr/local/google/home/caryclark/skps/9-30-13/skp"
+        #define OUT_DIR "/mnt/skia/opSkpClip/1/"
     #endif
 #endif
 
-static const char pictDir[] = IN_DIR ;
-static const char outSkpClipDir[] = OUT_DIR "skpClip";
-static const char outOldClipDir[] = OUT_DIR "oldClip";
+const struct {
+    int directory;
+    const char* filename;
+} skipOverSept[] = {
+    {9, "http___www_symptome_ch_.skp"}, // triangle clip with corner at x.999
+    {11, "http___www_menly_fr_.skp"},
+    {12, "http___www_banrasdr_com_.skp"},
+};
 
-static SkString make_filepath(const char* dir, const SkString& name) {
-    SkString path(dir);
-    size_t len = strlen(dir);
-    if (len > 0 && dir[len - 1] != PATH_SLASH[0]) {
-        path.append(PATH_SLASH);
+size_t skipOverSeptCount = sizeof(skipOverSept) / sizeof(skipOverSept[0]);
+
+enum TestStep {
+    kCompareBits,
+    kEncodeFiles,
+};
+
+enum {
+    kMaxLength = 128,
+    kMaxFiles = 128,
+    kSmallLimit = 1000,
+};
+
+struct TestResult {
+    void init(int dirNo) {
+        fDirNo = dirNo;
+        sk_bzero(fFilename, sizeof(fFilename));
+        fTestStep = kCompareBits;
+        fScaleOversized = true;
     }
+
+    SkString status() {
+        SkString outStr;
+        outStr.printf("%s %d %d\n", fFilename, fPixelError, fTime);
+        return outStr;
+    }
+
+    static void Test(int dirNo, const char* filename, TestStep testStep) {
+        TestResult test;
+        test.init(dirNo);
+        test.fTestStep = testStep;
+        strcpy(test.fFilename, filename);
+        test.testOne();
+    }
+
+    void test(int dirNo, const SkString& filename) {
+        init(dirNo);
+        strcpy(fFilename, filename.c_str());
+        testOne();
+    }
+
+    void testOne();
+
+    char fFilename[kMaxLength];
+    TestStep fTestStep;
+    int fDirNo;
+    int fPixelError;
+    int fTime;
+    bool fScaleOversized;
+};
+
+struct TestState {
+    void init(int dirNo, skiatest::Reporter* reporter) {
+        fReporter = reporter;
+        fResult.init(dirNo);
+        fFoundCount = 0;
+        TestState::fSmallCount = 0;
+        fSmallestError = 0;
+        sk_bzero(fFilesFound, sizeof(fFilesFound));
+        sk_bzero(fDirsFound, sizeof(fDirsFound));
+        sk_bzero(fError, sizeof(fError));
+    }
+
+    static bool bumpSmallCount() {
+        sk_atomic_inc(&fSmallCount);
+        return fSmallCount > kSmallLimit;
+    }
+
+    static void clearSmallCount() {
+        if (fSmallCount < kSmallLimit) {
+            fSmallCount = 0;
+        }
+    }
+
+    char fFilesFound[kMaxFiles][kMaxLength];
+    int fDirsFound[kMaxFiles];
+    int fError[kMaxFiles];
+    int fFoundCount;
+    static int fSmallCount;
+    int fSmallestError;
+    skiatest::Reporter* fReporter;
+    TestResult fResult;
+};
+
+int TestState::fSmallCount;
+
+struct TestRunner {
+    TestRunner(skiatest::Reporter* reporter, int threadCount)
+        : fNumThreads(threadCount)
+        , fReporter(reporter) {
+    }
+
+    ~TestRunner();
+    void render();
+    int fNumThreads;
+    SkTDArray<class TestRunnable*> fRunnables;
+    skiatest::Reporter* fReporter;
+};
+
+class TestRunnable : public SkRunnable {
+public:
+    TestRunnable(void (*testFun)(TestState*), int dirNo, TestRunner* runner) {
+        fState.init(dirNo, runner->fReporter);
+        fTestFun = testFun;
+    }
+
+    virtual void run() SK_OVERRIDE {
+        SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024);
+        (*fTestFun)(&fState);
+    }
+
+    TestState fState;
+    void (*fTestFun)(TestState*);
+};
+
+TestRunner::~TestRunner() {
+    for (int index = 0; index < fRunnables.count(); index++) {
+        SkDELETE(fRunnables[index]);
+    }
+}
+
+void TestRunner::render() {
+    SkThreadPool pool(fNumThreads);
+    for (int index = 0; index < fRunnables.count(); ++ index) {
+        pool.add(fRunnables[index]);
+    }
+}
+
+////////////////////////////////////////////////
+
+static const char outOpDir[] = OUT_DIR "opClip";
+static const char outOldDir[] = OUT_DIR "oldClip";
+static const char outSkpDir[] = OUT_DIR "skpTest";
+static const char outDiffDir[] = OUT_DIR "outTest";
+static const char outStatusDir[] = OUT_DIR "statusTest";
+
+static SkString make_filepath(int dirNo, const char* dir, const char* name) {
+    SkString path(dir);
+    if (dirNo) {
+        path.appendf("%d", dirNo);
+    }
+    path.append(PATH_SLASH);
     path.append(name);
     return path;
 }
 
-static SkString make_png_name(const SkString& filename) {
+static SkString make_in_dir_name(int dirNo) {
+    SkString dirName(IN_DIR);
+    dirName.appendf("%d", dirNo);
+    if (!sk_exists(dirName.c_str())) {
+        SkDebugf("could not read dir %s\n", dirName.c_str());
+        return SkString();
+    }
+    return dirName;
+}
+
+static bool make_one_out_dir(const char* outDirStr) {
+    SkString outDir = make_filepath(0, outDirStr, "");
+    if (!sk_exists(outDir.c_str())) {
+        if (!sk_mkdir(outDir.c_str())) {
+            SkDebugf("could not create dir %s\n", outDir.c_str());
+            return false;
+        }
+    }
+    return true;
+}
+
+static bool make_out_dirs() {
+    SkString outDir = make_filepath(0, OUT_DIR, "");
+    if (!sk_exists(outDir.c_str())) {
+        if (!sk_mkdir(outDir.c_str())) {
+            SkDebugf("could not create dir %s\n", outDir.c_str());
+            return false;
+        }
+    }
+    return make_one_out_dir(outOldDir)
+            && make_one_out_dir(outOpDir)
+            && make_one_out_dir(outSkpDir)
+            && make_one_out_dir(outDiffDir)
+            && make_one_out_dir(outStatusDir);
+}
+
+static SkString make_png_name(const char* filename) {
     SkString pngName = SkString(filename);
     pngName.remove(pngName.size() - 3, 3);
     pngName.append("png");
     return pngName;
 }
 
-static void testOne(const SkString& filename) {
-    if (filename == SkString("http___migracioncolombia_gov_co.skp")
-            || filename == SkString("http___miuki_info.skp")
-    ) {
-        return;
+static int similarBits(const SkBitmap& gr, const SkBitmap& sk) {
+    const int kRowCount = 3;
+    const int kThreshold = 3;
+    int width = SkTMin(gr.width(), sk.width());
+    if (width < kRowCount) {
+        return true;
     }
-#if DEBUG_SHOW_TEST_NAME
-    SkString testName(filename);
-    const char http[] = "http";
-    if (testName.startsWith(http)) {
-        testName.remove(0, sizeof(http) - 1);
+    int height = SkTMin(gr.height(), sk.height());
+    if (height < kRowCount) {
+        return true;
     }
-    while (testName.startsWith("_")) {
-        testName.remove(0, 1);
+    int errorTotal = 0;
+    SkTArray<int, true> errorRows;
+    errorRows.push_back_n(width * kRowCount);
+    SkAutoLockPixels autoGr(gr);
+    SkAutoLockPixels autoSk(sk);
+    for (int y = 0; y < height; ++y) {
+        SkPMColor* grRow = gr.getAddr32(0, y);
+        SkPMColor* skRow = sk.getAddr32(0, y);
+        int* base = &errorRows[0];
+        int* cOut = &errorRows[y % kRowCount];
+        for (int x = 0; x < width; ++x) {
+            SkPMColor grColor = grRow[x];
+            SkPMColor skColor = skRow[x];
+            int dr = SkGetPackedR32(grColor) - SkGetPackedR32(skColor);
+            int dg = SkGetPackedG32(grColor) - SkGetPackedG32(skColor);
+            int db = SkGetPackedB32(grColor) - SkGetPackedB32(skColor);
+            int error = cOut[x] = SkTMax(SkAbs32(dr), SkTMax(SkAbs32(dg), SkAbs32(db)));
+            if (error < kThreshold || x < 2) {
+                continue;
+            }
+            if (base[x - 2] < kThreshold
+                    || base[width + x - 2] < kThreshold
+                    || base[width * 2 + x - 2] < kThreshold
+                    || base[x - 1] < kThreshold
+                    || base[width + x - 1] < kThreshold
+                    || base[width * 2 + x - 1] < kThreshold
+                    || base[x] < kThreshold
+                    || base[width + x] < kThreshold
+                    || base[width * 2 + x] < kThreshold) {
+                continue;
+            }
+            errorTotal += error;
+        }
     }
-    const char dotSkp[] = ".skp";
-    if (testName.endsWith(dotSkp)) {
-        size_t len = testName.size();
-        testName.remove(len - (sizeof(dotSkp) - 1), sizeof(dotSkp) - 1);
-    }
-    testName.prepend("skp");
-    testName.append("1");
-    strncpy(DEBUG_FILENAME_STRING, testName.c_str(), DEBUG_FILENAME_STRING_LENGTH);
-#endif
-    SkString path = make_filepath(pictDir, filename);
-    SkFILEStream stream(path.c_str());
-    if (!stream.isValid()) {
-        return;
-    }
-    SkPicture* pic = SkPicture::CreateFromStream(&stream, &SkImageDecoder::DecodeMemory);
-    if (!pic) {
-        SkDebugf("unable to decode %s\n", filename.c_str());
-        return;
-    }
-    int width = pic->width();
-    int height = pic->height();
+    return errorTotal;
+}
 
-    SkBitmap bitmap;
-    int scale = 1;
-    do {
-        bitmap.setConfig(SkBitmap::kARGB_8888_Config, (width + scale - 1) / scale,
-            (height + scale - 1) / scale);
-        bool success = bitmap.allocPixels();
-        bitmap.eraseColor(SK_ColorWHITE);
-        if (success) {
-            break;
+static bool addError(TestState* data, const TestResult& testResult) {
+    bool foundSmaller = false;
+    int dCount = data->fFoundCount;
+    int pixelError = testResult.fPixelError;
+    if (data->fFoundCount < kMaxFiles) {
+        data->fError[dCount] = pixelError;
+        strcpy(data->fFilesFound[dCount], testResult.fFilename);
+        data->fDirsFound[dCount] = testResult.fDirNo;
+        ++data->fFoundCount;
+    } else if (pixelError > data->fSmallestError) {
+        int smallest = SK_MaxS32;
+        int smallestIndex = 0;
+        for (int index = 0; index < kMaxFiles; ++index) {
+            if (smallest > data->fError[index]) {
+                smallest = data->fError[index];
+                smallestIndex = index;
+            }
         }
-        SkDebugf("-%d-", scale);
-    } while ((scale *= 2) < 32);
-    if (scale >= 32) {
-        SkDebugf("unable to allocate bitmap for %s (w=%d h=%d)\n", filename.c_str(),
-                width, height);
-        return;
+        data->fError[smallestIndex] = pixelError;
+        strcpy(data->fFilesFound[smallestIndex], testResult.fFilename);
+        data->fDirsFound[smallestIndex] = testResult.fDirNo;
+        data->fSmallestError = SK_MaxS32;
+        for (int index = 0; index < kMaxFiles; ++index) {
+            if (data->fSmallestError > data->fError[index]) {
+                data->fSmallestError = data->fError[index];
+            }
+        }
+        SkDebugf("*%d*", data->fSmallestError);
+        foundSmaller = true;
     }
-    SkCanvas canvas(bitmap);
-    canvas.scale(1.0f / scale, 1.0f / scale);
-    SkString pngName = make_png_name(filename);
-    for (int i = 0; i < 2; ++i) {
-        bool useOp = i ? true : false;
-        canvas.setAllowSimplifyClip(useOp);
-        pic->draw(&canvas);
-        SkString outFile = make_filepath(useOp ? outSkpClipDir : outOldClipDir, pngName);
-        if (!SkImageEncoder::EncodeFile(outFile.c_str(), bitmap, SkImageEncoder::kPNG_Type,
-                100)) {
-            SkDebugf("unable to encode %s (width=%d height=%d)\n", pngName.c_str(),
-                     bitmap.width(), bitmap.height());
+    return foundSmaller;
+}
+
+
+
+static SkMSec timePict(SkPicture* pic, SkCanvas* canvas) {
+    canvas->save();
+    int pWidth = pic->width();
+    int pHeight = pic->height();
+    const int maxDimension = 1000;
+    const int slices = 3;
+    int xInterval = SkTMax(pWidth - maxDimension, 0) / (slices - 1);
+    int yInterval = SkTMax(pHeight - maxDimension, 0) / (slices - 1);
+    SkRect rect = {0, 0, SkIntToScalar(SkTMin(maxDimension, pWidth)), 
+            SkIntToScalar(SkTMin(maxDimension, pHeight))};
+    canvas->clipRect(rect);
+    SkMSec start = SkTime::GetMSecs();
+    for (int x = 0; x < slices; ++x) {
+        for (int y = 0; y < slices; ++y) {
+            pic->draw(canvas);
+            canvas->translate(0, SkIntToScalar(yInterval));
+        }
+        canvas->translate(SkIntToScalar(xInterval), SkIntToScalar(-yInterval * slices));
+    }
+    SkMSec end = SkTime::GetMSecs();
+    canvas->restore();
+    return end - start;
+}
+
+static void drawPict(SkPicture* pic, SkCanvas* canvas, int scale) {
+    canvas->clear(SK_ColorWHITE);
+    if (scale != 1) {
+        canvas->save();
+        canvas->scale(1.0f / scale, 1.0f / scale);
+    }
+    pic->draw(canvas);
+    if (scale != 1) {
+        canvas->restore();
+    }
+}
+
+static void writePict(const SkBitmap& bitmap, const char* outDir, const char* pngName) {
+    SkString outFile = make_filepath(0, outDir, pngName);
+    if (!SkImageEncoder::EncodeFile(outFile.c_str(), bitmap,
+            SkImageEncoder::kPNG_Type, 100)) {
+        SkDebugf("unable to encode gr %s (width=%d height=%d)\n", pngName,
+                    bitmap.width(), bitmap.height());
+    }
+}
+
+void TestResult::testOne() {
+    SkPicture* pic = NULL;
+    {
+    #if DEBUG_SHOW_TEST_NAME
+        if (fTestStep == kCompareBits) {
+            SkString testName(fFilename);
+            const char http[] = "http";
+            if (testName.startsWith(http)) {
+                testName.remove(0, sizeof(http) - 1);
+            }
+            while (testName.startsWith("_")) {
+                testName.remove(0, 1);
+            }
+            const char dotSkp[] = ".skp";
+            if (testName.endsWith(dotSkp)) {
+                size_t len = testName.size();
+                testName.remove(len - (sizeof(dotSkp) - 1), sizeof(dotSkp) - 1);
+            }
+            testName.prepend("skp");
+            testName.append("1");
+            strncpy(DEBUG_FILENAME_STRING, testName.c_str(), DEBUG_FILENAME_STRING_LENGTH);
+        } else if (fTestStep == kEncodeFiles) {
+            strncpy(DEBUG_FILENAME_STRING, "", DEBUG_FILENAME_STRING_LENGTH);
+        }
+    #endif
+        SkString path = make_filepath(fDirNo, IN_DIR, fFilename);
+        SkFILEStream stream(path.c_str());
+        if (!stream.isValid()) {
+            SkDebugf("invalid stream %s\n", path.c_str());
+            goto finish;
+        }
+        SkPicture* pic = SkPicture::CreateFromStream(&stream, &SkImageDecoder::DecodeMemory);
+        if (!pic) {
+            SkDebugf("unable to decode %s\n", fFilename);
+            goto finish;
+        }
+        int width = pic->width();
+        int height = pic->height();
+        SkBitmap oldBitmap, opBitmap;
+        int scale = 1;
+        do {
+            int dimX = (width + scale - 1) / scale;
+            int dimY = (height + scale - 1) / scale;
+            oldBitmap.setConfig(SkBitmap::kARGB_8888_Config, dimX, dimY);
+            opBitmap.setConfig(SkBitmap::kARGB_8888_Config, dimX, dimY);
+            bool success = oldBitmap.allocPixels() && opBitmap.allocPixels();
+            if (success) {
+                break;
+            }
+            SkDebugf("-%d-", scale);
+        } while ((scale *= 2) < 256);
+        if (scale >= 256) {
+            SkDebugf("unable to allocate bitmap for %s (w=%d h=%d)\n", fFilename,
+                    width, height);
+            return;
+        }
+        oldBitmap.eraseColor(SK_ColorWHITE);
+        SkCanvas oldCanvas(oldBitmap);
+        oldCanvas.setAllowSimplifyClip(false);
+        opBitmap.eraseColor(SK_ColorWHITE);
+        SkCanvas opCanvas(opBitmap);
+        opCanvas.setAllowSimplifyClip(true);
+        drawPict(pic, &oldCanvas, fScaleOversized ? scale : 1);
+        drawPict(pic, &opCanvas, fScaleOversized ? scale : 1);
+        if (fTestStep == kCompareBits) {
+            fPixelError = similarBits(oldBitmap, opBitmap);
+            int oldTime = timePict(pic, &oldCanvas);
+            int opTime = timePict(pic, &opCanvas);
+            fTime = oldTime - opTime;
+        } else if (fTestStep == kEncodeFiles) {
+            SkString pngStr = make_png_name(fFilename);
+            const char* pngName = pngStr.c_str();
+            writePict(oldBitmap, outOldDir, pngName);
+            writePict(opBitmap, outOpDir, pngName);
         }
     }
+finish:
     SkDELETE(pic);
 }
 
-const char* tryFixed[] = {
-    0
+static SkString makeStatusString(int dirNo) {
+    SkString statName;
+    statName.printf("stats%d.txt", dirNo);
+    SkString statusFile = make_filepath(0, outStatusDir, statName.c_str());
+    return statusFile;
+}
+
+class PreParser {
+public:
+    PreParser(int dirNo)
+        : fDirNo(dirNo)
+        , fIndex(0) {
+        SkString statusPath = makeStatusString(dirNo);
+        if (!sk_exists(statusPath.c_str())) {
+            return;
+        }
+        SkFILEStream reader;
+        reader.setPath(statusPath.c_str());
+        while (fetch(reader, &fResults.push_back()))
+            ;
+        fResults.pop_back();
+    }
+
+    bool fetch(SkFILEStream& reader, TestResult* result) {
+        char c;
+        int i = 0;
+        result->init(fDirNo);
+        result->fPixelError = 0;
+        result->fTime = 0;
+        do {
+            bool readOne = reader.read(&c, 1) != 0;
+            if (!readOne) {
+                SkASSERT(i == 0);
+                return false;
+            }
+            if (c == ' ') {
+                result->fFilename[i++] = '\0';
+                break;
+            }
+            result->fFilename[i++] = c;
+            SkASSERT(i < kMaxLength);
+        } while (true);
+        do {
+            SkAssertResult(reader.read(&c, 1));
+            if (c == ' ') {
+                break;
+            }
+            SkASSERT(c >= '0' && c <= '9');
+            result->fPixelError = result->fPixelError * 10 + (c - '0');
+        } while (true);
+        bool minus = false;
+        do {
+            SkAssertResult(reader.read(&c, 1));
+            if (c == '\n') {
+                break;
+            }
+            if (c == '-') {
+                minus = true;
+                continue;
+            }
+            SkASSERT(c >= '0' && c <= '9');
+            result->fTime = result->fTime * 10 + (c - '0');
+        } while (true);
+        if (minus) {
+            result->fTime = -result->fTime;
+        }
+        return true;
+    }
+
+    bool match(const SkString& filename, SkFILEWStream* stream, TestResult* result) {
+        if (fIndex < fResults.count()) {
+            *result = fResults[fIndex++];
+            SkASSERT(filename.equals(result->fFilename));
+            SkString outStr(result->status());
+            stream->write(outStr.c_str(), outStr.size());
+            return true;
+        }
+        return false;
+    }
+
+private:
+    int fDirNo;
+    int fIndex;
+    SkTArray<TestResult, true> fResults;
 };
 
-size_t tryFixedCount = sizeof(tryFixed) / sizeof(tryFixed[0]);
-
-const char* skipOver[] = {
-    "http___carpetplanet_ru.skp",  // cubic/cubic intersect
-    "http___carrot_is.skp",  // bridgeOp()  SkASSERT(unsortable || !current->done());
-
-/*!*/"http___dotsrc_org.skp",  // asserts in png decode
-    "http___frauen_magazin_com.skp",  // bridgeOp()  SkASSERT(unsortable || !current->done());
-    "http___i_gino_com.skp",  // unexpected cubic/quad coincidence
-                            // {61, 857, 61, 789.06897, 116.068977, 734, 184, 734}
-                            // {184, 734, 133.051727, 734, 97.0258636, 770.025879}
-    "http___ilkoora_com.skp",  // assert wind sum != min32 from markDoneBinary / findNextOp #28k
-/*!*/"http___migracioncolombia_gov_co.skp",  // crashes on picture decode
-    "http___mm4everfriends_com.skp",  // bumpSpan/addTCoincident (from calcPartialCoincidentWinding)
-    "http___mtrk_uz.skp",  // checkEnds() assert #36.3k
-    "http___pchappy_com_au.skp",  // bridgeOp() assert unsortable || ! empty #37.2k
-    "http___sciality_com.skp",  // bridgeOp()  SkASSERT(unsortable || !current->done()); #32.4k
-/*!*/"http___sozialticker_com.skp",  // asserts in png decode
-    "http___sudoestenegocios_com.skp",  // assert fT < 1 in addTCoincident
-    "http___thesuburbanite_com.skp",  // bridgeOp()  SkASSERT(unsortable || !current->done());
-
-    "http___fluentin3months_com.skp", // calcCommonCoincidentWinding from calcPartialCoincidentWinding #38.3k
-    "http___teachersbadi_blogspot_in.skp",  // calcCommonCoincidentWinding from calcPartialCoincidentWinding #53.4k
-    "http___wsms_ru.skp",  // assert wind sum != min32 from markDoneBinary / findNextOp #49.5k
-    "http___voycer_de.skp",  // calcCommonCoincidentWinding from calcPartialCoincidentWinding #47k
-    "http___77hz_jp.skp",  // addTCancel from calcCoincidentWinding #47.1k
-
-    "http___hostloco_com.skp",  // t < 0  AddIntersectsT
-/*!*/"http___oggicronaca_it.skp",  // asserts in png decode
-    "http___sergeychunkevich_com.skp",  // t < 0  AddIntersectsT
-    "http___tracksflow_com.skp",  // assert otherEnd >= 0 from nextChase
-    "http___autobutler_dk.skp",  // t < 0  AddIntersectsT
-    "http___onlinecollege_org.skp",  // bridgeOp() assert unsortable || ! empty #100.1k
-    "http___national_com_au.skp",  // bridgeOp() assert unsortable || ! empty #110.2k
-/*!*/"http___anitadongre_com.skp",  // exceptionally large width and height
-    "http___rentacheat_com.skp",  // bridgeOp() assert unsortable || ! empty #110.8k
-/*!*/"http___gruesse_de.skp",  // asserts in png decode
-/*!*/"http___crn_in.png",  // width=1250047
-    "http___breakmystyle_com.skp",  // assert qPt == lPt in quad intersection
-    "http___naoxrane_ru.skp",  // assert t4+...t0 == 0 in quartic roots #128.3k
-    "http___tcmevents_org.skp",  // assert in addTCoincident (from calcPartialCoincidentWinding) #143.3k
-/*!*/"http___listbuildingcashsecrets_com.skp",  // asserts in png decode #152.7k
-/*!*/"http___skyscraperpage_com.skp",  // asserts in png decode #155.5k
-    "http___mlk_com.skp",  // bridgeOp() assert unsortable || ! empty #158.7k
-    "http___sd_graphic_net.skp",  // bridgeOp() assert unsortable || ! empty #163.3k
-    "http___kopepasah_com.skp",  // checkEnds() assert #188.2k
-/*!*/"http___darkreloaded_com.skp",  // asserts in png decode #188.4k
-    "http___redbullskatearcade_es.skp",  // bridgeOp() assert unsortable || ! empty #192.5k
-    "http___partainasdemo250_org.skp",  //  bridgeOp() assert unsortable || ! empty #200.2k
-
-// these failures are from the new 10k set
-    "http___www_freerepublic_com_.skp",  // assert in opangle <
-    "http___www_lavoixdunord_fr_.skp",  // bridgeOp() assert unsortable || ! empty
-    "http___www_booking_com_.skp",  // bridgeOp() assert unsortable || ! empty
-    "http___www_fj_p_com_.skp",  // markWinding assert from findChaseOp
-    "http___www_leadpages_net_.skp",  // assert in opangle <
-    "http___www_despegar_com_mx_.skp",  // bridgeOp() assert unsortable || ! empty
-};
-
-size_t skipOverCount = sizeof(skipOver) / sizeof(skipOver[0]);
-
-static void PathOpsSkpClipTest(skiatest::Reporter* reporter) {
-    SkOSFile::Iter iter(pictDir, "skp");
+static bool doOneDir(TestState* state) {
+    int dirNo = state->fResult.fDirNo;
+    skiatest::Reporter* reporter = state->fReporter;
+    SkString dirName = make_in_dir_name(dirNo);
+    SkASSERT(dirName.size());
+    SkOSFile::Iter iter(dirName.c_str(), "skp");
     SkString filename;
     int testCount = 0;
+    PreParser preParser(dirNo);
+    SkFILEWStream statusStream(makeStatusString(dirNo).c_str());
     while (iter.next(&filename)) {
-        SkString pngName = make_png_name(filename);
-        SkString oldPng = make_filepath(outOldClipDir, pngName);
-        SkString newPng = make_filepath(outSkpClipDir, pngName);
-        if (sk_exists(oldPng.c_str()) && sk_exists(newPng.c_str())) {
-            reporter->bumpTestCount();
-            continue;
-        }
-        for (size_t index = 0; index < skipOverCount; ++index) {
-            if (skipOver[index] && strcmp(filename.c_str(), skipOver[index]) == 0) {
-                reporter->bumpTestCount();
+        for (size_t index = 0; index < skipOverSeptCount; ++index) {
+            if (skipOverSept[index].directory == dirNo
+                    && strcmp(filename.c_str(), skipOverSept[index].filename) == 0) {
                 goto skipOver;
             }
         }
-        testOne(filename);
+        if (preParser.match(filename, &statusStream, &state->fResult)) {
+            addError(state, state->fResult);
+            ++testCount;
+            goto checkEarlyExit;
+        }
+        if (state->fSmallestError > 5000000) {
+            return false;
+        }
+        {
+            TestResult& result = state->fResult;
+            result.test(dirNo, filename);
+            SkString outStr(result.status());
+            statusStream.write(outStr.c_str(), outStr.size());
+            statusStream.flush();
+            if (1) {
+                SkDebugf("%s", outStr.c_str());
+            }
+            bool noMatch = addError(state, state->fResult);
+            if (noMatch) {
+                state->clearSmallCount();
+            } else if (state->bumpSmallCount()) {
+                return false;
+            }
+        }
+        ++testCount;
         if (reporter->verbose()) {
             SkDebugf(".");
             if (++testCount % 100 == 0) {
@@ -209,85 +575,112 @@
             }
         }
 skipOver:
-        reporter->bumpTestCount();
-    }
-}
-
-static void bumpCount(skiatest::Reporter* reporter, bool skipping) {
-    if (reporter->verbose()) {
-        static int threadTestCount;
-        if (!skipping) {
+        if (reporter->verbose()) {
+            static int threadTestCount;
             SkDebugf(".");
+            sk_atomic_inc(&threadTestCount);
+            if (threadTestCount % 100 == 0) {
+                SkDebugf("%d\n", threadTestCount);
+            }
         }
-        sk_atomic_inc(&threadTestCount);
-        if (!skipping && threadTestCount % 100 == 0) {
-            SkDebugf("%d\n", threadTestCount);
+checkEarlyExit:
+        if (1 && testCount == 20) {
+            return true;
         }
-        if (skipping && threadTestCount % 10000 == 0) {
-            SkDebugf("%d\n", threadTestCount);
+    }
+    return true;
+}
+
+static bool initTest() {
+#if !defined SK_BUILD_FOR_WIN && !defined SK_BUILD_FOR_MAC
+    SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true);
+    SK_CONF_SET("images.png.suppressDecoderWarnings", true);
+#endif
+    return make_out_dirs();
+}
+
+static void encodeFound(skiatest::Reporter* reporter, TestState& state) {
+    if (reporter->verbose()) {
+        for (int index = 0; index < state.fFoundCount; ++index) {
+            SkDebugf("%d %s %d\n", state.fDirsFound[index], state.fFilesFound[index],
+                     state.fError[index]);
         }
     }
+    for (int index = 0; index < state.fFoundCount; ++index) {
+        TestResult::Test(state.fDirsFound[index], state.fFilesFound[index], kEncodeFiles);
+        if (state.fReporter->verbose()) SkDebugf("+");
+    }
 }
 
-static void testSkpClipMain(PathOpsThreadState* data) {
-    SkString str(data->fSerialNo);
-    testOne(str);
-    bumpCount(data->fReporter, false);
-    data->fReporter->bumpTestCount();
+static void PathOpsSkpClipTest(skiatest::Reporter* reporter) {
+    if (!initTest()) {
+        return;
+    }
+    SkTArray<TestResult, true> errors;
+    TestState state;
+    state.init(0, reporter);
+    for (int dirNo = 1; dirNo <= 100; ++dirNo) {
+        if (reporter->verbose()) {
+            SkDebugf("dirNo=%d\n", dirNo);
+        }
+        state.fResult.fDirNo = dirNo;
+        if (!doOneDir(&state)) {
+            break;
+        }
+    }
+    encodeFound(reporter, state);
+}
+
+static void testSkpClipMain(TestState* data) {
+        (void) doOneDir(data);
 }
 
 static void PathOpsSkpClipThreadedTest(skiatest::Reporter* reporter) {
-    int threadCount = initializeTests(reporter, "skpClipThreadedTest");
-    PathOpsThreadedTestRunner testRunner(reporter, threadCount);
-    SkOSFile::Iter iter(pictDir, "skp");
-    SkString filename;
-    while (iter.next(&filename)) {
-        SkString pngName = make_png_name(filename);
-        SkString oldPng = make_filepath(outOldClipDir, pngName);
-        SkString newPng = make_filepath(outSkpClipDir, pngName);
-        if (sk_exists(oldPng.c_str()) && sk_exists(newPng.c_str())) {
-            bumpCount(reporter, true);
-            continue;
-        }
-        for (size_t index = 0; index < skipOverCount; ++index) {
-            if (skipOver[index] && strcmp(filename.c_str(), skipOver[index]) == 0) {
-                bumpCount(reporter, true);
-                goto skipOver;
-            }
-        }
-        *testRunner.fRunnables.append() = SkNEW_ARGS(PathOpsThreadedRunnable,
-                (&testSkpClipMain, filename.c_str(), &testRunner));
-skipOver:
-        ;
+    if (!initTest()) {
+        return;
+    }
+    int threadCount = reporter->allowThreaded() ? SkThreadPool::kThreadPerCore : 1;
+    TestRunner testRunner(reporter, threadCount);
+    for (int dirNo = 1; dirNo <= 100; ++dirNo) {
+        *testRunner.fRunnables.append() = SkNEW_ARGS(TestRunnable,
+                (&testSkpClipMain, dirNo, &testRunner));
     }
     testRunner.render();
-}
-
-static void PathOpsSkpClipFixedTest(skiatest::Reporter* reporter) {
-    for (size_t index = 0; index < tryFixedCount; ) {
-        SkString filename(tryFixed[index]);
-        testOne(filename);
-        ++index;
-        if (reporter->verbose()) {
-            SkDebugf(".");
-            if (index % 100 == 0) {
-                SkDebugf("\n");
-            }
+    TestState state;
+    state.init(0, reporter);
+    for (int dirNo = 1; dirNo <= 100; ++dirNo) {
+        TestState& testState = testRunner.fRunnables[dirNo - 1]->fState;
+        for (int inner = 0; inner < testState.fFoundCount; ++inner) {
+            TestResult& testResult = testState.fResult;
+            SkASSERT(testResult.fDirNo == dirNo);
+            testResult.fPixelError = testState.fError[inner];
+            strcpy(testResult.fFilename, testState.fFilesFound[inner]);
+            addError(&state, testResult);
         }
-        reporter->bumpTestCount();
     }
+    encodeFound(reporter, state);
 }
 
 static void PathOpsSkpClipOneOffTest(skiatest::Reporter* reporter) {
-    SkString filename("http___78_cn_.skp");
-    testOne(filename);
+    if (!initTest()) {
+        return;
+    }
+    const int testIndex = 43 - 41;
+    int dirNo = skipOverSept[testIndex].directory;
+    SkAssertResult(make_in_dir_name(dirNo).size());
+    SkString filename(skipOverSept[testIndex].filename);
+    TestResult state;
+    state.test(dirNo, filename);
+    if (reporter->verbose()) {
+        SkDebugf("%s", state.status().c_str());
+    }
+    state.fTestStep = kEncodeFiles;
+    state.testOne();
 }
 
 #include "TestClassDef.h"
 DEFINE_TESTCLASS_SHORT(PathOpsSkpClipTest)
 
-DEFINE_TESTCLASS_SHORT(PathOpsSkpClipFixedTest)
-
 DEFINE_TESTCLASS_SHORT(PathOpsSkpClipOneOffTest)
 
 DEFINE_TESTCLASS_SHORT(PathOpsSkpClipThreadedTest)
diff --git a/tests/PathOpsSkpTest.cpp b/tests/PathOpsSkpTest.cpp
index b0feff7..7eb0a54 100755
--- a/tests/PathOpsSkpTest.cpp
+++ b/tests/PathOpsSkpTest.cpp
@@ -508,8 +508,6 @@
     testPathOp(reporter, path, pathB, kIntersect_PathOp);
 }
 
-#define TRY_BROKEN_TESTS 0
-#if TRY_BROKEN_TESTS
 static void skpfrauen_magazin_com83(skiatest::Reporter* reporter) {
     SkPath path;
     path.setFillType(SkPath::kEvenOdd_FillType);
@@ -534,6 +532,8 @@
     testPathOp(reporter, path, pathB, kIntersect_PathOp);
 }
 
+#define TRY_BROKEN_TESTS 0
+#if TRY_BROKEN_TESTS
 static void skpi_gino_com16(skiatest::Reporter* reporter) {
     SkPath path;
     path.setFillType(SkPath::kEvenOdd_FillType);
@@ -614,6 +614,7 @@
     pathB.close();
     testPathOp(reporter, path, pathB, kIntersect_PathOp);
 }
+#endif
 
 static void skpsudoestenegocios_com186(skiatest::Reporter* reporter) {
     SkPath path;
@@ -665,18 +666,933 @@
     pathB.close();
     testPathOp(reporter, path, pathB, kIntersect_PathOp);
 }
+
+static void skphostloco_com11(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(6.66133815e-16f, 648);
+    path.lineTo(25.8522835f, 648);
+    path.quadTo(27.5087376f, 647.999634f, 28.6807098f, 646.82843f);
+    path.quadTo(29.8518829f, 645.656433f, 29.8522835f, 644);
+    path.lineTo(29.8522835f, 467);
+    path.quadTo(29.8518829f, 465.343536f, 28.6807098f, 464.17157f);
+    path.quadTo(27.5087376f, 463.000397f, 25.8522835f, 463);
+    path.lineTo(2.22044605e-16f, 463);
+    path.lineTo(6.66133815e-16f, 648);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(0, 463);
+    pathB.lineTo(30, 463);
+    pathB.lineTo(30, 648);
+    pathB.lineTo(0, 648);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpsergeychunkevich_com8(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(0, 926);
+    path.lineTo(0, 0);
+    path.lineTo(1265, 0);
+    path.lineTo(1265, 926);
+    path.lineTo(0, 926);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(37, 374);
+    pathB.lineTo(37, 535);
+    pathB.cubicTo(37, 536.65686f, 35.6568565f, 538, 34, 538);
+    pathB.lineTo(1.02866934e-14f, 538);
+    pathB.lineTo(6.12303177e-17f, 371);
+    pathB.lineTo(34, 371);
+    pathB.cubicTo(35.6568565f, 371, 37, 372.34314f, 37, 374);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skptracksflow_com9(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(16, 56);
+    path.lineTo(32, 56);
+    path.lineTo(32, 72);
+    path.lineTo(16, 72);
+    path.lineTo(16, 56);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kEvenOdd_FillType);
+    pathB.moveTo(31.65625f, 70.0555649f);
+    pathB.lineTo(31.65625f, 70.0554962f);
+    pathB.lineTo(26.9727192f, 65.3615341f);
+    pathB.cubicTo(27.6210003f, 64.4029694f, 28.0048752f, 63.2470932f, 28.0048752f, 62.0027809f);
+    pathB.cubicTo(28.0048752f, 58.6875305f, 25.3199062f, 56, 22.0046558f, 56);
+    pathB.cubicTo(18.6894073f, 56, 16.0031872f, 58.6875305f, 16.0031872f, 62.0027809f);
+    pathB.cubicTo(16.0031872f, 65.3180008f, 18.6913433f, 68.0055618f, 22.0066261f, 68.0055618f);
+    pathB.cubicTo(23.2509995f, 68.0055618f, 24.4072189f, 67.6187515f, 25.3657818f, 66.9704056f);
+    pathB.lineTo(30.0599365f, 71.65625f);
+    pathB.lineTo(30.0600014f, 71.65625f);
+    pathB.cubicTo(30.2668133f, 71.875f, 30.5524693f, 71.9992828f, 30.868f, 71.9992828f);
+    pathB.cubicTo(31.4994049f, 71.9992828f, 32.0014687f, 71.4909363f, 32.0014687f, 70.8595276f);
+    pathB.cubicTo(32.0015335f, 70.5439072f, 31.875f, 70.2623444f, 31.65625f, 70.0555649f);
+    pathB.close();
+    pathB.moveTo(18.0054054f, 62.0027809f);
+    pathB.cubicTo(18.0054054f, 59.7925949f, 19.7970943f, 58.0009079f, 22.0072823f, 58.0009079f);
+    pathB.cubicTo(24.2174377f, 58.0009079f, 26.0091248f, 59.7925949f, 26.0091248f, 62.0027809f);
+    pathB.cubicTo(26.0091248f, 64.2129364f, 24.2174377f, 66.0046234f, 22.0072803f, 66.0046234f);
+    pathB.cubicTo(19.7970943f, 66.0045929f, 18.0054054f, 64.2129059f, 18.0054054f, 62.0027809f);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpautobutler_dk29(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(0, 926);
+    path.lineTo(0, 0);
+    path.lineTo(1265, 0);
+    path.lineTo(1265, 926);
+    path.lineTo(0, 926);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(21, 162);
+    pathB.lineTo(21, 301);
+    pathB.lineTo(8.57224448e-15f, 301);
+    pathB.lineTo(6.12303177e-17f, 162);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skponlinecollege_org144(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(179, 407);
+    path.cubicTo(177.34314f, 407, 176, 408.34314f, 176, 410);
+    path.lineTo(176, 436);
+    path.cubicTo(176, 437.65686f, 177.34314f, 439, 179, 439);
+    path.lineTo(337.002289f, 439);
+    path.cubicTo(338.105835f, 438.998779f, 339, 438.103821f, 339, 437);
+    path.lineTo(339, 409);
+    path.cubicTo(339, 407.896362f, 338.10611f, 407.001526f, 337.002838f, 407);
+    path.lineTo(179, 407);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(179, 408);
+    pathB.lineTo(337, 408);
+    pathB.cubicTo(338.65686f, 408, 340, 408.895416f, 340, 410);
+    pathB.lineTo(340, 436);
+    pathB.cubicTo(340, 437.65686f, 338.65686f, 439, 337, 439);
+    pathB.lineTo(179, 439);
+    pathB.cubicTo(177.895432f, 439, 177, 437.65686f, 177, 436);
+    pathB.lineTo(177, 410);
+    pathB.cubicTo(177, 408.895416f, 177.895432f, 408, 179, 408);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpnational_com_au81(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(807, 817);
+    path.quadTo(806.585876f, 817.000122f, 806.292908f, 817.292908f);
+    path.quadTo(806.000122f, 817.585876f, 806, 818);
+    path.lineTo(806, 881);
+    path.lineTo(1111, 881);
+    path.lineTo(1111, 818);
+    path.quadTo(1110.99988f, 817.585876f, 1110.70715f, 817.292908f);
+    path.quadTo(1110.41406f, 817.000122f, 1110, 817);
+    path.lineTo(807, 817);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(807, 817);
+    pathB.lineTo(1110, 817);
+    pathB.cubicTo(1110.55225f, 817, 1111, 817.447693f, 1111, 818);
+    pathB.lineTo(1111, 880);
+    pathB.lineTo(806, 880);
+    pathB.lineTo(806, 818);
+    pathB.cubicTo(806, 817.447693f, 806.447693f, 817, 807, 817);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skprentacheat_com30(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(967, 263);
+    path.quadTo(966.585876f, 263.000092f, 966.292908f, 263.292908f);
+    path.quadTo(966.000122f, 263.585876f, 966, 264);
+    path.lineTo(966, 301);
+    path.lineTo(1214, 301);
+    path.lineTo(1214, 264);
+    path.quadTo(1213.99988f, 263.585876f, 1213.70715f, 263.292908f);
+    path.quadTo(1213.41406f, 263.000092f, 1213, 263);
+    path.lineTo(967, 263);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(967, 263);
+    pathB.lineTo(1213, 263);
+    pathB.cubicTo(1213.55225f, 263, 1214, 263.447723f, 1214, 264);
+    pathB.lineTo(1214, 300);
+    pathB.lineTo(966, 300);
+    pathB.lineTo(966, 264);
+    pathB.cubicTo(966, 263.447723f, 966.447693f, 263, 967, 263);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpbreakmystyle_com10(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(271.032867f, -5.32907052e-15f);
+    path.lineTo(56.9671326f, -5.16253706e-15f);
+    path.quadTo(52.7835083f, 3.69968891f, 48.7416f, 7.74160004f);
+    path.quadTo(1, 55.4831848f, 1, 123);
+    path.quadTo(1, 190.516815f, 48.7416f, 238.258392f);
+    path.quadTo(96.4831848f, 286, 164, 286);
+    path.quadTo(231.516815f, 286, 279.258392f, 238.258392f);
+    path.quadTo(327, 190.516815f, 327, 123);
+    path.quadTo(327, 55.4831848f, 279.258392f, 7.74160004f);
+    path.quadTo(275.216431f, 3.69964004f, 271.032867f, -5.32907052e-15f);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(327, 123);
+    pathB.quadTo(327, 190.516815f, 279.258392f, 238.258392f);
+    pathB.quadTo(231.516815f, 286, 164, 286);
+    pathB.quadTo(96.4831848f, 286, 48.7416f, 238.258392f);
+    pathB.quadTo(1, 190.516815f, 1, 123);
+    pathB.quadTo(1, 55.4831848f, 48.7416f, 7.74160004f);
+    pathB.quadTo(96.4831848f, -40, 164, -40);
+    pathB.quadTo(231.516815f, -40, 279.258392f, 7.74160004f);
+    pathB.quadTo(327, 55.4831848f, 327, 123);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpsd_graphic_net104(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(475.421448f, 836.985962f);
+    path.lineTo(461.280975f, 841.990662f);
+    path.cubicTo(466.80899f, 857.609802f, 458.62854f, 874.752991f, 443.009399f, 880.281006f);
+    path.cubicTo(435.199829f, 883.044983f, 427.009247f, 882.381897f, 420.080048f, 879.075378f);
+    path.lineTo(413.620056f, 892.613037f);
+    path.quadTo(430.419983f, 900.629761f, 447.96701f, 894.43811f);
+    path.quadTo(448.00708f, 894.42395f, 448.014038f, 894.421509f);
+    path.quadTo(448.043976f, 894.410889f, 448.061066f, 894.404846f);
+    path.quadTo(465.596313f, 888.179932f, 473.613037f, 871.379944f);
+    path.quadTo(477.351227f, 863.546143f, 478, 855.549866f);
+    path.lineTo(478, 848.804321f);
+    path.quadTo(477.528076f, 842.93811f, 475.421448f, 836.985962f);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(405.592621f, 909.435547f);
+    pathB.lineTo(390.578583f, 867.014099f);
+    pathB.lineTo(433, 852.000061f);
+    pathB.lineTo(490.435486f, 879.40741f);
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+#if TRY_BROKEN_TESTS
+/* this cubic/quad pair
+ c = 430,280 430,278.895416 473.876068,278 528,278
+ q = 430,280 430.009796,277.101196 458.703552,275.050262
+ only intersect at the shared point (430,280)
+ they sort backwards because the tangent from pt[0] to control pt[1]
+ c' = (0.00000000000000000, -1.1045837402343750)
+ q' = (0.0097961425781250000, -2.8988037109375000)

+ suggests that the quad is counterclockwise of the cubic, when the reverse is true

+ the angle code is fooled because the control pt[1] of both the quad and cubic

+ is far away from cubic cntl [2] and quad pt [2].

+ Maybe in angle setup, this instability can be detected to suppress sorting on the initial tangent

+ Or the error term can be passed to NearRay that is magnified by the distance from the next ctrl?

+ */
+static void skpnaoxrane_ru23(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(458.703552f, 275.050262f);
+    path.quadTo(487.41687f, 273.000702f, 528, 273);
+    path.lineTo(529, 273);
+    path.quadTo(530.242371f, 273.000305f, 531.121338f, 273.878693f);
+    path.quadTo(531.999695f, 274.75766f, 532, 276);
+    path.lineTo(532, 378);
+    path.quadTo(531.990173f, 380.898804f, 503.296448f, 382.949738f);
+    path.quadTo(474.58313f, 384.999298f, 434, 385);
+    path.lineTo(433, 385);
+    path.quadTo(431.75766f, 384.999695f, 430.878693f, 384.121307f);
+    path.quadTo(430.000305f, 383.24234f, 430, 382);
+    path.lineTo(430, 280);
+    path.quadTo(430.009796f, 277.101196f, 458.703552f, 275.050262f);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(528, 278);
+    pathB.lineTo(529, 278);
+    pathB.cubicTo(530.65686f, 278, 532, 278, 532, 278);
+    pathB.lineTo(532, 378);
+    pathB.cubicTo(532, 379.104584f, 488.123932f, 380, 434, 380);
+    pathB.lineTo(433, 380);
+    pathB.cubicTo(431.34314f, 380, 430, 380, 430, 380);
+    pathB.lineTo(430, 280);
+    pathB.cubicTo(430, 278.895416f, 473.876068f, 278, 528, 278);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+/* didn't investigate thoroughly, but looks to be missorting quad and cubic
+    {{468.507751,560.724426}, {467.275146,552.856262}, {465.84668,547.288391}}
+    {{463.779907,542.671143}, {464.829529,542.672974}, {466.946289,550.755676}, {468.507751,560.724426}}
+    decision maker is case 14 leftLessThanRight
+ */
+static void skptcmevents_org23(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(465.503998f, 546);
+    path.lineTo(347, 546);
+    path.lineTo(347, 632);
+    path.lineTo(469.104248f, 632);
+    path.quadTo(470.79007f, 627.638672f, 471.833496f, 621.036255f);
+    path.quadTo(474.902588f, 601.562866f, 470.591064f, 574.024353f);
+    path.lineTo(468.507751f, 560.724426f);
+    path.quadTo(467.275146f, 552.856262f, 465.84668f, 547.288391f);
+    path.quadTo(465.670349f, 546.601501f, 465.503998f, 546);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(363.052246f, 542.495361f);
+    pathB.lineTo(463.779907f, 542.671143f);
+    pathB.cubicTo(464.829529f, 542.672974f, 466.946289f, 550.755676f, 468.507751f, 560.724426f);
+    pathB.lineTo(470.591064f, 574.024353f);
+    pathB.cubicTo(476.26178f, 610.226624f, 471.498932f, 639.557922f, 459.953003f, 639.537781f);
+    pathB.lineTo(368.727936f, 639.378601f);
+    pathB.cubicTo(351.933868f, 639.349304f, 337.053741f, 631.244324f, 335.492249f, 621.275574f);
+    pathB.lineTo(325.968597f, 560.475708f);
+    pathB.cubicTo(324.407104f, 550.506958f, 341.01001f, 542.456909f, 363.052246f, 542.495361f);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpredbullskatearcade_es16(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(936.765625f, 458.965302f);
+    path.cubicTo(937.028442f, 453.863251f, 933.145813f, 449.864502f, 928.093445f, 450.033905f);
+    path.lineTo(661.882263f, 458.958862f);
+    path.lineTo(661.875366f, 458.959106f);
+    path.cubicTo(656.828369f, 459.13205f, 652.525085f, 463.399719f, 652.258545f, 468.496124f);
+    path.lineTo(652.258179f, 468.503662f);
+    path.lineTo(649.021729f, 531.322754f);
+    path.cubicTo(648.75885f, 536.424805f, 652.641479f, 540.423523f, 657.693848f, 540.25415f);
+    path.lineTo(923.905029f, 531.329163f);
+    path.cubicTo(928.955017f, 531.159851f, 933.262268f, 526.890442f, 933.528809f, 521.791565f);
+    path.lineTo(933.529175f, 521.784363f);
+    path.lineTo(936.765625f, 458.965302f);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(661.882263f, 458.958862f);
+    pathB.lineTo(928.093445f, 450.033905f);
+    pathB.cubicTo(929.103882f, 450, 929.709961f, 454.108612f, 929.447144f, 459.210663f);
+    pathB.lineTo(926.210693f, 522.029724f);
+    pathB.cubicTo(926.079224f, 524.58075f, 925.153442f, 526.676208f, 924.143066f, 526.710083f);
+    pathB.lineTo(657.931885f, 535.635071f);
+    pathB.cubicTo(652.879456f, 535.804443f, 648.890259f, 533.873779f, 649.021729f, 531.322754f);
+    pathB.lineTo(652.258179f, 468.503662f);
+    pathB.cubicTo(652.520996f, 463.401611f, 656.829834f, 459.128235f, 661.882263f, 458.958862f);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpfinanzasdigital_com9(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(156, 126);
+    path.quadTo(154.343552f, 126.000397f, 153.17157f, 127.17157f);
+    path.quadTo(152.000397f, 128.343552f, 152, 130);
+    path.lineTo(152, 174);
+    path.lineTo(1114, 174);
+    path.lineTo(1114, 130);
+    path.quadTo(1113.99963f, 128.343552f, 1112.82837f, 127.17157f);
+    path.quadTo(1111.65649f, 126.000397f, 1110, 126);
+    path.lineTo(156, 126);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(156, 126);
+    pathB.lineTo(1110, 126);
+    pathB.cubicTo(1111.65686f, 126, 1113, 127.790863f, 1113, 130);
+    pathB.lineTo(1113, 174);
+    pathB.lineTo(153, 174);
+    pathB.lineTo(153, 130);
+    pathB.cubicTo(153, 127.790863f, 154.34314f, 126, 156, 126);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
 #endif
 
+static void skppartainasdemo250_org56(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(182.000015f, 645);
+    path.lineTo(182, 640);
+    path.cubicTo(174.322327f, 640, 166.644669f, 637.071045f, 160.786804f, 631.213196f);
+    path.cubicTo(149.071075f, 619.497437f, 149.071075f, 600.502563f, 160.786804f, 588.786804f);
+    path.lineTo(157.251266f, 585.251221f);
+    path.quadTo(147, 595.502502f, 147.000015f, 610);
+    path.quadTo(147, 624.482605f, 157.230255f, 634.727722f);
+    path.quadTo(157.251251f, 634.748779f, 157.251282f, 634.748779f);
+    path.quadTo(157.282852f, 634.780334f, 157.272263f, 634.769775f);
+    path.quadTo(167.517334f, 645, 182.000015f, 645);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(182, 659.497498f);
+    pathB.lineTo(206.748749f, 634.748718f);
+    pathB.lineTo(182.000015f, 610);
+    pathB.lineTo(132.502533f, 610);
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpmlk_com326(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(154, 670);
+    path.cubicTo(151.238571f, 670, 149, 672.238586f, 149, 675);
+    path.lineTo(149, 710.001465f);
+    path.cubicTo(149.000809f, 712.209961f, 150.791367f, 714, 153, 714);
+    path.lineTo(189, 714);
+    path.cubicTo(191.209137f, 714, 193, 712.209167f, 193, 710);
+    path.lineTo(193, 675);
+    path.cubicTo(193, 672.238586f, 190.761429f, 670, 188, 670);
+    path.lineTo(154, 670);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(154, 671);
+    pathB.lineTo(188, 671);
+    pathB.cubicTo(190.761429f, 671, 193, 672.790833f, 193, 675);
+    pathB.lineTo(193, 710);
+    pathB.cubicTo(193, 712.761414f, 190.761429f, 715, 188, 715);
+    pathB.lineTo(154, 715);
+    pathB.cubicTo(151.238571f, 715, 149, 712.761414f, 149, 710);
+    pathB.lineTo(149, 675);
+    pathB.cubicTo(149, 672.790833f, 151.238571f, 671, 154, 671);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpcyclist_friends_gr52(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(50, 182);
+    path.lineTo(1215, 182);
+    path.lineTo(1215, 202);
+    path.quadTo(1214.99951f, 204.070572f, 1213.53552f, 205.535538f);
+    path.quadTo(1212.07056f, 206.999496f, 1210, 207);
+    path.lineTo(55, 207);
+    path.quadTo(52.9294319f, 206.999496f, 51.4644661f, 205.535538f);
+    path.quadTo(50.0004997f, 204.070572f, 50, 202);
+    path.lineTo(50, 182);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(50, 183);
+    pathB.lineTo(1215, 183);
+    pathB.lineTo(1215, 202);
+    pathB.cubicTo(1215, 204.761429f, 1212.76147f, 207, 1210, 207);
+    pathB.lineTo(55, 207);
+    pathB.cubicTo(52.238575f, 207, 50, 204.761429f, 50, 202);
+    pathB.lineTo(50, 183);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+/* cubic ends just above opp line */
+static void skpwww_fj_p_com_22(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(172, 201);
+    path.lineTo(172, 202);
+    path.lineTo(220, 202);
+    path.cubicTo(221.65686f, 202, 223, 200.65686f, 223, 199);
+    path.cubicTo(223, 200.104568f, 221.65686f, 201, 220, 201);
+    path.lineTo(172, 201);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(161, 202);
+    pathB.lineTo(161, 199);
+    pathB.lineTo(223, 199.000015f);
+    pathB.lineTo(223, 202);
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+#define TRY_SEPT_BROKEN_TESTS 0
+#if TRY_SEPT_BROKEN_TESTS
+// pair of lines are not quite coincident, so sorting line/cubic fails (i think)
+static void skpwww_lavoixdunord_fr_11(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(806, 57);
+    path.cubicTo(806, 55.3431473f, 807.34314f, 54, 809, 54);
+    path.lineTo(930, 54);
+    path.cubicTo(931.65686f, 54, 933, 55.3431473f, 933, 57);
+    path.lineTo(933, 91);
+    path.cubicTo(933, 92.6568527f, 931.65686f, 94, 930, 94);
+    path.lineTo(809, 94);
+    path.cubicTo(807.34314f, 94, 806, 92.6568527f, 806, 91);
+    path.lineTo(806, 57);
+    path.close();
+    path.moveTo(808, 58);
+    path.cubicTo(808, 56.8954315f, 808.895447f, 56, 810, 56);
+    path.lineTo(929, 56);
+    path.cubicTo(930.104553f, 56, 931, 56.8954315f, 931, 58);
+    path.lineTo(931, 90);
+    path.cubicTo(931, 91.1045685f, 930.104553f, 92, 929, 92);
+    path.lineTo(810, 92);
+    path.cubicTo(808.895447f, 92, 808, 91.1045685f, 808, 90);
+    path.lineTo(808, 58);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(806, 54);
+    pathB.lineTo(808, 56);
+    pathB.lineTo(935.02002f, 56.0200005f);
+    pathB.lineTo(933, 54);
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+// pair of curves have nearly the same initial tangent but are sorting by
+// that alone sorts them incorrectly. Need to detect that tangents are nearly
+// identical and not reliable by themselves
+static void skppptv_com_62(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(173, 5342);
+    path.quadTo(171.343536f, 5342.00049f, 170.17157f, 5343.17139f);
+    path.quadTo(169.000397f, 5344.34375f, 169, 5346);
+    path.lineTo(169, 5372);
+    path.lineTo(234, 5372);
+    path.lineTo(234, 5346);
+    path.quadTo(233.999603f, 5344.34375f, 232.82843f, 5343.17139f);
+    path.quadTo(231.656464f, 5342.00049f, 230, 5342);
+    path.lineTo(173, 5342);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(173, 5342);
+    pathB.lineTo(230, 5342);
+    pathB.cubicTo(231.65686f, 5342, 233, 5343.79102f, 233, 5346);
+    pathB.lineTo(233, 5372);
+    pathB.lineTo(169, 5372);
+    pathB.lineTo(169, 5346);
+    pathB.cubicTo(169, 5343.79102f, 170.790863f, 5342, 173, 5342);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+// nearly identical to lavoixdunord -- to not-quite-coincident lines
+static void skpwww_booking_com_68(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(90, 187);
+    path.cubicTo(90, 185.34314f, 91.3431473f, 184, 93, 184);
+    path.lineTo(588, 184);
+    path.cubicTo(589.65686f, 184, 591, 185.34314f, 591, 187);
+    path.lineTo(591, 218);
+    path.cubicTo(591, 219.65686f, 589.65686f, 221, 588, 221);
+    path.lineTo(93, 221);
+    path.cubicTo(91.3431473f, 221, 90, 219.65686f, 90, 218);
+    path.lineTo(90, 187);
+    path.close();
+    path.moveTo(92, 188);
+    path.cubicTo(92, 186.895432f, 92.8954315f, 186, 94, 186);
+    path.lineTo(587, 186);
+    path.cubicTo(588.104553f, 186, 589, 186.895432f, 589, 188);
+    path.lineTo(589, 217);
+    path.cubicTo(589, 218.104568f, 588.104553f, 219, 587, 219);
+    path.lineTo(94, 219);
+    path.cubicTo(92.8954315f, 219, 92, 218.104568f, 92, 217);
+    path.lineTo(92, 188);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(90, 184);
+    pathB.lineTo(92, 186);
+    pathB.lineTo(593.02002f, 186.020004f);
+    pathB.lineTo(591, 184);
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+// visually looks like lavoixdunord and www_booking_com
+static void skpwww_despegar_com_mx_272(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(635, 1788);
+    path.cubicTo(635, 1786.34314f, 636.34314f, 1785, 638, 1785);
+    path.lineTo(832, 1785);
+    path.cubicTo(833.65686f, 1785, 835, 1786.34314f, 835, 1788);
+    path.lineTo(835, 1812);
+    path.cubicTo(835, 1813.65686f, 833.65686f, 1815, 832, 1815);
+    path.lineTo(638, 1815);
+    path.cubicTo(636.34314f, 1815, 635, 1813.65686f, 635, 1812);
+    path.lineTo(635, 1788);
+    path.close();
+    path.moveTo(637, 1789);
+    path.cubicTo(637, 1787.89539f, 637.895447f, 1787, 639, 1787);
+    path.lineTo(831, 1787);
+    path.cubicTo(832.104553f, 1787, 833, 1787.89539f, 833, 1789);
+    path.lineTo(833, 1811);
+    path.cubicTo(833, 1812.10461f, 832.104553f, 1813, 831, 1813);
+    path.lineTo(639, 1813);
+    path.cubicTo(637.895447f, 1813, 637, 1812.10461f, 637, 1811);
+    path.lineTo(637, 1789);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(835, 1785);
+    pathB.lineTo(833, 1787);
+    pathB.lineTo(832.97998f, 1817.02002f);
+    pathB.lineTo(835, 1815);
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+#endif
+
+static void skpwww_joomla_org_23(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(320, 347);
+    path.cubicTo(320, 344.238586f, 322.238586f, 342, 325, 342);
+    path.lineTo(416, 342);
+    path.cubicTo(418.761414f, 342, 421, 344.238586f, 421, 347);
+    path.cubicTo(421, 344.790863f, 418.761414f, 343, 416, 343);
+    path.lineTo(325, 343);
+    path.cubicTo(322.238586f, 343, 320, 344.790863f, 320, 347);
+    path.close();
+    path.moveTo(320, 378);
+    path.cubicTo(320, 380.761414f, 322.238586f, 383, 325, 383);
+    path.lineTo(416, 383);
+    path.cubicTo(418.761414f, 383, 421, 380.761414f, 421, 378);
+    path.cubicTo(421, 380.209137f, 418.761414f, 382, 416, 382);
+    path.lineTo(325, 382);
+    path.cubicTo(322.238586f, 382, 320, 380.209137f, 320, 378);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(320, 383);
+    pathB.lineTo(320, 378);
+    pathB.lineTo(421, 378.000031f);
+    pathB.lineTo(421, 383);
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpwww_macrumors_com_131(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(136, 14089);
+    path.lineTo(136, 14056);
+    path.lineTo(778, 14056);
+    path.lineTo(778, 14089);
+    path.quadTo(777.999573f, 14090.6562f, 776.82843f, 14091.8281f);
+    path.quadTo(775.656433f, 14093, 774, 14093);
+    path.lineTo(140, 14093);
+    path.quadTo(138.343552f, 14093, 137.17157f, 14091.8281f);
+    path.quadTo(136.000397f, 14090.6562f, 136, 14089);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kInverseWinding_FillType);
+    pathB.moveTo(136, 14057);
+    pathB.lineTo(778, 14057);
+    pathB.lineTo(778, 14089);
+    pathB.cubicTo(778, 14091.209f, 776.209167f, 14093, 774, 14093);
+    pathB.lineTo(140, 14093);
+    pathB.cubicTo(137.790863f, 14093, 136, 14091.209f, 136, 14089);
+    pathB.lineTo(136, 14057);
+    pathB.close();
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpwww_leadpages_net_84(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(377.1716f, 5910.17139f);
+    path.cubicTo(376.447723f, 5910.89551f, 376, 5911.89551f, 376, 5913);
+    path.lineTo(376, 5972);
+    path.cubicTo(376, 5974.20898f, 377.790863f, 5976, 380, 5976);
+    path.cubicTo(378.34314f, 5976, 377, 5974.20898f, 377, 5972);
+    path.lineTo(377, 5913);
+    path.cubicTo(377, 5912.17139f, 377.335785f, 5911.42188f, 377.878693f, 5910.87891f);
+    path.lineTo(377.1716f, 5910.17139f);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(376, 5909);
+    pathB.lineTo(378.481873f, 5909);
+    pathB.lineTo(379.999878f, 5976);
+    pathB.lineTo(376, 5976);
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+static void skpwww_briian_com_34(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(843, 216);
+    path.cubicTo(843, 213.238571f, 845.238586f, 211, 848, 211);
+    path.lineTo(1191, 211);
+    path.cubicTo(1193.76147f, 211, 1196, 213.238571f, 1196, 216);
+    path.lineTo(1196, 779);
+    path.cubicTo(1196, 781.761414f, 1193.76147f, 784, 1191, 784);
+    path.lineTo(848, 784);
+    path.cubicTo(845.238586f, 784, 843, 781.761414f, 843, 779);
+    path.lineTo(843, 216);
+    path.close();
+    path.moveTo(844, 217);
+    path.cubicTo(844, 214.238571f, 846.238586f, 212, 849, 212);
+    path.lineTo(1190, 212);
+    path.cubicTo(1192.76147f, 212, 1195, 214.238571f, 1195, 217);
+    path.lineTo(1195, 778);
+    path.cubicTo(1195, 779.65686f, 1192.76147f, 781, 1190, 781);
+    path.lineTo(849, 781);
+    path.cubicTo(846.238586f, 781, 844, 779.65686f, 844, 778);
+    path.lineTo(844, 217);
+    path.close();
+    SkPath pathB;
+    pathB.setFillType(SkPath::kWinding_FillType);
+    pathB.moveTo(843, 784);
+    pathB.lineTo(843, 779);
+    pathB.lineTo(1196, 779.000061f);
+    pathB.lineTo(1196, 784);
+    testPathOp(reporter, path, pathB, kIntersect_PathOp);
+}
+
+

+static void skpwww_sciality_com_100(skiatest::Reporter* reporter) {

+    SkPath path;

+    path.setFillType(SkPath::kEvenOdd_FillType);

+    path.moveTo(162, 468);

+    path.cubicTo(159.790863f, 468, 158, 469.790863f, 158, 472);

+    path.lineTo(158, 528);

+    path.cubicTo(158, 530.209106f, 159.790863f, 532, 162, 532);

+    path.lineTo(275, 532);

+    path.cubicTo(277.209137f, 532, 279, 530.209106f, 279, 528);

+    path.lineTo(279, 472);

+    path.cubicTo(279, 469.790863f, 277.209137f, 468, 275, 468);

+    path.lineTo(162, 468);

+    path.close();

+    SkPath pathB;

+    pathB.setFillType(SkPath::kWinding_FillType);

+    pathB.moveTo(275, 468);

+    pathB.cubicTo(276.65686f, 468, 278, 469.34314f, 278, 471);

+    pathB.lineTo(278, 529);

+    pathB.cubicTo(278, 530.65686f, 276.65686f, 532, 275, 532);

+    pathB.lineTo(161, 532);

+    pathB.cubicTo(159.34314f, 532, 158, 530.65686f, 158, 529);

+    pathB.lineTo(158, 471);

+    pathB.cubicTo(158, 469.34314f, 159.34314f, 468, 161, 468);

+    pathB.lineTo(275, 468);

+    pathB.close();

+    testPathOp(reporter, path, pathB, kIntersect_PathOp);

+}

+

+#if TRY_SEPT_BROKEN_TESTS
+static void skpwww_sciality_com_101(skiatest::Reporter* reporter) {

+    SkPath path;

+    path.setFillType(SkPath::kEvenOdd_FillType);

+    path.moveTo(162, 468);

+    path.cubicTo(159.790863f, 468, 158, 469.790863f, 158, 472);

+    path.lineTo(158, 528);

+    path.cubicTo(158, 530.209106f, 159.790863f, 532, 162, 532);

+    path.lineTo(275.009186f, 532);

+    path.cubicTo(276.661774f, 531.994995f, 278, 530.653748f, 278, 529);

+    path.lineTo(278, 471);

+    path.cubicTo(278, 469.346375f, 276.662079f, 468.005249f, 275.009705f, 468);

+    path.lineTo(162, 468);

+    path.close();

+    SkPath pathB;

+    pathB.setFillType(SkPath::kInverseWinding_FillType);

+    pathB.moveTo(161, 469);

+    pathB.lineTo(275, 469);

+    pathB.cubicTo(276.65686f, 469, 278, 469.895416f, 278, 471);

+    pathB.lineTo(278, 529);

+    pathB.cubicTo(278, 530.65686f, 276.65686f, 532, 275, 532);

+    pathB.lineTo(161, 532);

+    pathB.cubicTo(159.34314f, 532, 158, 530.65686f, 158, 529);

+    pathB.lineTo(158, 471);

+    pathB.cubicTo(158, 469.895416f, 159.34314f, 469, 161, 469);

+    pathB.close();

+    testPathOp(reporter, path, pathB, kIntersect_PathOp);

+}

+#endif
+
+static void skpwww_meb_gov_tr_5(skiatest::Reporter* reporter) {

+    SkPath path;

+    path.setFillType(SkPath::kEvenOdd_FillType);

+    path.moveTo(137.34314f, 145.34314f);

+    path.quadTo(139.687088f, 143.000793f, 143, 143);

+    path.lineTo(242, 143);

+    path.quadTo(245.312912f, 143.000793f, 247.65686f, 145.34314f);

+    path.quadTo(249.999207f, 147.687088f, 250, 151);

+    path.lineTo(250, 177);

+    path.lineTo(135, 177);

+    path.lineTo(135, 151);

+    path.quadTo(135.000793f, 147.687088f, 137.34314f, 145.34314f);

+    path.close();

+    SkPath pathB;

+    pathB.setFillType(SkPath::kWinding_FillType);

+    pathB.moveTo(135, 143);

+    pathB.lineTo(250, 143);

+    pathB.lineTo(250, 177);

+    pathB.lineTo(135, 177);

+    pathB.close();

+    testPathOp(reporter, path, pathB, kIntersect_PathOp);

+}

+

+#if TRY_SEPT_BROKEN_TESTS
+static void skpwww_meb_gov_tr_6(skiatest::Reporter* reporter) {

+    SkPath path;

+    path.setFillType(SkPath::kEvenOdd_FillType);

+    path.moveTo(143, 143);

+    path.quadTo(139.687088f, 143.000793f, 137.34314f, 145.34314f);

+    path.quadTo(135.000793f, 147.687088f, 135, 151);

+    path.lineTo(135, 177);

+    path.lineTo(250, 177);

+    path.lineTo(250, 151);

+    path.quadTo(249.999207f, 147.687088f, 247.65686f, 145.34314f);

+    path.quadTo(245.312912f, 143.000793f, 242, 143);

+    path.lineTo(143, 143);

+    path.close();

+    SkPath pathB;

+    pathB.setFillType(SkPath::kInverseWinding_FillType);

+    pathB.moveTo(143, 143);

+    pathB.lineTo(242, 143);

+    pathB.cubicTo(245.865997f, 143, 249, 146.581726f, 249, 151);

+    pathB.lineTo(249, 177);

+    pathB.lineTo(135, 177);

+    pathB.lineTo(135, 151);

+    pathB.cubicTo(135, 146.581726f, 138.581726f, 143, 143, 143);

+    pathB.close();

+    testPathOp(reporter, path, pathB, kIntersect_PathOp);

+}

+#endif
+
+static void skpgithub_io_25(skiatest::Reporter* reporter) {

+    SkPath path;

+    path.setFillType(SkPath::kEvenOdd_FillType);

+    path.moveTo(1001.87866f, 14.8786793f);

+    path.quadTo(1002.75745f, 14.0001001f, 1004, 14);

+    path.lineTo(1105, 14);

+    path.quadTo(1106.24255f, 14.0001001f, 1107.12134f, 14.8786793f);

+    path.quadTo(1107.99988f, 15.7574596f, 1108, 17);

+    path.lineTo(1108, 41);

+    path.quadTo(1107.99988f, 42.2425423f, 1107.12134f, 43.1213188f);

+    path.quadTo(1106.24255f, 43.9999008f, 1105, 44);

+    path.lineTo(1004, 44);

+    path.quadTo(1002.75745f, 43.9999008f, 1001.87866f, 43.1213188f);

+    path.quadTo(1001.00012f, 42.2425423f, 1001, 41);

+    path.lineTo(1001, 17);

+    path.quadTo(1001.00012f, 15.7574596f, 1001.87866f, 14.8786793f);

+    path.close();

+    SkPath pathB;

+    pathB.setFillType(SkPath::kInverseWinding_FillType);

+    pathB.moveTo(1005, 16);

+    pathB.lineTo(1104, 16);

+    pathB.cubicTo(1105.10461f, 16, 1106, 16.8954296f, 1106, 18);

+    pathB.lineTo(1106, 40);

+    pathB.cubicTo(1106, 41.1045685f, 1105.10461f, 42, 1104, 42);

+    pathB.lineTo(1005, 42);

+    pathB.cubicTo(1003.89545f, 42, 1003, 41.1045685f, 1003, 40);

+    pathB.lineTo(1003, 18);

+    pathB.cubicTo(1003, 16.8954296f, 1003.89545f, 16, 1005, 16);

+    pathB.close();

+    testPathOp(reporter, path, pathB, kIntersect_PathOp);

+}

+

+static void skpgithub_io_26(skiatest::Reporter* reporter) {

+    SkPath path;

+    path.setFillType(SkPath::kEvenOdd_FillType);

+    path.moveTo(1001.87866f, 14.8786793f);

+    path.quadTo(1002.75745f, 14.0001001f, 1004, 14);

+    path.lineTo(1105, 14);

+    path.quadTo(1106.24255f, 14.0001001f, 1107.12134f, 14.8786793f);

+    path.quadTo(1107.99988f, 15.7574596f, 1108, 17);

+    path.lineTo(1108, 41);

+    path.quadTo(1107.99988f, 42.2425423f, 1107.12134f, 43.1213188f);

+    path.quadTo(1106.24255f, 43.9999008f, 1105, 44);

+    path.lineTo(1004, 44);

+    path.quadTo(1002.75745f, 43.9999008f, 1001.87866f, 43.1213188f);

+    path.quadTo(1001.00012f, 42.2425423f, 1001, 41);

+    path.lineTo(1001, 17);

+    path.quadTo(1001.00012f, 15.7574596f, 1001.87866f, 14.8786793f);

+    path.close();

+    path.moveTo(1003, 18);

+    path.cubicTo(1003, 16.8954296f, 1003.89545f, 16, 1005, 16);

+    path.lineTo(1104, 16);

+    path.cubicTo(1105.10461f, 16, 1106, 16.8954296f, 1106, 18);

+    path.lineTo(1106, 40);

+    path.cubicTo(1106, 41.1045685f, 1105.10461f, 42, 1104, 42);

+    path.lineTo(1005, 42);

+    path.cubicTo(1003.89545f, 42, 1003, 41.1045685f, 1003, 40);

+    path.lineTo(1003, 18);

+    path.close();

+    SkPath pathB;

+    pathB.setFillType(SkPath::kWinding_FillType);

+    pathB.moveTo(1108, 14);

+    pathB.lineTo(1106, 16);

+    pathB.lineTo(1105.97998f, 46.0200005f);

+    pathB.lineTo(1108, 44);

+    testPathOp(reporter, path, pathB, kIntersect_PathOp);

+}

+
 static void (*firstTest)(skiatest::Reporter* ) = 0;
 
 static struct TestDesc tests[] = {
+#if TRY_SEPT_BROKEN_TESTS
+    TEST(skpwww_meb_gov_tr_6),
+    TEST(skpwww_sciality_com_101),
+    TEST(skpwww_booking_com_68),  // similar to lavoixdunord
+    TEST(skpwww_despegar_com_mx_272),  // similar to lavoixdunord
+    TEST(skpwww_lavoixdunord_fr_11),  // not quite coincident, sorting line/cubic fails
+    TEST(skppptv_com_62),  // cubic have nearly identical tangents, sort incorrectly
+#endif
 #if TRY_BROKEN_TESTS
     TEST(skppchappy_com_au102),
     TEST(skpsciality_com161),
-    TEST(skpsudoestenegocios_com186),
-    TEST(skpfrauen_magazin_com83),
     TEST(skpi_gino_com16),
+    TEST(skpnaoxrane_ru23),  // see test for failure evaluation
+    TEST(skptcmevents_org23),  // see test for (partial) failure evaluation
+    TEST(skpredbullskatearcade_es16),  // cubic have nearly identical tangents, sort incorrectly
+    TEST(skpfinanzasdigital_com9),  // cubic/quad tangents too close to sort
 #endif
+    TEST(skpgithub_io_26),
+    TEST(skpgithub_io_25),
+    TEST(skpwww_meb_gov_tr_5),
+    TEST(skpwww_sciality_com_100),
+    TEST(skpwww_joomla_org_23),
+    TEST(skpwww_macrumors_com_131),
+    TEST(skpwww_briian_com_34),
+    TEST(skpwww_leadpages_net_84),
+    TEST(skpwww_fj_p_com_22),
+    TEST(skppartainasdemo250_org56),
+    TEST(skpsd_graphic_net104),
+    TEST(skpbreakmystyle_com10),
+    TEST(skpnational_com_au81),
+    TEST(skprentacheat_com30),
+    TEST(skptracksflow_com9),
+    TEST(skpautobutler_dk29),
+    TEST(skponlinecollege_org144),
+    TEST(skphostloco_com11),
+    TEST(skpsergeychunkevich_com8),
+    TEST(skpmlk_com326),
+    TEST(skpcyclist_friends_gr52),
+    TEST(skpfrauen_magazin_com83),
+    TEST(skpthesuburbanite_com213),
+    TEST(skpsudoestenegocios_com186),
     TEST(skpmtrk_uz27),
     TEST(skpilkoora_com37),
     TEST(skpmm4everfriends_com43),
diff --git a/tests/PathOpsThreadedCommon.h b/tests/PathOpsThreadedCommon.h
index ee93390..a638cd2 100644
--- a/tests/PathOpsThreadedCommon.h
+++ b/tests/PathOpsThreadedCommon.h
@@ -68,6 +68,15 @@
         fTestFun = testFun;
     }
 
+    PathOpsThreadedRunnable(void (*testFun)(PathOpsThreadState*), int dirNo, const char* str,
+            PathOpsThreadedTestRunner* runner) {
+        SkASSERT(strlen(str) < sizeof(fState.fSerialNo) - 1);
+        fState.fA = dirNo;
+        strcpy(fState.fSerialNo, str);
+        fState.fReporter = runner->fReporter;
+        fTestFun = testFun;
+    }
+
     virtual void run() SK_OVERRIDE {
         SkBitmap bitmap;
         fState.fBitmap = &bitmap;
diff --git a/tests/PathOpsTypesTest.cpp b/tests/PathOpsTypesTest.cpp
new file mode 100755
index 0000000..6fd6e10
--- /dev/null
+++ b/tests/PathOpsTypesTest.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "PathOpsTestCommon.h"
+#include "Test.h"
+

+static const double roughlyTests[][2] = {

+    {5.0402503619650929e-005, 4.3178054475078825e-005}

+};

+

+static const size_t roughlyTestsCount = SK_ARRAY_COUNT(roughlyTests);
+

+static void PathOpsRoughlyTest(skiatest::Reporter* reporter) {
+    for (size_t index = 0; index < roughlyTestsCount; ++index) {
+        bool equal = RoughlyEqualUlps(roughlyTests[index][0], roughlyTests[index][1]);
+        REPORTER_ASSERT(reporter, equal);
+    }
+}

+

+#include "TestClassDef.h"
+DEFINE_TESTCLASS_SHORT(PathOpsRoughlyTest)
diff --git a/tests/SkpSkGrTest.cpp b/tests/SkpSkGrTest.cpp
new file mode 100755
index 0000000..d26659f
--- /dev/null
+++ b/tests/SkpSkGrTest.cpp
@@ -0,0 +1,759 @@
+#if !SK_SUPPORT_GPU
+#error "GPU support required"
+#endif
+
+#include "GrContext.h"
+#include "GrContextFactory.h"
+#include "GrRenderTarget.h"
+#include "SkGpuDevice.h"
+#include "gl/GrGLDefines.h"
+
+#include "SkBitmap.h"
+#include "SkColor.h"
+#include "SkDevice.h"
+#include "SkCanvas.h"
+#include "SkGraphics.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkStream.h"
+#include "SkOSFile.h"
+#include "SkPicture.h"
+#include "SkRTConf.h"
+#include "SkRunnable.h"
+#include "SkString.h"
+#include "SkTArray.h"
+#include "SkTDArray.h"
+#include "SkThreadPool.h"
+#include "SkTime.h"
+#include "Test.h"
+
+#ifdef SK_BUILD_FOR_WIN
+    #define PATH_SLASH "\\"
+    #define IN_DIR "D:\\9-30-13\\"
+    #define OUT_DIR "D:\\skpSkGr\\11\\"
+    #define LINE_FEED "\r\n"
+#else
+    #define PATH_SLASH "/"
+    #define IN_DIR "/usr/local/google/home/caryclark" PATH_SLASH "9-30-13-skp"
+    #define OUT_DIR "/media/01CD75512A7F9EE0/4" PATH_SLASH
+    #define LINE_FEED \n"
+#endif
+
+#define PATH_STR_SIZE 512
+
+static const struct {
+    int directory;
+    const char* filename;
+} skipOverSkGr[] = {
+    {1, "http___accuweather_com_.skp"},  // Couldn't convert bitmap to texture.http___absoku072_com_
+};
+
+static const size_t skipOverSkGrCount = 0; // SK_ARRAY_COUNT(skipOverSkGr);
+
+/////////////////////////////////////////
+
+class SkpSkGrThreadedRunnable;
+
+enum TestStep {
+    kCompareBits,
+    kEncodeFiles,
+};
+
+enum {
+    kMaxLength = 128,
+    kMaxFiles = 128,
+};
+
+struct TestResult {
+    void init(int dirNo) {
+        fDirNo = dirNo;
+        sk_bzero(fFilename, sizeof(fFilename));
+        fTestStep = kCompareBits;
+        fScaleOversized = true;
+    }
+
+    SkString status() {
+        SkString outStr;
+        outStr.printf("%s %d %d%s", fFilename, fPixelError, fTime, LINE_FEED);
+        return outStr;
+    }
+
+    static void Test(int dirNo, const char* filename, TestStep testStep, bool verbose) {
+        TestResult test;
+        test.init(dirNo);
+        test.fTestStep = testStep;
+        strcpy(test.fFilename, filename);
+        test.testOne();
+        if (verbose) {
+            SkDebugf("%s", test.status().c_str());
+        }
+    }
+
+    void test(int dirNo, const SkString& filename) {
+        init(dirNo);
+        strcpy(fFilename, filename.c_str());
+        testOne();
+    }
+
+    void testOne();
+    
+    char fFilename[kMaxLength];
+    TestStep fTestStep;
+    int fDirNo;
+    int fPixelError;
+    int fTime;
+    bool fScaleOversized;
+};
+
+struct SkpSkGrThreadState {
+    void init(int dirNo) {
+        fResult.init(dirNo);
+        fFoundCount = 0;
+        fSmallestError = 0;
+        sk_bzero(fFilesFound, sizeof(fFilesFound));
+        sk_bzero(fDirsFound, sizeof(fDirsFound));
+        sk_bzero(fError, sizeof(fError));
+    }
+    
+    char fFilesFound[kMaxFiles][kMaxLength];
+    int fDirsFound[kMaxFiles];
+    int fError[kMaxFiles];
+    int fFoundCount;
+    int fSmallestError;
+    skiatest::Reporter* fReporter;
+    TestResult fResult;
+};
+
+struct SkpSkGrThreadedTestRunner {
+    SkpSkGrThreadedTestRunner(skiatest::Reporter* reporter, int threadCount)
+        : fNumThreads(threadCount)
+        , fReporter(reporter) {
+    }
+
+    ~SkpSkGrThreadedTestRunner();
+    void render();
+    int fNumThreads;
+    SkTDArray<SkpSkGrThreadedRunnable*> fRunnables;
+    skiatest::Reporter* fReporter;
+};
+
+class SkpSkGrThreadedRunnable : public SkRunnable {
+public:
+    SkpSkGrThreadedRunnable(void (*testFun)(SkpSkGrThreadState*), int dirNo, const char* str,
+            SkpSkGrThreadedTestRunner* runner) {
+        SkASSERT(strlen(str) < sizeof(fState.fResult.fFilename) - 1);
+        fState.init(dirNo);
+        strcpy(fState.fResult.fFilename, str);
+        fState.fReporter = runner->fReporter;
+        fTestFun = testFun;
+    }
+
+    virtual void run() SK_OVERRIDE {
+        SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024);
+        (*fTestFun)(&fState);
+    }
+
+    SkpSkGrThreadState fState;
+    void (*fTestFun)(SkpSkGrThreadState*);
+};
+
+SkpSkGrThreadedTestRunner::~SkpSkGrThreadedTestRunner() {
+    for (int index = 0; index < fRunnables.count(); index++) {
+        SkDELETE(fRunnables[index]);
+    }
+}
+
+void SkpSkGrThreadedTestRunner::render() {
+    SkThreadPool pool(fNumThreads);
+    for (int index = 0; index < fRunnables.count(); ++ index) {
+        pool.add(fRunnables[index]);
+    }
+}
+
+////////////////////////////////////////////////
+
+static const char outGrDir[] = OUT_DIR "grTest";
+static const char outSkDir[] = OUT_DIR "skTest";
+static const char outSkpDir[] = OUT_DIR "skpTest";
+static const char outDiffDir[] = OUT_DIR "outTest";
+static const char outStatusDir[] = OUT_DIR "statusTest";
+
+static SkString make_filepath(int dirIndex, const char* dir, const char* name) {
+    SkString path(dir);
+    if (dirIndex) {
+        path.appendf("%d", dirIndex);
+    }
+    path.append(PATH_SLASH);
+    path.append(name);
+    return path;
+}
+
+static SkString make_in_dir_name(int dirIndex) {
+    SkString dirName(IN_DIR);
+    dirName.appendf("%d", dirIndex);
+    if (!sk_exists(dirName.c_str())) {
+        SkDebugf("could not read dir %s\n", dirName.c_str());
+        return SkString();
+    }
+    return dirName;
+}
+
+static bool make_out_dirs() {
+    SkString outDir = make_filepath(0, OUT_DIR, "");
+    if (!sk_exists(outDir.c_str())) {
+        if (!sk_mkdir(outDir.c_str())) {
+            SkDebugf("could not create dir %s\n", outDir.c_str());
+            return false;
+        }
+    }
+    SkString grDir = make_filepath(0, outGrDir, "");
+    if (!sk_exists(grDir.c_str())) {
+        if (!sk_mkdir(grDir.c_str())) {
+            SkDebugf("could not create dir %s\n", grDir.c_str());
+            return false;
+        }
+    }
+    SkString skDir = make_filepath(0, outSkDir, "");
+    if (!sk_exists(skDir.c_str())) {
+        if (!sk_mkdir(skDir.c_str())) {
+            SkDebugf("could not create dir %s\n", skDir.c_str());
+            return false;
+        }
+    }
+    SkString skpDir = make_filepath(0, outSkpDir, "");
+    if (!sk_exists(skpDir.c_str())) {
+        if (!sk_mkdir(skpDir.c_str())) {
+            SkDebugf("could not create dir %s\n", skpDir.c_str());
+            return false;
+        }
+    }
+    SkString diffDir = make_filepath(0, outDiffDir, "");
+    if (!sk_exists(diffDir.c_str())) {
+        if (!sk_mkdir(diffDir.c_str())) {
+            SkDebugf("could not create dir %s\n", diffDir.c_str());
+            return false;
+        }
+    }
+    SkString statusDir = make_filepath(0, outStatusDir, "");
+    if (!sk_exists(statusDir.c_str())) {
+        if (!sk_mkdir(statusDir.c_str())) {
+            SkDebugf("could not create dir %s\n", statusDir.c_str());
+            return false;
+        }
+    }
+    return true;
+}
+
+static SkString make_png_name(const char* filename) {
+    SkString pngName = SkString(filename);
+    pngName.remove(pngName.size() - 3, 3);
+    pngName.append("png");
+    return pngName;
+}
+
+typedef GrContextFactory::GLContextType GLContextType;
+#ifdef SK_BUILD_FOR_WIN
+static const GLContextType kAngle = GrContextFactory::kANGLE_GLContextType;
+#else
+static const GLContextType kNative = GrContextFactory::kNative_GLContextType;
+#endif
+
+static int similarBits(const SkBitmap& gr, const SkBitmap& sk) {
+    const int kRowCount = 3;
+    const int kThreshold = 3;
+    int width = SkTMin(gr.width(), sk.width());
+    if (width < kRowCount) {
+        return true;
+    }
+    int height = SkTMin(gr.height(), sk.height());
+    if (height < kRowCount) {
+        return true;
+    }
+    int errorTotal = 0;
+    SkTArray<char, true> errorRows;
+    errorRows.push_back_n(width * kRowCount);
+    SkAutoLockPixels autoGr(gr);
+    SkAutoLockPixels autoSk(sk);
+    char* base = &errorRows[0];
+    for (int y = 0; y < height; ++y) {
+        SkPMColor* grRow = gr.getAddr32(0, y);
+        SkPMColor* skRow = sk.getAddr32(0, y);
+        char* cOut = &errorRows[(y % kRowCount) * width];
+        for (int x = 0; x < width; ++x) {
+            SkPMColor grColor = grRow[x];
+            SkPMColor skColor = skRow[x];
+            int dr = SkGetPackedR32(grColor) - SkGetPackedR32(skColor);
+            int dg = SkGetPackedG32(grColor) - SkGetPackedG32(skColor);
+            int db = SkGetPackedB32(grColor) - SkGetPackedB32(skColor);
+            int error = SkTMax(SkAbs32(dr), SkTMax(SkAbs32(dg), SkAbs32(db)));
+            if ((cOut[x] = error >= kThreshold) && x >= 2
+                    && base[x - 2] && base[width + x - 2] && base[width * 2 + x - 2]
+                    && base[x - 1] && base[width + x - 1] && base[width * 2 + x - 1]
+                    && base[x - 0] && base[width + x - 0] && base[width * 2 + x - 0]) {
+                errorTotal += error;
+            }
+        }
+    }
+    return errorTotal;
+}
+
+static bool addError(SkpSkGrThreadState* data) {
+    bool foundSmaller = false;
+    int dCount = data->fFoundCount;
+    int pixelError = data->fResult.fPixelError;
+    if (data->fFoundCount < kMaxFiles) {
+        data->fError[dCount] = pixelError;
+        strcpy(data->fFilesFound[dCount], data->fResult.fFilename);
+        data->fDirsFound[dCount] = data->fResult.fDirNo;
+        ++data->fFoundCount;
+    } else if (pixelError > data->fSmallestError) {
+        int smallest = SK_MaxS32;
+        int smallestIndex = 0;
+        for (int index = 0; index < kMaxFiles; ++index) {
+            if (smallest > data->fError[index]) {
+                smallest = data->fError[index];
+                smallestIndex = index;
+            }
+        }
+        data->fError[smallestIndex] = pixelError;
+        strcpy(data->fFilesFound[smallestIndex], data->fResult.fFilename);
+        data->fDirsFound[smallestIndex] = data->fResult.fDirNo;
+        data->fSmallestError = SK_MaxS32;
+        for (int index = 0; index < kMaxFiles; ++index) {
+            if (data->fSmallestError > data->fError[index]) {
+                data->fSmallestError = data->fError[index];
+            }
+        }
+        SkDebugf("*%d*", data->fSmallestError);
+        foundSmaller = true;
+    }
+    return foundSmaller;
+}
+
+static SkMSec timePict(SkPicture* pic, SkCanvas* canvas) {
+    canvas->save();
+    int pWidth = pic->width();
+    int pHeight = pic->height();
+    const int maxDimension = 1000;
+    const int slices = 3;
+    int xInterval = SkTMax(pWidth - maxDimension, 0) / (slices - 1);
+    int yInterval = SkTMax(pHeight - maxDimension, 0) / (slices - 1);
+    SkRect rect = {0, 0, SkIntToScalar(SkTMin(maxDimension, pWidth)), 
+            SkIntToScalar(SkTMin(maxDimension, pHeight))};
+    canvas->clipRect(rect);
+    SkMSec start = SkTime::GetMSecs();
+    for (int x = 0; x < slices; ++x) {
+        for (int y = 0; y < slices; ++y) {
+            pic->draw(canvas);
+            canvas->translate(0, SkIntToScalar(yInterval));
+        }
+        canvas->translate(SkIntToScalar(xInterval), SkIntToScalar(-yInterval * slices));
+    }
+    SkMSec end = SkTime::GetMSecs();
+    canvas->restore();
+    return end - start;
+}
+
+static void drawPict(SkPicture* pic, SkCanvas* canvas, int scale) {
+    canvas->clear(SK_ColorWHITE);
+    if (scale != 1) {
+        canvas->save();
+        canvas->scale(1.0f / scale, 1.0f / scale);
+    }
+    pic->draw(canvas);
+    if (scale != 1) {
+        canvas->restore();
+    }
+}
+
+static void writePict(const SkBitmap& bitmap, const char* outDir, const char* pngName) {
+    SkString outFile = make_filepath(0, outDir, pngName);
+    if (!SkImageEncoder::EncodeFile(outFile.c_str(), bitmap,
+            SkImageEncoder::kPNG_Type, 100)) {
+        SkDebugf("unable to encode gr %s (width=%d height=%d)br \n", pngName,
+                    bitmap.width(), bitmap.height());
+    }
+}
+
+void TestResult::testOne() {
+    SkPicture* pic = NULL;
+    {
+        SkString d;
+        d.printf("    {%d, \"%s\"},", fDirNo, fFilename);
+        SkString path = make_filepath(fDirNo, IN_DIR, fFilename);
+        SkFILEStream stream(path.c_str());
+        if (!stream.isValid()) {
+            SkDebugf("invalid stream %s\n", path.c_str());
+            goto finish;
+        }
+        if (fTestStep == kEncodeFiles) {
+            size_t length = stream.getLength();
+            SkTArray<char, true> bytes;
+            bytes.push_back_n(length);
+            stream.read(&bytes[0], length);
+            stream.rewind();
+            SkString wPath = make_filepath(0, outSkpDir, fFilename);
+            SkFILEWStream wStream(wPath.c_str());
+            wStream.write(&bytes[0], length);
+            wStream.flush();
+        }
+        pic = SkPicture::CreateFromStream(&stream, &SkImageDecoder::DecodeMemory);
+        if (!pic) {
+            SkDebugf("unable to decode %s\n", fFilename);
+            goto finish;
+        }
+        int pWidth = pic->width();
+        int pHeight = pic->height();
+        int pLargerWH = SkTMax(pWidth, pHeight);
+        GrContextFactory contextFactory;
+#ifdef SK_BUILD_FOR_WIN
+        GrContext* context = contextFactory.get(kAngle);
+#else
+        GrContext* context = contextFactory.get(kNative);
+#endif
+        if (NULL == context) {
+            SkDebugf("unable to allocate context for %s\n", fFilename);
+            goto finish;
+        }
+        int maxWH = context->getMaxRenderTargetSize();
+        int scale = 1;
+        while (pLargerWH / scale > maxWH) {
+            scale *= 2;
+        }
+        SkBitmap bitmap;
+        SkIPoint dim;
+        do {
+            dim.fX = (pWidth + scale - 1) / scale;
+            dim.fY = (pHeight + scale - 1) / scale;
+            bitmap.setConfig(SkBitmap::kARGB_8888_Config, dim.fX, dim.fY);
+            bool success = bitmap.allocPixels();
+            if (success) {
+                break;
+            }
+            SkDebugf("-%d-", scale);
+        } while ((scale *= 2) < 256);
+        if (scale >= 256) {
+            SkDebugf("unable to allocate bitmap for %s (w=%d h=%d) (sw=%d sh=%d)\n",
+                    fFilename, pWidth, pHeight, dim.fX, dim.fY);
+            goto finish;
+        }
+        SkCanvas skCanvas(bitmap);
+        drawPict(pic, &skCanvas, fScaleOversized ? scale : 1);
+        GrTextureDesc desc;
+        desc.fConfig = kSkia8888_GrPixelConfig;
+        desc.fFlags = kRenderTarget_GrTextureFlagBit;
+        desc.fWidth = dim.fX;
+        desc.fHeight = dim.fY;
+        desc.fSampleCnt = 0;
+        SkAutoTUnref<GrTexture> texture(context->createUncachedTexture(desc, NULL, 0));
+        if (!texture) {
+            SkDebugf("unable to allocate texture for %s (w=%d h=%d)\n", fFilename,
+                dim.fX, dim.fY);
+            goto finish;
+        }
+        SkGpuDevice grDevice(context, texture.get());
+        SkCanvas grCanvas(&grDevice);
+        drawPict(pic, &grCanvas, fScaleOversized ? scale : 1);
+        const SkBitmap& grBitmap = grDevice.accessBitmap(false);
+        if (fTestStep == kCompareBits) {
+            fPixelError = similarBits(grBitmap, bitmap);
+            int skTime = timePict(pic, &skCanvas);
+            int grTime = timePict(pic, &grCanvas);
+            fTime = skTime - grTime;
+        } else if (fTestStep == kEncodeFiles) {
+            SkString pngStr = make_png_name(fFilename);
+            const char* pngName = pngStr.c_str();
+            writePict(grBitmap, outGrDir, pngName);
+            writePict(bitmap, outSkDir, pngName);
+        }
+    }
+finish:
+    SkDELETE(pic);
+}
+
+static SkString makeStatusString(int dirNo) {
+    SkString statName;
+    statName.printf("stats%d.txt", dirNo);
+    SkString statusFile = make_filepath(0, outStatusDir, statName.c_str());
+    return statusFile;
+}
+
+class PreParser {
+public:
+    PreParser(int dirNo)
+        : fDirNo(dirNo)
+        , fIndex(0)
+        , fStatusPath(makeStatusString(dirNo)) {
+        if (!sk_exists(fStatusPath.c_str())) {
+            return;
+        }
+        SkFILEStream reader;
+        reader.setPath(fStatusPath.c_str());
+        while (fetch(reader, &fResults.push_back()))
+            ;
+        fResults.pop_back();
+    }
+
+    bool fetch(SkFILEStream& reader, TestResult* result) {
+        char c;
+        int i = 0;
+        result->init(fDirNo);
+        result->fPixelError = 0;
+        result->fTime = 0;
+        do {
+            bool readOne = reader.read(&c, 1) != 0;
+            if (!readOne) {
+                SkASSERT(i == 0);
+                return false;
+            }
+            if (c == ' ') {
+                result->fFilename[i++] = '\0';
+                break;
+            }
+            result->fFilename[i++] = c;
+            SkASSERT(i < kMaxLength);
+        } while (true);
+        do {
+            SkAssertResult(reader.read(&c, 1) != 0);
+            if (c == ' ') {
+                break;
+            }
+            SkASSERT(c >= '0' && c <= '9');
+            result->fPixelError = result->fPixelError * 10 + (c - '0');
+        } while (true);
+        bool minus = false;
+        do {
+            if (reader.read(&c, 1) == 0) {
+                break;
+            }
+            if (c == '\r' && reader.read(&c, 1) == 0) {
+                break;
+            }
+            if (c == '\n') {
+                break;
+            }
+            if (c == '-') {
+                minus = true;
+                continue;
+            }
+            SkASSERT(c >= '0' && c <= '9');
+            result->fTime = result->fTime * 10 + (c - '0');
+        } while (true);
+        if (minus) {
+            result->fTime = -result->fTime;
+        }
+        return true;
+    }
+
+    bool match(const SkString& filename, SkFILEWStream* stream, TestResult* result) {
+        if (fIndex < fResults.count()) {
+            *result = fResults[fIndex++];
+            SkASSERT(filename.equals(result->fFilename));
+            SkString outStr(result->status());
+            stream->write(outStr.c_str(), outStr.size());
+            stream->flush();
+            return true;
+        }
+        return false;
+    }
+
+private:
+    int fDirNo;
+    int fIndex;
+    SkTArray<TestResult, true> fResults;
+    SkString fStatusPath;
+};
+
+static bool initTest() {
+#if !defined SK_BUILD_FOR_WIN && !defined SK_BUILD_FOR_MAC
+    SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true);
+    SK_CONF_SET("images.png.suppressDecoderWarnings", true);
+#endif
+    return make_out_dirs();
+}
+
+static void SkpSkGrTest(skiatest::Reporter* reporter) {
+    SkTArray<TestResult, true> errors;
+    if (!initTest()) {
+        return;
+    }
+    SkpSkGrThreadState state;
+    state.init(0);
+    int smallCount = 0;
+    for (int dirNo = 1; dirNo <= 100; ++dirNo) {
+        SkString pictDir = make_in_dir_name(dirNo);
+        SkASSERT(pictDir.size());
+        if (reporter->verbose()) {
+            SkDebugf("dirNo=%d\n", dirNo);
+        }
+        SkOSFile::Iter iter(pictDir.c_str(), "skp");
+        SkString filename;
+        int testCount = 0;
+        PreParser preParser(dirNo);
+        SkFILEWStream statusStream(makeStatusString(dirNo).c_str());
+        while (iter.next(&filename)) {
+            for (size_t index = 0; index < skipOverSkGrCount; ++index) {
+                if (skipOverSkGr[index].directory == dirNo
+                        && strcmp(filename.c_str(), skipOverSkGr[index].filename) == 0) {
+                    goto skipOver;
+                }
+            }
+            if (preParser.match(filename, &statusStream, &state.fResult)) {
+                addError(&state);
+                ++testCount;
+                goto checkEarlyExit;
+            }
+            if (state.fSmallestError > 5000000) {
+                goto breakOut;
+            }
+            {
+                TestResult& result = state.fResult;
+                result.test(dirNo, filename);
+                SkString outStr(result.status());
+                statusStream.write(outStr.c_str(), outStr.size());
+                statusStream.flush();
+                if (1) {
+                    SkDebugf("%s", outStr.c_str());
+                }
+                bool noMatch = addError(&state);
+                if (noMatch) {
+                    smallCount = 0;
+                } else if (++smallCount > 10000) {
+                    goto breakOut;
+                }
+            }
+            ++testCount;
+            if (reporter->verbose()) {
+                if (testCount % 100 == 0) {
+                    SkDebugf("#%d\n", testCount);
+                }
+            }
+    skipOver:
+            reporter->bumpTestCount();
+    checkEarlyExit:
+            if (1 && testCount == 20) {
+                break;
+            }
+        }
+    }
+breakOut:
+    if (reporter->verbose()) {
+        for (int index = 0; index < state.fFoundCount; ++index) {
+            SkDebugf("%d %s %d\n", state.fDirsFound[index], state.fFilesFound[index],
+                     state.fError[index]);
+        }
+    }
+    for (int index = 0; index < state.fFoundCount; ++index) {
+        TestResult::Test(state.fDirsFound[index], state.fFilesFound[index], kEncodeFiles,
+                reporter->verbose());
+        if (reporter->verbose()) SkDebugf("+");
+    }
+}
+
+static void bumpCount(skiatest::Reporter* reporter, bool skipping) {
+    if (reporter->verbose()) {
+        static int threadTestCount;
+        sk_atomic_inc(&threadTestCount);
+        if (!skipping && threadTestCount % 100 == 0) {
+            SkDebugf("#%d\n", threadTestCount);
+        }
+        if (skipping && threadTestCount % 10000 == 0) {
+            SkDebugf("#%d\n", threadTestCount);
+        }
+    }
+}
+
+static void testSkGrMain(SkpSkGrThreadState* data) {
+    data->fResult.testOne();
+    bumpCount(data->fReporter, false);
+    data->fReporter->bumpTestCount();
+}
+
+static void SkpSkGrThreadedTest(skiatest::Reporter* reporter) {
+    if (!initTest()) {
+        return;
+    }
+    int threadCount = reporter->allowThreaded() ? 3 : 1;
+    SkpSkGrThreadedTestRunner testRunner(reporter, threadCount);
+    for (int dirIndex = 1; dirIndex <= 100; ++dirIndex) {
+        SkString pictDir = make_in_dir_name(dirIndex);
+        if (pictDir.size() == 0) {
+            continue;
+        }
+        SkOSFile::Iter iter(pictDir.c_str(), "skp");
+        SkString filename;
+        while (iter.next(&filename)) {
+            SkString pngName = make_png_name(filename.c_str());
+            SkString oldPng = make_filepath(dirIndex, outSkDir, pngName.c_str());
+            SkString newPng = make_filepath(dirIndex, outGrDir, pngName.c_str());
+            if (sk_exists(oldPng.c_str()) && sk_exists(newPng.c_str())) {
+                bumpCount(reporter, true);
+                continue;
+            }
+            for (size_t index = 0; index < skipOverSkGrCount; ++index) {
+                if (skipOverSkGr[index].directory == dirIndex
+                        && strcmp(filename.c_str(), skipOverSkGr[index].filename) == 0) {
+                    bumpCount(reporter, true);
+                    goto skipOver;
+                }
+            }
+            *testRunner.fRunnables.append() = SkNEW_ARGS(SkpSkGrThreadedRunnable,
+                    (&testSkGrMain, dirIndex, filename.c_str(), &testRunner));
+    skipOver:
+            ;
+        }
+    }
+    testRunner.render();
+    SkpSkGrThreadState& max = testRunner.fRunnables[0]->fState;
+    for (int dirIndex = 2; dirIndex <= 100; ++dirIndex) {
+        SkpSkGrThreadState& state = testRunner.fRunnables[dirIndex - 1]->fState;
+        for (int index = 0; index < state.fFoundCount; ++index) {
+            int maxIdx = max.fFoundCount;
+            if (maxIdx < kMaxFiles) {
+                max.fError[maxIdx] = state.fError[index];
+                strcpy(max.fFilesFound[maxIdx], state.fFilesFound[index]);
+                max.fDirsFound[maxIdx] = state.fDirsFound[index];
+                ++max.fFoundCount;
+                continue;
+            }
+            for (maxIdx = 0; maxIdx < max.fFoundCount; ++maxIdx) {
+                if (max.fError[maxIdx] < state.fError[index]) {
+                    max.fError[maxIdx] = state.fError[index];
+                    strcpy(max.fFilesFound[maxIdx], state.fFilesFound[index]);
+                    max.fDirsFound[maxIdx] = state.fDirsFound[index];
+                    break;
+                }
+            }
+        }
+    }
+    TestResult encoder;
+    encoder.fTestStep = kEncodeFiles;
+    for (int index = 0; index < max.fFoundCount; ++index) {
+        encoder.fDirNo = max.fDirsFound[index];
+        strcpy(encoder.fFilename, max.fFilesFound[index]);
+        encoder.testOne();
+        SkDebugf("+");
+    }
+}
+
+static void SkpSkGrOneOffTest(skiatest::Reporter* reporter) {
+    if (!initTest()) {
+        return;
+    }
+    int testIndex = 166;
+    int dirIndex = skipOverSkGr[testIndex - 166].directory;
+    SkString pictDir = make_in_dir_name(dirIndex);
+    if (pictDir.size() == 0) {
+        return;
+    }
+    SkString filename(skipOverSkGr[testIndex - 166].filename);
+    TestResult::Test(dirIndex, filename.c_str(), kCompareBits, reporter->verbose());
+    TestResult::Test(dirIndex, filename.c_str(), kEncodeFiles, reporter->verbose());
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS_SHORT(SkpSkGrTest)
+
+DEFINE_TESTCLASS_SHORT(SkpSkGrOneOffTest)
+
+DEFINE_TESTCLASS_SHORT(SkpSkGrThreadedTest)