Make SkPath::isOval() and SkPath::isRRect return the orientation and starting index.

These are tracked in SkPathRef.

Unit tests are updated to test that the returned values are correct.
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2012233002

Review-Url: https://codereview.chromium.org/2012233002
diff --git a/src/core/SkPathRef.cpp b/src/core/SkPathRef.cpp
index 2fc2de9..74f3a22 100644
--- a/src/core/SkPathRef.cpp
+++ b/src/core/SkPathRef.cpp
@@ -56,6 +56,61 @@
     return SkRef(gEmpty);
 }
 
+static void transform_dir_and_start(const SkMatrix& matrix, bool isRRect, bool* isCCW,
+                                    unsigned* start) {
+    int inStart = *start;
+    int rm = 0;
+    if (isRRect) {
+        // Degenerate rrect indices to oval indices and remember the remainder.
+        // Ovals have one index per side whereas rrects have two.
+        rm = inStart & 0b1;
+        inStart /= 2;
+    }
+    // Is the antidiagonal non-zero (otherwise the diagonal is zero)
+    int antiDiag;
+    // Is the non-zero value in the top row (either kMScaleX or kMSkewX) negative
+    int topNeg;
+    // Are the two non-zero diagonal or antidiagonal values the same sign.
+    int sameSign;
+    if (matrix.get(SkMatrix::kMScaleX) != 0) {
+        antiDiag = 0b00;
+        if (matrix.get(SkMatrix::kMScaleX) > 0) {
+            topNeg = 0b00;
+            sameSign = matrix.get(SkMatrix::kMScaleY) > 0 ? 0b01 : 0b00;
+        } else {
+            topNeg = 0b10;
+            sameSign = matrix.get(SkMatrix::kMScaleY) > 0 ? 0b00 : 0b01;
+        }
+    } else {
+        antiDiag = 0b01;
+        if (matrix.get(SkMatrix::kMSkewX) > 0) {
+            topNeg = 0b00;
+            sameSign = matrix.get(SkMatrix::kMSkewY) > 0 ? 0b01 : 0b00;
+        } else {
+            topNeg = 0b10;
+            sameSign = matrix.get(SkMatrix::kMSkewY) > 0 ? 0b00 : 0b01;
+        }
+    }
+    if (sameSign != antiDiag) {
+        // This is a rotation (and maybe scale). The direction is unchanged.
+        // Trust me on the start computation (or draw yourself some pictures)
+        *start = (inStart + 4 - (topNeg | antiDiag)) % 4;
+        SkASSERT(*start < 4);
+        if (isRRect) {
+            *start = 2 * *start + rm;
+        }
+    } else {
+        // This is a mirror (and maybe scale). The direction is reversed.
+        *isCCW = !*isCCW;
+        // Trust me on the start computation (or draw yourself some pictures)
+        *start = (6 + (topNeg | antiDiag) - inStart) % 4;
+        SkASSERT(*start < 4);
+        if (isRRect) {
+            *start = 2 * *start + (rm ? 0 : 1);
+        }
+    }
+}
+
 void SkPathRef::CreateTransformedCopy(SkAutoTUnref<SkPathRef>* dst,
                                       const SkPathRef& src,
                                       const SkMatrix& matrix) {
@@ -90,15 +145,15 @@
     matrix.mapPoints((*dst)->fPoints, src.points(), src.fPointCnt);
 
     /*
-        *  Here we optimize the bounds computation, by noting if the bounds are
-        *  already known, and if so, we just transform those as well and mark
-        *  them as "known", rather than force the transformed path to have to
-        *  recompute them.
-        *
-        *  Special gotchas if the path is effectively empty (<= 1 point) or
-        *  if it is non-finite. In those cases bounds need to stay empty,
-        *  regardless of the matrix.
-        */
+     *  Here we optimize the bounds computation, by noting if the bounds are
+     *  already known, and if so, we just transform those as well and mark
+     *  them as "known", rather than force the transformed path to have to
+     *  recompute them.
+     *
+     *  Special gotchas if the path is effectively empty (<= 1 point) or
+     *  if it is non-finite. In those cases bounds need to stay empty,
+     *  regardless of the matrix.
+     */
     if (canXformBounds) {
         (*dst)->fBoundsIsDirty = false;
         if (src.fIsFinite) {
@@ -120,6 +175,13 @@
     bool rectStaysRect = matrix.rectStaysRect();
     (*dst)->fIsOval = src.fIsOval && rectStaysRect;
     (*dst)->fIsRRect = src.fIsRRect && rectStaysRect;
+    if ((*dst)->fIsOval || (*dst)->fIsRRect) {
+        unsigned start = src.fRRectOrOvalStartIdx;
+        bool isCCW = SkToBool(src.fRRectOrOvalIsCCW);
+        transform_dir_and_start(matrix, (*dst)->fIsRRect, &isCCW, &start);
+        (*dst)->fRRectOrOvalIsCCW = isCCW;
+        (*dst)->fRRectOrOvalStartIdx = start;
+    }
 
     SkDEBUGCODE((*dst)->validate();)
 }
@@ -137,6 +199,8 @@
     uint8_t segmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
     bool isOval  = (packed >> kIsOval_SerializationShift) & 1;
     bool isRRect  = (packed >> kIsRRect_SerializationShift) & 1;
+    bool rrectOrOvalIsCCW = (packed >> kRRectOrOvalIsCCW_SerializationShift) & 1;
+    unsigned rrectOrOvalStartIdx = (packed >> kRRectOrOvalStartIdx_SerializationShift) & 0x7;
 
     int32_t verbCount, pointCount, conicCount;
     ptrdiff_t maxPtrDiff = std::numeric_limits<ptrdiff_t>::max();
@@ -173,6 +237,8 @@
     ref->fSegmentMask = segmentMask;
     ref->fIsOval = isOval;
     ref->fIsRRect = isRRect;
+    ref->fRRectOrOvalIsCCW = rrectOrOvalIsCCW;
+    ref->fRRectOrOvalStartIdx = rrectOrOvalStartIdx;
     return ref;
 }
 
@@ -253,7 +319,9 @@
     // and fIsFinite are computed.
     const SkRect& bounds = this->getBounds();
 
-    int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
+    int32_t packed = ((fRRectOrOvalStartIdx & 7) << kRRectOrOvalStartIdx_SerializationShift) |
+                     ((fRRectOrOvalIsCCW & 1) << kRRectOrOvalIsCCW_SerializationShift) |
+                     ((fIsFinite & 1) << kIsFinite_SerializationShift) |
                      ((fIsOval & 1) << kIsOval_SerializationShift) |
                      ((fIsRRect & 1) << kIsRRect_SerializationShift) |
                      (fSegmentMask << kSegmentMask_SerializationShift);
@@ -298,6 +366,8 @@
     fSegmentMask = ref.fSegmentMask;
     fIsOval = ref.fIsOval;
     fIsRRect = ref.fIsRRect;
+    fRRectOrOvalIsCCW = ref.fRRectOrOvalIsCCW;
+    fRRectOrOvalStartIdx = ref.fRRectOrOvalStartIdx;
     SkDEBUGCODE(this->validate();)
 }
 
@@ -616,6 +686,16 @@
     SkASSERT(this->currSize() ==
                 fFreeSpace + sizeof(SkPoint) * fPointCnt + sizeof(uint8_t) * fVerbCnt);
 
+    if (fIsOval || fIsRRect) {
+        // Currently we don't allow both of these to be set, even though ovals are round rects.
+        SkASSERT(fIsOval != fIsRRect);
+        if (fIsOval) {
+            SkASSERT(fRRectOrOvalStartIdx < 4);
+        } else {
+            SkASSERT(fRRectOrOvalStartIdx < 8);
+        }
+    }
+
     if (!fBoundsIsDirty && !fBounds.isEmpty()) {
         bool isFinite = true;
         for (int i = 0; i < fPointCnt; ++i) {