| /* |
| * Copyright 2016 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <initializer_list> |
| #include <functional> |
| #include "Test.h" |
| #if SK_SUPPORT_GPU |
| #include "GrShape.h" |
| #include "SkPath.h" |
| #include "SkDashPathEffect.h" |
| |
| namespace { |
| class TestCase { |
| public: |
| TestCase(const SkRRect& rrect, const SkPaint& paint) : fBase(rrect, paint) { |
| this->init(); |
| } |
| |
| struct SelfExpectations { |
| bool fPEHasEffect; |
| bool fPEHasValidKey; |
| bool fStrokeApplies; |
| }; |
| |
| void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const; |
| |
| enum ComparisonExpecation { |
| kAllDifferent_ComparisonExpecation, |
| kSameUpToPE_ComparisonExpecation, |
| kSameUpToStroke_ComparisonExpecation, |
| kAllSame_ComparisonExpecation, |
| }; |
| |
| void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const; |
| |
| private: |
| void init() { |
| fAppliedPE = fBase.applyPathEffect(); |
| fAppliedPEThenStroke = fAppliedPE.applyFullStyle(); |
| fAppliedFull = fBase.applyFullStyle(); |
| |
| fBaseKeyIsValid = MakeKey(&fBaseKey, fBase); |
| fAppliedPEKeyIsValid = MakeKey(&fAppliedPEKey, fAppliedPE); |
| fAppliedPEThenStrokeKeyIsValid = MakeKey(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke); |
| fAppliedFullKeyIsValid = MakeKey(&fAppliedFullKey, fAppliedFull); |
| } |
| |
| using Key = SkTArray<uint32_t>; |
| |
| static bool MakeKey(Key* key, const GrShape& shape) { |
| int size = shape.unstyledKeySize(); |
| if (size <= 0) { |
| return false; |
| } |
| key->reset(size); |
| shape.writeUnstyledKey(key->begin()); |
| return true; |
| } |
| |
| GrShape fBase; |
| GrShape fAppliedPE; |
| GrShape fAppliedPEThenStroke; |
| GrShape fAppliedFull; |
| |
| Key fBaseKey; |
| Key fAppliedPEKey; |
| Key fAppliedPEThenStrokeKey; |
| Key fAppliedFullKey; |
| |
| bool fBaseKeyIsValid; |
| bool fAppliedPEKeyIsValid; |
| bool fAppliedPEThenStrokeKeyIsValid; |
| bool fAppliedFullKeyIsValid; |
| }; |
| |
| void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const { |
| // Applying the path effect and then the stroke should always be the same as applying |
| // both in one go. |
| REPORTER_ASSERT(reporter, fAppliedPEThenStrokeKey == fAppliedFullKey); |
| // The base's key should always be valid (unless the path is volatile) |
| REPORTER_ASSERT(reporter, fBaseKeyIsValid); |
| if (expectations.fPEHasEffect) { |
| REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey); |
| REPORTER_ASSERT(reporter, expectations.fPEHasEffect == fAppliedPEKeyIsValid); |
| REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey); |
| REPORTER_ASSERT(reporter, expectations.fPEHasEffect == fAppliedFullKeyIsValid); |
| if (expectations.fStrokeApplies && expectations.fPEHasValidKey) { |
| REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey); |
| REPORTER_ASSERT(reporter, expectations.fPEHasEffect == fAppliedFullKeyIsValid); |
| } |
| } else { |
| REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey); |
| if (expectations.fStrokeApplies) { |
| REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey); |
| } else { |
| REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey); |
| } |
| } |
| } |
| |
| void TestCase::compare(skiatest::Reporter* reporter, const TestCase& that, |
| ComparisonExpecation expectation) const { |
| switch (expectation) { |
| case kAllDifferent_ComparisonExpecation: |
| REPORTER_ASSERT(reporter, fBaseKey != that.fBaseKey); |
| REPORTER_ASSERT(reporter, fAppliedPEKey != that.fAppliedPEKey); |
| REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey); |
| break; |
| case kSameUpToPE_ComparisonExpecation: |
| REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey); |
| REPORTER_ASSERT(reporter, fAppliedPEKey != that.fAppliedPEKey); |
| REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey); |
| break; |
| case kSameUpToStroke_ComparisonExpecation: |
| REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey); |
| REPORTER_ASSERT(reporter, fAppliedPEKey == that.fAppliedPEKey); |
| REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey); |
| break; |
| case kAllSame_ComparisonExpecation: |
| REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey); |
| REPORTER_ASSERT(reporter, fAppliedPEKey == that.fAppliedPEKey); |
| REPORTER_ASSERT(reporter, fAppliedFullKey == that.fAppliedFullKey); |
| break; |
| } |
| } |
| } // namespace |
| |
| static sk_sp<SkPathEffect> make_dash() { |
| static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f }; |
| static const SkScalar kPhase = 0.75; |
| return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase); |
| } |
| |
| static sk_sp<SkPathEffect> make_null_dash() { |
| static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0}; |
| return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f); |
| } |
| |
| static void test_basic(skiatest::Reporter* reporter, const SkRRect& rrect) { |
| sk_sp<SkPathEffect> dashPE = make_dash(); |
| |
| TestCase::SelfExpectations expectations; |
| SkPaint fill; |
| |
| TestCase fillCase(rrect, fill); |
| expectations.fPEHasEffect = false; |
| expectations.fPEHasValidKey = false; |
| expectations.fStrokeApplies = false; |
| fillCase.testExpectations(reporter, expectations); |
| // Test that another GrShape instance built from the same primitive is the same. |
| TestCase(rrect, fill).compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); |
| |
| SkPaint stroke2RoundBevel; |
| stroke2RoundBevel.setStyle(SkPaint::kStroke_Style); |
| stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap); |
| stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join); |
| stroke2RoundBevel.setStrokeWidth(2.f); |
| TestCase stroke2RoundBevelCase(rrect, stroke2RoundBevel); |
| expectations.fPEHasValidKey = true; |
| expectations.fPEHasEffect = false; |
| expectations.fStrokeApplies = true; |
| stroke2RoundBevelCase.testExpectations(reporter, expectations); |
| TestCase(rrect, stroke2RoundBevel).compare(reporter, stroke2RoundBevelCase, |
| TestCase::kAllSame_ComparisonExpecation); |
| |
| SkPaint stroke2RoundBevelDash = stroke2RoundBevel; |
| stroke2RoundBevelDash.setPathEffect(make_dash()); |
| TestCase stroke2RoundBevelDashCase(rrect, stroke2RoundBevelDash); |
| expectations.fPEHasValidKey = true; |
| expectations.fPEHasEffect = true; |
| expectations.fStrokeApplies = true; |
| stroke2RoundBevelDashCase.testExpectations(reporter, expectations); |
| TestCase(rrect, stroke2RoundBevelDash).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); |
| } |
| |
| template <typename T> |
| static void test_stroke_param(skiatest::Reporter* reporter, const SkRRect& rrect, |
| std::function<void(SkPaint*, T)> setter, T a, T b) { |
| // Set the stroke width so that we don't get hairline. However, call the function second so that |
| // it can override. |
| SkPaint strokeA; |
| strokeA.setStyle(SkPaint::kStroke_Style); |
| strokeA.setStrokeWidth(2.f); |
| setter(&strokeA, a); |
| SkPaint strokeB; |
| strokeB.setStyle(SkPaint::kStroke_Style); |
| strokeB.setStrokeWidth(2.f); |
| setter(&strokeB, b); |
| |
| TestCase strokeACase(rrect, strokeA); |
| TestCase strokeBCase(rrect, strokeB); |
| strokeACase.compare(reporter, strokeBCase, TestCase::kSameUpToStroke_ComparisonExpecation); |
| |
| // Make sure stroking params don't affect fill style. |
| SkPaint fillA = strokeA, fillB = strokeB; |
| fillA.setStyle(SkPaint::kFill_Style); |
| fillB.setStyle(SkPaint::kFill_Style); |
| TestCase fillACase(rrect, fillA); |
| TestCase fillBCase(rrect, fillB); |
| fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation); |
| |
| // Make sure just applying the dash but not stroke gives the same key for both stroking |
| // variations. |
| SkPaint dashA = strokeA, dashB = strokeB; |
| dashA.setPathEffect(make_dash()); |
| dashB.setPathEffect(make_dash()); |
| TestCase dashACase(rrect, dashA); |
| TestCase dashBCase(rrect, dashB); |
| dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation); |
| } |
| |
| static void test_miter_limit(skiatest::Reporter* reporter, const SkRRect& rrect) { |
| // Miter limit should only matter when stroking with miter joins. It shouldn't affect other |
| // joins or fills. |
| SkPaint miterA; |
| miterA.setStyle(SkPaint::kStroke_Style); |
| miterA.setStrokeWidth(2.f); |
| miterA.setStrokeJoin(SkPaint::kMiter_Join); |
| miterA.setStrokeMiter(0.5f); |
| SkPaint miterB = miterA; |
| miterA.setStrokeMiter(0.6f); |
| |
| TestCase miterACase(rrect, miterA); |
| TestCase miterBCase(rrect, miterB); |
| miterACase.compare(reporter, miterBCase, TestCase::kSameUpToStroke_ComparisonExpecation); |
| |
| SkPaint noMiterA = miterA, noMiterB = miterB; |
| noMiterA.setStrokeJoin(SkPaint::kRound_Join); |
| noMiterB.setStrokeJoin(SkPaint::kRound_Join); |
| TestCase noMiterACase(rrect, noMiterA); |
| TestCase noMiterBCase(rrect, noMiterB); |
| noMiterACase.compare(reporter, noMiterBCase, TestCase::kAllSame_ComparisonExpecation); |
| |
| SkPaint fillA = miterA, fillB = miterB; |
| fillA.setStyle(SkPaint::kFill_Style); |
| fillB.setStyle(SkPaint::kFill_Style); |
| TestCase fillACase(rrect, fillA); |
| TestCase fillBCase(rrect, fillB); |
| fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation); |
| } |
| |
| static void test_dash_fill(skiatest::Reporter* reporter, const SkRRect& rrect) { |
| // A dash with no stroke should have no effect |
| for (auto md : {make_dash, make_null_dash}) { |
| SkPaint dashFill; |
| dashFill.setPathEffect(md()); |
| TestCase dashFillCase(rrect, dashFill); |
| |
| TestCase fillCase(rrect, SkPaint()); |
| dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); |
| } |
| } |
| |
| void test_null_dash(skiatest::Reporter* reporter, const SkRRect& rrect) { |
| SkPaint fill; |
| SkPaint stroke; |
| stroke.setStyle(SkPaint::kStroke_Style); |
| stroke.setStrokeWidth(1.f); |
| SkPaint dash; |
| dash.setStyle(SkPaint::kStroke_Style); |
| dash.setStrokeWidth(1.f); |
| dash.setPathEffect(make_dash()); |
| SkPaint nullDash; |
| nullDash.setStyle(SkPaint::kStroke_Style); |
| nullDash.setStrokeWidth(1.f); |
| nullDash.setPathEffect(make_null_dash()); |
| |
| TestCase fillCase(rrect, fill); |
| TestCase strokeCase(rrect, stroke); |
| TestCase dashCase(rrect, dash); |
| TestCase nullDashCase(rrect, nullDash); |
| |
| nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation); |
| nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation); |
| nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation); |
| } |
| |
| DEF_TEST(GrShape, reporter) { |
| sk_sp<SkPathEffect> dashPE = make_dash(); |
| |
| for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)), |
| SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4)}) { |
| test_basic(reporter, rr); |
| test_dash_fill(reporter, rr); |
| test_null_dash(reporter, rr); |
| // Test modifying various stroke params. |
| test_stroke_param<SkScalar>( |
| reporter, rr, |
| [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, |
| SkIntToScalar(2), SkIntToScalar(4)); |
| test_stroke_param<SkPaint::Cap>( |
| reporter, rr, |
| [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);}, |
| SkPaint::kButt_Cap, SkPaint::kRound_Cap); |
| test_stroke_param<SkPaint::Join>( |
| reporter, rr, |
| [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, |
| SkPaint::kMiter_Join, SkPaint::kRound_Join); |
| test_miter_limit(reporter, rr); |
| } |
| } |
| |
| #endif |