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