GrShapeTest.cpp revision 06115ee4300ef6756729dfbcb3e2fc70ebf0413a
1/* 2 * Copyright 2016 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include <initializer_list> 9#include <functional> 10#include "Test.h" 11#if SK_SUPPORT_GPU 12#include "GrShape.h" 13#include "SkCanvas.h" 14#include "SkDashPathEffect.h" 15#include "SkPath.h" 16#include "SkPathOps.h" 17#include "SkSurface.h" 18 19using Key = SkTArray<uint32_t>; 20 21static bool make_key(Key* key, const GrShape& shape) { 22 int size = shape.unstyledKeySize(); 23 if (size <= 0) { 24 key->reset(0); 25 return false; 26 } 27 SkASSERT(size); 28 key->reset(size); 29 shape.writeUnstyledKey(key->begin()); 30 return true; 31} 32 33static bool paths_fill_same(const SkPath& a, const SkPath& b) { 34 SkPath pathXor; 35 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor); 36 return pathXor.isEmpty(); 37} 38 39static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) { 40 static constexpr int kRes = 2000; 41 // This tolerance is in units of 1/kRes fractions of the bounds width/height. 42 static constexpr int kTol = 0; 43 GR_STATIC_ASSERT(kRes % 4 == 0); 44 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes); 45 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info); 46 surface->getCanvas()->clear(0x0); 47 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2); 48 SkMatrix matrix; 49 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit); 50 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol)); 51 surface->getCanvas()->clipRect(clip, SkRegion::kDifference_Op); 52 surface->getCanvas()->concat(matrix); 53 SkPaint whitePaint; 54 whitePaint.setColor(SK_ColorWHITE); 55 surface->getCanvas()->drawPath(path, whitePaint); 56 SkPixmap pixmap; 57 surface->getCanvas()->peekPixels(&pixmap); 58#if defined(SK_BUILD_FOR_WIN) 59 // The static constexpr version in #else causes cl.exe to crash. 60 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1)); 61#else 62 static constexpr uint8_t kZeros[kRes] = {0}; 63#endif 64 for (int y = 0; y < kRes/4; ++y) { 65 const uint8_t* row = pixmap.addr8(0, y); 66 if (0 != memcmp(kZeros, row, kRes)) { 67 return false; 68 } 69 } 70#ifdef SK_BUILD_FOR_WIN 71 free(const_cast<uint8_t*>(kZeros)); 72#endif 73 return true; 74} 75 76namespace { 77class TestCase { 78public: 79 template <typename GEO> 80 TestCase(const GEO& geo, const SkPaint& paint, skiatest::Reporter* r, 81 SkScalar scale = SK_Scalar1) : fBase(geo, paint) { 82 this->init(r, scale); 83 } 84 85 struct SelfExpectations { 86 bool fPEHasEffect; 87 bool fPEHasValidKey; 88 bool fStrokeApplies; 89 }; 90 91 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const; 92 93 enum ComparisonExpecation { 94 kAllDifferent_ComparisonExpecation, 95 kSameUpToPE_ComparisonExpecation, 96 kSameUpToStroke_ComparisonExpecation, 97 kAllSame_ComparisonExpecation, 98 }; 99 100 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const; 101 102 const GrShape& baseShape() const { return fBase; } 103 const GrShape& appliedPathEffectShape() const { return fAppliedPE; } 104 const GrShape& appliedFullStyleShape() const { return fAppliedFull; } 105 106 // The returned array's count will be 0 if the key shape has no key. 107 const Key& baseKey() const { return fBaseKey; } 108 const Key& appliedPathEffectKey() const { return fAppliedPEKey; } 109 const Key& appliedFullStyleKey() const { return fAppliedFullKey; } 110 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; } 111 112private: 113 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) { 114 SkPath path; 115 shape.asPath(&path); 116 // If the bounds are empty, the path ought to be as well. 117 if (bounds.isEmpty()) { 118 REPORTER_ASSERT(r, path.isEmpty()); 119 return; 120 } 121 if (path.isEmpty()) { 122 return; 123 } 124 REPORTER_ASSERT(r, test_bounds_by_rasterizing(path, bounds)); 125 } 126 127 void init(skiatest::Reporter* r, SkScalar scale) { 128 fAppliedPE = fBase.applyStyle(GrStyle::Apply::kPathEffectOnly, scale); 129 fAppliedPEThenStroke = fAppliedPE.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, 130 scale); 131 fAppliedFull = fBase.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale); 132 133 make_key(&fBaseKey, fBase); 134 make_key(&fAppliedPEKey, fAppliedPE); 135 make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke); 136 make_key(&fAppliedFullKey, fAppliedFull); 137 138 // Applying the path effect and then the stroke should always be the same as applying 139 // both in one go. 140 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey); 141 SkPath a, b; 142 fAppliedPEThenStroke.asPath(&a); 143 fAppliedFull.asPath(&b); 144 // If the output of the path effect is a rrect then it is possible for a and b to be 145 // different paths that fill identically. The reason is that fAppliedFull will do this: 146 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path 147 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However, 148 // now that there is no longer a path effect, the direction and starting index get 149 // canonicalized before the stroke. 150 if (fAppliedPE.asRRect(nullptr, nullptr, nullptr)) { 151 REPORTER_ASSERT(r, paths_fill_same(a, b)); 152 } else { 153 REPORTER_ASSERT(r, a == b); 154 } 155 REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty()); 156 157 SkPath path; 158 fBase.asPath(&path); 159 REPORTER_ASSERT(r, path.isEmpty() == fBase.isEmpty()); 160 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase.segmentMask()); 161 fAppliedPE.asPath(&path); 162 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE.isEmpty()); 163 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE.segmentMask()); 164 fAppliedFull.asPath(&path); 165 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull.isEmpty()); 166 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull.segmentMask()); 167 168 CheckBounds(r, fBase, fBase.bounds()); 169 CheckBounds(r, fAppliedPE, fAppliedPE.bounds()); 170 CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds()); 171 CheckBounds(r, fAppliedFull, fAppliedFull.bounds()); 172 SkRect styledBounds; 173 fBase.styledBounds(&styledBounds); 174 CheckBounds(r, fAppliedFull, styledBounds); 175 fAppliedPE.styledBounds(&styledBounds); 176 CheckBounds(r, fAppliedFull, styledBounds); 177 178 // Check that the same path is produced when style is applied by GrShape and GrStyle. 179 SkPath preStyle; 180 SkPath postPathEffect; 181 SkPath postAllStyle; 182 183 fBase.asPath(&preStyle); 184 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle); 185 if (fBase.style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle, 186 scale)) { 187 // run postPathEffect through GrShape to get any geometry reductions that would have 188 // occurred to fAppliedPE. 189 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect); 190 191 SkPath testPath; 192 fAppliedPE.asPath(&testPath); 193 REPORTER_ASSERT(r, testPath == postPathEffect); 194 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE.style().strokeRec())); 195 } 196 SkStrokeRec::InitStyle fillOrHairline; 197 if (fBase.style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) { 198 // run postPathEffect through GrShape to get any reductions that would have occurred 199 // to fAppliedFull. 200 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle); 201 202 SkPath testPath; 203 fAppliedFull.asPath(&testPath); 204 REPORTER_ASSERT(r, testPath == postAllStyle); 205 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) { 206 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleFill()); 207 } else { 208 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline()); 209 } 210 } 211 } 212 213 GrShape fBase; 214 GrShape fAppliedPE; 215 GrShape fAppliedPEThenStroke; 216 GrShape fAppliedFull; 217 218 Key fBaseKey; 219 Key fAppliedPEKey; 220 Key fAppliedPEThenStrokeKey; 221 Key fAppliedFullKey; 222}; 223 224void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const { 225 // The base's key should always be valid (unless the path is volatile) 226 REPORTER_ASSERT(reporter, fBaseKey.count()); 227 if (expectations.fPEHasEffect) { 228 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey); 229 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count())); 230 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey); 231 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count())); 232 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) { 233 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey); 234 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count())); 235 } 236 } else { 237 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey); 238 SkPath a, b; 239 fBase.asPath(&a); 240 fAppliedPE.asPath(&b); 241 REPORTER_ASSERT(reporter, a == b); 242 if (expectations.fStrokeApplies) { 243 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey); 244 } else { 245 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey); 246 } 247 } 248} 249 250void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b, 251 const Key& keyA, const Key& keyB) { 252 // GrShape only respects the input winding direction and start point for rrect shapes 253 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect 254 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled 255 // key will differ. GrShape will have canonicalized the direction and start point for the shape 256 // without the path effect. If *both* have path effects then they should have both preserved 257 // the direction and starting point. 258 259 // The asRRect() output params are all initialized just to silence compiler warnings about 260 // uninitialized variables. 261 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty(); 262 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction; 263 unsigned startA = ~0U, startB = ~0U; 264 265 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA); 266 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB); 267 bool aHasPE = a.style().hasPathEffect(); 268 bool bHasPE = b.style().hasPathEffect(); 269 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE); 270 SkPath pathA, pathB; 271 a.asPath(&pathA); 272 b.asPath(&pathB); 273 if (allowSameRRectButDiffStartAndDir) { 274 REPORTER_ASSERT(r, rrectA == rrectB); 275 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB)); 276 } else { 277 REPORTER_ASSERT(r, pathA == pathB); 278 REPORTER_ASSERT(r, keyA == keyB); 279 REPORTER_ASSERT(r, aIsRRect == bIsRRect); 280 if (aIsRRect) { 281 REPORTER_ASSERT(r, rrectA == rrectB); 282 REPORTER_ASSERT(r, dirA == dirB); 283 REPORTER_ASSERT(r, startA == startB); 284 } 285 } 286 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty()); 287 REPORTER_ASSERT(r, a.knownToBeClosed() == b.knownToBeClosed()); 288 REPORTER_ASSERT(r, a.bounds() == b.bounds()); 289 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask()); 290} 291 292void TestCase::compare(skiatest::Reporter* r, const TestCase& that, 293 ComparisonExpecation expectation) const { 294 SkPath a, b; 295 switch (expectation) { 296 case kAllDifferent_ComparisonExpecation: 297 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey); 298 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey); 299 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); 300 break; 301 case kSameUpToPE_ComparisonExpecation: 302 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); 303 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey); 304 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); 305 break; 306 case kSameUpToStroke_ComparisonExpecation: 307 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); 308 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey); 309 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); 310 break; 311 case kAllSame_ComparisonExpecation: 312 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); 313 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey); 314 check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey, 315 that.fAppliedFullKey); 316 break; 317 } 318} 319} // namespace 320 321static sk_sp<SkPathEffect> make_dash() { 322 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f }; 323 static const SkScalar kPhase = 0.75; 324 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase); 325} 326 327static sk_sp<SkPathEffect> make_null_dash() { 328 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0}; 329 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f); 330} 331 332template<typename GEO> 333static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { 334 sk_sp<SkPathEffect> dashPE = make_dash(); 335 336 TestCase::SelfExpectations expectations; 337 SkPaint fill; 338 339 TestCase fillCase(geo, fill, reporter); 340 expectations.fPEHasEffect = false; 341 expectations.fPEHasValidKey = false; 342 expectations.fStrokeApplies = false; 343 fillCase.testExpectations(reporter, expectations); 344 // Test that another GrShape instance built from the same primitive is the same. 345 TestCase(geo, fill, reporter).compare(reporter, fillCase, 346 TestCase::kAllSame_ComparisonExpecation); 347 348 SkPaint stroke2RoundBevel; 349 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style); 350 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap); 351 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join); 352 stroke2RoundBevel.setStrokeWidth(2.f); 353 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter); 354 expectations.fPEHasValidKey = true; 355 expectations.fPEHasEffect = false; 356 expectations.fStrokeApplies = true; 357 stroke2RoundBevelCase.testExpectations(reporter, expectations); 358 TestCase(geo, stroke2RoundBevel, reporter).compare(reporter, stroke2RoundBevelCase, 359 TestCase::kAllSame_ComparisonExpecation); 360 361 SkPaint stroke2RoundBevelDash = stroke2RoundBevel; 362 stroke2RoundBevelDash.setPathEffect(make_dash()); 363 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter); 364 expectations.fPEHasValidKey = true; 365 expectations.fPEHasEffect = true; 366 expectations.fStrokeApplies = true; 367 stroke2RoundBevelDashCase.testExpectations(reporter, expectations); 368 TestCase(geo, stroke2RoundBevelDash, reporter).compare(reporter, stroke2RoundBevelDashCase, 369 TestCase::kAllSame_ComparisonExpecation); 370 371 fillCase.compare(reporter, stroke2RoundBevelCase, 372 TestCase::kSameUpToStroke_ComparisonExpecation); 373 fillCase.compare(reporter, stroke2RoundBevelDashCase, 374 TestCase::kSameUpToPE_ComparisonExpecation); 375 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase, 376 TestCase::kSameUpToPE_ComparisonExpecation); 377 378 // Stroke and fill cases 379 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel; 380 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style); 381 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter); 382 expectations.fPEHasValidKey = true; 383 expectations.fPEHasEffect = false; 384 expectations.fStrokeApplies = true; 385 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations); 386 TestCase(geo, stroke2RoundBevelAndFill, reporter).compare(reporter, 387 stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation); 388 389 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash; 390 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style); 391 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter); 392 expectations.fPEHasValidKey = true; 393 expectations.fPEHasEffect = true; 394 expectations.fStrokeApplies = true; 395 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations); 396 TestCase(geo, stroke2RoundBevelAndFillDash, reporter).compare( 397 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation); 398 399 stroke2RoundBevelAndFillCase.compare(reporter, stroke2RoundBevelCase, 400 TestCase::kSameUpToStroke_ComparisonExpecation); 401 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelDashCase, 402 TestCase::kSameUpToStroke_ComparisonExpecation); 403 stroke2RoundBevelAndFillCase.compare(reporter, stroke2RoundBevelAndFillDashCase, 404 TestCase::kSameUpToPE_ComparisonExpecation); 405 406 SkPaint hairline; 407 hairline.setStyle(SkPaint::kStroke_Style); 408 hairline.setStrokeWidth(0.f); 409 TestCase hairlineCase(geo, hairline, reporter); 410 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill. 411 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); 412 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline()); 413 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline()); 414 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline()); 415} 416 417template<typename GEO> 418static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { 419 sk_sp<SkPathEffect> dashPE = make_dash(); 420 421 static const SkScalar kS1 = 1.f; 422 static const SkScalar kS2 = 2.f; 423 424 SkPaint fill; 425 TestCase fillCase1(geo, fill, reporter, kS1); 426 TestCase fillCase2(geo, fill, reporter, kS2); 427 // Scale doesn't affect fills. 428 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation); 429 430 SkPaint hairline; 431 hairline.setStyle(SkPaint::kStroke_Style); 432 hairline.setStrokeWidth(0.f); 433 TestCase hairlineCase1(geo, hairline, reporter, kS1); 434 TestCase hairlineCase2(geo, hairline, reporter, kS2); 435 // Scale doesn't affect hairlines. 436 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation); 437 438 SkPaint stroke; 439 stroke.setStyle(SkPaint::kStroke_Style); 440 stroke.setStrokeWidth(2.f); 441 TestCase strokeCase1(geo, stroke, reporter, kS1); 442 TestCase strokeCase2(geo, stroke, reporter, kS2); 443 // Scale affects the stroke. 444 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation); 445 446 SkPaint strokeDash = stroke; 447 strokeDash.setPathEffect(make_dash()); 448 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1); 449 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2); 450 // Scale affects the dash and the stroke. 451 strokeDashCase1.compare(reporter, strokeDashCase2, TestCase::kSameUpToPE_ComparisonExpecation); 452 453 // Stroke and fill cases 454 SkPaint strokeAndFill = stroke; 455 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style); 456 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1); 457 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2); 458 // Scale affects the stroke. Though, this can wind up creating a rect when the input is a rect. 459 // In that case we wind up with a pure geometry key and the geometries are the same. 460 SkRRect rrect; 461 if (strokeAndFillCase1.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)) { 462 // We currently only expect to get here in the rect->rect case. 463 REPORTER_ASSERT(reporter, rrect.isRect()); 464 REPORTER_ASSERT(reporter, 465 strokeAndFillCase1.baseShape().asRRect(&rrect, nullptr, nullptr) && 466 rrect.isRect()); 467 strokeAndFillCase1.compare(reporter, strokeAndFillCase2, 468 TestCase::kAllSame_ComparisonExpecation); 469 } else { 470 strokeAndFillCase1.compare(reporter, strokeAndFillCase2, 471 TestCase::kSameUpToStroke_ComparisonExpecation); 472 } 473 474 SkPaint strokeAndFillDash = strokeDash; 475 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style); 476 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1); 477 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2); 478 // Scale affects the path effect and stroke. 479 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2, 480 TestCase::kSameUpToPE_ComparisonExpecation); 481} 482 483template <typename GEO, typename T> 484static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo, 485 std::function<void(SkPaint*, T)> setter, T a, T b, 486 bool paramAffectsStroke, 487 bool paramAffectsDashAndStroke) { 488 // Set the stroke width so that we don't get hairline. However, call the setter afterward so 489 // that it can override the stroke width. 490 SkPaint strokeA; 491 strokeA.setStyle(SkPaint::kStroke_Style); 492 strokeA.setStrokeWidth(2.f); 493 setter(&strokeA, a); 494 SkPaint strokeB; 495 strokeB.setStyle(SkPaint::kStroke_Style); 496 strokeB.setStrokeWidth(2.f); 497 setter(&strokeB, b); 498 499 TestCase strokeACase(geo, strokeA, reporter); 500 TestCase strokeBCase(geo, strokeB, reporter); 501 if (paramAffectsStroke) { 502 strokeACase.compare(reporter, strokeBCase, TestCase::kSameUpToStroke_ComparisonExpecation); 503 } else { 504 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation); 505 } 506 507 SkPaint strokeAndFillA = strokeA; 508 SkPaint strokeAndFillB = strokeB; 509 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style); 510 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style); 511 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter); 512 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter); 513 if (paramAffectsStroke) { 514 strokeAndFillACase.compare(reporter, strokeAndFillBCase, 515 TestCase::kSameUpToStroke_ComparisonExpecation); 516 } else { 517 strokeAndFillACase.compare(reporter, strokeAndFillBCase, 518 TestCase::kAllSame_ComparisonExpecation); 519 } 520 521 // Make sure stroking params don't affect fill style. 522 SkPaint fillA = strokeA, fillB = strokeB; 523 fillA.setStyle(SkPaint::kFill_Style); 524 fillB.setStyle(SkPaint::kFill_Style); 525 TestCase fillACase(geo, fillA, reporter); 526 TestCase fillBCase(geo, fillB, reporter); 527 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation); 528 529 // Make sure just applying the dash but not stroke gives the same key for both stroking 530 // variations. 531 SkPaint dashA = strokeA, dashB = strokeB; 532 dashA.setPathEffect(make_dash()); 533 dashB.setPathEffect(make_dash()); 534 TestCase dashACase(geo, dashA, reporter); 535 TestCase dashBCase(geo, dashB, reporter); 536 if (paramAffectsDashAndStroke) { 537 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation); 538 } else { 539 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation); 540 } 541 542 SkPaint dashStrokeAndFillA = dashA, dashStrokeAndFillB = dashB; 543 dashStrokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style); 544 dashStrokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style); 545 TestCase dashStrokeAndFillACase(geo, dashStrokeAndFillA, reporter); 546 TestCase dashStrokeAndFillBCase(geo, dashStrokeAndFillB, reporter); 547 if (paramAffectsDashAndStroke) { 548 dashStrokeAndFillACase.compare(reporter, dashStrokeAndFillBCase, 549 TestCase::kSameUpToStroke_ComparisonExpecation); 550 } else { 551 dashStrokeAndFillACase.compare(reporter, dashStrokeAndFillBCase, 552 TestCase::kAllSame_ComparisonExpecation); 553 } 554} 555 556template <typename GEO, typename T> 557static void test_stroke_param(skiatest::Reporter* reporter, const GEO& geo, 558 std::function<void(SkPaint*, T)> setter, T a, T b) { 559 test_stroke_param_impl(reporter, geo, setter, a, b, true, true); 560}; 561 562template <typename GEO> 563static void test_stroke_cap(skiatest::Reporter* reporter, const GEO& geo) { 564 GrShape shape(geo, GrStyle(SkStrokeRec::kHairline_InitStyle)); 565 // The cap should only affect shapes that may be open. 566 bool affectsStroke = !shape.knownToBeClosed(); 567 // Dashing adds ends that need caps. 568 bool affectsDashAndStroke = true; 569 test_stroke_param_impl<GEO, SkPaint::Cap>( 570 reporter, 571 geo, 572 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);}, 573 SkPaint::kButt_Cap, SkPaint::kRound_Cap, 574 affectsStroke, 575 affectsDashAndStroke); 576}; 577 578template <typename GEO> 579static void test_miter_limit(skiatest::Reporter* reporter, const GEO& geo) { 580 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) { 581 p->setStrokeJoin(SkPaint::kMiter_Join); 582 p->setStrokeMiter(miter); 583 }; 584 585 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) { 586 p->setStrokeJoin(SkPaint::kRound_Join); 587 p->setStrokeMiter(miter); 588 }; 589 590 // The miter limit should affect stroked and dashed-stroked cases when the join type is 591 // miter. 592 test_stroke_param_impl<GEO, SkScalar>( 593 reporter, 594 geo, 595 setMiterJoinAndLimit, 596 0.5f, 0.75f, 597 true, 598 true); 599 600 // The miter limit should not affect stroked and dashed-stroked cases when the join type is 601 // not miter. 602 test_stroke_param_impl<GEO, SkScalar>( 603 reporter, 604 geo, 605 setOtherJoinAndLimit, 606 0.5f, 0.75f, 607 false, 608 false); 609} 610 611template<typename GEO> 612static void test_dash_fill(skiatest::Reporter* reporter, const GEO& geo) { 613 // A dash with no stroke should have no effect 614 using DashFactoryFn = sk_sp<SkPathEffect>(*)(); 615 for (DashFactoryFn md : {&make_dash, &make_null_dash}) { 616 SkPaint dashFill; 617 dashFill.setPathEffect((*md)()); 618 TestCase dashFillCase(geo, dashFill, reporter); 619 620 TestCase fillCase(geo, SkPaint(), reporter); 621 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); 622 } 623} 624 625template<typename GEO> 626void test_null_dash(skiatest::Reporter* reporter, const GEO& geo) { 627 SkPaint fill; 628 SkPaint stroke; 629 stroke.setStyle(SkPaint::kStroke_Style); 630 stroke.setStrokeWidth(1.f); 631 SkPaint dash; 632 dash.setStyle(SkPaint::kStroke_Style); 633 dash.setStrokeWidth(1.f); 634 dash.setPathEffect(make_dash()); 635 SkPaint nullDash; 636 nullDash.setStyle(SkPaint::kStroke_Style); 637 nullDash.setStrokeWidth(1.f); 638 nullDash.setPathEffect(make_null_dash()); 639 640 TestCase fillCase(geo, fill, reporter); 641 TestCase strokeCase(geo, stroke, reporter); 642 TestCase dashCase(geo, dash, reporter); 643 TestCase nullDashCase(geo, nullDash, reporter); 644 645 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation); 646 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation); 647 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation); 648} 649 650template <typename GEO> 651void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) { 652 /** 653 * This path effect takes any input path and turns it into a rrect. It passes through stroke 654 * info. 655 */ 656 class RRectPathEffect : SkPathEffect { 657 public: 658 static const SkRRect& RRect() { 659 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5); 660 return kRRect; 661 } 662 663 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 664 const SkRect* cullR) const override { 665 dst->reset(); 666 dst->addRRect(RRect()); 667 return true; 668 } 669 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 670 *dst = RRect().getBounds(); 671 } 672 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); } 673 Factory getFactory() const override { return nullptr; } 674 void toString(SkString*) const override {} 675 private: 676 RRectPathEffect() {} 677 }; 678 679 SkPaint fill; 680 TestCase fillGeoCase(geo, fill, reporter); 681 682 SkPaint pe; 683 pe.setPathEffect(RRectPathEffect::Make()); 684 TestCase geoPECase(geo, pe, reporter); 685 686 SkPaint peStroke; 687 peStroke.setPathEffect(RRectPathEffect::Make()); 688 peStroke.setStrokeWidth(2.f); 689 peStroke.setStyle(SkPaint::kStroke_Style); 690 TestCase geoPEStrokeCase(geo, peStroke, reporter); 691 692 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation); 693 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation); 694 geoPECase.compare(reporter, geoPEStrokeCase, 695 TestCase::kSameUpToStroke_ComparisonExpecation); 696 697 TestCase rrectFillCase(RRectPathEffect::RRect(), fill, reporter); 698 SkPaint stroke = peStroke; 699 stroke.setPathEffect(nullptr); 700 TestCase rrectStrokeCase(RRectPathEffect::RRect(), stroke, reporter); 701 702 SkRRect rrect; 703 // Applying the path effect should make a SkRRect shape. There is no further stroking in the 704 // geoPECase, so the full style should be the same as just the PE. 705 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr)); 706 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); 707 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey()); 708 709 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)); 710 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); 711 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey()); 712 713 // In the PE+stroke case applying the full style should be the same as just stroking the rrect. 714 REPORTER_ASSERT(reporter, 715 geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr)); 716 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); 717 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey()); 718 719 REPORTER_ASSERT(reporter, 720 !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)); 721 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == 722 rrectStrokeCase.appliedFullStyleKey()); 723} 724 725template <typename GEO> 726void test_unknown_path_effect(skiatest::Reporter* reporter, const GEO& geo) { 727 /** 728 * This path effect just adds two lineTos to the input path. 729 */ 730 class AddLineTosPathEffect : SkPathEffect { 731 public: 732 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 733 const SkRect* cullR) const override { 734 *dst = src; 735 dst->lineTo(0, 0); 736 dst->lineTo(10, 10); 737 return true; 738 } 739 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 740 *dst = src; 741 dst->growToInclude(0, 0); 742 dst->growToInclude(10, 10); 743 } 744 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); } 745 Factory getFactory() const override { return nullptr; } 746 void toString(SkString*) const override {} 747 private: 748 AddLineTosPathEffect() {} 749 }; 750 751 // This path effect should make the keys invalid when it is applied. We only produce a path 752 // effect key for dash path effects. So the only way another arbitrary path effect can produce 753 // a styled result with a key is to produce a non-path shape that has a purely geometric key. 754 SkPaint peStroke; 755 peStroke.setPathEffect(AddLineTosPathEffect::Make()); 756 peStroke.setStrokeWidth(2.f); 757 peStroke.setStyle(SkPaint::kStroke_Style); 758 TestCase geoPEStrokeCase(geo, peStroke, reporter); 759 TestCase::SelfExpectations expectations; 760 expectations.fPEHasEffect = true; 761 expectations.fPEHasValidKey = false; 762 expectations.fStrokeApplies = true; 763 geoPEStrokeCase.testExpectations(reporter, expectations); 764} 765 766template <typename GEO> 767void test_make_hairline_path_effect(skiatest::Reporter* reporter, const GEO& geo, bool isNonPath) { 768 /** 769 * This path effect just changes the stroke rec to hairline. 770 */ 771 class MakeHairlinePathEffect : SkPathEffect { 772 public: 773 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec, 774 const SkRect* cullR) const override { 775 *dst = src; 776 strokeRec->setHairlineStyle(); 777 return true; 778 } 779 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; } 780 static sk_sp<SkPathEffect> Make() { 781 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect); 782 } 783 Factory getFactory() const override { return nullptr; } 784 void toString(SkString*) const override {} 785 private: 786 MakeHairlinePathEffect() {} 787 }; 788 789 SkPaint fill; 790 SkPaint pe; 791 pe.setPathEffect(MakeHairlinePathEffect::Make()); 792 793 TestCase peCase(geo, pe, reporter); 794 795 SkPath a, b, c; 796 peCase.baseShape().asPath(&a); 797 peCase.appliedPathEffectShape().asPath(&b); 798 peCase.appliedFullStyleShape().asPath(&c); 799 if (isNonPath) { 800 // RRect types can have a change in start index or direction after the PE is applied. This 801 // is because once the PE is applied, GrShape may canonicalize the dir and index since it 802 // is not germane to the styling any longer. 803 // Instead we just check that the paths would fill the same both before and after styling. 804 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 805 REPORTER_ASSERT(reporter, paths_fill_same(a, c)); 806 } else { 807 REPORTER_ASSERT(reporter, a == b); 808 REPORTER_ASSERT(reporter, a == c); 809 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty()); 810 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty()); 811 } 812 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline()); 813 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline()); 814} 815 816/** 817 * isNonPath indicates whether the initial shape made from the path is expected to be recognized 818 * as a simpler shape type (e.g. rrect) 819 */ 820void test_volatile_path(skiatest::Reporter* reporter, const SkPath& path, 821 bool isNonPath) { 822 SkPath vPath(path); 823 vPath.setIsVolatile(true); 824 825 SkPaint dashAndStroke; 826 dashAndStroke.setPathEffect(make_dash()); 827 dashAndStroke.setStrokeWidth(2.f); 828 dashAndStroke.setStyle(SkPaint::kStroke_Style); 829 TestCase volatileCase(vPath, dashAndStroke, reporter); 830 // We expect a shape made from a volatile path to have a key iff the shape is recognized 831 // as a specialized geometry. 832 if (isNonPath) { 833 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count())); 834 // In this case all the keys should be identical to the non-volatile case. 835 TestCase nonVolatileCase(path, dashAndStroke, reporter); 836 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation); 837 } else { 838 // None of the keys should be valid. 839 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count())); 840 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count())); 841 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count())); 842 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count())); 843 } 844} 845 846template <typename GEO> 847void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const GEO& geo) { 848 /** 849 * This path effect returns an empty path. 850 */ 851 class EmptyPathEffect : SkPathEffect { 852 public: 853 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 854 const SkRect* cullR) const override { 855 dst->reset(); 856 return true; 857 } 858 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 859 dst->setEmpty(); 860 } 861 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new EmptyPathEffect); } 862 Factory getFactory() const override { return nullptr; } 863 void toString(SkString*) const override {} 864 private: 865 EmptyPathEffect() {} 866 }; 867 868 SkPath emptyPath; 869 GrShape emptyShape(emptyPath); 870 Key emptyKey; 871 make_key(&emptyKey, emptyShape); 872 REPORTER_ASSERT(reporter, emptyShape.isEmpty()); 873 874 SkPaint pe; 875 pe.setPathEffect(EmptyPathEffect::Make()); 876 TestCase geoCase(geo, pe, reporter); 877 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleKey() == emptyKey); 878 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectKey() == emptyKey); 879 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectThenStrokeKey() == emptyKey); 880 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectShape().isEmpty()); 881 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleShape().isEmpty()); 882 883 SkPaint peStroke; 884 peStroke.setPathEffect(EmptyPathEffect::Make()); 885 peStroke.setStrokeWidth(2.f); 886 peStroke.setStyle(SkPaint::kStroke_Style); 887 TestCase geoPEStrokeCase(geo, peStroke, reporter); 888 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey); 889 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey); 890 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey); 891 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty()); 892 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty()); 893} 894 895void test_empty_shape(skiatest::Reporter* reporter) { 896 SkPath emptyPath; 897 SkPaint fill; 898 TestCase fillEmptyCase(emptyPath, fill, reporter); 899 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty()); 900 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty()); 901 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty()); 902 903 Key emptyKey(fillEmptyCase.baseKey()); 904 REPORTER_ASSERT(reporter, emptyKey.count()); 905 TestCase::SelfExpectations expectations; 906 expectations.fStrokeApplies = false; 907 expectations.fPEHasEffect = false; 908 // This will test whether applying style preserves emptiness 909 fillEmptyCase.testExpectations(reporter, expectations); 910 911 // Stroking an empty path should have no effect 912 SkPath emptyPath2; 913 SkPaint stroke; 914 stroke.setStrokeWidth(2.f); 915 stroke.setStyle(SkPaint::kStroke_Style); 916 TestCase strokeEmptyCase(emptyPath2, stroke, reporter); 917 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation); 918 919 // Dashing and stroking an empty path should have no effect 920 SkPath emptyPath3; 921 SkPaint dashAndStroke; 922 dashAndStroke.setPathEffect(make_dash()); 923 dashAndStroke.setStrokeWidth(2.f); 924 dashAndStroke.setStyle(SkPaint::kStroke_Style); 925 TestCase dashAndStrokeEmptyCase(emptyPath3, dashAndStroke, reporter); 926 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase, 927 TestCase::kAllSame_ComparisonExpecation); 928 929 // A shape made from an empty rrect should behave the same as an empty path. 930 SkRRect emptyRRect = SkRRect::MakeRect(SkRect::MakeEmpty()); 931 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type); 932 TestCase dashAndStrokeEmptyRRectCase(emptyRRect, dashAndStroke, reporter); 933 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase, 934 TestCase::kAllSame_ComparisonExpecation); 935 936 // Same for a rect. 937 SkRect emptyRect = SkRect::MakeEmpty(); 938 TestCase dashAndStrokeEmptyRectCase(emptyRect, dashAndStroke, reporter); 939 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase, 940 TestCase::kAllSame_ComparisonExpecation); 941} 942 943DEF_TEST(GrShape, reporter) { 944 for (auto r : { SkRect::MakeWH(10, 20), 945 SkRect::MakeWH(-10, -20), 946 SkRect::MakeWH(-10, 20), 947 SkRect::MakeWH(10, -20)}) { 948 test_basic(reporter, r); 949 test_scale(reporter, r); 950 test_dash_fill(reporter, r); 951 test_null_dash(reporter, r); 952 // Test modifying various stroke params. 953 test_stroke_param<SkRect, SkScalar>( 954 reporter, r, 955 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, 956 SkIntToScalar(2), SkIntToScalar(4)); 957 test_stroke_param<SkRect, SkPaint::Join>( 958 reporter, r, 959 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, 960 SkPaint::kMiter_Join, SkPaint::kRound_Join); 961 test_stroke_cap(reporter, r); 962 test_miter_limit(reporter, r); 963 test_path_effect_makes_rrect(reporter, r); 964 test_unknown_path_effect(reporter, r); 965 test_path_effect_makes_empty_shape(reporter, r); 966 test_make_hairline_path_effect(reporter, r, true); 967 } 968 969 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)), 970 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4), 971 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) { 972 test_basic(reporter, rr); 973 test_scale(reporter, rr); 974 test_dash_fill(reporter, rr); 975 test_null_dash(reporter, rr); 976 // Test modifying various stroke params. 977 test_stroke_param<SkRRect, SkScalar>( 978 reporter, rr, 979 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, 980 SkIntToScalar(2), SkIntToScalar(4)); 981 test_stroke_param<SkRRect, SkPaint::Join>( 982 reporter, rr, 983 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, 984 SkPaint::kMiter_Join, SkPaint::kRound_Join); 985 test_stroke_cap(reporter, rr); 986 test_miter_limit(reporter, rr); 987 test_path_effect_makes_rrect(reporter, rr); 988 test_unknown_path_effect(reporter, rr); 989 test_path_effect_makes_empty_shape(reporter, rr); 990 test_make_hairline_path_effect(reporter, rr, true); 991 } 992 993 struct TestPath { 994 TestPath(const SkPath& path, bool isRRectFill, bool isRRectStroke ,const SkRRect& rrect) 995 : fPath(path) 996 , fIsRRectForFill(isRRectFill) 997 , fIsRRectForStroke(isRRectStroke) 998 , fRRect(rrect) {} 999 SkPath fPath; 1000 bool fIsRRectForFill; 1001 bool fIsRRectForStroke; 1002 SkRRect fRRect; 1003 }; 1004 SkTArray<TestPath> paths; 1005 1006 SkPath circlePath; 1007 circlePath.addCircle(10, 10, 10); 1008 paths.emplace_back(circlePath, true, true, SkRRect::MakeOval(SkRect::MakeWH(20,20))); 1009 1010 SkPath rectPath; 1011 rectPath.addRect(SkRect::MakeWH(10, 10)); 1012 paths.emplace_back(rectPath, true, true, SkRRect::MakeRect(SkRect::MakeWH(10, 10))); 1013 1014 SkPath openRectPath; 1015 openRectPath.moveTo(0, 0); 1016 openRectPath.lineTo(10, 0); 1017 openRectPath.lineTo(10, 10); 1018 openRectPath.lineTo(0, 10); 1019 paths.emplace_back(openRectPath, true, false, SkRRect::MakeRect(SkRect::MakeWH(10, 10))); 1020 1021 SkPath quadPath; 1022 quadPath.quadTo(10, 10, 5, 8); 1023 paths.emplace_back(quadPath, false, false, SkRRect()); 1024 1025 for (auto testPath : paths) { 1026 const SkPath& path = testPath.fPath; 1027 // These tests all assume that the original GrShape for fill and stroke will be the same. 1028 // However, that is not the case in special cases (e.g. a unclosed rect becomes a RRect 1029 // GrShape with a fill style but becomes a Path GrShape when stroked). 1030 if (testPath.fIsRRectForFill == testPath.fIsRRectForStroke) { 1031 test_basic(reporter, path); 1032 test_null_dash(reporter, path); 1033 test_path_effect_makes_rrect(reporter, path); 1034 } 1035 test_scale(reporter, path); 1036 // This test uses a stroking paint, hence use of fIsRRectForStroke 1037 test_volatile_path(reporter, path, testPath.fIsRRectForStroke); 1038 test_dash_fill(reporter, path); 1039 // Test modifying various stroke params. 1040 test_stroke_param<SkPath, SkScalar>( 1041 reporter, path, 1042 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, 1043 SkIntToScalar(2), SkIntToScalar(4)); 1044 test_stroke_param<SkPath, SkPaint::Join>( 1045 reporter, path, 1046 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, 1047 SkPaint::kMiter_Join, SkPaint::kRound_Join); 1048 test_stroke_cap(reporter, path); 1049 test_miter_limit(reporter, path); 1050 test_unknown_path_effect(reporter, path); 1051 test_path_effect_makes_empty_shape(reporter, path); 1052 test_make_hairline_path_effect(reporter, path, testPath.fIsRRectForStroke); 1053 1054 SkPaint fillPaint; 1055 TestCase fillPathCase(path, fillPaint, reporter); 1056 SkRRect rrect; 1057 REPORTER_ASSERT(reporter, testPath.fIsRRectForFill == 1058 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr)); 1059 if (testPath.fIsRRectForFill) { 1060 TestCase fillPathCase2(path, fillPaint, reporter); 1061 REPORTER_ASSERT(reporter, rrect == testPath.fRRect); 1062 TestCase fillRRectCase(rrect, fillPaint, reporter); 1063 fillPathCase2.compare(reporter, fillRRectCase, TestCase::kAllSame_ComparisonExpecation); 1064 } 1065 1066 SkPaint strokePaint; 1067 strokePaint.setStrokeWidth(3.f); 1068 strokePaint.setStyle(SkPaint::kStroke_Style); 1069 TestCase strokePathCase(path, strokePaint, reporter); 1070 REPORTER_ASSERT(reporter, testPath.fIsRRectForStroke == 1071 strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr)); 1072 if (testPath.fIsRRectForStroke) { 1073 REPORTER_ASSERT(reporter, rrect == testPath.fRRect); 1074 TestCase strokeRRectCase(rrect, strokePaint, reporter); 1075 strokePathCase.compare(reporter, strokeRRectCase, 1076 TestCase::kAllSame_ComparisonExpecation); 1077 } 1078 } 1079 1080 // Test a volatile empty path. 1081 test_volatile_path(reporter, SkPath(), true); 1082 1083 test_empty_shape(reporter); 1084} 1085 1086#endif 1087