GrShapeTest.cpp revision ac5fcea9c3ec32a87bfd8cb96531e82097a1d861
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 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1) 86 : fBase(shape) { 87 this->init(r, scale); 88 } 89 90 struct SelfExpectations { 91 bool fPEHasEffect; 92 bool fPEHasValidKey; 93 bool fStrokeApplies; 94 }; 95 96 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const; 97 98 enum ComparisonExpecation { 99 kAllDifferent_ComparisonExpecation, 100 kSameUpToPE_ComparisonExpecation, 101 kSameUpToStroke_ComparisonExpecation, 102 kAllSame_ComparisonExpecation, 103 }; 104 105 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const; 106 107 const GrShape& baseShape() const { return fBase; } 108 const GrShape& appliedPathEffectShape() const { return fAppliedPE; } 109 const GrShape& appliedFullStyleShape() const { return fAppliedFull; } 110 111 // The returned array's count will be 0 if the key shape has no key. 112 const Key& baseKey() const { return fBaseKey; } 113 const Key& appliedPathEffectKey() const { return fAppliedPEKey; } 114 const Key& appliedFullStyleKey() const { return fAppliedFullKey; } 115 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; } 116 117private: 118 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) { 119 SkPath path; 120 shape.asPath(&path); 121 // If the bounds are empty, the path ought to be as well. 122 if (bounds.isEmpty()) { 123 REPORTER_ASSERT(r, path.isEmpty()); 124 return; 125 } 126 if (path.isEmpty()) { 127 return; 128 } 129 // The bounds API explicitly calls out that it does not consider inverseness. 130 SkPath p = path; 131 p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType())); 132 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds)); 133 } 134 135 void init(skiatest::Reporter* r, SkScalar scale) { 136 fAppliedPE = fBase.applyStyle(GrStyle::Apply::kPathEffectOnly, scale); 137 fAppliedPEThenStroke = fAppliedPE.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, 138 scale); 139 fAppliedFull = fBase.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale); 140 141 make_key(&fBaseKey, fBase); 142 make_key(&fAppliedPEKey, fAppliedPE); 143 make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke); 144 make_key(&fAppliedFullKey, fAppliedFull); 145 146 // Applying the path effect and then the stroke should always be the same as applying 147 // both in one go. 148 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey); 149 SkPath a, b; 150 fAppliedPEThenStroke.asPath(&a); 151 fAppliedFull.asPath(&b); 152 // If the output of the path effect is a rrect then it is possible for a and b to be 153 // different paths that fill identically. The reason is that fAppliedFull will do this: 154 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path 155 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However, 156 // now that there is no longer a path effect, the direction and starting index get 157 // canonicalized before the stroke. 158 if (fAppliedPE.asRRect(nullptr, nullptr, nullptr, nullptr)) { 159 REPORTER_ASSERT(r, paths_fill_same(a, b)); 160 } else { 161 REPORTER_ASSERT(r, a == b); 162 } 163 REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty()); 164 165 SkPath path; 166 fBase.asPath(&path); 167 REPORTER_ASSERT(r, path.isEmpty() == fBase.isEmpty()); 168 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase.segmentMask()); 169 fAppliedPE.asPath(&path); 170 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE.isEmpty()); 171 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE.segmentMask()); 172 fAppliedFull.asPath(&path); 173 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull.isEmpty()); 174 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull.segmentMask()); 175 176 CheckBounds(r, fBase, fBase.bounds()); 177 CheckBounds(r, fAppliedPE, fAppliedPE.bounds()); 178 CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds()); 179 CheckBounds(r, fAppliedFull, fAppliedFull.bounds()); 180 SkRect styledBounds; 181 fBase.styledBounds(&styledBounds); 182 CheckBounds(r, fAppliedFull, styledBounds); 183 fAppliedPE.styledBounds(&styledBounds); 184 CheckBounds(r, fAppliedFull, styledBounds); 185 186 // Check that the same path is produced when style is applied by GrShape and GrStyle. 187 SkPath preStyle; 188 SkPath postPathEffect; 189 SkPath postAllStyle; 190 191 fBase.asPath(&preStyle); 192 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle); 193 if (fBase.style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle, 194 scale)) { 195 // run postPathEffect through GrShape to get any geometry reductions that would have 196 // occurred to fAppliedPE. 197 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect); 198 199 SkPath testPath; 200 fAppliedPE.asPath(&testPath); 201 REPORTER_ASSERT(r, testPath == postPathEffect); 202 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE.style().strokeRec())); 203 } 204 SkStrokeRec::InitStyle fillOrHairline; 205 if (fBase.style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) { 206 SkPath testPath; 207 fAppliedFull.asPath(&testPath); 208 if (fBase.style().hasPathEffect()) { 209 // Because GrShape always does two-stage application when there is a path effect 210 // there may be a reduction/canonicalization step between the path effect and 211 // strokerec not reflected in postAllStyle since it applied both the path effect 212 // and strokerec without analyzing the intermediate path. 213 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath)); 214 } else { 215 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape 216 // would apply. 217 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle); 218 REPORTER_ASSERT(r, testPath == postAllStyle); 219 } 220 221 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) { 222 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleFill()); 223 } else { 224 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline()); 225 } 226 } 227 } 228 229 GrShape fBase; 230 GrShape fAppliedPE; 231 GrShape fAppliedPEThenStroke; 232 GrShape fAppliedFull; 233 234 Key fBaseKey; 235 Key fAppliedPEKey; 236 Key fAppliedPEThenStrokeKey; 237 Key fAppliedFullKey; 238}; 239 240void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const { 241 // The base's key should always be valid (unless the path is volatile) 242 REPORTER_ASSERT(reporter, fBaseKey.count()); 243 if (expectations.fPEHasEffect) { 244 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey); 245 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count())); 246 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey); 247 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count())); 248 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) { 249 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey); 250 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count())); 251 } 252 } else { 253 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey); 254 SkPath a, b; 255 fBase.asPath(&a); 256 fAppliedPE.asPath(&b); 257 REPORTER_ASSERT(reporter, a == b); 258 if (expectations.fStrokeApplies) { 259 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey); 260 } else { 261 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey); 262 } 263 } 264} 265 266static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) { 267 SkPath path; 268 shape.asPath(&path); 269 if (shape.style().hasNonDashPathEffect()) { 270 return false; 271 } 272 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle(); 273 return strokeRecStyle == SkStrokeRec::kStroke_Style || 274 strokeRecStyle == SkStrokeRec::kHairline_Style || 275 (shape.style().isSimpleFill() && path.isConvex()); 276} 277 278static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b, 279 const Key& keyA, const Key& keyB) { 280 // GrShape only respects the input winding direction and start point for rrect shapes 281 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect 282 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled 283 // key will differ. GrShape will have canonicalized the direction and start point for the shape 284 // without the path effect. If *both* have path effects then they should have both preserved 285 // the direction and starting point. 286 287 // The asRRect() output params are all initialized just to silence compiler warnings about 288 // uninitialized variables. 289 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty(); 290 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction; 291 unsigned startA = ~0U, startB = ~0U; 292 bool invertedA = true, invertedB = true; 293 294 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA); 295 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB); 296 bool aHasPE = a.style().hasPathEffect(); 297 bool bHasPE = b.style().hasPathEffect(); 298 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE); 299 300 SkPath pathA, pathB; 301 a.asPath(&pathA); 302 b.asPath(&pathB); 303 304 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a 305 // non-inverse fill type (or vice versa). 306 bool ignoreInversenessDifference = false; 307 if (pathA.isInverseFillType() != pathB.isInverseFillType()) { 308 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b; 309 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a; 310 bool canDropInverse1 = s1->style().isDashed(); 311 bool canDropInverse2 = s2->style().isDashed(); 312 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2); 313 } 314 bool ignoreWindingVsEvenOdd = false; 315 if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) != 316 SkPath::ConvertToNonInverseFillType(pathB.getFillType())) { 317 bool aCanChange = can_interchange_winding_and_even_odd_fill(a); 318 bool bCanChange = can_interchange_winding_and_even_odd_fill(b); 319 if (aCanChange != bCanChange) { 320 ignoreWindingVsEvenOdd = true; 321 } 322 } 323 if (allowSameRRectButDiffStartAndDir) { 324 REPORTER_ASSERT(r, rrectA == rrectB); 325 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB)); 326 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB); 327 } else { 328 SkPath pA = pathA; 329 SkPath pB = pathB; 330 if (ignoreInversenessDifference) { 331 pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType())); 332 pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType())); 333 } 334 if (ignoreWindingVsEvenOdd) { 335 pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType 336 : SkPath::kEvenOdd_FillType); 337 pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType 338 : SkPath::kEvenOdd_FillType); 339 } 340 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) { 341 REPORTER_ASSERT(r, keyA == keyB); 342 } else { 343 REPORTER_ASSERT(r, keyA != keyB); 344 } 345 if (a.style().isSimpleFill() != b.style().isSimpleFill()) { 346 // GrShape will close paths with simple fill style. Make the non-filled path closed 347 // so that the comparision will succeed. Make sure both are closed before comparing. 348 pA.close(); 349 pB.close(); 350 } 351 REPORTER_ASSERT(r, pA == pB); 352 REPORTER_ASSERT(r, aIsRRect == bIsRRect); 353 if (aIsRRect) { 354 REPORTER_ASSERT(r, rrectA == rrectB); 355 REPORTER_ASSERT(r, dirA == dirB); 356 REPORTER_ASSERT(r, startA == startB); 357 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB); 358 } 359 } 360 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty()); 361 REPORTER_ASSERT(r, a.knownToBeClosed() == b.knownToBeClosed()); 362 REPORTER_ASSERT(r, a.bounds() == b.bounds()); 363 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask()); 364 SkPoint pts[4]; 365 REPORTER_ASSERT(r, a.asLine(pts) == b.asLine(pts + 2)); 366 if (a.asLine(pts)) { 367 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]); 368 } 369} 370 371void TestCase::compare(skiatest::Reporter* r, const TestCase& that, 372 ComparisonExpecation expectation) const { 373 SkPath a, b; 374 switch (expectation) { 375 case kAllDifferent_ComparisonExpecation: 376 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey); 377 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey); 378 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); 379 break; 380 case kSameUpToPE_ComparisonExpecation: 381 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); 382 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey); 383 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); 384 break; 385 case kSameUpToStroke_ComparisonExpecation: 386 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); 387 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey); 388 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); 389 break; 390 case kAllSame_ComparisonExpecation: 391 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); 392 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey); 393 check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey, 394 that.fAppliedFullKey); 395 break; 396 } 397} 398} // namespace 399 400static sk_sp<SkPathEffect> make_dash() { 401 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f }; 402 static const SkScalar kPhase = 0.75; 403 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase); 404} 405 406static sk_sp<SkPathEffect> make_null_dash() { 407 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0}; 408 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f); 409} 410 411template<typename GEO> 412static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { 413 sk_sp<SkPathEffect> dashPE = make_dash(); 414 415 TestCase::SelfExpectations expectations; 416 SkPaint fill; 417 418 TestCase fillCase(geo, fill, reporter); 419 expectations.fPEHasEffect = false; 420 expectations.fPEHasValidKey = false; 421 expectations.fStrokeApplies = false; 422 fillCase.testExpectations(reporter, expectations); 423 // Test that another GrShape instance built from the same primitive is the same. 424 TestCase(geo, fill, reporter).compare(reporter, fillCase, 425 TestCase::kAllSame_ComparisonExpecation); 426 427 SkPaint stroke2RoundBevel; 428 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style); 429 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap); 430 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join); 431 stroke2RoundBevel.setStrokeWidth(2.f); 432 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter); 433 expectations.fPEHasValidKey = true; 434 expectations.fPEHasEffect = false; 435 expectations.fStrokeApplies = true; 436 stroke2RoundBevelCase.testExpectations(reporter, expectations); 437 TestCase(geo, stroke2RoundBevel, reporter).compare(reporter, stroke2RoundBevelCase, 438 TestCase::kAllSame_ComparisonExpecation); 439 440 SkPaint stroke2RoundBevelDash = stroke2RoundBevel; 441 stroke2RoundBevelDash.setPathEffect(make_dash()); 442 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter); 443 expectations.fPEHasValidKey = true; 444 expectations.fPEHasEffect = true; 445 expectations.fStrokeApplies = true; 446 stroke2RoundBevelDashCase.testExpectations(reporter, expectations); 447 TestCase(geo, stroke2RoundBevelDash, reporter).compare(reporter, stroke2RoundBevelDashCase, 448 TestCase::kAllSame_ComparisonExpecation); 449 450 fillCase.compare(reporter, stroke2RoundBevelCase, 451 TestCase::kSameUpToStroke_ComparisonExpecation); 452 fillCase.compare(reporter, stroke2RoundBevelDashCase, 453 TestCase::kSameUpToPE_ComparisonExpecation); 454 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase, 455 TestCase::kSameUpToPE_ComparisonExpecation); 456 457 // Stroke and fill cases 458 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel; 459 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style); 460 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter); 461 expectations.fPEHasValidKey = true; 462 expectations.fPEHasEffect = false; 463 expectations.fStrokeApplies = true; 464 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations); 465 TestCase(geo, stroke2RoundBevelAndFill, reporter).compare(reporter, 466 stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation); 467 468 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash; 469 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style); 470 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter); 471 expectations.fPEHasValidKey = true; 472 expectations.fPEHasEffect = false; 473 expectations.fStrokeApplies = true; 474 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations); 475 TestCase(geo, stroke2RoundBevelAndFillDash, reporter).compare( 476 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation); 477 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase, 478 TestCase::kAllSame_ComparisonExpecation); 479 480 SkPaint hairline; 481 hairline.setStyle(SkPaint::kStroke_Style); 482 hairline.setStrokeWidth(0.f); 483 TestCase hairlineCase(geo, hairline, reporter); 484 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill. 485 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); 486 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline()); 487 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline()); 488 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline()); 489} 490 491template<typename GEO> 492static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { 493 sk_sp<SkPathEffect> dashPE = make_dash(); 494 495 static const SkScalar kS1 = 1.f; 496 static const SkScalar kS2 = 2.f; 497 498 SkPaint fill; 499 TestCase fillCase1(geo, fill, reporter, kS1); 500 TestCase fillCase2(geo, fill, reporter, kS2); 501 // Scale doesn't affect fills. 502 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation); 503 504 SkPaint hairline; 505 hairline.setStyle(SkPaint::kStroke_Style); 506 hairline.setStrokeWidth(0.f); 507 TestCase hairlineCase1(geo, hairline, reporter, kS1); 508 TestCase hairlineCase2(geo, hairline, reporter, kS2); 509 // Scale doesn't affect hairlines. 510 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation); 511 512 SkPaint stroke; 513 stroke.setStyle(SkPaint::kStroke_Style); 514 stroke.setStrokeWidth(2.f); 515 TestCase strokeCase1(geo, stroke, reporter, kS1); 516 TestCase strokeCase2(geo, stroke, reporter, kS2); 517 // Scale affects the stroke. 518 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation); 519 520 SkPaint strokeDash = stroke; 521 strokeDash.setPathEffect(make_dash()); 522 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1); 523 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2); 524 // Scale affects the dash and the stroke. 525 strokeDashCase1.compare(reporter, strokeDashCase2, TestCase::kSameUpToPE_ComparisonExpecation); 526 527 // Stroke and fill cases 528 SkPaint strokeAndFill = stroke; 529 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style); 530 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1); 531 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2); 532 SkPaint strokeAndFillDash = strokeDash; 533 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style); 534 // Dash is ignored for stroke and fill 535 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1); 536 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2); 537 // Scale affects the stroke. Though, this can wind up creating a rect when the input is a rect. 538 // In that case we wind up with a pure geometry key and the geometries are the same. 539 SkRRect rrect; 540 if (strokeAndFillCase1.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr, nullptr)) { 541 // We currently only expect to get here in the rect->rect case. 542 REPORTER_ASSERT(reporter, rrect.isRect()); 543 REPORTER_ASSERT(reporter, 544 strokeAndFillCase1.baseShape().asRRect(&rrect, nullptr, nullptr, nullptr) && 545 rrect.isRect()); 546 strokeAndFillCase1.compare(reporter, strokeAndFillCase2, 547 TestCase::kAllSame_ComparisonExpecation); 548 } else { 549 strokeAndFillCase1.compare(reporter, strokeAndFillCase2, 550 TestCase::kSameUpToStroke_ComparisonExpecation); 551 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2, 552 TestCase::kSameUpToStroke_ComparisonExpecation); 553 } 554 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1, 555 TestCase::kAllSame_ComparisonExpecation); 556 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2, 557 TestCase::kAllSame_ComparisonExpecation); 558} 559 560template <typename GEO, typename T> 561static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo, 562 std::function<void(SkPaint*, T)> setter, T a, T b, 563 bool paramAffectsStroke, 564 bool paramAffectsDashAndStroke) { 565 // Set the stroke width so that we don't get hairline. However, call the setter afterward so 566 // that it can override the stroke width. 567 SkPaint strokeA; 568 strokeA.setStyle(SkPaint::kStroke_Style); 569 strokeA.setStrokeWidth(2.f); 570 setter(&strokeA, a); 571 SkPaint strokeB; 572 strokeB.setStyle(SkPaint::kStroke_Style); 573 strokeB.setStrokeWidth(2.f); 574 setter(&strokeB, b); 575 576 TestCase strokeACase(geo, strokeA, reporter); 577 TestCase strokeBCase(geo, strokeB, reporter); 578 if (paramAffectsStroke) { 579 strokeACase.compare(reporter, strokeBCase, TestCase::kSameUpToStroke_ComparisonExpecation); 580 } else { 581 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation); 582 } 583 584 SkPaint strokeAndFillA = strokeA; 585 SkPaint strokeAndFillB = strokeB; 586 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style); 587 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style); 588 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter); 589 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter); 590 if (paramAffectsStroke) { 591 strokeAndFillACase.compare(reporter, strokeAndFillBCase, 592 TestCase::kSameUpToStroke_ComparisonExpecation); 593 } else { 594 strokeAndFillACase.compare(reporter, strokeAndFillBCase, 595 TestCase::kAllSame_ComparisonExpecation); 596 } 597 598 // Make sure stroking params don't affect fill style. 599 SkPaint fillA = strokeA, fillB = strokeB; 600 fillA.setStyle(SkPaint::kFill_Style); 601 fillB.setStyle(SkPaint::kFill_Style); 602 TestCase fillACase(geo, fillA, reporter); 603 TestCase fillBCase(geo, fillB, reporter); 604 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation); 605 606 // Make sure just applying the dash but not stroke gives the same key for both stroking 607 // variations. 608 SkPaint dashA = strokeA, dashB = strokeB; 609 dashA.setPathEffect(make_dash()); 610 dashB.setPathEffect(make_dash()); 611 TestCase dashACase(geo, dashA, reporter); 612 TestCase dashBCase(geo, dashB, reporter); 613 if (paramAffectsDashAndStroke) { 614 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation); 615 } else { 616 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation); 617 } 618} 619 620template <typename GEO, typename T> 621static void test_stroke_param(skiatest::Reporter* reporter, const GEO& geo, 622 std::function<void(SkPaint*, T)> setter, T a, T b) { 623 test_stroke_param_impl(reporter, geo, setter, a, b, true, true); 624}; 625 626template <typename GEO> 627static void test_stroke_cap(skiatest::Reporter* reporter, const GEO& geo) { 628 GrShape shape(geo, GrStyle(SkStrokeRec::kHairline_InitStyle)); 629 // The cap should only affect shapes that may be open. 630 bool affectsStroke = !shape.knownToBeClosed(); 631 // Dashing adds ends that need caps. 632 bool affectsDashAndStroke = true; 633 test_stroke_param_impl<GEO, SkPaint::Cap>( 634 reporter, 635 geo, 636 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);}, 637 SkPaint::kButt_Cap, SkPaint::kRound_Cap, 638 affectsStroke, 639 affectsDashAndStroke); 640}; 641 642template <typename GEO> 643static void test_miter_limit(skiatest::Reporter* reporter, const GEO& geo) { 644 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) { 645 p->setStrokeJoin(SkPaint::kMiter_Join); 646 p->setStrokeMiter(miter); 647 }; 648 649 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) { 650 p->setStrokeJoin(SkPaint::kRound_Join); 651 p->setStrokeMiter(miter); 652 }; 653 654 // The miter limit should affect stroked and dashed-stroked cases when the join type is 655 // miter. 656 test_stroke_param_impl<GEO, SkScalar>( 657 reporter, 658 geo, 659 setMiterJoinAndLimit, 660 0.5f, 0.75f, 661 true, 662 true); 663 664 // The miter limit should not affect stroked and dashed-stroked cases when the join type is 665 // not miter. 666 test_stroke_param_impl<GEO, SkScalar>( 667 reporter, 668 geo, 669 setOtherJoinAndLimit, 670 0.5f, 0.75f, 671 false, 672 false); 673} 674 675template<typename GEO> 676static void test_dash_fill(skiatest::Reporter* reporter, const GEO& geo) { 677 // A dash with no stroke should have no effect 678 using DashFactoryFn = sk_sp<SkPathEffect>(*)(); 679 for (DashFactoryFn md : {&make_dash, &make_null_dash}) { 680 SkPaint dashFill; 681 dashFill.setPathEffect((*md)()); 682 TestCase dashFillCase(geo, dashFill, reporter); 683 684 TestCase fillCase(geo, SkPaint(), reporter); 685 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); 686 } 687} 688 689template<typename GEO> 690void test_null_dash(skiatest::Reporter* reporter, const GEO& geo) { 691 SkPaint fill; 692 SkPaint stroke; 693 stroke.setStyle(SkPaint::kStroke_Style); 694 stroke.setStrokeWidth(1.f); 695 SkPaint dash; 696 dash.setStyle(SkPaint::kStroke_Style); 697 dash.setStrokeWidth(1.f); 698 dash.setPathEffect(make_dash()); 699 SkPaint nullDash; 700 nullDash.setStyle(SkPaint::kStroke_Style); 701 nullDash.setStrokeWidth(1.f); 702 nullDash.setPathEffect(make_null_dash()); 703 704 TestCase fillCase(geo, fill, reporter); 705 TestCase strokeCase(geo, stroke, reporter); 706 TestCase dashCase(geo, dash, reporter); 707 TestCase nullDashCase(geo, nullDash, reporter); 708 709 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation); 710 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation); 711 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation); 712} 713 714template <typename GEO> 715void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) { 716 /** 717 * This path effect takes any input path and turns it into a rrect. It passes through stroke 718 * info. 719 */ 720 class RRectPathEffect : SkPathEffect { 721 public: 722 static const SkRRect& RRect() { 723 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5); 724 return kRRect; 725 } 726 727 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 728 const SkRect* cullR) const override { 729 dst->reset(); 730 dst->addRRect(RRect()); 731 return true; 732 } 733 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 734 *dst = RRect().getBounds(); 735 } 736 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); } 737 Factory getFactory() const override { return nullptr; } 738 void toString(SkString*) const override {} 739 private: 740 RRectPathEffect() {} 741 }; 742 743 SkPaint fill; 744 TestCase fillGeoCase(geo, fill, reporter); 745 746 SkPaint pe; 747 pe.setPathEffect(RRectPathEffect::Make()); 748 TestCase geoPECase(geo, pe, reporter); 749 750 SkPaint peStroke; 751 peStroke.setPathEffect(RRectPathEffect::Make()); 752 peStroke.setStrokeWidth(2.f); 753 peStroke.setStyle(SkPaint::kStroke_Style); 754 TestCase geoPEStrokeCase(geo, peStroke, reporter); 755 756 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation); 757 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation); 758 geoPECase.compare(reporter, geoPEStrokeCase, 759 TestCase::kSameUpToStroke_ComparisonExpecation); 760 761 TestCase rrectFillCase(RRectPathEffect::RRect(), fill, reporter); 762 SkPaint stroke = peStroke; 763 stroke.setPathEffect(nullptr); 764 TestCase rrectStrokeCase(RRectPathEffect::RRect(), stroke, reporter); 765 766 SkRRect rrect; 767 // Applying the path effect should make a SkRRect shape. There is no further stroking in the 768 // geoPECase, so the full style should be the same as just the PE. 769 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr, 770 nullptr)); 771 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); 772 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey()); 773 774 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr, 775 nullptr)); 776 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); 777 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey()); 778 779 // In the PE+stroke case applying the full style should be the same as just stroking the rrect. 780 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr, 781 nullptr, nullptr)); 782 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); 783 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey()); 784 785 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr, 786 nullptr, nullptr)); 787 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == 788 rrectStrokeCase.appliedFullStyleKey()); 789} 790 791template <typename GEO> 792void test_unknown_path_effect(skiatest::Reporter* reporter, const GEO& geo) { 793 /** 794 * This path effect just adds two lineTos to the input path. 795 */ 796 class AddLineTosPathEffect : SkPathEffect { 797 public: 798 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 799 const SkRect* cullR) const override { 800 *dst = src; 801 dst->lineTo(0, 0); 802 dst->lineTo(10, 10); 803 return true; 804 } 805 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 806 *dst = src; 807 dst->growToInclude(0, 0); 808 dst->growToInclude(10, 10); 809 } 810 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); } 811 Factory getFactory() const override { return nullptr; } 812 void toString(SkString*) const override {} 813 private: 814 AddLineTosPathEffect() {} 815 }; 816 817 // This path effect should make the keys invalid when it is applied. We only produce a path 818 // effect key for dash path effects. So the only way another arbitrary path effect can produce 819 // a styled result with a key is to produce a non-path shape that has a purely geometric key. 820 SkPaint peStroke; 821 peStroke.setPathEffect(AddLineTosPathEffect::Make()); 822 peStroke.setStrokeWidth(2.f); 823 peStroke.setStyle(SkPaint::kStroke_Style); 824 TestCase geoPEStrokeCase(geo, peStroke, reporter); 825 TestCase::SelfExpectations expectations; 826 expectations.fPEHasEffect = true; 827 expectations.fPEHasValidKey = false; 828 expectations.fStrokeApplies = true; 829 geoPEStrokeCase.testExpectations(reporter, expectations); 830} 831 832template <typename GEO> 833void test_make_hairline_path_effect(skiatest::Reporter* reporter, const GEO& geo, bool isNonPath) { 834 /** 835 * This path effect just changes the stroke rec to hairline. 836 */ 837 class MakeHairlinePathEffect : SkPathEffect { 838 public: 839 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec, 840 const SkRect* cullR) const override { 841 *dst = src; 842 strokeRec->setHairlineStyle(); 843 return true; 844 } 845 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; } 846 static sk_sp<SkPathEffect> Make() { 847 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect); 848 } 849 Factory getFactory() const override { return nullptr; } 850 void toString(SkString*) const override {} 851 private: 852 MakeHairlinePathEffect() {} 853 }; 854 855 SkPaint fill; 856 SkPaint pe; 857 pe.setPathEffect(MakeHairlinePathEffect::Make()); 858 859 TestCase peCase(geo, pe, reporter); 860 861 SkPath a, b, c; 862 peCase.baseShape().asPath(&a); 863 peCase.appliedPathEffectShape().asPath(&b); 864 peCase.appliedFullStyleShape().asPath(&c); 865 if (isNonPath) { 866 // RRect types can have a change in start index or direction after the PE is applied. This 867 // is because once the PE is applied, GrShape may canonicalize the dir and index since it 868 // is not germane to the styling any longer. 869 // Instead we just check that the paths would fill the same both before and after styling. 870 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 871 REPORTER_ASSERT(reporter, paths_fill_same(a, c)); 872 } else { 873 // The base shape cannot perform canonicalization on the path's fill type because of an 874 // unknown path effect. However, after the path effect is applied the resulting hairline 875 // shape will canonicalize the path fill type since hairlines (and stroking in general) 876 // don't distinguish between even/odd and non-zero winding. 877 a.setFillType(b.getFillType()); 878 REPORTER_ASSERT(reporter, a == b); 879 REPORTER_ASSERT(reporter, a == c); 880 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty()); 881 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty()); 882 } 883 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline()); 884 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline()); 885} 886 887/** 888 * isNonPath indicates whether the initial shape made from the path is expected to be recognized 889 * as a simpler shape type (e.g. rrect) 890 */ 891void test_volatile_path(skiatest::Reporter* reporter, const SkPath& path, 892 bool isNonPath) { 893 SkPath vPath(path); 894 vPath.setIsVolatile(true); 895 896 SkPaint dashAndStroke; 897 dashAndStroke.setPathEffect(make_dash()); 898 dashAndStroke.setStrokeWidth(2.f); 899 dashAndStroke.setStyle(SkPaint::kStroke_Style); 900 TestCase volatileCase(vPath, dashAndStroke, reporter); 901 // We expect a shape made from a volatile path to have a key iff the shape is recognized 902 // as a specialized geometry. 903 if (isNonPath) { 904 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count())); 905 // In this case all the keys should be identical to the non-volatile case. 906 TestCase nonVolatileCase(path, dashAndStroke, reporter); 907 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation); 908 } else { 909 // None of the keys should be valid. 910 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count())); 911 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count())); 912 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count())); 913 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count())); 914 } 915} 916 917template <typename GEO> 918void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const GEO& geo) { 919 /** 920 * This path effect returns an empty path. 921 */ 922 class EmptyPathEffect : SkPathEffect { 923 public: 924 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 925 const SkRect* cullR) const override { 926 dst->reset(); 927 return true; 928 } 929 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 930 dst->setEmpty(); 931 } 932 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new EmptyPathEffect); } 933 Factory getFactory() const override { return nullptr; } 934 void toString(SkString*) const override {} 935 private: 936 EmptyPathEffect() {} 937 }; 938 939 SkPath emptyPath; 940 GrShape emptyShape(emptyPath); 941 Key emptyKey; 942 make_key(&emptyKey, emptyShape); 943 REPORTER_ASSERT(reporter, emptyShape.isEmpty()); 944 945 SkPaint pe; 946 pe.setPathEffect(EmptyPathEffect::Make()); 947 TestCase geoCase(geo, pe, reporter); 948 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleKey() == emptyKey); 949 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectKey() == emptyKey); 950 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectThenStrokeKey() == emptyKey); 951 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectShape().isEmpty()); 952 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleShape().isEmpty()); 953 954 SkPaint peStroke; 955 peStroke.setPathEffect(EmptyPathEffect::Make()); 956 peStroke.setStrokeWidth(2.f); 957 peStroke.setStyle(SkPaint::kStroke_Style); 958 TestCase geoPEStrokeCase(geo, peStroke, reporter); 959 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey); 960 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey); 961 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey); 962 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty()); 963 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty()); 964} 965 966template <typename GEO> 967void test_path_effect_fails(skiatest::Reporter* reporter, const GEO& geo) { 968 /** 969 * This path effect returns an empty path. 970 */ 971 class FailurePathEffect : SkPathEffect { 972 public: 973 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 974 const SkRect* cullR) const override { 975 return false; 976 } 977 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 978 *dst = src; 979 } 980 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); } 981 Factory getFactory() const override { return nullptr; } 982 void toString(SkString*) const override {} 983 private: 984 FailurePathEffect() {} 985 }; 986 987 SkPaint fill; 988 TestCase fillCase(geo, fill, reporter); 989 990 SkPaint pe; 991 pe.setPathEffect(FailurePathEffect::Make()); 992 TestCase peCase(geo, pe, reporter); 993 994 SkPaint stroke; 995 stroke.setStrokeWidth(2.f); 996 stroke.setStyle(SkPaint::kStroke_Style); 997 TestCase strokeCase(geo, stroke, reporter); 998 999 SkPaint peStroke = stroke; 1000 peStroke.setPathEffect(FailurePathEffect::Make()); 1001 TestCase peStrokeCase(geo, peStroke, reporter); 1002 1003 // In general the path effect failure can cause some of the TestCase::compare() tests to fail 1004 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the 1005 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its 1006 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from 1007 // closing it but after the effect fails we can (for the fill+pe case). This causes different 1008 // routes through GrShape to have equivalent but different representations of the path (closed 1009 // or not) but that fill the same. 1010 SkPath a; 1011 SkPath b; 1012 fillCase.appliedPathEffectShape().asPath(&a); 1013 peCase.appliedPathEffectShape().asPath(&b); 1014 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1015 1016 fillCase.appliedFullStyleShape().asPath(&a); 1017 peCase.appliedFullStyleShape().asPath(&b); 1018 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1019 1020 strokeCase.appliedPathEffectShape().asPath(&a); 1021 peStrokeCase.appliedPathEffectShape().asPath(&b); 1022 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1023 1024 strokeCase.appliedFullStyleShape().asPath(&a); 1025 peStrokeCase.appliedFullStyleShape().asPath(&b); 1026 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1027} 1028 1029void test_empty_shape(skiatest::Reporter* reporter) { 1030 SkPath emptyPath; 1031 SkPaint fill; 1032 TestCase fillEmptyCase(emptyPath, fill, reporter); 1033 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty()); 1034 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty()); 1035 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty()); 1036 1037 Key emptyKey(fillEmptyCase.baseKey()); 1038 REPORTER_ASSERT(reporter, emptyKey.count()); 1039 TestCase::SelfExpectations expectations; 1040 expectations.fStrokeApplies = false; 1041 expectations.fPEHasEffect = false; 1042 // This will test whether applying style preserves emptiness 1043 fillEmptyCase.testExpectations(reporter, expectations); 1044 1045 // Stroking an empty path should have no effect 1046 SkPath emptyPath2; 1047 SkPaint stroke; 1048 stroke.setStrokeWidth(2.f); 1049 stroke.setStyle(SkPaint::kStroke_Style); 1050 TestCase strokeEmptyCase(emptyPath2, stroke, reporter); 1051 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation); 1052 1053 // Dashing and stroking an empty path should have no effect 1054 SkPath emptyPath3; 1055 SkPaint dashAndStroke; 1056 dashAndStroke.setPathEffect(make_dash()); 1057 dashAndStroke.setStrokeWidth(2.f); 1058 dashAndStroke.setStyle(SkPaint::kStroke_Style); 1059 TestCase dashAndStrokeEmptyCase(emptyPath3, dashAndStroke, reporter); 1060 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase, 1061 TestCase::kAllSame_ComparisonExpecation); 1062 1063 // A shape made from an empty rrect should behave the same as an empty path. 1064 SkRRect emptyRRect = SkRRect::MakeRect(SkRect::MakeEmpty()); 1065 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type); 1066 TestCase dashAndStrokeEmptyRRectCase(emptyRRect, dashAndStroke, reporter); 1067 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase, 1068 TestCase::kAllSame_ComparisonExpecation); 1069 1070 // Same for a rect. 1071 SkRect emptyRect = SkRect::MakeEmpty(); 1072 TestCase dashAndStrokeEmptyRectCase(emptyRect, dashAndStroke, reporter); 1073 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase, 1074 TestCase::kAllSame_ComparisonExpecation); 1075} 1076 1077// rect and oval types have rrect start indices that collapse to the same point. Here we select the 1078// canonical point in these cases. 1079unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) { 1080 switch (rrect.getType()) { 1081 case SkRRect::kRect_Type: 1082 return (s + 1) & 0b110; 1083 case SkRRect::kOval_Type: 1084 return s & 0b110; 1085 default: 1086 return s; 1087 } 1088} 1089 1090void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) { 1091 enum Style { 1092 kFill, 1093 kStroke, 1094 kHairline, 1095 kStrokeAndFill 1096 }; 1097 1098 // SkStrokeRec has no default cons., so init with kFill before calling the setters below. 1099 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle, 1100 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle}; 1101 strokeRecs[kFill].setFillStyle(); 1102 strokeRecs[kStroke].setStrokeStyle(2.f); 1103 strokeRecs[kHairline].setHairlineStyle(); 1104 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true); 1105 sk_sp<SkPathEffect> dashEffect = make_dash(); 1106 1107 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs)); 1108 1109 auto index = [](bool inverted, 1110 SkPath::Direction dir, 1111 unsigned start, 1112 Style style, 1113 bool dash) -> int { 1114 return inverted * (2 * 8 * kStyleCnt * 2) + 1115 dir * ( 8 * kStyleCnt * 2) + 1116 start * ( kStyleCnt * 2) + 1117 style * ( 2) + 1118 dash; 1119 }; 1120 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1); 1121 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1; 1122 SkAutoTArray<GrShape> shapes(cnt); 1123 for (bool inverted : {false, true}) { 1124 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 1125 for (unsigned start = 0; start < 8; ++start) { 1126 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) { 1127 for (bool dash : {false, true}) { 1128 SkPathEffect* pe = dash ? dashEffect.get() : nullptr; 1129 shapes[index(inverted, dir, start, style, dash)] = 1130 GrShape(rrect, dir, start, SkToBool(inverted), 1131 GrStyle(strokeRecs[style], pe)); 1132 } 1133 } 1134 } 1135 } 1136 } 1137 1138 // Get the keys for some example shape instances that we'll use for comparision against the 1139 // rest. 1140 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction; 1141 static constexpr unsigned kExamplesStart = 0; 1142 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill, 1143 false)]; 1144 Key exampleFillCaseKey; 1145 make_key(&exampleFillCaseKey, exampleFillCase); 1146 1147 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart, 1148 kStrokeAndFill, false)]; 1149 Key exampleStrokeAndFillCaseKey; 1150 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase); 1151 1152 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill, 1153 false)]; 1154 Key exampleInvFillCaseKey; 1155 make_key(&exampleInvFillCaseKey, exampleInvFillCase); 1156 1157 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart, 1158 kStrokeAndFill, false)]; 1159 Key exampleInvStrokeAndFillCaseKey; 1160 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase); 1161 1162 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke, 1163 false)]; 1164 Key exampleStrokeCaseKey; 1165 make_key(&exampleStrokeCaseKey, exampleStrokeCase); 1166 1167 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke, 1168 false)]; 1169 Key exampleInvStrokeCaseKey; 1170 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase); 1171 1172 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart, 1173 kHairline, false)]; 1174 Key exampleHairlineCaseKey; 1175 make_key(&exampleHairlineCaseKey, exampleHairlineCase); 1176 1177 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart, 1178 kHairline, false)]; 1179 Key exampleInvHairlineCaseKey; 1180 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase); 1181 1182 // These are dummy initializations to suppress warnings. 1183 SkRRect queryRR = SkRRect::MakeEmpty(); 1184 SkPath::Direction queryDir = SkPath::kCW_Direction; 1185 unsigned queryStart = ~0U; 1186 bool queryInverted = true; 1187 1188 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted)); 1189 REPORTER_ASSERT(r, queryRR == rrect); 1190 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1191 REPORTER_ASSERT(r, 0 == queryStart); 1192 REPORTER_ASSERT(r, !queryInverted); 1193 1194 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart, 1195 &queryInverted)); 1196 REPORTER_ASSERT(r, queryRR == rrect); 1197 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1198 REPORTER_ASSERT(r, 0 == queryStart); 1199 REPORTER_ASSERT(r, queryInverted); 1200 1201 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart, 1202 &queryInverted)); 1203 REPORTER_ASSERT(r, queryRR == rrect); 1204 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1205 REPORTER_ASSERT(r, 0 == queryStart); 1206 REPORTER_ASSERT(r, !queryInverted); 1207 1208 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart, 1209 &queryInverted)); 1210 REPORTER_ASSERT(r, queryRR == rrect); 1211 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1212 REPORTER_ASSERT(r, 0 == queryStart); 1213 REPORTER_ASSERT(r, queryInverted); 1214 1215 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart, 1216 &queryInverted)); 1217 REPORTER_ASSERT(r, queryRR == rrect); 1218 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1219 REPORTER_ASSERT(r, 0 == queryStart); 1220 REPORTER_ASSERT(r, !queryInverted); 1221 1222 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart, 1223 &queryInverted)); 1224 REPORTER_ASSERT(r, queryRR == rrect); 1225 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1226 REPORTER_ASSERT(r, 0 == queryStart); 1227 REPORTER_ASSERT(r, queryInverted); 1228 1229 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted)); 1230 REPORTER_ASSERT(r, queryRR == rrect); 1231 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1232 REPORTER_ASSERT(r, 0 == queryStart); 1233 REPORTER_ASSERT(r, !queryInverted); 1234 1235 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, 1236 &queryInverted)); 1237 REPORTER_ASSERT(r, queryRR == rrect); 1238 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1239 REPORTER_ASSERT(r, 0 == queryStart); 1240 REPORTER_ASSERT(r, queryInverted); 1241 1242 // Remember that the key reflects the geometry before styling is applied. 1243 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey); 1244 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey); 1245 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey); 1246 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey); 1247 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey); 1248 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey); 1249 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey); 1250 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey); 1251 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey); 1252 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey); 1253 1254 for (bool inverted : {false, true}) { 1255 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 1256 for (unsigned start = 0; start < 8; ++start) { 1257 for (bool dash : {false, true}) { 1258 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)]; 1259 Key fillCaseKey; 1260 make_key(&fillCaseKey, fillCase); 1261 1262 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start, 1263 kStrokeAndFill, dash)]; 1264 Key strokeAndFillCaseKey; 1265 make_key(&strokeAndFillCaseKey, strokeAndFillCase); 1266 1267 // Both fill and stroke-and-fill shapes must respect the inverseness and both 1268 // ignore dashing. 1269 REPORTER_ASSERT(r, !fillCase.style().pathEffect()); 1270 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect()); 1271 TestCase a(fillCase, r); 1272 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r); 1273 TestCase c(strokeAndFillCase, r); 1274 TestCase d(inverted ? exampleInvStrokeAndFillCase 1275 : exampleStrokeAndFillCase, r); 1276 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation); 1277 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation); 1278 1279 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)]; 1280 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline, 1281 dash)]; 1282 1283 TestCase e(strokeCase, r); 1284 TestCase g(hairlineCase, r); 1285 1286 // Both hairline and stroke shapes must respect the dashing. 1287 if (dash) { 1288 // Dashing always ignores the inverseness. skbug.com/5421 1289 TestCase f(exampleStrokeCase, r); 1290 TestCase h(exampleHairlineCase, r); 1291 unsigned expectedStart = canonicalize_rrect_start(start, rrect); 1292 REPORTER_ASSERT(r, strokeCase.style().pathEffect()); 1293 REPORTER_ASSERT(r, hairlineCase.style().pathEffect()); 1294 1295 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart, 1296 &queryInverted)); 1297 REPORTER_ASSERT(r, queryRR == rrect); 1298 REPORTER_ASSERT(r, queryDir == dir); 1299 REPORTER_ASSERT(r, queryStart == expectedStart); 1300 REPORTER_ASSERT(r, !queryInverted); 1301 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart, 1302 &queryInverted)); 1303 REPORTER_ASSERT(r, queryRR == rrect); 1304 REPORTER_ASSERT(r, queryDir == dir); 1305 REPORTER_ASSERT(r, queryStart == expectedStart); 1306 REPORTER_ASSERT(r, !queryInverted); 1307 1308 // The pre-style case for the dash will match the non-dash example iff the 1309 // dir and start match (dir=cw, start=0). 1310 if (0 == expectedStart && SkPath::kCW_Direction == dir) { 1311 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation); 1312 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation); 1313 } else { 1314 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation); 1315 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation); 1316 } 1317 } else { 1318 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r); 1319 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r); 1320 REPORTER_ASSERT(r, !strokeCase.style().pathEffect()); 1321 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect()); 1322 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation); 1323 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation); 1324 } 1325 } 1326 } 1327 } 1328 } 1329} 1330 1331DEF_TEST(GrShape, reporter) { 1332 for (auto r : { SkRect::MakeWH(10, 20), 1333 SkRect::MakeWH(-10, -20), 1334 SkRect::MakeWH(-10, 20), 1335 SkRect::MakeWH(10, -20)}) { 1336 test_basic(reporter, r); 1337 test_scale(reporter, r); 1338 test_dash_fill(reporter, r); 1339 test_null_dash(reporter, r); 1340 // Test modifying various stroke params. 1341 test_stroke_param<SkRect, SkScalar>( 1342 reporter, r, 1343 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, 1344 SkIntToScalar(2), SkIntToScalar(4)); 1345 test_stroke_param<SkRect, SkPaint::Join>( 1346 reporter, r, 1347 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, 1348 SkPaint::kMiter_Join, SkPaint::kRound_Join); 1349 test_stroke_cap(reporter, r); 1350 test_miter_limit(reporter, r); 1351 test_path_effect_makes_rrect(reporter, r); 1352 test_unknown_path_effect(reporter, r); 1353 test_path_effect_makes_empty_shape(reporter, r); 1354 test_path_effect_fails(reporter, r); 1355 test_make_hairline_path_effect(reporter, r, true); 1356 GrShape shape(r); 1357 REPORTER_ASSERT(reporter, !shape.asLine(nullptr)); 1358 } 1359 1360 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)), 1361 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4), 1362 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) { 1363 test_basic(reporter, rr); 1364 test_rrect(reporter, rr); 1365 test_scale(reporter, rr); 1366 test_dash_fill(reporter, rr); 1367 test_null_dash(reporter, rr); 1368 // Test modifying various stroke params. 1369 test_stroke_param<SkRRect, SkScalar>( 1370 reporter, rr, 1371 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, 1372 SkIntToScalar(2), SkIntToScalar(4)); 1373 test_stroke_param<SkRRect, SkPaint::Join>( 1374 reporter, rr, 1375 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, 1376 SkPaint::kMiter_Join, SkPaint::kRound_Join); 1377 test_stroke_cap(reporter, rr); 1378 test_miter_limit(reporter, rr); 1379 test_path_effect_makes_rrect(reporter, rr); 1380 test_unknown_path_effect(reporter, rr); 1381 test_path_effect_makes_empty_shape(reporter, rr); 1382 test_path_effect_fails(reporter, rr); 1383 test_make_hairline_path_effect(reporter, rr, true); 1384 GrShape shape(rr); 1385 REPORTER_ASSERT(reporter, !shape.asLine(nullptr)); 1386 } 1387 1388 struct TestPath { 1389 TestPath(const SkPath& path, bool isRRectFill, bool isRRectStroke, bool isLine, 1390 const SkRRect& rrect) 1391 : fPath(path) 1392 , fIsRRectForFill(isRRectFill) 1393 , fIsRRectForStroke(isRRectStroke) 1394 , fIsLine(isLine) 1395 , fRRect(rrect) {} 1396 SkPath fPath; 1397 bool fIsRRectForFill; 1398 bool fIsRRectForStroke; 1399 bool fIsLine; 1400 SkRRect fRRect; 1401 }; 1402 SkTArray<TestPath> paths; 1403 1404 SkPath circlePath; 1405 circlePath.addCircle(10, 10, 10); 1406 paths.emplace_back(circlePath, true, true, false, SkRRect::MakeOval(SkRect::MakeWH(20,20))); 1407 1408 SkPath rectPath; 1409 rectPath.addRect(SkRect::MakeWH(10, 10)); 1410 paths.emplace_back(rectPath, true, true, false, SkRRect::MakeRect(SkRect::MakeWH(10, 10))); 1411 1412 SkPath openRectPath; 1413 openRectPath.moveTo(0, 0); 1414 openRectPath.lineTo(10, 0); 1415 openRectPath.lineTo(10, 10); 1416 openRectPath.lineTo(0, 10); 1417 paths.emplace_back(openRectPath, true, false, false, SkRRect::MakeRect(SkRect::MakeWH(10, 10))); 1418 1419 SkPath quadPath; 1420 quadPath.quadTo(10, 10, 5, 8); 1421 paths.emplace_back(quadPath, false, false, false, SkRRect()); 1422 1423 SkPath linePath; 1424 linePath.lineTo(10, 10); 1425 paths.emplace_back(linePath, false, false, true, SkRRect()); 1426 1427 for (auto testPath : paths) { 1428 for (bool inverseFill : {false, true}) { 1429 if (inverseFill) { 1430 if (testPath.fPath.getFillType() == SkPath::kEvenOdd_FillType) { 1431 testPath.fPath.setFillType(SkPath::kInverseEvenOdd_FillType); 1432 } else { 1433 SkASSERT(testPath.fPath.getFillType() == SkPath::kWinding_FillType); 1434 testPath.fPath.setFillType(SkPath::kInverseWinding_FillType); 1435 } 1436 } 1437 const SkPath& path = testPath.fPath; 1438 // These tests all assume that the original GrShape for fill and stroke will be the 1439 // same. 1440 // However, that is not the case in special cases (e.g. an unclosed rect becomes a RRect 1441 // GrShape with a fill style but becomes a Path GrShape when stroked). 1442 if (testPath.fIsRRectForFill == testPath.fIsRRectForStroke) { 1443 test_basic(reporter, path); 1444 test_null_dash(reporter, path); 1445 test_path_effect_makes_rrect(reporter, path); 1446 } 1447 test_scale(reporter, path); 1448 // This test uses a stroking paint, hence use of fIsRRectForStroke 1449 test_volatile_path(reporter, path, testPath.fIsRRectForStroke); 1450 test_dash_fill(reporter, path); 1451 // Test modifying various stroke params. 1452 test_stroke_param<SkPath, SkScalar>( 1453 reporter, path, 1454 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, 1455 SkIntToScalar(2), SkIntToScalar(4)); 1456 test_stroke_param<SkPath, SkPaint::Join>( 1457 reporter, path, 1458 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, 1459 SkPaint::kMiter_Join, SkPaint::kRound_Join); 1460 test_stroke_cap(reporter, path); 1461 test_miter_limit(reporter, path); 1462 test_unknown_path_effect(reporter, path); 1463 test_path_effect_makes_empty_shape(reporter, path); 1464 test_path_effect_fails(reporter, path); 1465 test_make_hairline_path_effect(reporter, path, testPath.fIsRRectForStroke); 1466 } 1467 } 1468 1469 for (auto testPath : paths) { 1470 const SkPath& path = testPath.fPath; 1471 1472 SkPaint fillPaint; 1473 TestCase fillPathCase(path, fillPaint, reporter); 1474 SkRRect rrect; 1475 REPORTER_ASSERT(reporter, testPath.fIsRRectForFill == 1476 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr, 1477 nullptr)); 1478 if (testPath.fIsRRectForFill) { 1479 TestCase fillPathCase2(testPath.fPath, fillPaint, reporter); 1480 REPORTER_ASSERT(reporter, rrect == testPath.fRRect); 1481 TestCase fillRRectCase(rrect, fillPaint, reporter); 1482 fillPathCase2.compare(reporter, fillRRectCase, 1483 TestCase::kAllSame_ComparisonExpecation); 1484 } 1485 SkPaint strokePaint; 1486 strokePaint.setStrokeWidth(3.f); 1487 strokePaint.setStyle(SkPaint::kStroke_Style); 1488 TestCase strokePathCase(path, strokePaint, reporter); 1489 REPORTER_ASSERT(reporter, testPath.fIsRRectForStroke == 1490 strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr, 1491 nullptr)); 1492 if (testPath.fIsRRectForStroke) { 1493 REPORTER_ASSERT(reporter, rrect == testPath.fRRect); 1494 TestCase strokeRRectCase(rrect, strokePaint, reporter); 1495 strokePathCase.compare(reporter, strokeRRectCase, 1496 TestCase::kAllSame_ComparisonExpecation); 1497 } 1498 REPORTER_ASSERT(reporter, testPath.fIsLine == fillPathCase.baseShape().asLine(nullptr)); 1499 REPORTER_ASSERT(reporter, testPath.fIsLine == strokePathCase.baseShape().asLine(nullptr)); 1500 } 1501 1502 // Test a volatile empty path. 1503 test_volatile_path(reporter, SkPath(), true); 1504 1505 test_empty_shape(reporter); 1506} 1507 1508#endif 1509