Index: tests/GrShapeTest.cpp |
diff --git a/tests/GrShapeTest.cpp b/tests/GrShapeTest.cpp |
index 0e83b697d86819af1d9d5af30374fee537fb4876..3f23a1c2ac0766599e11ec8cbc560734e4244293 100644 |
--- a/tests/GrShapeTest.cpp |
+++ b/tests/GrShapeTest.cpp |
@@ -13,6 +13,7 @@ |
#include "SkCanvas.h" |
#include "SkDashPathEffect.h" |
#include "SkPath.h" |
+#include "SkPathOps.h" |
#include "SkSurface.h" |
using Key = SkTArray<uint32_t>; |
@@ -29,6 +30,12 @@ static bool make_key(Key* key, const GrShape& shape) { |
return true; |
} |
+static bool paths_fill_same(const SkPath& a, const SkPath& b) { |
+ SkPath pathXor; |
+ Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor); |
+ return pathXor.isEmpty(); |
+} |
+ |
static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) { |
static constexpr int kRes = 2000; |
// This tolerance is in units of 1/kRes fractions of the bounds width/height. |
@@ -134,7 +141,17 @@ private: |
SkPath a, b; |
fAppliedPEThenStroke.asPath(&a); |
fAppliedFull.asPath(&b); |
- REPORTER_ASSERT(r, a == b); |
+ // If the output of the path effect is a rrect then it is possible for a and b to be |
+ // different paths that fill identically. The reason is that fAppliedFull will do this: |
+ // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path |
+ // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However, |
+ // now that there is no longer a path effect, the direction and starting index get |
+ // canonicalized before the stroke. |
+ if (fAppliedPE.asRRect(nullptr, nullptr, nullptr)) { |
+ REPORTER_ASSERT(r, paths_fill_same(a, b)); |
+ } else { |
+ REPORTER_ASSERT(r, a == b); |
+ } |
REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty()); |
SkPath path; |
@@ -227,53 +244,71 @@ void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations e |
} |
} |
-void TestCase::compare(skiatest::Reporter* reporter, const TestCase& that, |
+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 aIsRRect = a.asRRect(&rrectA, &dirA, &startA); |
+ bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB); |
+ bool aHasPE = a.style().hasPathEffect(); |
+ bool bHasPE = b.style().hasPathEffect(); |
+ bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE); |
+ SkPath pathA, pathB; |
+ a.asPath(&pathA); |
+ b.asPath(&pathB); |
+ if (allowSameRRectButDiffStartAndDir) { |
+ REPORTER_ASSERT(r, rrectA == rrectB); |
+ REPORTER_ASSERT(r, paths_fill_same(pathA, pathB)); |
+ } else { |
+ REPORTER_ASSERT(r, pathA == pathB); |
+ REPORTER_ASSERT(r, keyA == keyB); |
+ 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, a.isEmpty() == b.isEmpty()); |
+ REPORTER_ASSERT(r, a.knownToBeClosed() == b.knownToBeClosed()); |
+ REPORTER_ASSERT(r, a.bounds() == b.bounds()); |
+} |
+ |
+void TestCase::compare(skiatest::Reporter* r, const TestCase& that, |
ComparisonExpecation expectation) const { |
SkPath a, b; |
switch (expectation) { |
case kAllDifferent_ComparisonExpecation: |
- REPORTER_ASSERT(reporter, fBaseKey != that.fBaseKey); |
- REPORTER_ASSERT(reporter, fAppliedPEKey != that.fAppliedPEKey); |
- REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey); |
+ REPORTER_ASSERT(r, fBaseKey != that.fBaseKey); |
+ REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey); |
+ REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); |
break; |
case kSameUpToPE_ComparisonExpecation: |
- REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey); |
- fBase.asPath(&a); |
- that.fBase.asPath(&b); |
- REPORTER_ASSERT(reporter, a == b); |
- REPORTER_ASSERT(reporter, fBase.isEmpty() == that.fBase.isEmpty()); |
- REPORTER_ASSERT(reporter, fAppliedPEKey != that.fAppliedPEKey); |
- REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey); |
+ check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); |
+ REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey); |
+ REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); |
break; |
case kSameUpToStroke_ComparisonExpecation: |
- REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey); |
- fBase.asPath(&a); |
- that.fBase.asPath(&b); |
- REPORTER_ASSERT(reporter, a == b); |
- REPORTER_ASSERT(reporter, fBase.isEmpty() == that.fBase.isEmpty()); |
- REPORTER_ASSERT(reporter, fAppliedPEKey == that.fAppliedPEKey); |
- fAppliedPE.asPath(&a); |
- that.fAppliedPE.asPath(&b); |
- REPORTER_ASSERT(reporter, a == b); |
- REPORTER_ASSERT(reporter, fAppliedPE.isEmpty() == that.fAppliedPE.isEmpty()); |
- REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey); |
+ check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); |
+ check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey); |
+ REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); |
break; |
case kAllSame_ComparisonExpecation: |
- REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey); |
- fBase.asPath(&a); |
- that.fBase.asPath(&b); |
- REPORTER_ASSERT(reporter, a == b); |
- REPORTER_ASSERT(reporter, fBase.isEmpty() == that.fBase.isEmpty()); |
- REPORTER_ASSERT(reporter, fAppliedPEKey == that.fAppliedPEKey); |
- fAppliedPE.asPath(&a); |
- that.fAppliedPE.asPath(&b); |
- REPORTER_ASSERT(reporter, a == b); |
- REPORTER_ASSERT(reporter, fAppliedPE.isEmpty() == that.fAppliedPE.isEmpty()); |
- REPORTER_ASSERT(reporter, fAppliedFullKey == that.fAppliedFullKey); |
- fAppliedFull.asPath(&a); |
- that.fAppliedFull.asPath(&b); |
- REPORTER_ASSERT(reporter, a == b); |
- REPORTER_ASSERT(reporter, fAppliedFull.isEmpty() == that.fAppliedFull.isEmpty()); |
+ check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); |
+ check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey); |
+ check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey, |
+ that.fAppliedFullKey); |
break; |
} |
} |
@@ -419,10 +454,12 @@ static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { |
// Scale affects the stroke. Though, this can wind up creating a rect when the input is a rect. |
// In that case we wind up with a pure geometry key and the geometries are the same. |
SkRRect rrect; |
- if (strokeAndFillCase1.appliedFullStyleShape().asRRect(&rrect)) { |
+ if (strokeAndFillCase1.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)) { |
// We currently only expect to get here in the rect->rect case. |
REPORTER_ASSERT(reporter, rrect.isRect()); |
- REPORTER_ASSERT(reporter, strokeAndFillCase1.baseShape().asRRect(&rrect) && rrect.isRect()); |
+ REPORTER_ASSERT(reporter, |
+ strokeAndFillCase1.baseShape().asRRect(&rrect, nullptr, nullptr) && |
+ rrect.isRect()); |
strokeAndFillCase1.compare(reporter, strokeAndFillCase2, |
TestCase::kAllSame_ComparisonExpecation); |
} else { |
@@ -661,20 +698,22 @@ void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) |
SkRRect rrect; |
// Applying the path effect should make a SkRRect shape. There is no further stroking in the |
// geoPECase, so the full style should be the same as just the PE. |
- REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect)); |
+ REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr)); |
REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); |
REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey()); |
- REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect)); |
+ REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)); |
REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); |
REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey()); |
// In the PE+stroke case applying the full style should be the same as just stroking the rrect. |
- REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect)); |
+ REPORTER_ASSERT(reporter, |
+ geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr)); |
REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); |
REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey()); |
- REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect)); |
+ REPORTER_ASSERT(reporter, |
+ !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)); |
REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == |
rrectStrokeCase.appliedFullStyleKey()); |
} |
@@ -749,21 +788,25 @@ void test_make_hairline_path_effect(skiatest::Reporter* reporter, const GEO& geo |
TestCase peCase(geo, pe, reporter); |
- SkPath a, b; |
+ SkPath a, b, c; |
peCase.baseShape().asPath(&a); |
peCase.appliedPathEffectShape().asPath(&b); |
- REPORTER_ASSERT(reporter, a == b); |
- peCase.appliedFullStyleShape().asPath(&b); |
- REPORTER_ASSERT(reporter, a == b); |
- REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline()); |
- REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline()); |
+ peCase.appliedFullStyleShape().asPath(&c); |
if (isNonPath) { |
- REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey() == peCase.baseKey()); |
- REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey() == peCase.baseKey()); |
+ // RRect types can have a change in start index or direction after the PE is applied. This |
+ // is because once the PE is applied, GrShape may canonicalize the dir and index since it |
+ // is not germane to the styling any longer. |
+ // Instead we just check that the paths would fill the same both before and after styling. |
+ REPORTER_ASSERT(reporter, paths_fill_same(a, b)); |
+ REPORTER_ASSERT(reporter, paths_fill_same(a, c)); |
} else { |
+ REPORTER_ASSERT(reporter, a == b); |
+ REPORTER_ASSERT(reporter, a == c); |
REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty()); |
REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty()); |
} |
+ REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline()); |
+ REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline()); |
} |
/** |
@@ -894,10 +937,34 @@ void test_empty_shape(skiatest::Reporter* reporter) { |
} |
DEF_TEST(GrShape, reporter) { |
- sk_sp<SkPathEffect> dashPE = make_dash(); |
+ for (auto r : { SkRect::MakeWH(10, 20), |
+ SkRect::MakeWH(-10, -20), |
+ SkRect::MakeWH(-10, 20), |
+ SkRect::MakeWH(10, -20)}) { |
+ test_basic(reporter, r); |
+ test_scale(reporter, r); |
+ test_dash_fill(reporter, r); |
+ test_null_dash(reporter, r); |
+ // Test modifying various stroke params. |
+ test_stroke_param<SkRect, SkScalar>( |
+ reporter, r, |
+ [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, |
+ SkIntToScalar(2), SkIntToScalar(4)); |
+ test_stroke_param<SkRect, SkPaint::Join>( |
+ reporter, r, |
+ [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, |
+ SkPaint::kMiter_Join, SkPaint::kRound_Join); |
+ test_stroke_cap(reporter, r); |
+ test_miter_limit(reporter, r); |
+ test_path_effect_makes_rrect(reporter, r); |
+ test_unknown_path_effect(reporter, r); |
+ test_path_effect_makes_empty_shape(reporter, r); |
+ test_make_hairline_path_effect(reporter, r, true); |
+ } |
for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)), |
- SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4)}) { |
+ SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4), |
+ SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) { |
test_basic(reporter, rr); |
test_scale(reporter, rr); |
test_dash_fill(reporter, rr); |
@@ -984,11 +1051,12 @@ DEF_TEST(GrShape, reporter) { |
TestCase fillPathCase(path, fillPaint, reporter); |
SkRRect rrect; |
REPORTER_ASSERT(reporter, testPath.fIsRRectForFill == |
- fillPathCase.baseShape().asRRect(&rrect)); |
+ fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr)); |
if (testPath.fIsRRectForFill) { |
+ TestCase fillPathCase2(path, fillPaint, reporter); |
REPORTER_ASSERT(reporter, rrect == testPath.fRRect); |
TestCase fillRRectCase(rrect, fillPaint, reporter); |
- fillPathCase.compare(reporter, fillRRectCase, TestCase::kAllSame_ComparisonExpecation); |
+ fillPathCase2.compare(reporter, fillRRectCase, TestCase::kAllSame_ComparisonExpecation); |
} |
SkPaint strokePaint; |
@@ -996,12 +1064,12 @@ DEF_TEST(GrShape, reporter) { |
strokePaint.setStyle(SkPaint::kStroke_Style); |
TestCase strokePathCase(path, strokePaint, reporter); |
REPORTER_ASSERT(reporter, testPath.fIsRRectForStroke == |
- strokePathCase.baseShape().asRRect(&rrect)); |
+ strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr)); |
if (testPath.fIsRRectForStroke) { |
REPORTER_ASSERT(reporter, rrect == testPath.fRRect); |
TestCase strokeRRectCase(rrect, strokePaint, reporter); |
strokePathCase.compare(reporter, strokeRRectCase, |
- TestCase::kAllSame_ComparisonExpecation); |
+ TestCase::kAllSame_ComparisonExpecation); |
} |
} |