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