Make SkPath cache the result of cheapComputeDirection.
Review URL: https://codereview.appspot.com/6810111

git-svn-id: http://skia.googlecode.com/svn/trunk@6394 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index be5f612..ee02c65 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -106,15 +106,15 @@
     };
 
     /**
-     *  Return the path's convexity, as stored in the path. If it is currently
-     *  unknown, and the computeIfUnknown bool is true, then this will first
-     *  call ComputeConvexity() and then return that (cached) value.
+     *  Return the path's convexity, as stored in the path. If it is currently unknown,
+     *  then this function will attempt to compute the convexity (and cache the result).
      */
     Convexity getConvexity() const {
-        if (kUnknown_Convexity == fConvexity) {
-            fConvexity = (uint8_t)ComputeConvexity(*this);
+        if (kUnknown_Convexity != fConvexity) {
+            return static_cast<Convexity>(fConvexity);
+        } else {
+            return this->internalGetConvexity();
         }
-        return (Convexity)fConvexity;
     }
 
     /**
@@ -127,8 +127,8 @@
 
     /**
      *  Store a convexity setting in the path. There is no automatic check to
-     *  see if this value actually agress with the return value from
-     *  ComputeConvexity().
+     *  see if this value actually agrees with the return value that would be
+     *  computed by getConvexity().
      *
      *  Note: even if this is set to a "known" value, if the path is later
      *  changed (e.g. lineTo(), addRect(), etc.) then the cached value will be
@@ -137,20 +137,6 @@
     void setConvexity(Convexity);
 
     /**
-     *  Compute the convexity of the specified path. This does not look at the
-     *  value stored in the path, but computes it directly from the path's data.
-     *
-     *  This never returns kUnknown_Convexity.
-     *
-     *  If there is more than one contour, this returns kConcave_Convexity.
-     *  If the contour is degenerate (e.g. there are fewer than 3 non-degenerate
-     *  segments), then this returns kConvex_Convexity.
-     *  The contour is treated as if it were closed, even if there is no kClose
-     *  verb.
-     */
-    static Convexity ComputeConvexity(const SkPath&);
-
-    /**
      *  DEPRECATED: use getConvexity()
      *  Returns true if the path is flagged as being convex. This is not a
      *  confirmed by any analysis, it is just the value set earlier.
@@ -498,17 +484,25 @@
     void close();
 
     enum Direction {
+        /** Direction either has not been or could not be computed */
+        kUnknown_Direction,
         /** clockwise direction for adding closed contours */
         kCW_Direction,
         /** counter-clockwise direction for adding closed contours */
-        kCCW_Direction
+        kCCW_Direction,
     };
 
+    static Direction OppositeDirection(Direction dir) {
+        static const Direction kOppositeDir[] = {kUnknown_Direction, kCCW_Direction, kCW_Direction};
+        return kOppositeDir[dir];
+    }
+
     /**
      *  Tries to quickly compute the direction of the first non-degenerate
      *  contour. If it can be computed, return true and set dir to that
      *  direction. If it cannot be (quickly) determined, return false and ignore
-     *  the dir parameter.
+     *  the dir parameter. If the direction was determined, it is cached to make
+     *  subsequent calls return quickly.
      */
     bool cheapComputeDirection(Direction* dir) const;
 
@@ -518,6 +512,7 @@
      *  specified direction.
      */
     bool cheapIsDirection(Direction dir) const {
+        SkASSERT(kCW_Direction == dir || kCCW_Direction == dir);
         Direction computedDir;
         return this->cheapComputeDirection(&computedDir) && computedDir == dir;
     }
@@ -827,11 +822,12 @@
 
 private:
     enum SerializationOffsets {
-        kIsFinite_SerializationShift = 25,
-        kIsOval_SerializationShift = 24,
-        kConvexity_SerializationShift = 16,
-        kFillType_SerializationShift = 8,
-        kSegmentMask_SerializationShift = 0
+        kDirection_SerializationShift = 26, // requires 2 bits
+        kIsFinite_SerializationShift = 25,  // requires 1 bit
+        kIsOval_SerializationShift = 24,    // requires 1 bit
+        kConvexity_SerializationShift = 16, // requires 2 bits
+        kFillType_SerializationShift = 8,   // requires 2 bits
+        kSegmentMask_SerializationShift = 0 // requires 3 bits
     };
 
 #if SK_DEBUG_PATH_REF
@@ -865,6 +861,7 @@
     uint8_t             fSegmentMask;
     mutable uint8_t     fBoundsIsDirty;
     mutable uint8_t     fConvexity;
+    mutable uint8_t     fDirection;
     mutable SkBool8     fIsFinite;    // only meaningful if bounds are valid
     mutable SkBool8     fIsOval;
 #ifdef SK_BUILD_FOR_ANDROID
@@ -900,8 +897,11 @@
 
     inline bool hasOnlyMoveTos() const;
 
+    Convexity internalGetConvexity() const;
+
     friend class SkAutoPathBoundsUpdate;
     friend class SkAutoDisableOvalCheck;
+    friend class SkAutoDisableDirectionCheck;
     friend class SkBench_AddPathTest; // perf test pathTo/reversePathTo
 };
 
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 6d055f5..d4ef1a6 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -110,6 +110,21 @@
     bool    fSaved;
 };
 
+class SkAutoDisableDirectionCheck {
+public:
+    SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
+        fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
+    }
+
+    ~SkAutoDisableDirectionCheck() {
+        fPath->fDirection = fSaved;
+    }
+
+private:
+    SkPath*              fPath;
+    SkPath::Direction    fSaved;
+};
+
 /*  This guy's constructor/destructor bracket a path editing operation. It is
     used when we know the bounds of the amount we are going to add to the path
     (usually a new contour, but not required).
@@ -206,6 +221,7 @@
     , fFillType(kWinding_FillType)
     , fBoundsIsDirty(true) {
     fConvexity = kUnknown_Convexity;
+    fDirection = kUnknown_Direction;
     fSegmentMask = 0;
     fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
     fIsOval = false;
@@ -228,6 +244,7 @@
     fFillType       = src.fFillType;
     fBoundsIsDirty  = src.fBoundsIsDirty;
     fConvexity      = src.fConvexity;
+    fDirection      = src.fDirection;
     fIsFinite       = src.fIsFinite;
     fSegmentMask    = src.fSegmentMask;
     fLastMoveToIndex = src.fLastMoveToIndex;
@@ -252,6 +269,7 @@
         fFillType       = src.fFillType;
         fBoundsIsDirty  = src.fBoundsIsDirty;
         fConvexity      = src.fConvexity;
+        fDirection      = src.fDirection;
         fIsFinite       = src.fIsFinite;
         fSegmentMask    = src.fSegmentMask;
         fLastMoveToIndex = src.fLastMoveToIndex;
@@ -284,6 +302,7 @@
         SkTSwap<uint8_t>(fFillType, other.fFillType);
         SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
         SkTSwap<uint8_t>(fConvexity, other.fConvexity);
+        SkTSwap<uint8_t>(fDirection, other.fDirection);
         SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
         SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
         SkTSwap<SkBool8>(fIsOval, other.fIsOval);
@@ -313,6 +332,7 @@
     GEN_ID_INC;
     fBoundsIsDirty = true;
     fConvexity = kUnknown_Convexity;
+    fDirection = kUnknown_Direction;
     fSegmentMask = 0;
     fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
     fIsOval = false;
@@ -566,12 +586,13 @@
     do {                                 \
         fBoundsIsDirty = true;           \
         fConvexity = kUnknown_Convexity; \
+        fDirection = kUnknown_Direction; \
         fIsOval = false;                 \
     } while (0)
 
-#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE    \
-    do {                                        \
-        fBoundsIsDirty = true;                  \
+#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE   \
+    do {                                                    \
+        fBoundsIsDirty = true;                              \
     } while (0)
 
 void SkPath::incReserve(U16CPU inc) {
@@ -591,7 +612,7 @@
     ed.growForVerb(kMove_Verb)->set(x, y);
 
     GEN_ID_INC;
-    DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
+    DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
 }
 
 void SkPath::rMoveTo(SkScalar x, SkScalar y) {
@@ -718,6 +739,9 @@
 
 void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
                      SkScalar bottom, Direction dir) {
+    fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
+    SkAutoDisableDirectionCheck addc(this);
+
     SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
 
     this->incReserve(5);
@@ -792,7 +816,10 @@
         return;
     }
 
+    fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
+
     SkAutoPathBoundsUpdate apbu(this, rect);
+    SkAutoDisableDirectionCheck(this);
 
     if (skip_hori) {
         rx = halfW;
@@ -935,8 +962,14 @@
        moveTo() would mark the path non empty.
      */
     fIsOval = hasOnlyMoveTos();
+    if (fIsOval) {
+        fDirection = dir;
+    } else {
+        fDirection = kUnknown_Direction;
+    }
 
     SkAutoDisableOvalCheck adoc(this);
+    SkAutoDisableDirectionCheck addc(this);
 
     SkAutoPathBoundsUpdate apbu(this, oval);
 
@@ -1456,6 +1489,7 @@
         dst->swap(tmp);
         SkPathRef::Editor ed(&dst->fPathRef);
         matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
+        dst->fDirection = kUnknown_Direction;
     } else {
         /*
          *  If we're not in perspective, we can transform all of the points at
@@ -1494,6 +1528,21 @@
             dst->fConvexity = fConvexity;
         }
 
+        if (kUnknown_Direction == fDirection) {
+            dst->fDirection = kUnknown_Direction;
+        } else {
+            SkScalar det2x2 =
+                SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
+                SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
+            if (det2x2 < 0) {
+                dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
+            } else if (det2x2 > 0) {
+                dst->fDirection = fDirection;
+            } else {
+                dst->fDirection = kUnknown_Direction;
+            }
+        }
+
         // It's an oval only if it stays a rect.
         dst->fIsOval = fIsOval && matrix.rectStaysRect();
 
@@ -1858,7 +1907,8 @@
                      ((fIsOval & 1) << kIsOval_SerializationShift) |
                      (fConvexity << kConvexity_SerializationShift) |
                      (fFillType << kFillType_SerializationShift) |
-                     (fSegmentMask << kSegmentMask_SerializationShift);
+                     (fSegmentMask << kSegmentMask_SerializationShift) |
+                     (fDirection << kDirection_SerializationShift);
 
     buffer.write32(packed);
 
@@ -1882,7 +1932,8 @@
     fIsOval = (packed >> kIsOval_SerializationShift) & 1;
     fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
     fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
-    fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xFF;
+    fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
+    fDirection = (packed >> kDirection_SerializationShift) & 0x3;
 
 #if NEW_PICTURE_FORMAT
     fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
@@ -2017,7 +2068,10 @@
 
 // only valid for a single contour
 struct Convexicator {
-    Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
+    Convexicator()
+    : fPtCount(0)
+    , fConvexity(SkPath::kConvex_Convexity)
+    , fDirection(SkPath::kUnknown_Direction) {
         fSign = 0;
         // warnings
         fCurrPt.set(0, 0);
@@ -2031,6 +2085,9 @@
 
     SkPath::Convexity getConvexity() const { return fConvexity; }
 
+    /** The direction returned is only valid if the path is determined convex */
+    SkPath::Direction getDirection() const { return fDirection; }
+
     void addPt(const SkPoint& pt) {
         if (SkPath::kConcave_Convexity == fConvexity) {
             return;
@@ -2078,9 +2135,15 @@
         int sign = CrossProductSign(fVec0, fVec1);
         if (0 == fSign) {
             fSign = sign;
+            if (1 == sign) {
+                fDirection = SkPath::kCW_Direction;
+            } else if (-1 == sign) {
+                fDirection = SkPath::kCCW_Direction;
+            }
         } else if (sign) {
             if (fSign != sign) {
                 fConvexity = SkPath::kConcave_Convexity;
+                fDirection = SkPath::kUnknown_Direction;
             }
         }
     }
@@ -2090,13 +2153,15 @@
     int                 fPtCount;   // non-degenerate points
     int                 fSign;
     SkPath::Convexity   fConvexity;
+    SkPath::Direction   fDirection;
     int                 fDx, fDy, fSx, fSy;
 };
 
-SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
+SkPath::Convexity SkPath::internalGetConvexity() const {
+    SkASSERT(kUnknown_Convexity == fConvexity);
     SkPoint         pts[4];
     SkPath::Verb    verb;
-    SkPath::Iter    iter(path, true);
+    SkPath::Iter    iter(*this, true);
 
     int             contourCount = 0;
     int             count;
@@ -2106,6 +2171,7 @@
         switch (verb) {
             case kMove_Verb:
                 if (++contourCount > 1) {
+                    fConvexity = kConcave_Convexity;
                     return kConcave_Convexity;
                 }
                 pts[1] = pts[0];
@@ -2120,6 +2186,7 @@
                 break;
             default:
                 SkDEBUGFAIL("bad verb");
+                fConvexity = kConcave_Convexity;
                 return kConcave_Convexity;
         }
 
@@ -2128,10 +2195,15 @@
         }
         // early exit
         if (kConcave_Convexity == state.getConvexity()) {
+            fConvexity = kConcave_Convexity;
             return kConcave_Convexity;
         }
     }
-    return state.getConvexity();
+    fConvexity = state.getConvexity();
+    if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
+        fDirection = state.getDirection();
+    }
+    return static_cast<Convexity>(fConvexity);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -2282,11 +2354,10 @@
     return minIndex;
 }
 
-static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
+static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
     if (dir) {
         *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
     }
-    return true;
 }
 
 #if 0
@@ -2356,6 +2427,11 @@
 //    dumpPath(*this);
     // don't want to pay the cost for computing this if it
     // is unknown, so we don't call isConvex()
+
+    if (kUnknown_Direction != fDirection) {
+        *dir = static_cast<Direction>(fDirection);
+        return true;
+    }
     const Convexity conv = this->getConvexityOrUnknown();
 
     ContourIter iter(*fPathRef.get());
@@ -2377,9 +2453,11 @@
             // precision. This is because the vectors computed between distant
             // points may lose too much precision.
             if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
+                fDirection = *dir;
                 return true;
             }
             if (convex_dir_test<double, toDouble>(n, pts, dir)) {
+                fDirection = *dir;
                 return true;
             } else {
                 return false;
@@ -2423,9 +2501,10 @@
                 int next = find_diff_pt(pts, index, n, 1);
                 SkASSERT(next != index);
                 cross = cross_prod(pts[prev], pts[index], pts[next]);
-                // if we get a zero, but the pts aren't on top of each other, then
-                // we can just look at the direction
-                if (0 == cross) {
+                // if we get a zero and the points are horizontal, then we look at the spread in 
+                // x-direction. We really should continue to walk away from the degeneracy until
+                // there is a divergence.
+                if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
                     // construct the subtract so we get the correct Direction below
                     cross = pts[index].fX - pts[next].fX;
                 }
@@ -2438,8 +2517,13 @@
             }
         }
     }
-
-    return ymaxCross ? crossToDir(ymaxCross, dir) : false;
+    if (ymaxCross) {
+        crossToDir(ymaxCross, dir);
+        fDirection = *dir;
+        return true;
+    } else {
+        return false;
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/GrAAConvexPathRenderer.cpp b/src/gpu/GrAAConvexPathRenderer.cpp
index 4ac6163..ebfcd7b 100644
--- a/src/gpu/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/GrAAConvexPathRenderer.cpp
@@ -209,9 +209,7 @@
     SkScalar det2x2 = SkScalarMul(m.get(SkMatrix::kMScaleX), m.get(SkMatrix::kMScaleY)) -
                       SkScalarMul(m.get(SkMatrix::kMSkewX), m.get(SkMatrix::kMSkewY));
     if (det2x2 < 0) {
-        GR_STATIC_ASSERT(0 == SkPath::kCW_Direction || 1 == SkPath::kCW_Direction);
-        GR_STATIC_ASSERT(0 == SkPath::kCCW_Direction || 1 == SkPath::kCCW_Direction);
-        *dir = static_cast<SkPath::Direction>(*dir ^ 0x1);
+        *dir = SkPath::OppositeDirection(*dir);
     }
     return true;
 }
@@ -223,7 +221,7 @@
                   int* vCount,
                   int* iCount) {
     SkPath::Iter iter(path, true);
-    // This renderer overemphasises very thin path regions. We use the distance
+    // This renderer over-emphasizes very thin path regions. We use the distance
     // to the path from the sample to compute coverage. Every pixel intersected
     // by the path will be hit and the maximum distance is sqrt(2)/2. We don't
     // notice that the sample may be close to a very thin area of the path and
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index df2c7e4..add66a2 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -344,19 +344,23 @@
     REPORTER_ASSERT(reporter, SkStrokeRec::kFill_Style == rec.getStyle());
 }
 
-/**
- * cheapIsDirection can take a shortcut when a path is marked convex.
- * This function ensures that we always test cheapIsDirection when the path
- * is flagged with unknown convexity status.
- */
-static void check_direction(SkPath* path,
-                            SkPath::Direction expectedDir,
-                            skiatest::Reporter* reporter) {
-    if (SkPath::kConvex_Convexity == path->getConvexity()) {
-        REPORTER_ASSERT(reporter, path->cheapIsDirection(expectedDir));
-        path->setConvexity(SkPath::kUnknown_Convexity);
+// Set this for paths that don't have a consistent direction such as a bowtie.
+// (cheapComputeDirection is not expected to catch these.)
+static const SkPath::Direction kDontCheckDir = static_cast<SkPath::Direction>(-1);
+
+static void check_direction(skiatest::Reporter* reporter, const SkPath& path,
+                            SkPath::Direction expected) {
+    if (expected == kDontCheckDir) {
+        return;
     }
-    REPORTER_ASSERT(reporter, path->cheapIsDirection(expectedDir));
+    SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
+
+    SkPath::Direction dir;
+    if (copy.cheapComputeDirection(&dir)) {
+        REPORTER_ASSERT(reporter, dir == expected);
+    } else {
+        REPORTER_ASSERT(reporter, SkPath::kUnknown_Direction == expected);
+    }
 }
 
 static void test_direction(skiatest::Reporter* reporter) {
@@ -394,7 +398,7 @@
         path.reset();
         bool valid = SkParsePath::FromSVGString(gCW[i], &path);
         REPORTER_ASSERT(reporter, valid);
-        check_direction(&path, SkPath::kCW_Direction, reporter);
+        check_direction(reporter, path, SkPath::kCW_Direction);
     }
 
     static const char* gCCW[] = {
@@ -410,7 +414,7 @@
         path.reset();
         bool valid = SkParsePath::FromSVGString(gCCW[i], &path);
         REPORTER_ASSERT(reporter, valid);
-        check_direction(&path, SkPath::kCCW_Direction, reporter);
+        check_direction(reporter, path, SkPath::kCCW_Direction);
     }
 
     // Test two donuts, each wound a different direction. Only the outer contour
@@ -418,12 +422,12 @@
     path.reset();
     path.addCircle(0, 0, SkIntToScalar(2), SkPath::kCW_Direction);
     path.addCircle(0, 0, SkIntToScalar(1), SkPath::kCCW_Direction);
-    check_direction(&path, SkPath::kCW_Direction, reporter);
+    check_direction(reporter, path, SkPath::kCW_Direction);
 
     path.reset();
     path.addCircle(0, 0, SkIntToScalar(1), SkPath::kCW_Direction);
     path.addCircle(0, 0, SkIntToScalar(2), SkPath::kCCW_Direction);
-    check_direction(&path, SkPath::kCCW_Direction, reporter);
+    check_direction(reporter, path, SkPath::kCCW_Direction);
 
 #ifdef SK_SCALAR_IS_FLOAT
     // triangle with one point really far from the origin.
@@ -432,7 +436,7 @@
     path.moveTo(SkFloatToScalar(SkBits2Float(0x501c7652)), SkFloatToScalar(SkBits2Float(0x501c7652)));
     path.lineTo(110 * SK_Scalar1, -10 * SK_Scalar1);
     path.lineTo(-10 * SK_Scalar1, 60 * SK_Scalar1);
-    check_direction(&path, SkPath::kCCW_Direction, reporter);
+    check_direction(reporter, path, SkPath::kCCW_Direction);
 #endif
 }
 
@@ -595,7 +599,8 @@
 
 static void check_convexity(skiatest::Reporter* reporter, const SkPath& path,
                             SkPath::Convexity expected) {
-    SkPath::Convexity c = SkPath::ComputeConvexity(path);
+    SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
+    SkPath::Convexity c = copy.getConvexity();
     REPORTER_ASSERT(reporter, c == expected);
 }
 
@@ -604,12 +609,14 @@
     pt.moveTo(0, 0);
     pt.close();
     check_convexity(reporter, pt, SkPath::kConvex_Convexity);
+    check_direction(reporter, pt, SkPath::kUnknown_Direction);
 
     SkPath line;
     line.moveTo(12*SK_Scalar1, 20*SK_Scalar1);
     line.lineTo(-12*SK_Scalar1, -20*SK_Scalar1);
     line.close();
-    check_convexity(reporter, pt, SkPath::kConvex_Convexity);
+    check_convexity(reporter, line, SkPath::kConvex_Convexity);
+    check_direction(reporter, line, SkPath::kUnknown_Direction);
 
     SkPath triLeft;
     triLeft.moveTo(0, 0);
@@ -617,6 +624,7 @@
     triLeft.lineTo(SK_Scalar1, SK_Scalar1);
     triLeft.close();
     check_convexity(reporter, triLeft, SkPath::kConvex_Convexity);
+    check_direction(reporter, triLeft, SkPath::kCW_Direction);
 
     SkPath triRight;
     triRight.moveTo(0, 0);
@@ -624,6 +632,7 @@
     triRight.lineTo(SK_Scalar1, SK_Scalar1);
     triRight.close();
     check_convexity(reporter, triRight, SkPath::kConvex_Convexity);
+    check_direction(reporter, triRight, SkPath::kCCW_Direction);
 
     SkPath square;
     square.moveTo(0, 0);
@@ -632,6 +641,7 @@
     square.lineTo(0, SK_Scalar1);
     square.close();
     check_convexity(reporter, square, SkPath::kConvex_Convexity);
+    check_direction(reporter, square, SkPath::kCW_Direction);
 
     SkPath redundantSquare;
     redundantSquare.moveTo(0, 0);
@@ -648,6 +658,7 @@
     redundantSquare.lineTo(0, SK_Scalar1);
     redundantSquare.close();
     check_convexity(reporter, redundantSquare, SkPath::kConvex_Convexity);
+    check_direction(reporter, redundantSquare, SkPath::kCW_Direction);
 
     SkPath bowTie;
     bowTie.moveTo(0, 0);
@@ -664,6 +675,7 @@
     bowTie.lineTo(0, SK_Scalar1);
     bowTie.close();
     check_convexity(reporter, bowTie, SkPath::kConcave_Convexity);
+    check_direction(reporter, bowTie, kDontCheckDir);
 
     SkPath spiral;
     spiral.moveTo(0, 0);
@@ -675,6 +687,7 @@
     spiral.lineTo(50*SK_Scalar1, 75*SK_Scalar1);
     spiral.close();
     check_convexity(reporter, spiral, SkPath::kConcave_Convexity);
+    check_direction(reporter, spiral, kDontCheckDir);
 
     SkPath dent;
     dent.moveTo(0, 0);
@@ -684,6 +697,7 @@
     dent.lineTo(-200*SK_Scalar1, 100*SK_Scalar1);
     dent.close();
     check_convexity(reporter, dent, SkPath::kConcave_Convexity);
+    check_direction(reporter, dent, SkPath::kCW_Direction);
 }
 
 static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p,
@@ -721,44 +735,44 @@
 }
 
 static void test_convexity(skiatest::Reporter* reporter) {
-    static const SkPath::Convexity C = SkPath::kConcave_Convexity;
-    static const SkPath::Convexity V = SkPath::kConvex_Convexity;
-
     SkPath path;
 
-    REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+    check_convexity(reporter, path, SkPath::kConvex_Convexity);
     path.addCircle(0, 0, SkIntToScalar(10));
-    REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+    check_convexity(reporter, path, SkPath::kConvex_Convexity);
     path.addCircle(0, 0, SkIntToScalar(10));   // 2nd circle
-    REPORTER_ASSERT(reporter, C == SkPath::ComputeConvexity(path));
+    check_convexity(reporter, path, SkPath::kConcave_Convexity);
+
     path.reset();
     path.addRect(0, 0, SkIntToScalar(10), SkIntToScalar(10), SkPath::kCCW_Direction);
-    REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+    check_convexity(reporter, path, SkPath::kConvex_Convexity);
     REPORTER_ASSERT(reporter, path.cheapIsDirection(SkPath::kCCW_Direction));
+
     path.reset();
     path.addRect(0, 0, SkIntToScalar(10), SkIntToScalar(10), SkPath::kCW_Direction);
-    REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+    check_convexity(reporter, path, SkPath::kConvex_Convexity);
     REPORTER_ASSERT(reporter, path.cheapIsDirection(SkPath::kCW_Direction));
 
     static const struct {
         const char*         fPathStr;
         SkPath::Convexity   fExpectedConvexity;
+        SkPath::Direction   fExpectedDirection;
     } gRec[] = {
-        { "", SkPath::kConvex_Convexity },
-        { "0 0", SkPath::kConvex_Convexity },
-        { "0 0 10 10", SkPath::kConvex_Convexity },
-        { "0 0 10 10 20 20 0 0 10 10", SkPath::kConcave_Convexity },
-        { "0 0 10 10 10 20", SkPath::kConvex_Convexity },
-        { "0 0 10 10 10 0", SkPath::kConvex_Convexity },
-        { "0 0 10 10 10 0 0 10", SkPath::kConcave_Convexity },
-        { "0 0 10 0 0 10 -10 -10", SkPath::kConcave_Convexity },
+        { "", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
+        { "0 0", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
+        { "0 0 10 10", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
+        { "0 0 10 10 20 20 0 0 10 10", SkPath::kConcave_Convexity, SkPath::kUnknown_Direction },
+        { "0 0 10 10 10 20", SkPath::kConvex_Convexity, SkPath::kCW_Direction },
+        { "0 0 10 10 10 0", SkPath::kConvex_Convexity, SkPath::kCCW_Direction },
+        { "0 0 10 10 10 0 0 10", SkPath::kConcave_Convexity, kDontCheckDir },
+        { "0 0 10 0 0 10 -10 -10", SkPath::kConcave_Convexity, SkPath::kCW_Direction },
     };
 
     for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
         SkPath path;
         setFromString(&path, gRec[i].fPathStr);
-        SkPath::Convexity c = SkPath::ComputeConvexity(path);
-        REPORTER_ASSERT(reporter, c == gRec[i].fExpectedConvexity);
+        check_convexity(reporter, path, gRec[i].fExpectedConvexity);
+        check_direction(reporter, path, gRec[i].fExpectedDirection);
     }
 }
 
@@ -1454,33 +1468,46 @@
 }
 
 static void check_for_circle(skiatest::Reporter* reporter,
-                             const SkPath& path, bool expected) {
+                             const SkPath& path,
+                             bool expectedCircle,
+                             SkPath::Direction expectedDir) {
     SkRect rect;
-    REPORTER_ASSERT(reporter, path.isOval(&rect) == expected);
-    if (expected) {
+    REPORTER_ASSERT(reporter, path.isOval(&rect) == expectedCircle);
+    REPORTER_ASSERT(reporter, path.cheapIsDirection(expectedDir));
+   
+    if (expectedCircle) {
         REPORTER_ASSERT(reporter, rect.height() == rect.width());
     }
 }
 
 static void test_circle_skew(skiatest::Reporter* reporter,
-                             const SkPath& path) {
+                             const SkPath& path,
+                             SkPath::Direction dir) {
     SkPath tmp;
 
     SkMatrix m;
     m.setSkew(SkIntToScalar(3), SkIntToScalar(5));
     path.transform(m, &tmp);
-    check_for_circle(reporter, tmp, false);
+    // this matrix reverses the direction.
+    if (SkPath::kCCW_Direction == dir) {
+        dir = SkPath::kCW_Direction;
+    } else {
+        SkASSERT(SkPath::kCW_Direction == dir);
+        dir = SkPath::kCCW_Direction;
+    }
+    check_for_circle(reporter, tmp, false, dir);
 }
 
 static void test_circle_translate(skiatest::Reporter* reporter,
-                                  const SkPath& path) {
+                                  const SkPath& path,
+                                  SkPath::Direction dir) {
     SkPath tmp;
 
     // translate at small offset
     SkMatrix m;
     m.setTranslate(SkIntToScalar(15), SkIntToScalar(15));
     path.transform(m, &tmp);
-    check_for_circle(reporter, tmp, true);
+    check_for_circle(reporter, tmp, true, dir);
 
     tmp.reset();
     m.reset();
@@ -1488,47 +1515,102 @@
     // translate at a relatively big offset
     m.setTranslate(SkIntToScalar(1000), SkIntToScalar(1000));
     path.transform(m, &tmp);
-    check_for_circle(reporter, tmp, true);
+    check_for_circle(reporter, tmp, true, dir);
 }
 
 static void test_circle_rotate(skiatest::Reporter* reporter,
-                               const SkPath& path) {
+                               const SkPath& path,
+                               SkPath::Direction dir) {
     for (int angle = 0; angle < 360; ++angle) {
         SkPath tmp;
         SkMatrix m;
         m.setRotate(SkIntToScalar(angle));
         path.transform(m, &tmp);
 
-        // TODO: a rotated circle whose rotated angle is not a mutiple of 90
+        // TODO: a rotated circle whose rotated angle is not a multiple of 90
         // degrees is not an oval anymore, this can be improved.  we made this
         // for the simplicity of our implementation.
         if (angle % 90 == 0) {
-            check_for_circle(reporter, tmp, true);
+            check_for_circle(reporter, tmp, true, dir);
         } else {
-            check_for_circle(reporter, tmp, false);
+            check_for_circle(reporter, tmp, false, dir);
         }
     }
 }
 
+static void test_circle_mirror_x(skiatest::Reporter* reporter,
+                                 const SkPath& path,
+                                 SkPath::Direction dir) {
+    SkPath tmp;
+    SkMatrix m;
+    m.reset();
+    m.setScaleX(-SK_Scalar1);
+    path.transform(m, &tmp);
+
+    if (SkPath::kCW_Direction == dir) {
+        dir = SkPath::kCCW_Direction;
+    } else {
+        SkASSERT(SkPath::kCCW_Direction == dir);
+        dir = SkPath::kCW_Direction;
+    }
+
+    check_for_circle(reporter, tmp, true, dir);
+}
+
+static void test_circle_mirror_y(skiatest::Reporter* reporter,
+                                 const SkPath& path,
+                                 SkPath::Direction dir) {
+    SkPath tmp;
+    SkMatrix m;
+    m.reset();
+    m.setScaleY(-SK_Scalar1);
+    path.transform(m, &tmp);
+
+    if (SkPath::kCW_Direction == dir) {
+        dir = SkPath::kCCW_Direction;
+    } else {
+        SkASSERT(SkPath::kCCW_Direction == dir);
+        dir = SkPath::kCW_Direction;
+    }
+
+    check_for_circle(reporter, tmp, true, dir);
+}
+
+static void test_circle_mirror_xy(skiatest::Reporter* reporter,
+                                 const SkPath& path,
+                                 SkPath::Direction dir) {
+    SkPath tmp;
+    SkMatrix m;
+    m.reset();
+    m.setScaleX(-SK_Scalar1);
+    m.setScaleY(-SK_Scalar1);
+    path.transform(m, &tmp);
+
+    check_for_circle(reporter, tmp, true, dir);
+}
+
 static void test_circle_with_direction(skiatest::Reporter* reporter,
                                        SkPath::Direction dir) {
     SkPath path;
 
     // circle at origin
     path.addCircle(0, 0, SkIntToScalar(20), dir);
-    check_for_circle(reporter, path, true);
-    test_circle_rotate(reporter, path);
-    test_circle_translate(reporter, path);
-    test_circle_skew(reporter, path);
+    check_for_circle(reporter, path, true, dir);
+    test_circle_rotate(reporter, path, dir);
+    test_circle_translate(reporter, path, dir);
+    test_circle_skew(reporter, path, dir);
 
     // circle at an offset at (10, 10)
     path.reset();
     path.addCircle(SkIntToScalar(10), SkIntToScalar(10),
                    SkIntToScalar(20), dir);
-    check_for_circle(reporter, path, true);
-    test_circle_rotate(reporter, path);
-    test_circle_translate(reporter, path);
-    test_circle_skew(reporter, path);
+    check_for_circle(reporter, path, true, dir);
+    test_circle_rotate(reporter, path, dir);
+    test_circle_translate(reporter, path, dir);
+    test_circle_skew(reporter, path, dir);
+    test_circle_mirror_x(reporter, path, dir);
+    test_circle_mirror_y(reporter, path, dir);
+    test_circle_mirror_xy(reporter, path, dir);
 }
 
 static void test_circle_with_add_paths(skiatest::Reporter* reporter) {
@@ -1537,7 +1619,10 @@
     SkPath rect;
     SkPath empty;
 
-    circle.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
+    static const SkPath::Direction kCircleDir = SkPath::kCW_Direction;
+    static const SkPath::Direction kCircleDirOpposite = SkPath::kCCW_Direction;
+
+    circle.addCircle(0, 0, SkIntToScalar(10), kCircleDir);
     rect.addRect(SkIntToScalar(5), SkIntToScalar(5),
                  SkIntToScalar(20), SkIntToScalar(20), SkPath::kCW_Direction);
 
@@ -1550,17 +1635,17 @@
     // empty + circle (translate)
     path = empty;
     path.addPath(circle, translate);
-    check_for_circle(reporter, path, false);
+    check_for_circle(reporter, path, false, kCircleDir);
 
     // circle + empty (translate)
     path = circle;
     path.addPath(empty, translate);
-    check_for_circle(reporter, path, false);
+    check_for_circle(reporter, path, false, kCircleDir);
 
     // test reverseAddPath
     path = circle;
     path.reverseAddPath(rect);
-    check_for_circle(reporter, path, false);
+    check_for_circle(reporter, path, false, kCircleDirOpposite);
 }
 
 static void test_circle(skiatest::Reporter* reporter) {
@@ -1571,19 +1656,19 @@
     SkPath path;
     path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
     path.addCircle(0, 0, SkIntToScalar(20), SkPath::kCW_Direction);
-    check_for_circle(reporter, path, false);
+    check_for_circle(reporter, path, false, SkPath::kCW_Direction);
 
     // some extra lineTo() would make isOval() fail
     path.reset();
     path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
     path.lineTo(0, 0);
-    check_for_circle(reporter, path, false);
+    check_for_circle(reporter, path, false, SkPath::kCW_Direction);
 
     // not back to the original point
     path.reset();
     path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
     path.setLastPt(SkIntToScalar(5), SkIntToScalar(5));
-    check_for_circle(reporter, path, false);
+    check_for_circle(reporter, path, false, SkPath::kCW_Direction);
 
     test_circle_with_add_paths(reporter);
 }