Index: tests/GrShapeTest.cpp |
diff --git a/tests/GrShapeTest.cpp b/tests/GrShapeTest.cpp |
index 5d1fa908be434ac34f2251fdd9c856fa218762b8..9246c73f40f3419141bea2a97dbca5a1b3f8aba1 100644 |
--- a/tests/GrShapeTest.cpp |
+++ b/tests/GrShapeTest.cpp |
@@ -433,6 +433,76 @@ static sk_sp<SkPathEffect> make_null_dash() { |
return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f); |
} |
+////////////////////////////////////////////////////////////////////////////// |
+// These functions allow tests to check for special cases where style gets |
+// applied by GrShape in its constructor (without calling GrShape::applyStyle). |
+// These unfortunately rely on knowing details of GrShape's implementation. |
+// These predicates are factored out here to avoid littering the rest of the |
+// test code with GrShape implementation details. |
+ |
+static bool path_is_axis_aligned_line(const SkPath& path) { |
+ SkPoint pts[2]; |
+ if (!path.isLine(pts)) { |
+ return false; |
+ } |
+ return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY; |
+} |
+ |
+static bool path_is_unclosed_rect(const SkPath& path) { |
+ bool closed; |
+ return path.isRect(nullptr, &closed, nullptr) && !closed; |
+} |
+ |
+// Will a GrShape constructed from a geometry perform a geometric transformation if the style is |
+// simple fill that would not otherwise be applied. |
+template <typename GEO> static bool fill_changes_geom(const GEO& geo) { return false; } |
+template <> bool fill_changes_geom<SkPath>(const SkPath& path) { |
+ // unclosed rects get closed. Lines get turned into empty geometry |
+ return path_is_unclosed_rect(path) || (path.isLine(nullptr) && !path.isInverseFillType()); |
+} |
+ |
+// Will a GrShape constructed from the geometry with a stroke style (without path effect) perform a |
+// geometric transformation that applies the the stroke immediately without storing a stroke style. |
+template <typename GEO> static bool stroke_is_converted_to_fill(const GEO& geo) { return false; } |
+template <> bool stroke_is_converted_to_fill(const SkPath& path) { |
+ // converted to a rrect. |
+ return path_is_axis_aligned_line(path); |
+} |
+ |
+// Will a GrShape constructed from the geometry with a stroke-and-fill style (without path effect) |
+// perform a geometric transformation that applies the the stroke immediately without storing a |
+// stroke-and-fill style. |
+template <typename GEO> static bool stroke_and_fill_is_converted_to_fill(const GEO& geo, const SkPaint& paint); |
+template <> bool stroke_and_fill_is_converted_to_fill(const SkRect& rect, const SkPaint& paint) { |
+ SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); |
+ // Converted to an outset rectangle. |
+ return paint.getStrokeJoin() == SkPaint::kMiter_Join && |
+ paint.getStrokeMiter() >= SK_ScalarSqrt2; |
+} |
+template <> bool stroke_and_fill_is_converted_to_fill(const SkPath& path, const SkPaint& paint) { |
+ SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); |
+ if (path_is_axis_aligned_line(path)) { |
+ // The fill is ignored (zero area) and the stroke is converted to a rrect. |
+ return true; |
+ } |
+ SkRect rect; |
+ unsigned start; |
+ SkPath::Direction dir; |
+ if (SkPathPriv::IsSimpleClosedRect(path, &rect, &dir, &start)) { |
+ return stroke_and_fill_is_converted_to_fill<SkRect>(rect, paint); |
+ } |
+ return false; |
+} |
+template <> bool stroke_and_fill_is_converted_to_fill(const SkRRect& rr, const SkPaint& paint) { |
+ SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); |
+ if (rr.isRect()) { |
+ return stroke_and_fill_is_converted_to_fill<SkRect>(rr.rect(), paint); |
+ } |
+ return false; |
+} |
+ |
+////////////////////////////////////////////////////////////////////////////// |
+ |
template<typename GEO> |
static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { |
sk_sp<SkPathEffect> dashPE = make_dash(); |
@@ -457,7 +527,7 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { |
TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter); |
expectations.fPEHasValidKey = true; |
expectations.fPEHasEffect = false; |
- expectations.fStrokeApplies = true; |
+ expectations.fStrokeApplies = !stroke_is_converted_to_fill(geo); |
stroke2RoundBevelCase.testExpectations(reporter, expectations); |
TestCase(geo, stroke2RoundBevel, reporter).compare(reporter, stroke2RoundBevelCase, |
TestCase::kAllSame_ComparisonExpecation); |
@@ -472,12 +542,24 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { |
TestCase(geo, stroke2RoundBevelDash, reporter).compare(reporter, stroke2RoundBevelDashCase, |
TestCase::kAllSame_ComparisonExpecation); |
- fillCase.compare(reporter, stroke2RoundBevelCase, |
- TestCase::kSameUpToStroke_ComparisonExpecation); |
- fillCase.compare(reporter, stroke2RoundBevelDashCase, |
- TestCase::kSameUpToPE_ComparisonExpecation); |
- stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase, |
- TestCase::kSameUpToPE_ComparisonExpecation); |
+ if (fill_changes_geom(geo) || stroke_is_converted_to_fill(geo)) { |
+ fillCase.compare(reporter, stroke2RoundBevelCase, |
+ TestCase::kAllDifferent_ComparisonExpecation); |
+ fillCase.compare(reporter, stroke2RoundBevelDashCase, |
+ TestCase::kAllDifferent_ComparisonExpecation); |
+ } else { |
+ fillCase.compare(reporter, stroke2RoundBevelCase, |
+ TestCase::kSameUpToStroke_ComparisonExpecation); |
+ fillCase.compare(reporter, stroke2RoundBevelDashCase, |
+ TestCase::kSameUpToPE_ComparisonExpecation); |
+ } |
+ if (stroke_is_converted_to_fill(geo)) { |
+ stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase, |
+ TestCase::kAllDifferent_ComparisonExpecation); |
+ } else { |
+ stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase, |
+ TestCase::kSameUpToPE_ComparisonExpecation); |
+ } |
// Stroke and fill cases |
SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel; |
@@ -485,7 +567,7 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { |
TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter); |
expectations.fPEHasValidKey = true; |
expectations.fPEHasEffect = false; |
- expectations.fStrokeApplies = true; |
+ expectations.fStrokeApplies = !stroke_is_converted_to_fill(geo); |
stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations); |
TestCase(geo, stroke2RoundBevelAndFill, reporter).compare(reporter, |
stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation); |
@@ -495,7 +577,7 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { |
TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter); |
expectations.fPEHasValidKey = true; |
expectations.fPEHasEffect = false; |
- expectations.fStrokeApplies = true; |
+ expectations.fStrokeApplies = !stroke_is_converted_to_fill(geo); |
stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations); |
TestCase(geo, stroke2RoundBevelAndFillDash, reporter).compare( |
reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation); |
@@ -506,18 +588,17 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { |
hairline.setStyle(SkPaint::kStroke_Style); |
hairline.setStrokeWidth(0.f); |
TestCase hairlineCase(geo, hairline, reporter); |
- // Since hairline style doesn't change the SkPath data, it is keyed identically to fill. |
- hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); |
+ // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except |
+ // in the line and unclosed rect cases). |
+ if (fill_changes_geom(geo)) { |
+ hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation); |
+ } else { |
+ hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); |
+ } |
REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline()); |
REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline()); |
REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline()); |
-} |
-// Was the shape pre-style geometry stored as something other than a general path. This somewhat |
-// relies on knowing the internals of GrShape to know that this combination of tests is sufficient. |
-static bool is_non_path(const GrShape& shape) { |
- return shape.asRRect(nullptr, nullptr, nullptr, nullptr) || shape.asLine(nullptr, nullptr) || |
- shape.isEmpty(); |
} |
template<typename GEO> |
@@ -527,15 +608,6 @@ static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { |
static const SkScalar kS1 = 1.f; |
static const SkScalar kS2 = 2.f; |
- // Scale may affect the key for stroked results. However, there are two ways in which that may |
- // not occur. The base shape may instantly recognized that the geo + stroke is equivalent to |
- // a simple filled geometry. An example is a stroked line may become a filled rrect. |
- // Alternatively, after applying the style the output path may be recognized as a simpler shape |
- // causing the shape with style applied to have a purely geometric key rather than a key derived |
- // from the base geometry and the style params (and scale factor). |
- auto wasSimplified = [](const TestCase& c) { |
- return !c.baseShape().style().applies() || is_non_path(c.appliedFullStyleShape()); |
- }; |
SkPaint fill; |
TestCase fillCase1(geo, fill, reporter, kS1); |
TestCase fillCase2(geo, fill, reporter, kS2); |
@@ -556,8 +628,8 @@ static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { |
TestCase strokeCase1(geo, stroke, reporter, kS1); |
TestCase strokeCase2(geo, stroke, reporter, kS2); |
// Scale affects the stroke |
- if (wasSimplified(strokeCase1)) { |
- REPORTER_ASSERT(reporter, wasSimplified(strokeCase2)); |
+ if (stroke_is_converted_to_fill(geo)) { |
+ REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies()); |
strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation); |
} else { |
strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation); |
@@ -568,14 +640,8 @@ static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { |
TestCase strokeDashCase1(geo, strokeDash, reporter, kS1); |
TestCase strokeDashCase2(geo, strokeDash, reporter, kS2); |
// Scale affects the dash and the stroke. |
- if (wasSimplified(strokeDashCase1)) { |
- REPORTER_ASSERT(reporter, wasSimplified(strokeDashCase2)); |
- strokeDashCase1.compare(reporter, strokeDashCase2, |
- TestCase::kAllSame_ComparisonExpecation); |
- } else { |
- strokeDashCase1.compare(reporter, strokeDashCase2, |
- TestCase::kSameUpToPE_ComparisonExpecation); |
- } |
+ strokeDashCase1.compare(reporter, strokeDashCase2, |
+ TestCase::kSameUpToPE_ComparisonExpecation); |
// Stroke and fill cases |
SkPaint strokeAndFill = stroke; |
@@ -587,13 +653,11 @@ static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { |
// Dash is ignored for stroke and fill |
TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1); |
TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2); |
- // Scale affects the stroke. Scale affects the stroke, but check to make sure this didn't |
- // become a simpler shape (e.g. stroke-and-filled rect can become a rect), in which case the |
- // scale shouldn't matter and the geometries should agree. |
- if (wasSimplified(strokeAndFillCase1)) { |
- REPORTER_ASSERT(reporter, wasSimplified(strokeAndFillCase1)); |
- REPORTER_ASSERT(reporter, wasSimplified(strokeAndFillDashCase1)); |
- REPORTER_ASSERT(reporter, wasSimplified(strokeAndFillDashCase2)); |
+ // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g. |
+ // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the |
+ // geometries should agree. |
+ if (stroke_and_fill_is_converted_to_fill(geo, strokeAndFillDash)) { |
+ REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies()); |
strokeAndFillCase1.compare(reporter, strokeAndFillCase2, |
TestCase::kAllSame_ComparisonExpecation); |
strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2, |
@@ -629,12 +693,12 @@ static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo, |
if (paramAffectsStroke) { |
// If stroking is immediately incorporated into a geometric transformation then the base |
// shapes will differ. |
- if (strokeACase.baseShape().style().applies()) { |
+ if (stroke_is_converted_to_fill(geo)) { |
strokeACase.compare(reporter, strokeBCase, |
- TestCase::kSameUpToStroke_ComparisonExpecation); |
+ TestCase::kAllDifferent_ComparisonExpecation); |
} else { |
strokeACase.compare(reporter, strokeBCase, |
- TestCase::kAllDifferent_ComparisonExpecation); |
+ TestCase::kSameUpToStroke_ComparisonExpecation); |
} |
} else { |
strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation); |
@@ -649,12 +713,13 @@ static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo, |
if (paramAffectsStroke) { |
// If stroking is immediately incorporated into a geometric transformation then the base |
// shapes will differ. |
- if (strokeAndFillACase.baseShape().style().applies()) { |
+ if (stroke_and_fill_is_converted_to_fill(geo, strokeAndFillA) || |
+ stroke_and_fill_is_converted_to_fill(geo, strokeAndFillB)) { |
strokeAndFillACase.compare(reporter, strokeAndFillBCase, |
- TestCase::kSameUpToStroke_ComparisonExpecation); |
+ TestCase::kAllDifferent_ComparisonExpecation); |
} else { |
strokeAndFillACase.compare(reporter, strokeAndFillBCase, |
- TestCase::kAllDifferent_ComparisonExpecation); |
+ TestCase::kSameUpToStroke_ComparisonExpecation); |
} |
} else { |
strokeAndFillACase.compare(reporter, strokeAndFillBCase, |
@@ -677,13 +742,7 @@ static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo, |
TestCase dashACase(geo, dashA, reporter); |
TestCase dashBCase(geo, dashB, reporter); |
if (paramAffectsDashAndStroke) { |
- // If stroking is immediately incorporated into a geometric transformation then the base |
- // shapes will differ. |
- if (dashACase.baseShape().style().applies()) { |
- dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation); |
- } else { |
- dashACase.compare(reporter, dashBCase, TestCase::kAllDifferent_ComparisonExpecation); |
- } |
+ dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation); |
} else { |
dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation); |
} |
@@ -801,9 +860,21 @@ void test_null_dash(skiatest::Reporter* reporter, const GEO& geo) { |
TestCase dashCase(geo, dash, reporter); |
TestCase nullDashCase(geo, nullDash, reporter); |
- nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation); |
+ // We expect the null dash to be ignored so nullDashCase should match strokeCase, always. |
nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation); |
- nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation); |
+ // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation |
+ // on construction in order to determine how to compare the fill and stroke. |
+ if (fill_changes_geom(geo) || stroke_is_converted_to_fill(geo)) { |
+ nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation); |
+ } else { |
+ nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation); |
+ } |
+ // In the null dash case we may immediately convert to a fill, but not for the normal dash case. |
+ if (stroke_is_converted_to_fill(geo)) { |
+ nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation); |
+ } else { |
+ nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation); |
+ } |
} |
template <typename GEO> |
@@ -848,8 +919,16 @@ void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) |
peStroke.setStyle(SkPaint::kStroke_Style); |
TestCase geoPEStrokeCase(geo, peStroke, reporter); |
- fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation); |
- fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation); |
+ // Check whether constructing the filled case would cause the base shape to have a different |
+ // geometry (because of a geometric transformation upon initial GrShape construction). |
+ if (fill_changes_geom(geo)) { |
+ fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation); |
+ fillGeoCase.compare(reporter, geoPEStrokeCase, |
+ TestCase::kAllDifferent_ComparisonExpecation); |
+ } else { |
+ fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation); |
+ fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation); |
+ } |
geoPECase.compare(reporter, geoPEStrokeCase, |
TestCase::kSameUpToStroke_ComparisonExpecation); |
@@ -1197,6 +1276,9 @@ void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) { |
strokeRecs[kStroke].setStrokeStyle(2.f); |
strokeRecs[kHairline].setHairlineStyle(); |
strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true); |
+ // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before |
+ // applyStyle() is called. |
+ strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f); |
sk_sp<SkPathEffect> dashEffect = make_dash(); |
static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs)); |
@@ -1698,16 +1780,9 @@ DEF_TEST(GrShape, reporter) { |
} |
} |
const SkPath& path = testPath.fPath; |
- // These tests all assume that the original GrShape for fill and stroke will be the |
- // same. |
- // However, that is not the case in special cases (e.g. an unclosed rect becomes a RRect |
- // GrShape with a fill style but becomes a Path GrShape when stroked). Similarly, a path |
- // that is a line becomes empty when filled but is special-cased as a line when stroked. |
- if (testPath.fIsRRectForFill == testPath.fIsRRectForStroke && !testPath.fIsLine) { |
- test_basic(reporter, path); |
- test_null_dash(reporter, path); |
- test_path_effect_makes_rrect(reporter, path); |
- } |
+ test_basic(reporter, path); |
+ test_null_dash(reporter, path); |
+ test_path_effect_makes_rrect(reporter, path); |
test_scale(reporter, path); |
// This test uses a stroking paint, hence use of fIsRRectForStroke |
test_volatile_path(reporter, path, testPath.fIsRRectForStroke || testPath.fIsLine); |