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