/* * 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 #include #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; 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 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 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 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 static void test_stroke_param(skiatest::Reporter* reporter, const SkRRect& rrect, std::function 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 using DashFactoryFn = sk_sp(*)(); for (DashFactoryFn 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 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( reporter, rr, [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, SkIntToScalar(2), SkIntToScalar(4)); test_stroke_param( reporter, rr, [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);}, SkPaint::kButt_Cap, SkPaint::kRound_Cap); test_stroke_param( reporter, rr, [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, SkPaint::kMiter_Join, SkPaint::kRound_Join); test_miter_limit(reporter, rr); } } #endif