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