Add methods for converting GrShape to filled style

Bug: skia:
Change-Id: I6726428d1358909972adec8d63686b637ef5bb5e
Reviewed-on: https://skia-review.googlesource.com/40222
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/GrShape.cpp b/src/gpu/GrShape.cpp
index 2212478..4691ede 100644
--- a/src/gpu/GrShape.cpp
+++ b/src/gpu/GrShape.cpp
@@ -31,6 +31,82 @@
     return *this;
 }
 
+static bool flip_inversion(bool originalIsInverted, GrShape::FillInversion inversion) {
+    switch (inversion) {
+        case GrShape::FillInversion::kPreserve:
+            return false;
+        case GrShape::FillInversion::kFlip:
+            return true;
+        case GrShape::FillInversion::kForceInverted:
+            return !originalIsInverted;
+        case GrShape::FillInversion::kForceNoninverted:
+            return originalIsInverted;
+    }
+    return false;
+}
+
+static bool is_inverted(bool originalIsInverted, GrShape::FillInversion inversion) {
+    switch (inversion) {
+        case GrShape::FillInversion::kPreserve:
+            return originalIsInverted;
+        case GrShape::FillInversion::kFlip:
+            return !originalIsInverted;
+        case GrShape::FillInversion::kForceInverted:
+            return true;
+        case GrShape::FillInversion::kForceNoninverted:
+            return false;
+    }
+    return false;
+}
+
+GrShape GrShape::MakeFilled(const GrShape& original, FillInversion inversion) {
+    if (original.style().isSimpleFill() && !flip_inversion(original.inverseFilled(), inversion)) {
+        // By returning the original rather than falling through we can preserve any inherited style
+        // key. Otherwise, we wipe it out below since the style change invalidates it.
+        return original;
+    }
+    GrShape result;
+    switch (original.fType) {
+        case Type::kRRect:
+            result.fType = original.fType;
+            result.fRRectData.fRRect = original.fRRectData.fRRect;
+            result.fRRectData.fDir = kDefaultRRectDir;
+            result.fRRectData.fStart = kDefaultRRectStart;
+            result.fRRectData.fInverted = is_inverted(original.fRRectData.fInverted, inversion);
+            break;
+        case Type::kLine:
+            // Lines don't fill.
+            if (is_inverted(original.fLineData.fInverted, inversion)) {
+                result.fType = Type::kInvertedEmpty;
+            } else {
+                result.fType = Type::kEmpty;
+            }
+            break;
+        case Type::kEmpty:
+            result.fType = is_inverted(false, inversion) ? Type::kInvertedEmpty :  Type::kEmpty;
+            break;
+        case Type::kInvertedEmpty:
+            result.fType = is_inverted(true, inversion) ? Type::kInvertedEmpty :  Type::kEmpty;
+            break;
+        case Type::kPath:
+            result.initType(Type::kPath, &original.fPathData.fPath);
+            result.fPathData.fGenID = original.fPathData.fGenID;
+            if (flip_inversion(original.fPathData.fPath.isInverseFillType(), inversion)) {
+                result.fPathData.fPath.toggleInverseFillType();
+            }
+            if (!original.style().isSimpleFill()) {
+                // Going from a non-filled style to fill may allow additional simplifications (e.g.
+                // closing an open rect that wasn't closed in the original shape because it had
+                // stroke style).
+                result.attemptToSimplifyPath();
+            }
+            break;
+    }
+    // We don't copy the inherited key since it can contain path effect information that we just
+    // stripped.
+    return result;
+}
+
 SkRect GrShape::bounds() const {
     // Bounds where left == bottom or top == right can indicate a line or point shape. We return
     // inverted bounds for a truly empty shape.
@@ -206,7 +282,7 @@
         // We want ApplyFullStyle(ApplyPathEffect(shape)) to have the same key as
         // ApplyFullStyle(shape).
         // The full key is structured as (geo,path_effect,stroke).
-        // If we do ApplyPathEffect we get get,path_effect as the inherited key. If we then
+        // If we do ApplyPathEffect we get geo,path_effect as the inherited key. If we then
         // do ApplyFullStyle we'll memcpy geo,path_effect into the new inherited key
         // and then append the style key (which should now be stroke only) at the end.
         int parentCnt = parent.fInheritedKey.count();
diff --git a/src/gpu/GrShape.h b/src/gpu/GrShape.h
index 5226c23..2b48eca 100644
--- a/src/gpu/GrShape.h
+++ b/src/gpu/GrShape.h
@@ -30,7 +30,7 @@
  *
  * Currently this can only be constructed from a path, rect, or rrect though it can become a path
  * applying style to the geometry. The idea is to expand this to cover most or all of the geometries
- * that have SkCanvas::draw APIs.
+ * that have fast paths in the GPU backend.
  */
 class GrShape {
 public:
@@ -121,6 +121,25 @@
 
     ~GrShape() { this->changeType(Type::kEmpty); }
 
+    /**
+     * Informs MakeFilled on how to modify that shape's fill rule when making a simple filled
+     * version of the shape.
+     */
+    enum class FillInversion {
+        kPreserve,
+        kFlip,
+        kForceNoninverted,
+        kForceInverted
+    };
+    /**
+     * Makes a filled shape from the pre-styled original shape and optionally modifies whether
+     * the fill is inverted or not. It's important to note that the original shape's geometry
+     * may already have been modified if doing so was neutral with respect to its style
+     * (e.g. filled paths are always closed when stored in a shape and dashed paths are always
+     * made non-inverted since dashing ignores inverseness).
+     */
+    static GrShape MakeFilled(const GrShape& original, FillInversion = FillInversion::kPreserve);
+
     const GrStyle& style() const { return fStyle; }
 
     /**
@@ -347,6 +366,7 @@
     void writeUnstyledKey(uint32_t* key) const;
 
 private:
+
     enum class Type {
         kEmpty,
         kInvertedEmpty,
diff --git a/tests/GrShapeTest.cpp b/tests/GrShapeTest.cpp
index 719e5cd..087ac52 100644
--- a/tests/GrShapeTest.cpp
+++ b/tests/GrShapeTest.cpp
@@ -78,6 +78,179 @@
     return true;
 }
 
+static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
+    SkPath path;
+    shape.asPath(&path);
+    if (shape.style().hasNonDashPathEffect()) {
+        return false;
+    }
+    const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
+    return strokeRecStyle == SkStrokeRec::kStroke_Style ||
+           strokeRecStyle == SkStrokeRec::kHairline_Style ||
+           (shape.style().isSimpleFill() && path.isConvex());
+}
+
+static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
+                              const Key& keyA, const Key& keyB) {
+    // GrShape only respects the input winding direction and start point for rrect shapes
+    // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
+    // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
+    // key will differ. GrShape will have canonicalized the direction and start point for the shape
+    // without the path effect. If *both* have path effects then they should have both preserved
+    // the direction and starting point.
+
+    // The asRRect() output params are all initialized just to silence compiler warnings about
+    // uninitialized variables.
+    SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
+    SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
+    unsigned startA = ~0U, startB = ~0U;
+    bool invertedA = true, invertedB = true;
+
+    bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
+    bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
+    bool aHasPE = a.style().hasPathEffect();
+    bool bHasPE = b.style().hasPathEffect();
+    bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
+    // GrShape will close paths with simple fill style.
+    bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
+    SkPath pathA, pathB;
+    a.asPath(&pathA);
+    b.asPath(&pathB);
+
+    // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
+    // non-inverse fill type  (or vice versa).
+    bool ignoreInversenessDifference = false;
+    if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
+        const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
+        const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
+        bool canDropInverse1 = s1->style().isDashed();
+        bool canDropInverse2 = s2->style().isDashed();
+        ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
+    }
+    bool ignoreWindingVsEvenOdd = false;
+    if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
+        SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
+        bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
+        bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
+        if (aCanChange != bCanChange) {
+            ignoreWindingVsEvenOdd = true;
+        }
+    }
+    if (allowSameRRectButDiffStartAndDir) {
+        REPORTER_ASSERT(r, rrectA == rrectB);
+        REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
+        REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
+    } else {
+        SkPath pA = pathA;
+        SkPath pB = pathB;
+        REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
+        REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
+        if (ignoreInversenessDifference) {
+            pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
+            pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
+        }
+        if (ignoreWindingVsEvenOdd) {
+            pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
+                                                  : SkPath::kEvenOdd_FillType);
+            pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
+                                                  : SkPath::kEvenOdd_FillType);
+        }
+        if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
+            REPORTER_ASSERT(r, keyA == keyB);
+        } else {
+            REPORTER_ASSERT(r, keyA != keyB);
+        }
+        if (allowedClosednessDiff) {
+            // GrShape will close paths with simple fill style. Make the non-filled path closed
+            // so that the comparision will succeed. Make sure both are closed before comparing.
+            pA.close();
+            pB.close();
+        }
+        REPORTER_ASSERT(r, pA == pB);
+        REPORTER_ASSERT(r, aIsRRect == bIsRRect);
+        if (aIsRRect) {
+            REPORTER_ASSERT(r, rrectA == rrectB);
+            REPORTER_ASSERT(r, dirA == dirB);
+            REPORTER_ASSERT(r, startA == startB);
+            REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
+        }
+    }
+    REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
+    REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
+    // closedness can affect convexity.
+    REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
+    if (a.knownToBeConvex()) {
+        REPORTER_ASSERT(r, pathA.isConvex());
+    }
+    if (b.knownToBeConvex()) {
+        REPORTER_ASSERT(r, pathB.isConvex());
+    }
+    REPORTER_ASSERT(r, a.bounds() == b.bounds());
+    REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
+    // Init these to suppress warnings.
+    SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
+    bool invertedLine[2] {true, true};
+    REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
+    // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
+    // doesn't (since the PE can set any fill type on its output path).
+    // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
+    // then they may disagree about inverseness.
+    if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
+        a.style().isDashed() == b.style().isDashed()) {
+        REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
+                           b.mayBeInverseFilledAfterStyling());
+    }
+    if (a.asLine(nullptr, nullptr)) {
+        REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
+        REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
+        REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
+        REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
+    }
+    REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
+}
+
+void test_inversions(skiatest::Reporter* r, const GrShape& shape, const Key& shapeKey) {
+    GrShape preserve = GrShape::MakeFilled(shape, GrShape::FillInversion::kPreserve);
+    Key preserveKey;
+    make_key(&preserveKey, preserve);
+
+    GrShape flip = GrShape::MakeFilled(shape, GrShape::FillInversion::kFlip);
+    Key flipKey;
+    make_key(&flipKey, flip);
+
+    GrShape inverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceInverted);
+    Key invertedKey;
+    make_key(&invertedKey, inverted);
+
+    GrShape noninverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceNoninverted);
+    Key noninvertedKey;
+    make_key(&noninvertedKey, noninverted);
+
+    if (invertedKey.count() || noninvertedKey.count()) {
+        REPORTER_ASSERT(r, invertedKey != noninvertedKey);
+    }
+    if (shape.style().isSimpleFill()) {
+        check_equivalence(r, shape, preserve, shapeKey, preserveKey);
+    }
+    if (shape.inverseFilled()) {
+        check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
+        check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
+    } else {
+        check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
+        check_equivalence(r, flip, inverted, flipKey, invertedKey);
+    }
+
+    GrShape doubleFlip = GrShape::MakeFilled(flip, GrShape::FillInversion::kFlip);
+    Key doubleFlipKey;
+    make_key(&doubleFlipKey, doubleFlip);
+    // It can be the case that the double flip has no key but preserve does. This happens when the
+    // original shape has an inherited style key. That gets dropped on the first inversion flip.
+    if (preserveKey.count() && !doubleFlipKey.count()) {
+        preserveKey.reset();
+    }
+    check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
+}
+
 namespace {
 /**
  * Geo is a factory for creating a GrShape from another representation. It also answers some
@@ -400,6 +573,9 @@
                 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline());
             }
         }
+        test_inversions(r, fBase, fBaseKey);
+        test_inversions(r, fAppliedPE, fAppliedPEKey);
+        test_inversions(r, fAppliedFull, fAppliedFullKey);
     }
 
     GrShape fBase;
@@ -439,137 +615,6 @@
     }
 }
 
-static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
-    SkPath path;
-    shape.asPath(&path);
-    if (shape.style().hasNonDashPathEffect()) {
-        return false;
-    }
-    const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
-    return strokeRecStyle == SkStrokeRec::kStroke_Style ||
-           strokeRecStyle == SkStrokeRec::kHairline_Style ||
-           (shape.style().isSimpleFill() && path.isConvex());
-}
-
-static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
-                              const Key& keyA, const Key& keyB) {
-    // GrShape only respects the input winding direction and start point for rrect shapes
-    // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
-    // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
-    // key will differ. GrShape will have canonicalized the direction and start point for the shape
-    // without the path effect. If *both* have path effects then they should have both preserved
-    // the direction and starting point.
-
-    // The asRRect() output params are all initialized just to silence compiler warnings about
-    // uninitialized variables.
-    SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
-    SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
-    unsigned startA = ~0U, startB = ~0U;
-    bool invertedA = true, invertedB = true;
-
-    bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
-    bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
-    bool aHasPE = a.style().hasPathEffect();
-    bool bHasPE = b.style().hasPathEffect();
-    bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
-    // GrShape will close paths with simple fill style.
-    bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
-    SkPath pathA, pathB;
-    a.asPath(&pathA);
-    b.asPath(&pathB);
-
-    // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
-    // non-inverse fill type  (or vice versa).
-    bool ignoreInversenessDifference = false;
-    if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
-        const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
-        const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
-        bool canDropInverse1 = s1->style().isDashed();
-        bool canDropInverse2 = s2->style().isDashed();
-        ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
-    }
-    bool ignoreWindingVsEvenOdd = false;
-    if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
-        SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
-        bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
-        bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
-        if (aCanChange != bCanChange) {
-            ignoreWindingVsEvenOdd = true;
-        }
-    }
-    if (allowSameRRectButDiffStartAndDir) {
-        REPORTER_ASSERT(r, rrectA == rrectB);
-        REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
-        REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
-    } else {
-        SkPath pA = pathA;
-        SkPath pB = pathB;
-        REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
-        REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
-        if (ignoreInversenessDifference) {
-            pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
-            pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
-        }
-        if (ignoreWindingVsEvenOdd) {
-            pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
-                                                  : SkPath::kEvenOdd_FillType);
-            pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
-                                                  : SkPath::kEvenOdd_FillType);
-        }
-        if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
-            REPORTER_ASSERT(r, keyA == keyB);
-        } else {
-            REPORTER_ASSERT(r, keyA != keyB);
-        }
-        if (allowedClosednessDiff) {
-            // GrShape will close paths with simple fill style. Make the non-filled path closed
-            // so that the comparision will succeed. Make sure both are closed before comparing.
-            pA.close();
-            pB.close();
-        }
-        REPORTER_ASSERT(r, pA == pB);
-        REPORTER_ASSERT(r, aIsRRect == bIsRRect);
-        if (aIsRRect) {
-            REPORTER_ASSERT(r, rrectA == rrectB);
-            REPORTER_ASSERT(r, dirA == dirB);
-            REPORTER_ASSERT(r, startA == startB);
-            REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
-        }
-    }
-    REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
-    REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
-    // closedness can affect convexity.
-    REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
-    if (a.knownToBeConvex()) {
-        REPORTER_ASSERT(r, pathA.isConvex());
-    }
-    if (b.knownToBeConvex()) {
-        REPORTER_ASSERT(r, pathB.isConvex());
-    }
-    REPORTER_ASSERT(r, a.bounds() == b.bounds());
-    REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
-    // Init these to suppress warnings.
-    SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
-    bool invertedLine[2] {true, true};
-    REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
-    // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
-    // doesn't (since the PE can set any fill type on its output path).
-    // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
-    // then they may disagree about inverseness.
-    if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
-        a.style().isDashed() == b.style().isDashed()) {
-        REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
-                           b.mayBeInverseFilledAfterStyling());
-    }
-    if (a.asLine(nullptr, nullptr)) {
-        REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
-        REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
-        REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
-        REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
-    }
-    REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
-}
-
 void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
                        ComparisonExpecation expectation) const {
     SkPath a, b;