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 "SkRectPriv.h" 18 #include "SkSurface.h" 19 #include "SkClipOpPriv.h" 20 21 uint32_t GrShape::testingOnly_getOriginalGenerationID() const { 22 if (const auto* lp = this->originalPathForListeners()) { 23 return lp->getGenerationID(); 24 } 25 return SkPath().getGenerationID(); 26 } 27 28 bool GrShape::testingOnly_isPath() const { 29 return Type::kPath == fType; 30 } 31 32 bool GrShape::testingOnly_isNonVolatilePath() const { 33 return Type::kPath == fType && !fPathData.fPath.isVolatile(); 34 } 35 36 using Key = SkTArray<uint32_t>; 37 38 static bool make_key(Key* key, const GrShape& shape) { 39 int size = shape.unstyledKeySize(); 40 if (size <= 0) { 41 key->reset(0); 42 return false; 43 } 44 SkASSERT(size); 45 key->reset(size); 46 shape.writeUnstyledKey(key->begin()); 47 return true; 48 } 49 50 static bool paths_fill_same(const SkPath& a, const SkPath& b) { 51 SkPath pathXor; 52 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor); 53 return pathXor.isEmpty(); 54 } 55 56 static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) { 57 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is 58 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid 59 // rendering within the bounds (with a tolerance). Then we render the path and check that 60 // everything got clipped out. 61 static constexpr int kRes = 2000; 62 // This tolerance is in units of 1/kRes fractions of the bounds width/height. 63 static constexpr int kTol = 0; 64 GR_STATIC_ASSERT(kRes % 4 == 0); 65 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes); 66 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info); 67 surface->getCanvas()->clear(0x0); 68 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2); 69 SkMatrix matrix; 70 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit); 71 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol)); 72 surface->getCanvas()->clipRect(clip, kDifference_SkClipOp); 73 surface->getCanvas()->concat(matrix); 74 SkPaint whitePaint; 75 whitePaint.setColor(SK_ColorWHITE); 76 surface->getCanvas()->drawPath(path, whitePaint); 77 SkPixmap pixmap; 78 surface->getCanvas()->peekPixels(&pixmap); 79 #if defined(SK_BUILD_FOR_WIN) 80 // The static constexpr version in #else causes cl.exe to crash. 81 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1)); 82 #else 83 static constexpr uint8_t kZeros[kRes] = {0}; 84 #endif 85 for (int y = 0; y < kRes; ++y) { 86 const uint8_t* row = pixmap.addr8(0, y); 87 if (0 != memcmp(kZeros, row, kRes)) { 88 return false; 89 } 90 } 91 #ifdef SK_BUILD_FOR_WIN 92 free(const_cast<uint8_t*>(kZeros)); 93 #endif 94 return true; 95 } 96 97 static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) { 98 SkPath path; 99 shape.asPath(&path); 100 if (shape.style().hasNonDashPathEffect()) { 101 return false; 102 } 103 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle(); 104 return strokeRecStyle == SkStrokeRec::kStroke_Style || 105 strokeRecStyle == SkStrokeRec::kHairline_Style || 106 (shape.style().isSimpleFill() && path.isConvex()); 107 } 108 109 static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b, 110 const Key& keyA, const Key& keyB) { 111 // GrShape only respects the input winding direction and start point for rrect shapes 112 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect 113 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled 114 // key will differ. GrShape will have canonicalized the direction and start point for the shape 115 // without the path effect. If *both* have path effects then they should have both preserved 116 // the direction and starting point. 117 118 // The asRRect() output params are all initialized just to silence compiler warnings about 119 // uninitialized variables. 120 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty(); 121 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction; 122 unsigned startA = ~0U, startB = ~0U; 123 bool invertedA = true, invertedB = true; 124 125 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA); 126 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB); 127 bool aHasPE = a.style().hasPathEffect(); 128 bool bHasPE = b.style().hasPathEffect(); 129 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE); 130 // GrShape will close paths with simple fill style. 131 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill()); 132 SkPath pathA, pathB; 133 a.asPath(&pathA); 134 b.asPath(&pathB); 135 136 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a 137 // non-inverse fill type (or vice versa). 138 bool ignoreInversenessDifference = false; 139 if (pathA.isInverseFillType() != pathB.isInverseFillType()) { 140 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b; 141 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a; 142 bool canDropInverse1 = s1->style().isDashed(); 143 bool canDropInverse2 = s2->style().isDashed(); 144 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2); 145 } 146 bool ignoreWindingVsEvenOdd = false; 147 if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) != 148 SkPath::ConvertToNonInverseFillType(pathB.getFillType())) { 149 bool aCanChange = can_interchange_winding_and_even_odd_fill(a); 150 bool bCanChange = can_interchange_winding_and_even_odd_fill(b); 151 if (aCanChange != bCanChange) { 152 ignoreWindingVsEvenOdd = true; 153 } 154 } 155 if (allowSameRRectButDiffStartAndDir) { 156 REPORTER_ASSERT(r, rrectA == rrectB); 157 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB)); 158 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB); 159 } else { 160 SkPath pA = pathA; 161 SkPath pB = pathB; 162 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType()); 163 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType()); 164 if (ignoreInversenessDifference) { 165 pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType())); 166 pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType())); 167 } 168 if (ignoreWindingVsEvenOdd) { 169 pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType 170 : SkPath::kEvenOdd_FillType); 171 pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType 172 : SkPath::kEvenOdd_FillType); 173 } 174 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) { 175 REPORTER_ASSERT(r, keyA == keyB); 176 } else { 177 REPORTER_ASSERT(r, keyA != keyB); 178 } 179 if (allowedClosednessDiff) { 180 // GrShape will close paths with simple fill style. Make the non-filled path closed 181 // so that the comparision will succeed. Make sure both are closed before comparing. 182 pA.close(); 183 pB.close(); 184 } 185 REPORTER_ASSERT(r, pA == pB); 186 REPORTER_ASSERT(r, aIsRRect == bIsRRect); 187 if (aIsRRect) { 188 REPORTER_ASSERT(r, rrectA == rrectB); 189 REPORTER_ASSERT(r, dirA == dirB); 190 REPORTER_ASSERT(r, startA == startB); 191 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB); 192 } 193 } 194 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty()); 195 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed()); 196 // closedness can affect convexity. 197 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex()); 198 if (a.knownToBeConvex()) { 199 REPORTER_ASSERT(r, pathA.isConvex()); 200 } 201 if (b.knownToBeConvex()) { 202 REPORTER_ASSERT(r, pathB.isConvex()); 203 } 204 REPORTER_ASSERT(r, a.bounds() == b.bounds()); 205 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask()); 206 // Init these to suppress warnings. 207 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ; 208 bool invertedLine[2] {true, true}; 209 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1])); 210 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other 211 // doesn't (since the PE can set any fill type on its output path). 212 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other 213 // then they may disagree about inverseness. 214 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() && 215 a.style().isDashed() == b.style().isDashed()) { 216 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() == 217 b.mayBeInverseFilledAfterStyling()); 218 } 219 if (a.asLine(nullptr, nullptr)) { 220 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]); 221 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]); 222 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled()); 223 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled()); 224 } 225 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled()); 226 } 227 228 static void check_original_path_ids(skiatest::Reporter* r, const GrShape& base, const GrShape& pe, 229 const GrShape& peStroke, const GrShape& full) { 230 bool baseIsNonVolatilePath = base.testingOnly_isNonVolatilePath(); 231 bool peIsPath = pe.testingOnly_isPath(); 232 bool peStrokeIsPath = peStroke.testingOnly_isPath(); 233 bool fullIsPath = full.testingOnly_isPath(); 234 235 REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath); 236 237 uint32_t baseID = base.testingOnly_getOriginalGenerationID(); 238 uint32_t peID = pe.testingOnly_getOriginalGenerationID(); 239 uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID(); 240 uint32_t fullID = full.testingOnly_getOriginalGenerationID(); 241 242 // All empty paths have the same gen ID 243 uint32_t emptyID = SkPath().getGenerationID(); 244 245 // If we started with a real path, then our genID should match that path's gen ID (and not be 246 // empty). If we started with a simple shape or a volatile path, our original path should have 247 // been reset. 248 REPORTER_ASSERT(r, baseIsNonVolatilePath == (baseID != emptyID)); 249 250 // For the derived shapes, if they're simple types, their original paths should have been reset 251 REPORTER_ASSERT(r, peIsPath || (peID == emptyID)); 252 REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID)); 253 REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID)); 254 255 if (!peIsPath) { 256 // If the path effect produces a simple shape, then there are no unbroken chains to test 257 return; 258 } 259 260 // From here on, we know that the path effect produced a shape that was a "real" path 261 262 if (baseIsNonVolatilePath) { 263 REPORTER_ASSERT(r, baseID == peID); 264 } 265 266 if (peStrokeIsPath) { 267 REPORTER_ASSERT(r, peID == peStrokeID); 268 REPORTER_ASSERT(r, peStrokeID == fullID); 269 } 270 271 if (baseIsNonVolatilePath && peStrokeIsPath) { 272 REPORTER_ASSERT(r, baseID == peStrokeID); 273 REPORTER_ASSERT(r, baseID == fullID); 274 } 275 } 276 277 void test_inversions(skiatest::Reporter* r, const GrShape& shape, const Key& shapeKey) { 278 GrShape preserve = GrShape::MakeFilled(shape, GrShape::FillInversion::kPreserve); 279 Key preserveKey; 280 make_key(&preserveKey, preserve); 281 282 GrShape flip = GrShape::MakeFilled(shape, GrShape::FillInversion::kFlip); 283 Key flipKey; 284 make_key(&flipKey, flip); 285 286 GrShape inverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceInverted); 287 Key invertedKey; 288 make_key(&invertedKey, inverted); 289 290 GrShape noninverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceNoninverted); 291 Key noninvertedKey; 292 make_key(&noninvertedKey, noninverted); 293 294 if (invertedKey.count() || noninvertedKey.count()) { 295 REPORTER_ASSERT(r, invertedKey != noninvertedKey); 296 } 297 if (shape.style().isSimpleFill()) { 298 check_equivalence(r, shape, preserve, shapeKey, preserveKey); 299 } 300 if (shape.inverseFilled()) { 301 check_equivalence(r, preserve, inverted, preserveKey, invertedKey); 302 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey); 303 } else { 304 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey); 305 check_equivalence(r, flip, inverted, flipKey, invertedKey); 306 } 307 308 GrShape doubleFlip = GrShape::MakeFilled(flip, GrShape::FillInversion::kFlip); 309 Key doubleFlipKey; 310 make_key(&doubleFlipKey, doubleFlip); 311 // It can be the case that the double flip has no key but preserve does. This happens when the 312 // original shape has an inherited style key. That gets dropped on the first inversion flip. 313 if (preserveKey.count() && !doubleFlipKey.count()) { 314 preserveKey.reset(); 315 } 316 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey); 317 } 318 319 namespace { 320 /** 321 * Geo is a factory for creating a GrShape from another representation. It also answers some 322 * questions about expected behavior for GrShape given the inputs. 323 */ 324 class Geo { 325 public: 326 virtual ~Geo() {} 327 virtual GrShape makeShape(const SkPaint&) const = 0; 328 virtual SkPath path() const = 0; 329 // These functions allow tests to check for special cases where style gets 330 // applied by GrShape in its constructor (without calling GrShape::applyStyle). 331 // These unfortunately rely on knowing details of GrShape's implementation. 332 // These predicates are factored out here to avoid littering the rest of the 333 // test code with GrShape implementation details. 334 virtual bool fillChangesGeom() const { return false; } 335 virtual bool strokeIsConvertedToFill() const { return false; } 336 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; } 337 // Is this something we expect GrShape to recognize as something simpler than a path. 338 virtual bool isNonPath(const SkPaint& paint) const { return true; } 339 }; 340 341 class RectGeo : public Geo { 342 public: 343 RectGeo(const SkRect& rect) : fRect(rect) {} 344 345 SkPath path() const override { 346 SkPath path; 347 path.addRect(fRect); 348 return path; 349 } 350 351 GrShape makeShape(const SkPaint& paint) const override { 352 return GrShape(fRect, paint); 353 } 354 355 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override { 356 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); 357 // Converted to an outset rectangle. 358 return paint.getStrokeJoin() == SkPaint::kMiter_Join && 359 paint.getStrokeMiter() >= SK_ScalarSqrt2; 360 } 361 362 private: 363 SkRect fRect; 364 }; 365 366 class RRectGeo : public Geo { 367 public: 368 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {} 369 370 GrShape makeShape(const SkPaint& paint) const override { 371 return GrShape(fRRect, paint); 372 } 373 374 SkPath path() const override { 375 SkPath path; 376 path.addRRect(fRRect); 377 return path; 378 } 379 380 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override { 381 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); 382 if (fRRect.isRect()) { 383 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint); 384 } 385 return false; 386 } 387 388 private: 389 SkRRect fRRect; 390 }; 391 392 class PathGeo : public Geo { 393 public: 394 enum class Invert { kNo, kYes }; 395 396 PathGeo(const SkPath& path, Invert invert) : fPath(path) { 397 SkASSERT(!path.isInverseFillType()); 398 if (Invert::kYes == invert) { 399 if (fPath.getFillType() == SkPath::kEvenOdd_FillType) { 400 fPath.setFillType(SkPath::kInverseEvenOdd_FillType); 401 } else { 402 SkASSERT(fPath.getFillType() == SkPath::kWinding_FillType); 403 fPath.setFillType(SkPath::kInverseWinding_FillType); 404 } 405 } 406 } 407 408 GrShape makeShape(const SkPaint& paint) const override { 409 return GrShape(fPath, paint); 410 } 411 412 SkPath path() const override { return fPath; } 413 414 bool fillChangesGeom() const override { 415 // unclosed rects get closed. Lines get turned into empty geometry 416 return this->isUnclosedRect() || fPath.isLine(nullptr); 417 } 418 419 bool strokeIsConvertedToFill() const override { 420 return this->isAxisAlignedLine(); 421 } 422 423 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override { 424 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); 425 if (this->isAxisAlignedLine()) { 426 // The fill is ignored (zero area) and the stroke is converted to a rrect. 427 return true; 428 } 429 SkRect rect; 430 unsigned start; 431 SkPath::Direction dir; 432 if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) { 433 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint); 434 } 435 return false; 436 } 437 438 bool isNonPath(const SkPaint& paint) const override { 439 return fPath.isLine(nullptr) || fPath.isEmpty(); 440 } 441 442 private: 443 bool isAxisAlignedLine() const { 444 SkPoint pts[2]; 445 if (!fPath.isLine(pts)) { 446 return false; 447 } 448 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY; 449 } 450 451 bool isUnclosedRect() const { 452 bool closed; 453 return fPath.isRect(nullptr, &closed, nullptr) && !closed; 454 } 455 456 SkPath fPath; 457 }; 458 459 class RRectPathGeo : public PathGeo { 460 public: 461 enum class RRectForStroke { kNo, kYes }; 462 463 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke, 464 Invert invert) 465 : PathGeo(path, invert) 466 , fRRect(equivalentRRect) 467 , fRRectForStroke(rrectForStroke) {} 468 469 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke, 470 Invert invert) 471 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {} 472 473 bool isNonPath(const SkPaint& paint) const override { 474 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) { 475 return true; 476 } 477 return false; 478 } 479 480 const SkRRect& rrect() const { return fRRect; } 481 482 private: 483 SkRRect fRRect; 484 RRectForStroke fRRectForStroke; 485 }; 486 487 class TestCase { 488 public: 489 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r, 490 SkScalar scale = SK_Scalar1) : fBase(geo.makeShape(paint)) { 491 this->init(r, scale); 492 } 493 494 template<typename... ShapeArgs> 495 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs) 496 : fBase(shapeArgs...) { 497 this->init(r, SK_Scalar1); 498 } 499 500 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1) 501 : fBase(shape) { 502 this->init(r, scale); 503 } 504 505 struct SelfExpectations { 506 bool fPEHasEffect; 507 bool fPEHasValidKey; 508 bool fStrokeApplies; 509 }; 510 511 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const; 512 513 enum ComparisonExpecation { 514 kAllDifferent_ComparisonExpecation, 515 kSameUpToPE_ComparisonExpecation, 516 kSameUpToStroke_ComparisonExpecation, 517 kAllSame_ComparisonExpecation, 518 }; 519 520 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const; 521 522 const GrShape& baseShape() const { return fBase; } 523 const GrShape& appliedPathEffectShape() const { return fAppliedPE; } 524 const GrShape& appliedFullStyleShape() const { return fAppliedFull; } 525 526 // The returned array's count will be 0 if the key shape has no key. 527 const Key& baseKey() const { return fBaseKey; } 528 const Key& appliedPathEffectKey() const { return fAppliedPEKey; } 529 const Key& appliedFullStyleKey() const { return fAppliedFullKey; } 530 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; } 531 532 private: 533 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) { 534 SkPath path; 535 shape.asPath(&path); 536 // If the bounds are empty, the path ought to be as well. 537 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) { 538 REPORTER_ASSERT(r, path.isEmpty()); 539 return; 540 } 541 if (path.isEmpty()) { 542 return; 543 } 544 // The bounds API explicitly calls out that it does not consider inverseness. 545 SkPath p = path; 546 p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType())); 547 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds)); 548 } 549 550 void init(skiatest::Reporter* r, SkScalar scale) { 551 fAppliedPE = fBase.applyStyle(GrStyle::Apply::kPathEffectOnly, scale); 552 fAppliedPEThenStroke = fAppliedPE.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, 553 scale); 554 fAppliedFull = fBase.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale); 555 556 make_key(&fBaseKey, fBase); 557 make_key(&fAppliedPEKey, fAppliedPE); 558 make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke); 559 make_key(&fAppliedFullKey, fAppliedFull); 560 561 // All shapes should report the same "original" path, so that path renderers can get to it 562 // if necessary. 563 check_original_path_ids(r, fBase, fAppliedPE, fAppliedPEThenStroke, fAppliedFull); 564 565 // Applying the path effect and then the stroke should always be the same as applying 566 // both in one go. 567 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey); 568 SkPath a, b; 569 fAppliedPEThenStroke.asPath(&a); 570 fAppliedFull.asPath(&b); 571 // If the output of the path effect is a rrect then it is possible for a and b to be 572 // different paths that fill identically. The reason is that fAppliedFull will do this: 573 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path 574 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However, 575 // now that there is no longer a path effect, the direction and starting index get 576 // canonicalized before the stroke. 577 if (fAppliedPE.asRRect(nullptr, nullptr, nullptr, nullptr)) { 578 REPORTER_ASSERT(r, paths_fill_same(a, b)); 579 } else { 580 REPORTER_ASSERT(r, a == b); 581 } 582 REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty()); 583 584 SkPath path; 585 fBase.asPath(&path); 586 REPORTER_ASSERT(r, path.isEmpty() == fBase.isEmpty()); 587 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase.segmentMask()); 588 fAppliedPE.asPath(&path); 589 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE.isEmpty()); 590 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE.segmentMask()); 591 fAppliedFull.asPath(&path); 592 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull.isEmpty()); 593 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull.segmentMask()); 594 595 CheckBounds(r, fBase, fBase.bounds()); 596 CheckBounds(r, fAppliedPE, fAppliedPE.bounds()); 597 CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds()); 598 CheckBounds(r, fAppliedFull, fAppliedFull.bounds()); 599 SkRect styledBounds = fBase.styledBounds(); 600 CheckBounds(r, fAppliedFull, styledBounds); 601 styledBounds = fAppliedPE.styledBounds(); 602 CheckBounds(r, fAppliedFull, styledBounds); 603 604 // Check that the same path is produced when style is applied by GrShape and GrStyle. 605 SkPath preStyle; 606 SkPath postPathEffect; 607 SkPath postAllStyle; 608 609 fBase.asPath(&preStyle); 610 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle); 611 if (fBase.style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle, 612 scale)) { 613 // run postPathEffect through GrShape to get any geometry reductions that would have 614 // occurred to fAppliedPE. 615 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect); 616 617 SkPath testPath; 618 fAppliedPE.asPath(&testPath); 619 REPORTER_ASSERT(r, testPath == postPathEffect); 620 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE.style().strokeRec())); 621 } 622 SkStrokeRec::InitStyle fillOrHairline; 623 if (fBase.style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) { 624 SkPath testPath; 625 fAppliedFull.asPath(&testPath); 626 if (fBase.style().hasPathEffect()) { 627 // Because GrShape always does two-stage application when there is a path effect 628 // there may be a reduction/canonicalization step between the path effect and 629 // strokerec not reflected in postAllStyle since it applied both the path effect 630 // and strokerec without analyzing the intermediate path. 631 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath)); 632 } else { 633 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape 634 // would apply. 635 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle); 636 REPORTER_ASSERT(r, testPath == postAllStyle); 637 } 638 639 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) { 640 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleFill()); 641 } else { 642 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline()); 643 } 644 } 645 test_inversions(r, fBase, fBaseKey); 646 test_inversions(r, fAppliedPE, fAppliedPEKey); 647 test_inversions(r, fAppliedFull, fAppliedFullKey); 648 } 649 650 GrShape fBase; 651 GrShape fAppliedPE; 652 GrShape fAppliedPEThenStroke; 653 GrShape fAppliedFull; 654 655 Key fBaseKey; 656 Key fAppliedPEKey; 657 Key fAppliedPEThenStrokeKey; 658 Key fAppliedFullKey; 659 }; 660 661 void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const { 662 // The base's key should always be valid (unless the path is volatile) 663 REPORTER_ASSERT(reporter, fBaseKey.count()); 664 if (expectations.fPEHasEffect) { 665 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey); 666 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count())); 667 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey); 668 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count())); 669 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) { 670 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey); 671 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count())); 672 } 673 } else { 674 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey); 675 SkPath a, b; 676 fBase.asPath(&a); 677 fAppliedPE.asPath(&b); 678 REPORTER_ASSERT(reporter, a == b); 679 if (expectations.fStrokeApplies) { 680 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey); 681 } else { 682 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey); 683 } 684 } 685 } 686 687 void TestCase::compare(skiatest::Reporter* r, const TestCase& that, 688 ComparisonExpecation expectation) const { 689 SkPath a, b; 690 switch (expectation) { 691 case kAllDifferent_ComparisonExpecation: 692 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey); 693 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey); 694 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); 695 break; 696 case kSameUpToPE_ComparisonExpecation: 697 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); 698 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey); 699 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); 700 break; 701 case kSameUpToStroke_ComparisonExpecation: 702 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); 703 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey); 704 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey); 705 break; 706 case kAllSame_ComparisonExpecation: 707 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey); 708 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey); 709 check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey, 710 that.fAppliedFullKey); 711 break; 712 } 713 } 714 } // namespace 715 716 static sk_sp<SkPathEffect> make_dash() { 717 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f }; 718 static const SkScalar kPhase = 0.75; 719 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase); 720 } 721 722 static sk_sp<SkPathEffect> make_null_dash() { 723 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0}; 724 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f); 725 } 726 727 // We make enough TestCases, and they're large enough, that on Google3 builds we exceed 728 // the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap. 729 template <typename... Args> 730 static std::unique_ptr<TestCase> make_TestCase(Args&&... args) { 731 return std::unique_ptr<TestCase>{ new TestCase(std::forward<Args>(args)...) }; 732 } 733 734 static void test_basic(skiatest::Reporter* reporter, const Geo& geo) { 735 sk_sp<SkPathEffect> dashPE = make_dash(); 736 737 TestCase::SelfExpectations expectations; 738 SkPaint fill; 739 740 TestCase fillCase(geo, fill, reporter); 741 expectations.fPEHasEffect = false; 742 expectations.fPEHasValidKey = false; 743 expectations.fStrokeApplies = false; 744 fillCase.testExpectations(reporter, expectations); 745 // Test that another GrShape instance built from the same primitive is the same. 746 make_TestCase(geo, fill, reporter) 747 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); 748 749 SkPaint stroke2RoundBevel; 750 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style); 751 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap); 752 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join); 753 stroke2RoundBevel.setStrokeWidth(2.f); 754 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter); 755 expectations.fPEHasValidKey = true; 756 expectations.fPEHasEffect = false; 757 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill(); 758 stroke2RoundBevelCase.testExpectations(reporter, expectations); 759 make_TestCase(geo, stroke2RoundBevel, reporter) 760 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation); 761 762 SkPaint stroke2RoundBevelDash = stroke2RoundBevel; 763 stroke2RoundBevelDash.setPathEffect(make_dash()); 764 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter); 765 expectations.fPEHasValidKey = true; 766 expectations.fPEHasEffect = true; 767 expectations.fStrokeApplies = true; 768 stroke2RoundBevelDashCase.testExpectations(reporter, expectations); 769 make_TestCase(geo, stroke2RoundBevelDash, reporter) 770 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation); 771 772 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) { 773 fillCase.compare(reporter, stroke2RoundBevelCase, 774 TestCase::kAllDifferent_ComparisonExpecation); 775 fillCase.compare(reporter, stroke2RoundBevelDashCase, 776 TestCase::kAllDifferent_ComparisonExpecation); 777 } else { 778 fillCase.compare(reporter, stroke2RoundBevelCase, 779 TestCase::kSameUpToStroke_ComparisonExpecation); 780 fillCase.compare(reporter, stroke2RoundBevelDashCase, 781 TestCase::kSameUpToPE_ComparisonExpecation); 782 } 783 if (geo.strokeIsConvertedToFill()) { 784 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase, 785 TestCase::kAllDifferent_ComparisonExpecation); 786 } else { 787 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase, 788 TestCase::kSameUpToPE_ComparisonExpecation); 789 } 790 791 // Stroke and fill cases 792 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel; 793 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style); 794 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter); 795 expectations.fPEHasValidKey = true; 796 expectations.fPEHasEffect = false; 797 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill(); 798 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations); 799 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare( 800 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation); 801 802 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash; 803 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style); 804 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter); 805 expectations.fPEHasValidKey = true; 806 expectations.fPEHasEffect = false; 807 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill(); 808 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations); 809 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare( 810 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation); 811 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase, 812 TestCase::kAllSame_ComparisonExpecation); 813 814 SkPaint hairline; 815 hairline.setStyle(SkPaint::kStroke_Style); 816 hairline.setStrokeWidth(0.f); 817 TestCase hairlineCase(geo, hairline, reporter); 818 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except 819 // in the line and unclosed rect cases). 820 if (geo.fillChangesGeom()) { 821 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation); 822 } else { 823 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); 824 } 825 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline()); 826 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline()); 827 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline()); 828 829 } 830 831 static void test_scale(skiatest::Reporter* reporter, const Geo& geo) { 832 sk_sp<SkPathEffect> dashPE = make_dash(); 833 834 static const SkScalar kS1 = 1.f; 835 static const SkScalar kS2 = 2.f; 836 837 SkPaint fill; 838 TestCase fillCase1(geo, fill, reporter, kS1); 839 TestCase fillCase2(geo, fill, reporter, kS2); 840 // Scale doesn't affect fills. 841 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation); 842 843 SkPaint hairline; 844 hairline.setStyle(SkPaint::kStroke_Style); 845 hairline.setStrokeWidth(0.f); 846 TestCase hairlineCase1(geo, hairline, reporter, kS1); 847 TestCase hairlineCase2(geo, hairline, reporter, kS2); 848 // Scale doesn't affect hairlines. 849 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation); 850 851 SkPaint stroke; 852 stroke.setStyle(SkPaint::kStroke_Style); 853 stroke.setStrokeWidth(2.f); 854 TestCase strokeCase1(geo, stroke, reporter, kS1); 855 TestCase strokeCase2(geo, stroke, reporter, kS2); 856 // Scale affects the stroke 857 if (geo.strokeIsConvertedToFill()) { 858 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies()); 859 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation); 860 } else { 861 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation); 862 } 863 864 SkPaint strokeDash = stroke; 865 strokeDash.setPathEffect(make_dash()); 866 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1); 867 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2); 868 // Scale affects the dash and the stroke. 869 strokeDashCase1.compare(reporter, strokeDashCase2, 870 TestCase::kSameUpToPE_ComparisonExpecation); 871 872 // Stroke and fill cases 873 SkPaint strokeAndFill = stroke; 874 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style); 875 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1); 876 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2); 877 SkPaint strokeAndFillDash = strokeDash; 878 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style); 879 // Dash is ignored for stroke and fill 880 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1); 881 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2); 882 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g. 883 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the 884 // geometries should agree. 885 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) { 886 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies()); 887 strokeAndFillCase1.compare(reporter, strokeAndFillCase2, 888 TestCase::kAllSame_ComparisonExpecation); 889 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2, 890 TestCase::kAllSame_ComparisonExpecation); 891 } else { 892 strokeAndFillCase1.compare(reporter, strokeAndFillCase2, 893 TestCase::kSameUpToStroke_ComparisonExpecation); 894 } 895 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1, 896 TestCase::kAllSame_ComparisonExpecation); 897 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2, 898 TestCase::kAllSame_ComparisonExpecation); 899 } 900 901 template <typename T> 902 static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo, 903 std::function<void(SkPaint*, T)> setter, T a, T b, 904 bool paramAffectsStroke, 905 bool paramAffectsDashAndStroke) { 906 // Set the stroke width so that we don't get hairline. However, call the setter afterward so 907 // that it can override the stroke width. 908 SkPaint strokeA; 909 strokeA.setStyle(SkPaint::kStroke_Style); 910 strokeA.setStrokeWidth(2.f); 911 setter(&strokeA, a); 912 SkPaint strokeB; 913 strokeB.setStyle(SkPaint::kStroke_Style); 914 strokeB.setStrokeWidth(2.f); 915 setter(&strokeB, b); 916 917 TestCase strokeACase(geo, strokeA, reporter); 918 TestCase strokeBCase(geo, strokeB, reporter); 919 if (paramAffectsStroke) { 920 // If stroking is immediately incorporated into a geometric transformation then the base 921 // shapes will differ. 922 if (geo.strokeIsConvertedToFill()) { 923 strokeACase.compare(reporter, strokeBCase, 924 TestCase::kAllDifferent_ComparisonExpecation); 925 } else { 926 strokeACase.compare(reporter, strokeBCase, 927 TestCase::kSameUpToStroke_ComparisonExpecation); 928 } 929 } else { 930 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation); 931 } 932 933 SkPaint strokeAndFillA = strokeA; 934 SkPaint strokeAndFillB = strokeB; 935 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style); 936 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style); 937 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter); 938 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter); 939 if (paramAffectsStroke) { 940 // If stroking is immediately incorporated into a geometric transformation then the base 941 // shapes will differ. 942 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) || 943 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) { 944 strokeAndFillACase.compare(reporter, strokeAndFillBCase, 945 TestCase::kAllDifferent_ComparisonExpecation); 946 } else { 947 strokeAndFillACase.compare(reporter, strokeAndFillBCase, 948 TestCase::kSameUpToStroke_ComparisonExpecation); 949 } 950 } else { 951 strokeAndFillACase.compare(reporter, strokeAndFillBCase, 952 TestCase::kAllSame_ComparisonExpecation); 953 } 954 955 // Make sure stroking params don't affect fill style. 956 SkPaint fillA = strokeA, fillB = strokeB; 957 fillA.setStyle(SkPaint::kFill_Style); 958 fillB.setStyle(SkPaint::kFill_Style); 959 TestCase fillACase(geo, fillA, reporter); 960 TestCase fillBCase(geo, fillB, reporter); 961 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation); 962 963 // Make sure just applying the dash but not stroke gives the same key for both stroking 964 // variations. 965 SkPaint dashA = strokeA, dashB = strokeB; 966 dashA.setPathEffect(make_dash()); 967 dashB.setPathEffect(make_dash()); 968 TestCase dashACase(geo, dashA, reporter); 969 TestCase dashBCase(geo, dashB, reporter); 970 if (paramAffectsDashAndStroke) { 971 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation); 972 } else { 973 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation); 974 } 975 } 976 977 template <typename T> 978 static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo, 979 std::function<void(SkPaint*, T)> setter, T a, T b) { 980 test_stroke_param_impl(reporter, geo, setter, a, b, true, true); 981 }; 982 983 static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) { 984 SkPaint hairline; 985 hairline.setStrokeWidth(0); 986 hairline.setStyle(SkPaint::kStroke_Style); 987 GrShape shape = geo.makeShape(hairline); 988 // The cap should only affect shapes that may be open. 989 bool affectsStroke = !shape.knownToBeClosed(); 990 // Dashing adds ends that need caps. 991 bool affectsDashAndStroke = true; 992 test_stroke_param_impl<SkPaint::Cap>( 993 reporter, 994 geo, 995 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);}, 996 SkPaint::kButt_Cap, SkPaint::kRound_Cap, 997 affectsStroke, 998 affectsDashAndStroke); 999 }; 1000 1001 static bool shape_known_not_to_have_joins(const GrShape& shape) { 1002 return shape.asLine(nullptr, nullptr) || shape.isEmpty(); 1003 } 1004 1005 static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) { 1006 SkPaint hairline; 1007 hairline.setStrokeWidth(0); 1008 hairline.setStyle(SkPaint::kStroke_Style); 1009 GrShape shape = geo.makeShape(hairline); 1010 // GrShape recognizes certain types don't have joins and will prevent the join type from 1011 // affecting the style key. 1012 // Dashing doesn't add additional joins. However, GrShape currently loses track of this 1013 // after applying the dash. 1014 bool affectsStroke = !shape_known_not_to_have_joins(shape); 1015 test_stroke_param_impl<SkPaint::Join>( 1016 reporter, 1017 geo, 1018 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, 1019 SkPaint::kRound_Join, SkPaint::kBevel_Join, 1020 affectsStroke, true); 1021 }; 1022 1023 static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) { 1024 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) { 1025 p->setStrokeJoin(SkPaint::kMiter_Join); 1026 p->setStrokeMiter(miter); 1027 }; 1028 1029 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) { 1030 p->setStrokeJoin(SkPaint::kRound_Join); 1031 p->setStrokeMiter(miter); 1032 }; 1033 1034 SkPaint hairline; 1035 hairline.setStrokeWidth(0); 1036 hairline.setStyle(SkPaint::kStroke_Style); 1037 GrShape shape = geo.makeShape(hairline); 1038 bool mayHaveJoins = !shape_known_not_to_have_joins(shape); 1039 1040 // The miter limit should affect stroked and dashed-stroked cases when the join type is 1041 // miter. 1042 test_stroke_param_impl<SkScalar>( 1043 reporter, 1044 geo, 1045 setMiterJoinAndLimit, 1046 0.5f, 0.75f, 1047 mayHaveJoins, 1048 true); 1049 1050 // The miter limit should not affect stroked and dashed-stroked cases when the join type is 1051 // not miter. 1052 test_stroke_param_impl<SkScalar>( 1053 reporter, 1054 geo, 1055 setOtherJoinAndLimit, 1056 0.5f, 0.75f, 1057 false, 1058 false); 1059 } 1060 1061 static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) { 1062 // A dash with no stroke should have no effect 1063 using DashFactoryFn = sk_sp<SkPathEffect>(*)(); 1064 for (DashFactoryFn md : {&make_dash, &make_null_dash}) { 1065 SkPaint dashFill; 1066 dashFill.setPathEffect((*md)()); 1067 TestCase dashFillCase(geo, dashFill, reporter); 1068 1069 TestCase fillCase(geo, SkPaint(), reporter); 1070 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); 1071 } 1072 } 1073 1074 void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) { 1075 SkPaint fill; 1076 SkPaint stroke; 1077 stroke.setStyle(SkPaint::kStroke_Style); 1078 stroke.setStrokeWidth(1.f); 1079 SkPaint dash; 1080 dash.setStyle(SkPaint::kStroke_Style); 1081 dash.setStrokeWidth(1.f); 1082 dash.setPathEffect(make_dash()); 1083 SkPaint nullDash; 1084 nullDash.setStyle(SkPaint::kStroke_Style); 1085 nullDash.setStrokeWidth(1.f); 1086 nullDash.setPathEffect(make_null_dash()); 1087 1088 TestCase fillCase(geo, fill, reporter); 1089 TestCase strokeCase(geo, stroke, reporter); 1090 TestCase dashCase(geo, dash, reporter); 1091 TestCase nullDashCase(geo, nullDash, reporter); 1092 1093 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always. 1094 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation); 1095 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation 1096 // on construction in order to determine how to compare the fill and stroke. 1097 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) { 1098 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation); 1099 } else { 1100 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation); 1101 } 1102 // In the null dash case we may immediately convert to a fill, but not for the normal dash case. 1103 if (geo.strokeIsConvertedToFill()) { 1104 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation); 1105 } else { 1106 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation); 1107 } 1108 } 1109 1110 void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) { 1111 /** 1112 * This path effect takes any input path and turns it into a rrect. It passes through stroke 1113 * info. 1114 */ 1115 class RRectPathEffect : SkPathEffect { 1116 public: 1117 static const SkRRect& RRect() { 1118 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5); 1119 return kRRect; 1120 } 1121 1122 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 1123 const SkRect* cullR) const override { 1124 dst->reset(); 1125 dst->addRRect(RRect()); 1126 return true; 1127 } 1128 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 1129 *dst = RRect().getBounds(); 1130 } 1131 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); } 1132 Factory getFactory() const override { return nullptr; } 1133 void toString(SkString*) const override {} 1134 private: 1135 RRectPathEffect() {} 1136 }; 1137 1138 SkPaint fill; 1139 TestCase fillGeoCase(geo, fill, reporter); 1140 1141 SkPaint pe; 1142 pe.setPathEffect(RRectPathEffect::Make()); 1143 TestCase geoPECase(geo, pe, reporter); 1144 1145 SkPaint peStroke; 1146 peStroke.setPathEffect(RRectPathEffect::Make()); 1147 peStroke.setStrokeWidth(2.f); 1148 peStroke.setStyle(SkPaint::kStroke_Style); 1149 TestCase geoPEStrokeCase(geo, peStroke, reporter); 1150 1151 // Check whether constructing the filled case would cause the base shape to have a different 1152 // geometry (because of a geometric transformation upon initial GrShape construction). 1153 if (geo.fillChangesGeom()) { 1154 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation); 1155 fillGeoCase.compare(reporter, geoPEStrokeCase, 1156 TestCase::kAllDifferent_ComparisonExpecation); 1157 } else { 1158 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation); 1159 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation); 1160 } 1161 geoPECase.compare(reporter, geoPEStrokeCase, 1162 TestCase::kSameUpToStroke_ComparisonExpecation); 1163 1164 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill); 1165 SkPaint stroke = peStroke; 1166 stroke.setPathEffect(nullptr); 1167 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke); 1168 1169 SkRRect rrect; 1170 // Applying the path effect should make a SkRRect shape. There is no further stroking in the 1171 // geoPECase, so the full style should be the same as just the PE. 1172 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr, 1173 nullptr)); 1174 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); 1175 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey()); 1176 1177 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr, 1178 nullptr)); 1179 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); 1180 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey()); 1181 1182 // In the PE+stroke case applying the full style should be the same as just stroking the rrect. 1183 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr, 1184 nullptr, nullptr)); 1185 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); 1186 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey()); 1187 1188 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr, 1189 nullptr, nullptr)); 1190 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == 1191 rrectStrokeCase.appliedFullStyleKey()); 1192 } 1193 1194 void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) { 1195 /** 1196 * This path effect just adds two lineTos to the input path. 1197 */ 1198 class AddLineTosPathEffect : SkPathEffect { 1199 public: 1200 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 1201 const SkRect* cullR) const override { 1202 *dst = src; 1203 // To avoid triggering data-based keying of paths with few verbs we add many segments. 1204 for (int i = 0; i < 100; ++i) { 1205 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i)); 1206 } 1207 return true; 1208 } 1209 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 1210 *dst = src; 1211 SkRectPriv::GrowToInclude(dst, {0, 0}); 1212 SkRectPriv::GrowToInclude(dst, {100, 100}); 1213 } 1214 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); } 1215 Factory getFactory() const override { return nullptr; } 1216 void toString(SkString*) const override {} 1217 private: 1218 AddLineTosPathEffect() {} 1219 }; 1220 1221 // This path effect should make the keys invalid when it is applied. We only produce a path 1222 // effect key for dash path effects. So the only way another arbitrary path effect can produce 1223 // a styled result with a key is to produce a non-path shape that has a purely geometric key. 1224 SkPaint peStroke; 1225 peStroke.setPathEffect(AddLineTosPathEffect::Make()); 1226 peStroke.setStrokeWidth(2.f); 1227 peStroke.setStyle(SkPaint::kStroke_Style); 1228 TestCase geoPEStrokeCase(geo, peStroke, reporter); 1229 TestCase::SelfExpectations expectations; 1230 expectations.fPEHasEffect = true; 1231 expectations.fPEHasValidKey = false; 1232 expectations.fStrokeApplies = true; 1233 geoPEStrokeCase.testExpectations(reporter, expectations); 1234 } 1235 1236 void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) { 1237 /** 1238 * This path effect just changes the stroke rec to hairline. 1239 */ 1240 class MakeHairlinePathEffect : SkPathEffect { 1241 public: 1242 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec, 1243 const SkRect* cullR) const override { 1244 *dst = src; 1245 strokeRec->setHairlineStyle(); 1246 return true; 1247 } 1248 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; } 1249 static sk_sp<SkPathEffect> Make() { 1250 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect); 1251 } 1252 Factory getFactory() const override { return nullptr; } 1253 void toString(SkString*) const override {} 1254 private: 1255 MakeHairlinePathEffect() {} 1256 }; 1257 1258 SkPaint fill; 1259 SkPaint pe; 1260 pe.setPathEffect(MakeHairlinePathEffect::Make()); 1261 1262 TestCase peCase(geo, pe, reporter); 1263 1264 SkPath a, b, c; 1265 peCase.baseShape().asPath(&a); 1266 peCase.appliedPathEffectShape().asPath(&b); 1267 peCase.appliedFullStyleShape().asPath(&c); 1268 if (geo.isNonPath(pe)) { 1269 // RRect types can have a change in start index or direction after the PE is applied. This 1270 // is because once the PE is applied, GrShape may canonicalize the dir and index since it 1271 // is not germane to the styling any longer. 1272 // Instead we just check that the paths would fill the same both before and after styling. 1273 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1274 REPORTER_ASSERT(reporter, paths_fill_same(a, c)); 1275 } else { 1276 // The base shape cannot perform canonicalization on the path's fill type because of an 1277 // unknown path effect. However, after the path effect is applied the resulting hairline 1278 // shape will canonicalize the path fill type since hairlines (and stroking in general) 1279 // don't distinguish between even/odd and non-zero winding. 1280 a.setFillType(b.getFillType()); 1281 REPORTER_ASSERT(reporter, a == b); 1282 REPORTER_ASSERT(reporter, a == c); 1283 // If the resulting path is small enough then it will have a key. 1284 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1285 REPORTER_ASSERT(reporter, paths_fill_same(a, c)); 1286 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty()); 1287 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty()); 1288 } 1289 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline()); 1290 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline()); 1291 } 1292 1293 void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) { 1294 SkPath vPath = geo.path(); 1295 vPath.setIsVolatile(true); 1296 1297 SkPaint dashAndStroke; 1298 dashAndStroke.setPathEffect(make_dash()); 1299 dashAndStroke.setStrokeWidth(2.f); 1300 dashAndStroke.setStyle(SkPaint::kStroke_Style); 1301 TestCase volatileCase(reporter, vPath, dashAndStroke); 1302 // We expect a shape made from a volatile path to have a key iff the shape is recognized 1303 // as a specialized geometry. 1304 if (geo.isNonPath(dashAndStroke)) { 1305 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count())); 1306 // In this case all the keys should be identical to the non-volatile case. 1307 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke); 1308 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation); 1309 } else { 1310 // None of the keys should be valid. 1311 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count())); 1312 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count())); 1313 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count())); 1314 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count())); 1315 } 1316 } 1317 1318 void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) { 1319 /** 1320 * This path effect returns an empty path (possibly inverted) 1321 */ 1322 class EmptyPathEffect : SkPathEffect { 1323 public: 1324 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 1325 const SkRect* cullR) const override { 1326 dst->reset(); 1327 if (fInvert) { 1328 dst->toggleInverseFillType(); 1329 } 1330 return true; 1331 } 1332 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 1333 dst->setEmpty(); 1334 } 1335 static sk_sp<SkPathEffect> Make(bool invert) { 1336 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert)); 1337 } 1338 Factory getFactory() const override { return nullptr; } 1339 void toString(SkString*) const override {} 1340 private: 1341 bool fInvert; 1342 EmptyPathEffect(bool invert) : fInvert(invert) {} 1343 }; 1344 1345 SkPath emptyPath; 1346 GrShape emptyShape(emptyPath); 1347 Key emptyKey; 1348 make_key(&emptyKey, emptyShape); 1349 REPORTER_ASSERT(reporter, emptyShape.isEmpty()); 1350 1351 emptyPath.toggleInverseFillType(); 1352 GrShape invertedEmptyShape(emptyPath); 1353 Key invertedEmptyKey; 1354 make_key(&invertedEmptyKey, invertedEmptyShape); 1355 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty()); 1356 1357 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey); 1358 1359 SkPaint pe; 1360 pe.setPathEffect(EmptyPathEffect::Make(false)); 1361 TestCase geoPECase(geo, pe, reporter); 1362 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey); 1363 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey); 1364 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey); 1365 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty()); 1366 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty()); 1367 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled()); 1368 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled()); 1369 1370 SkPaint peStroke; 1371 peStroke.setPathEffect(EmptyPathEffect::Make(false)); 1372 peStroke.setStrokeWidth(2.f); 1373 peStroke.setStyle(SkPaint::kStroke_Style); 1374 TestCase geoPEStrokeCase(geo, peStroke, reporter); 1375 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey); 1376 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey); 1377 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey); 1378 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty()); 1379 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty()); 1380 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled()); 1381 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled()); 1382 pe.setPathEffect(EmptyPathEffect::Make(true)); 1383 1384 TestCase geoPEInvertCase(geo, pe, reporter); 1385 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey); 1386 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey); 1387 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey); 1388 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty()); 1389 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty()); 1390 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled()); 1391 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled()); 1392 1393 peStroke.setPathEffect(EmptyPathEffect::Make(true)); 1394 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter); 1395 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey); 1396 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey); 1397 REPORTER_ASSERT(reporter, 1398 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey); 1399 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty()); 1400 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty()); 1401 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled()); 1402 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled()); 1403 } 1404 1405 void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) { 1406 /** 1407 * This path effect always fails to apply. 1408 */ 1409 class FailurePathEffect : SkPathEffect { 1410 public: 1411 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 1412 const SkRect* cullR) const override { 1413 return false; 1414 } 1415 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 1416 *dst = src; 1417 } 1418 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); } 1419 Factory getFactory() const override { return nullptr; } 1420 void toString(SkString*) const override {} 1421 private: 1422 FailurePathEffect() {} 1423 }; 1424 1425 SkPaint fill; 1426 TestCase fillCase(geo, fill, reporter); 1427 1428 SkPaint pe; 1429 pe.setPathEffect(FailurePathEffect::Make()); 1430 TestCase peCase(geo, pe, reporter); 1431 1432 SkPaint stroke; 1433 stroke.setStrokeWidth(2.f); 1434 stroke.setStyle(SkPaint::kStroke_Style); 1435 TestCase strokeCase(geo, stroke, reporter); 1436 1437 SkPaint peStroke = stroke; 1438 peStroke.setPathEffect(FailurePathEffect::Make()); 1439 TestCase peStrokeCase(geo, peStroke, reporter); 1440 1441 // In general the path effect failure can cause some of the TestCase::compare() tests to fail 1442 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the 1443 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its 1444 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from 1445 // closing it but after the effect fails we can (for the fill+pe case). This causes different 1446 // routes through GrShape to have equivalent but different representations of the path (closed 1447 // or not) but that fill the same. 1448 SkPath a; 1449 SkPath b; 1450 fillCase.appliedPathEffectShape().asPath(&a); 1451 peCase.appliedPathEffectShape().asPath(&b); 1452 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1453 1454 fillCase.appliedFullStyleShape().asPath(&a); 1455 peCase.appliedFullStyleShape().asPath(&b); 1456 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1457 1458 strokeCase.appliedPathEffectShape().asPath(&a); 1459 peStrokeCase.appliedPathEffectShape().asPath(&b); 1460 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1461 1462 strokeCase.appliedFullStyleShape().asPath(&a); 1463 peStrokeCase.appliedFullStyleShape().asPath(&b); 1464 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1465 } 1466 1467 DEF_TEST(GrShape_empty_shape, reporter) { 1468 SkPath emptyPath; 1469 SkPath invertedEmptyPath; 1470 invertedEmptyPath.toggleInverseFillType(); 1471 SkPaint fill; 1472 TestCase fillEmptyCase(reporter, emptyPath, fill); 1473 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty()); 1474 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty()); 1475 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty()); 1476 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled()); 1477 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled()); 1478 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled()); 1479 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill); 1480 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty()); 1481 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty()); 1482 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty()); 1483 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled()); 1484 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled()); 1485 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled()); 1486 1487 Key emptyKey(fillEmptyCase.baseKey()); 1488 REPORTER_ASSERT(reporter, emptyKey.count()); 1489 Key inverseEmptyKey(fillInvertedEmptyCase.baseKey()); 1490 REPORTER_ASSERT(reporter, inverseEmptyKey.count()); 1491 TestCase::SelfExpectations expectations; 1492 expectations.fStrokeApplies = false; 1493 expectations.fPEHasEffect = false; 1494 // This will test whether applying style preserves emptiness 1495 fillEmptyCase.testExpectations(reporter, expectations); 1496 fillInvertedEmptyCase.testExpectations(reporter, expectations); 1497 1498 // Stroking an empty path should have no effect 1499 SkPaint stroke; 1500 stroke.setStrokeWidth(2.f); 1501 stroke.setStyle(SkPaint::kStroke_Style); 1502 stroke.setStrokeJoin(SkPaint::kRound_Join); 1503 stroke.setStrokeCap(SkPaint::kRound_Cap); 1504 TestCase strokeEmptyCase(reporter, emptyPath, stroke); 1505 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation); 1506 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke); 1507 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase, 1508 TestCase::kAllSame_ComparisonExpecation); 1509 1510 // Dashing and stroking an empty path should have no effect 1511 SkPaint dashAndStroke; 1512 dashAndStroke.setPathEffect(make_dash()); 1513 dashAndStroke.setStrokeWidth(2.f); 1514 dashAndStroke.setStyle(SkPaint::kStroke_Style); 1515 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke); 1516 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase, 1517 TestCase::kAllSame_ComparisonExpecation); 1518 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke); 1519 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill. 1520 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase, 1521 TestCase::kAllSame_ComparisonExpecation); 1522 1523 // A shape made from an empty rrect should behave the same as an empty path when filled but not 1524 // when stroked. However, dashing an empty rrect produces an empty path leaving nothing to 1525 // stroke - so equivalent to filling an empty path. 1526 SkRRect emptyRRect = SkRRect::MakeEmpty(); 1527 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type); 1528 1529 TestCase fillEmptyRRectCase(reporter, emptyRRect, fill); 1530 fillEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation); 1531 1532 TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke); 1533 strokeEmptyRRectCase.compare(reporter, strokeEmptyCase, 1534 TestCase::kAllDifferent_ComparisonExpecation); 1535 1536 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke); 1537 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase, 1538 TestCase::kAllSame_ComparisonExpecation); 1539 1540 static constexpr SkPath::Direction kDir = SkPath::kCCW_Direction; 1541 static constexpr int kStart = 0; 1542 1543 TestCase fillInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, GrStyle(fill)); 1544 fillInvertedEmptyRRectCase.compare(reporter, fillInvertedEmptyCase, 1545 TestCase::kAllSame_ComparisonExpecation); 1546 1547 TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, 1548 GrStyle(stroke)); 1549 strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase, 1550 TestCase::kAllDifferent_ComparisonExpecation); 1551 1552 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true, 1553 GrStyle(dashAndStroke)); 1554 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase, 1555 TestCase::kAllSame_ComparisonExpecation); 1556 1557 // Same for a rect. 1558 SkRect emptyRect = SkRect::MakeEmpty(); 1559 TestCase fillEmptyRectCase(reporter, emptyRect, fill); 1560 fillEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation); 1561 1562 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke); 1563 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase, 1564 TestCase::kAllSame_ComparisonExpecation); 1565 1566 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir, 1567 kStart, true, GrStyle(dashAndStroke)); 1568 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill. 1569 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase, 1570 TestCase::kAllSame_ComparisonExpecation); 1571 } 1572 1573 // rect and oval types have rrect start indices that collapse to the same point. Here we select the 1574 // canonical point in these cases. 1575 unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) { 1576 switch (rrect.getType()) { 1577 case SkRRect::kRect_Type: 1578 return (s + 1) & 0b110; 1579 case SkRRect::kOval_Type: 1580 return s & 0b110; 1581 default: 1582 return s; 1583 } 1584 } 1585 1586 void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) { 1587 enum Style { 1588 kFill, 1589 kStroke, 1590 kHairline, 1591 kStrokeAndFill 1592 }; 1593 1594 // SkStrokeRec has no default cons., so init with kFill before calling the setters below. 1595 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle, 1596 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle}; 1597 strokeRecs[kFill].setFillStyle(); 1598 strokeRecs[kStroke].setStrokeStyle(2.f); 1599 strokeRecs[kHairline].setHairlineStyle(); 1600 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true); 1601 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before 1602 // applyStyle() is called. 1603 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f); 1604 sk_sp<SkPathEffect> dashEffect = make_dash(); 1605 1606 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs)); 1607 1608 auto index = [](bool inverted, 1609 SkPath::Direction dir, 1610 unsigned start, 1611 Style style, 1612 bool dash) -> int { 1613 return inverted * (2 * 8 * kStyleCnt * 2) + 1614 dir * ( 8 * kStyleCnt * 2) + 1615 start * ( kStyleCnt * 2) + 1616 style * ( 2) + 1617 dash; 1618 }; 1619 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1); 1620 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1; 1621 SkAutoTArray<GrShape> shapes(cnt); 1622 for (bool inverted : {false, true}) { 1623 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 1624 for (unsigned start = 0; start < 8; ++start) { 1625 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) { 1626 for (bool dash : {false, true}) { 1627 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr; 1628 shapes[index(inverted, dir, start, style, dash)] = 1629 GrShape(rrect, dir, start, SkToBool(inverted), 1630 GrStyle(strokeRecs[style], std::move(pe))); 1631 } 1632 } 1633 } 1634 } 1635 } 1636 1637 // Get the keys for some example shape instances that we'll use for comparision against the 1638 // rest. 1639 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction; 1640 static constexpr unsigned kExamplesStart = 0; 1641 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill, 1642 false)]; 1643 Key exampleFillCaseKey; 1644 make_key(&exampleFillCaseKey, exampleFillCase); 1645 1646 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart, 1647 kStrokeAndFill, false)]; 1648 Key exampleStrokeAndFillCaseKey; 1649 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase); 1650 1651 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill, 1652 false)]; 1653 Key exampleInvFillCaseKey; 1654 make_key(&exampleInvFillCaseKey, exampleInvFillCase); 1655 1656 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart, 1657 kStrokeAndFill, false)]; 1658 Key exampleInvStrokeAndFillCaseKey; 1659 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase); 1660 1661 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke, 1662 false)]; 1663 Key exampleStrokeCaseKey; 1664 make_key(&exampleStrokeCaseKey, exampleStrokeCase); 1665 1666 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke, 1667 false)]; 1668 Key exampleInvStrokeCaseKey; 1669 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase); 1670 1671 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart, 1672 kHairline, false)]; 1673 Key exampleHairlineCaseKey; 1674 make_key(&exampleHairlineCaseKey, exampleHairlineCase); 1675 1676 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart, 1677 kHairline, false)]; 1678 Key exampleInvHairlineCaseKey; 1679 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase); 1680 1681 // These are dummy initializations to suppress warnings. 1682 SkRRect queryRR = SkRRect::MakeEmpty(); 1683 SkPath::Direction queryDir = SkPath::kCW_Direction; 1684 unsigned queryStart = ~0U; 1685 bool queryInverted = true; 1686 1687 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted)); 1688 REPORTER_ASSERT(r, queryRR == rrect); 1689 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1690 REPORTER_ASSERT(r, 0 == queryStart); 1691 REPORTER_ASSERT(r, !queryInverted); 1692 1693 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart, 1694 &queryInverted)); 1695 REPORTER_ASSERT(r, queryRR == rrect); 1696 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1697 REPORTER_ASSERT(r, 0 == queryStart); 1698 REPORTER_ASSERT(r, queryInverted); 1699 1700 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart, 1701 &queryInverted)); 1702 REPORTER_ASSERT(r, queryRR == rrect); 1703 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1704 REPORTER_ASSERT(r, 0 == queryStart); 1705 REPORTER_ASSERT(r, !queryInverted); 1706 1707 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart, 1708 &queryInverted)); 1709 REPORTER_ASSERT(r, queryRR == rrect); 1710 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1711 REPORTER_ASSERT(r, 0 == queryStart); 1712 REPORTER_ASSERT(r, queryInverted); 1713 1714 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart, 1715 &queryInverted)); 1716 REPORTER_ASSERT(r, queryRR == rrect); 1717 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1718 REPORTER_ASSERT(r, 0 == queryStart); 1719 REPORTER_ASSERT(r, !queryInverted); 1720 1721 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart, 1722 &queryInverted)); 1723 REPORTER_ASSERT(r, queryRR == rrect); 1724 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1725 REPORTER_ASSERT(r, 0 == queryStart); 1726 REPORTER_ASSERT(r, queryInverted); 1727 1728 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted)); 1729 REPORTER_ASSERT(r, queryRR == rrect); 1730 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1731 REPORTER_ASSERT(r, 0 == queryStart); 1732 REPORTER_ASSERT(r, !queryInverted); 1733 1734 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, 1735 &queryInverted)); 1736 REPORTER_ASSERT(r, queryRR == rrect); 1737 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1738 REPORTER_ASSERT(r, 0 == queryStart); 1739 REPORTER_ASSERT(r, queryInverted); 1740 1741 // Remember that the key reflects the geometry before styling is applied. 1742 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey); 1743 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey); 1744 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey); 1745 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey); 1746 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey); 1747 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey); 1748 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey); 1749 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey); 1750 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey); 1751 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey); 1752 1753 for (bool inverted : {false, true}) { 1754 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 1755 for (unsigned start = 0; start < 8; ++start) { 1756 for (bool dash : {false, true}) { 1757 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)]; 1758 Key fillCaseKey; 1759 make_key(&fillCaseKey, fillCase); 1760 1761 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start, 1762 kStrokeAndFill, dash)]; 1763 Key strokeAndFillCaseKey; 1764 make_key(&strokeAndFillCaseKey, strokeAndFillCase); 1765 1766 // Both fill and stroke-and-fill shapes must respect the inverseness and both 1767 // ignore dashing. 1768 REPORTER_ASSERT(r, !fillCase.style().pathEffect()); 1769 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect()); 1770 TestCase a(fillCase, r); 1771 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r); 1772 TestCase c(strokeAndFillCase, r); 1773 TestCase d(inverted ? exampleInvStrokeAndFillCase 1774 : exampleStrokeAndFillCase, r); 1775 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation); 1776 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation); 1777 1778 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)]; 1779 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline, 1780 dash)]; 1781 1782 TestCase e(strokeCase, r); 1783 TestCase g(hairlineCase, r); 1784 1785 // Both hairline and stroke shapes must respect the dashing. 1786 if (dash) { 1787 // Dashing always ignores the inverseness. skbug.com/5421 1788 TestCase f(exampleStrokeCase, r); 1789 TestCase h(exampleHairlineCase, r); 1790 unsigned expectedStart = canonicalize_rrect_start(start, rrect); 1791 REPORTER_ASSERT(r, strokeCase.style().pathEffect()); 1792 REPORTER_ASSERT(r, hairlineCase.style().pathEffect()); 1793 1794 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart, 1795 &queryInverted)); 1796 REPORTER_ASSERT(r, queryRR == rrect); 1797 REPORTER_ASSERT(r, queryDir == dir); 1798 REPORTER_ASSERT(r, queryStart == expectedStart); 1799 REPORTER_ASSERT(r, !queryInverted); 1800 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart, 1801 &queryInverted)); 1802 REPORTER_ASSERT(r, queryRR == rrect); 1803 REPORTER_ASSERT(r, queryDir == dir); 1804 REPORTER_ASSERT(r, queryStart == expectedStart); 1805 REPORTER_ASSERT(r, !queryInverted); 1806 1807 // The pre-style case for the dash will match the non-dash example iff the 1808 // dir and start match (dir=cw, start=0). 1809 if (0 == expectedStart && SkPath::kCW_Direction == dir) { 1810 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation); 1811 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation); 1812 } else { 1813 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation); 1814 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation); 1815 } 1816 } else { 1817 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r); 1818 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r); 1819 REPORTER_ASSERT(r, !strokeCase.style().pathEffect()); 1820 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect()); 1821 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation); 1822 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation); 1823 } 1824 } 1825 } 1826 } 1827 } 1828 } 1829 1830 DEF_TEST(GrShape_lines, r) { 1831 static constexpr SkPoint kA { 1, 1}; 1832 static constexpr SkPoint kB { 5, -9}; 1833 static constexpr SkPoint kC {-3, 17}; 1834 1835 SkPath lineAB; 1836 lineAB.moveTo(kA); 1837 lineAB.lineTo(kB); 1838 1839 SkPath lineBA; 1840 lineBA.moveTo(kB); 1841 lineBA.lineTo(kA); 1842 1843 SkPath lineAC; 1844 lineAC.moveTo(kB); 1845 lineAC.lineTo(kC); 1846 1847 SkPath invLineAB = lineAB; 1848 invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType); 1849 1850 SkPaint fill; 1851 SkPaint stroke; 1852 stroke.setStyle(SkPaint::kStroke_Style); 1853 stroke.setStrokeWidth(2.f); 1854 SkPaint hairline; 1855 hairline.setStyle(SkPaint::kStroke_Style); 1856 hairline.setStrokeWidth(0.f); 1857 SkPaint dash = stroke; 1858 dash.setPathEffect(make_dash()); 1859 1860 TestCase fillAB(r, lineAB, fill); 1861 TestCase fillEmpty(r, SkPath(), fill); 1862 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation); 1863 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr)); 1864 1865 SkPath path; 1866 path.toggleInverseFillType(); 1867 TestCase fillEmptyInverted(r, path, fill); 1868 TestCase fillABInverted(r, invLineAB, fill); 1869 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation); 1870 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr)); 1871 1872 TestCase strokeAB(r, lineAB, stroke); 1873 TestCase strokeBA(r, lineBA, stroke); 1874 TestCase strokeAC(r, lineAC, stroke); 1875 1876 TestCase hairlineAB(r, lineAB, hairline); 1877 TestCase hairlineBA(r, lineBA, hairline); 1878 TestCase hairlineAC(r, lineAC, hairline); 1879 1880 TestCase dashAB(r, lineAB, dash); 1881 TestCase dashBA(r, lineBA, dash); 1882 TestCase dashAC(r, lineAC, dash); 1883 1884 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation); 1885 1886 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation); 1887 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation); 1888 1889 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation); 1890 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation); 1891 1892 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation); 1893 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation); 1894 1895 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation); 1896 1897 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how 1898 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed). 1899 bool canonicalizeAsAB; 1900 SkPoint canonicalPts[2] {kA, kB}; 1901 // Init these to suppress warnings. 1902 bool inverted = true; 1903 SkPoint pts[2] {{0, 0}, {0, 0}}; 1904 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted); 1905 if (pts[0] == kA && pts[1] == kB) { 1906 canonicalizeAsAB = true; 1907 } else if (pts[1] == kA && pts[0] == kB) { 1908 canonicalizeAsAB = false; 1909 SkTSwap(canonicalPts[0], canonicalPts[1]); 1910 } else { 1911 ERRORF(r, "Should return pts (a,b) or (b, a)"); 1912 return; 1913 }; 1914 1915 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA, 1916 TestCase::kSameUpToPE_ComparisonExpecation); 1917 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted && 1918 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]); 1919 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted && 1920 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]); 1921 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted && 1922 pts[0] == kA && pts[1] == kB); 1923 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted && 1924 pts[0] == kB && pts[1] == kA); 1925 1926 1927 TestCase strokeInvAB(r, invLineAB, stroke); 1928 TestCase hairlineInvAB(r, invLineAB, hairline); 1929 TestCase dashInvAB(r, invLineAB, dash); 1930 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation); 1931 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation); 1932 // Dashing ignores inverse. 1933 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation); 1934 1935 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted && 1936 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]); 1937 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted && 1938 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]); 1939 // Dashing ignores inverse. 1940 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted && 1941 pts[0] == kA && pts[1] == kB); 1942 1943 } 1944 1945 DEF_TEST(GrShape_stroked_lines, r) { 1946 static constexpr SkScalar kIntervals1[] = {1.f, 0.f}; 1947 auto dash1 = SkDashPathEffect::Make(kIntervals1, SK_ARRAY_COUNT(kIntervals1), 0.f); 1948 REPORTER_ASSERT(r, dash1); 1949 static constexpr SkScalar kIntervals2[] = {10.f, 0.f, 5.f, 0.f}; 1950 auto dash2 = SkDashPathEffect::Make(kIntervals2, SK_ARRAY_COUNT(kIntervals2), 10.f); 1951 REPORTER_ASSERT(r, dash2); 1952 1953 sk_sp<SkPathEffect> pathEffects[] = {nullptr, std::move(dash1), std::move(dash2)}; 1954 1955 for (const auto& pe : pathEffects) { 1956 // Paints to try 1957 SkPaint buttCap; 1958 buttCap.setStyle(SkPaint::kStroke_Style); 1959 buttCap.setStrokeWidth(4); 1960 buttCap.setStrokeCap(SkPaint::kButt_Cap); 1961 buttCap.setPathEffect(pe); 1962 1963 SkPaint squareCap = buttCap; 1964 squareCap.setStrokeCap(SkPaint::kSquare_Cap); 1965 squareCap.setPathEffect(pe); 1966 1967 SkPaint roundCap = buttCap; 1968 roundCap.setStrokeCap(SkPaint::kRound_Cap); 1969 roundCap.setPathEffect(pe); 1970 1971 // vertical 1972 SkPath linePath; 1973 linePath.moveTo(4, 4); 1974 linePath.lineTo(4, 5); 1975 1976 SkPaint fill; 1977 1978 make_TestCase(r, linePath, buttCap)->compare( 1979 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill), 1980 TestCase::kAllSame_ComparisonExpecation); 1981 1982 make_TestCase(r, linePath, squareCap)->compare( 1983 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill), 1984 TestCase::kAllSame_ComparisonExpecation); 1985 1986 make_TestCase(r, linePath, roundCap)->compare(r, 1987 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill), 1988 TestCase::kAllSame_ComparisonExpecation); 1989 1990 // horizontal 1991 linePath.reset(); 1992 linePath.moveTo(4, 4); 1993 linePath.lineTo(5, 4); 1994 1995 make_TestCase(r, linePath, buttCap)->compare( 1996 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill), 1997 TestCase::kAllSame_ComparisonExpecation); 1998 make_TestCase(r, linePath, squareCap)->compare( 1999 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill), 2000 TestCase::kAllSame_ComparisonExpecation); 2001 make_TestCase(r, linePath, roundCap)->compare( 2002 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill), 2003 TestCase::kAllSame_ComparisonExpecation); 2004 2005 // point 2006 linePath.reset(); 2007 linePath.moveTo(4, 4); 2008 linePath.lineTo(4, 4); 2009 2010 make_TestCase(r, linePath, buttCap)->compare( 2011 r, TestCase(r, SkRect::MakeEmpty(), fill), 2012 TestCase::kAllSame_ComparisonExpecation); 2013 make_TestCase(r, linePath, squareCap)->compare( 2014 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill), 2015 TestCase::kAllSame_ComparisonExpecation); 2016 make_TestCase(r, linePath, roundCap)->compare( 2017 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill), 2018 TestCase::kAllSame_ComparisonExpecation); 2019 } 2020 } 2021 2022 DEF_TEST(GrShape_short_path_keys, r) { 2023 SkPaint paints[4]; 2024 paints[1].setStyle(SkPaint::kStroke_Style); 2025 paints[1].setStrokeWidth(5.f); 2026 paints[2].setStyle(SkPaint::kStroke_Style); 2027 paints[2].setStrokeWidth(0.f); 2028 paints[3].setStyle(SkPaint::kStrokeAndFill_Style); 2029 paints[3].setStrokeWidth(5.f); 2030 2031 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB, 2032 TestCase::ComparisonExpecation expectation) { 2033 SkPath volatileA = pathA; 2034 SkPath volatileB = pathB; 2035 volatileA.setIsVolatile(true); 2036 volatileB.setIsVolatile(true); 2037 for (const SkPaint& paint : paints) { 2038 REPORTER_ASSERT(r, !GrShape(volatileA, paint).hasUnstyledKey()); 2039 REPORTER_ASSERT(r, !GrShape(volatileB, paint).hasUnstyledKey()); 2040 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) { 2041 TestCase caseA(PathGeo(pathA, invert), paint, r); 2042 TestCase caseB(PathGeo(pathB, invert), paint, r); 2043 caseA.compare(r, caseB, expectation); 2044 } 2045 } 2046 }; 2047 2048 SkPath pathA; 2049 SkPath pathB; 2050 2051 // Two identical paths 2052 pathA.lineTo(10.f, 10.f); 2053 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f); 2054 2055 pathB.lineTo(10.f, 10.f); 2056 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f); 2057 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation); 2058 2059 // Give path b a different point 2060 pathB.reset(); 2061 pathB.lineTo(10.f, 10.f); 2062 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f); 2063 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation); 2064 2065 // Give path b a different conic weight 2066 pathB.reset(); 2067 pathB.lineTo(10.f, 10.f); 2068 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f); 2069 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation); 2070 2071 // Give path b an extra lineTo verb 2072 pathB.reset(); 2073 pathB.lineTo(10.f, 10.f); 2074 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f); 2075 pathB.lineTo(50.f, 50.f); 2076 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation); 2077 2078 // Give path b a close 2079 pathB.reset(); 2080 pathB.lineTo(10.f, 10.f); 2081 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f); 2082 pathB.close(); 2083 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation); 2084 } 2085 2086 DEF_TEST(GrShape, reporter) { 2087 SkTArray<std::unique_ptr<Geo>> geos; 2088 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos; 2089 2090 for (auto r : { SkRect::MakeWH(10, 20), 2091 SkRect::MakeWH(-10, -20), 2092 SkRect::MakeWH(-10, 20), 2093 SkRect::MakeWH(10, -20)}) { 2094 geos.emplace_back(new RectGeo(r)); 2095 SkPath rectPath; 2096 rectPath.addRect(r); 2097 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes, 2098 PathGeo::Invert::kNo)); 2099 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes, 2100 PathGeo::Invert::kYes)); 2101 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes, 2102 PathGeo::Invert::kNo)); 2103 } 2104 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)), 2105 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4), 2106 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) { 2107 geos.emplace_back(new RRectGeo(rr)); 2108 test_rrect(reporter, rr); 2109 SkPath rectPath; 2110 rectPath.addRRect(rr); 2111 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes, 2112 PathGeo::Invert::kNo)); 2113 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes, 2114 PathGeo::Invert::kYes)); 2115 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr, 2116 RRectPathGeo::RRectForStroke::kYes, 2117 PathGeo::Invert::kNo)); 2118 } 2119 2120 { 2121 SkPath openRectPath; 2122 openRectPath.moveTo(0, 0); 2123 openRectPath.lineTo(10, 0); 2124 openRectPath.lineTo(10, 10); 2125 openRectPath.lineTo(0, 10); 2126 geos.emplace_back(new RRectPathGeo( 2127 openRectPath, SkRect::MakeWH(10, 10), 2128 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo)); 2129 geos.emplace_back(new RRectPathGeo( 2130 openRectPath, SkRect::MakeWH(10, 10), 2131 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes)); 2132 rrectPathGeos.emplace_back(new RRectPathGeo( 2133 openRectPath, SkRect::MakeWH(10, 10), 2134 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo)); 2135 } 2136 2137 { 2138 SkPath quadPath; 2139 quadPath.quadTo(10, 10, 5, 8); 2140 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo)); 2141 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes)); 2142 } 2143 2144 { 2145 SkPath linePath; 2146 linePath.lineTo(10, 10); 2147 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo)); 2148 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes)); 2149 } 2150 2151 // Horizontal and vertical paths become rrects when stroked. 2152 { 2153 SkPath vLinePath; 2154 vLinePath.lineTo(0, 10); 2155 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo)); 2156 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes)); 2157 } 2158 2159 { 2160 SkPath hLinePath; 2161 hLinePath.lineTo(10, 0); 2162 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo)); 2163 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes)); 2164 } 2165 2166 for (int i = 0; i < geos.count(); ++i) { 2167 test_basic(reporter, *geos[i]); 2168 test_scale(reporter, *geos[i]); 2169 test_dash_fill(reporter, *geos[i]); 2170 test_null_dash(reporter, *geos[i]); 2171 // Test modifying various stroke params. 2172 test_stroke_param<SkScalar>( 2173 reporter, *geos[i], 2174 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, 2175 SkIntToScalar(2), SkIntToScalar(4)); 2176 test_stroke_join(reporter, *geos[i]); 2177 test_stroke_cap(reporter, *geos[i]); 2178 test_miter_limit(reporter, *geos[i]); 2179 test_path_effect_makes_rrect(reporter, *geos[i]); 2180 test_unknown_path_effect(reporter, *geos[i]); 2181 test_path_effect_makes_empty_shape(reporter, *geos[i]); 2182 test_path_effect_fails(reporter, *geos[i]); 2183 test_make_hairline_path_effect(reporter, *geos[i]); 2184 test_volatile_path(reporter, *geos[i]); 2185 } 2186 2187 for (int i = 0; i < rrectPathGeos.count(); ++i) { 2188 const RRectPathGeo& rrgeo = *rrectPathGeos[i]; 2189 SkPaint fillPaint; 2190 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint); 2191 SkRRect rrect; 2192 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) == 2193 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr, 2194 nullptr)); 2195 if (rrgeo.isNonPath(fillPaint)) { 2196 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint); 2197 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect()); 2198 TestCase fillRRectCase(reporter, rrect, fillPaint); 2199 fillPathCase2.compare(reporter, fillRRectCase, 2200 TestCase::kAllSame_ComparisonExpecation); 2201 } 2202 SkPaint strokePaint; 2203 strokePaint.setStrokeWidth(3.f); 2204 strokePaint.setStyle(SkPaint::kStroke_Style); 2205 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint); 2206 if (rrgeo.isNonPath(strokePaint)) { 2207 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr, 2208 nullptr)); 2209 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect()); 2210 TestCase strokeRRectCase(reporter, rrect, strokePaint); 2211 strokePathCase.compare(reporter, strokeRRectCase, 2212 TestCase::kAllSame_ComparisonExpecation); 2213 } 2214 } 2215 2216 // Test a volatile empty path. 2217 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo)); 2218 } 2219 2220 #endif 2221