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