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