GrShapeTest.cpp revision 73603f3c52ffd89fe9d035be827b566a0e7d3b79
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 dst->lineTo(0, 0); 1082 dst->lineTo(10, 10); 1083 return true; 1084 } 1085 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 1086 *dst = src; 1087 dst->growToInclude(0, 0); 1088 dst->growToInclude(10, 10); 1089 } 1090 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); } 1091 Factory getFactory() const override { return nullptr; } 1092 void toString(SkString*) const override {} 1093 private: 1094 AddLineTosPathEffect() {} 1095 }; 1096 1097 // This path effect should make the keys invalid when it is applied. We only produce a path 1098 // effect key for dash path effects. So the only way another arbitrary path effect can produce 1099 // a styled result with a key is to produce a non-path shape that has a purely geometric key. 1100 SkPaint peStroke; 1101 peStroke.setPathEffect(AddLineTosPathEffect::Make()); 1102 peStroke.setStrokeWidth(2.f); 1103 peStroke.setStyle(SkPaint::kStroke_Style); 1104 TestCase geoPEStrokeCase(geo, peStroke, reporter); 1105 TestCase::SelfExpectations expectations; 1106 expectations.fPEHasEffect = true; 1107 expectations.fPEHasValidKey = false; 1108 expectations.fStrokeApplies = true; 1109 geoPEStrokeCase.testExpectations(reporter, expectations); 1110} 1111 1112void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) { 1113 /** 1114 * This path effect just changes the stroke rec to hairline. 1115 */ 1116 class MakeHairlinePathEffect : SkPathEffect { 1117 public: 1118 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec, 1119 const SkRect* cullR) const override { 1120 *dst = src; 1121 strokeRec->setHairlineStyle(); 1122 return true; 1123 } 1124 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; } 1125 static sk_sp<SkPathEffect> Make() { 1126 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect); 1127 } 1128 Factory getFactory() const override { return nullptr; } 1129 void toString(SkString*) const override {} 1130 private: 1131 MakeHairlinePathEffect() {} 1132 }; 1133 1134 SkPaint fill; 1135 SkPaint pe; 1136 pe.setPathEffect(MakeHairlinePathEffect::Make()); 1137 1138 TestCase peCase(geo, pe, reporter); 1139 1140 SkPath a, b, c; 1141 peCase.baseShape().asPath(&a); 1142 peCase.appliedPathEffectShape().asPath(&b); 1143 peCase.appliedFullStyleShape().asPath(&c); 1144 if (geo.isNonPath(pe)) { 1145 // RRect types can have a change in start index or direction after the PE is applied. This 1146 // is because once the PE is applied, GrShape may canonicalize the dir and index since it 1147 // is not germane to the styling any longer. 1148 // Instead we just check that the paths would fill the same both before and after styling. 1149 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1150 REPORTER_ASSERT(reporter, paths_fill_same(a, c)); 1151 } else { 1152 // The base shape cannot perform canonicalization on the path's fill type because of an 1153 // unknown path effect. However, after the path effect is applied the resulting hairline 1154 // shape will canonicalize the path fill type since hairlines (and stroking in general) 1155 // don't distinguish between even/odd and non-zero winding. 1156 a.setFillType(b.getFillType()); 1157 REPORTER_ASSERT(reporter, a == b); 1158 REPORTER_ASSERT(reporter, a == c); 1159 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty()); 1160 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty()); 1161 } 1162 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline()); 1163 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline()); 1164} 1165 1166void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) { 1167 SkPath vPath = geo.path(); 1168 vPath.setIsVolatile(true); 1169 1170 SkPaint dashAndStroke; 1171 dashAndStroke.setPathEffect(make_dash()); 1172 dashAndStroke.setStrokeWidth(2.f); 1173 dashAndStroke.setStyle(SkPaint::kStroke_Style); 1174 TestCase volatileCase(reporter, vPath, dashAndStroke); 1175 // We expect a shape made from a volatile path to have a key iff the shape is recognized 1176 // as a specialized geometry. 1177 if (geo.isNonPath(dashAndStroke)) { 1178 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count())); 1179 // In this case all the keys should be identical to the non-volatile case. 1180 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke); 1181 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation); 1182 } else { 1183 // None of the keys should be valid. 1184 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count())); 1185 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count())); 1186 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count())); 1187 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count())); 1188 } 1189} 1190 1191void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) { 1192 /** 1193 * This path effect returns an empty path. 1194 */ 1195 class EmptyPathEffect : SkPathEffect { 1196 public: 1197 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 1198 const SkRect* cullR) const override { 1199 dst->reset(); 1200 return true; 1201 } 1202 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 1203 dst->setEmpty(); 1204 } 1205 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new EmptyPathEffect); } 1206 Factory getFactory() const override { return nullptr; } 1207 void toString(SkString*) const override {} 1208 private: 1209 EmptyPathEffect() {} 1210 }; 1211 1212 SkPath emptyPath; 1213 GrShape emptyShape(emptyPath); 1214 Key emptyKey; 1215 make_key(&emptyKey, emptyShape); 1216 REPORTER_ASSERT(reporter, emptyShape.isEmpty()); 1217 1218 SkPaint pe; 1219 pe.setPathEffect(EmptyPathEffect::Make()); 1220 TestCase geoCase(geo, pe, reporter); 1221 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleKey() == emptyKey); 1222 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectKey() == emptyKey); 1223 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectThenStrokeKey() == emptyKey); 1224 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectShape().isEmpty()); 1225 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleShape().isEmpty()); 1226 1227 SkPaint peStroke; 1228 peStroke.setPathEffect(EmptyPathEffect::Make()); 1229 peStroke.setStrokeWidth(2.f); 1230 peStroke.setStyle(SkPaint::kStroke_Style); 1231 TestCase geoPEStrokeCase(geo, peStroke, reporter); 1232 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey); 1233 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey); 1234 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey); 1235 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty()); 1236 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty()); 1237} 1238 1239void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) { 1240 /** 1241 * This path effect always fails to apply. 1242 */ 1243 class FailurePathEffect : SkPathEffect { 1244 public: 1245 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, 1246 const SkRect* cullR) const override { 1247 return false; 1248 } 1249 void computeFastBounds(SkRect* dst, const SkRect& src) const override { 1250 *dst = src; 1251 } 1252 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); } 1253 Factory getFactory() const override { return nullptr; } 1254 void toString(SkString*) const override {} 1255 private: 1256 FailurePathEffect() {} 1257 }; 1258 1259 SkPaint fill; 1260 TestCase fillCase(geo, fill, reporter); 1261 1262 SkPaint pe; 1263 pe.setPathEffect(FailurePathEffect::Make()); 1264 TestCase peCase(geo, pe, reporter); 1265 1266 SkPaint stroke; 1267 stroke.setStrokeWidth(2.f); 1268 stroke.setStyle(SkPaint::kStroke_Style); 1269 TestCase strokeCase(geo, stroke, reporter); 1270 1271 SkPaint peStroke = stroke; 1272 peStroke.setPathEffect(FailurePathEffect::Make()); 1273 TestCase peStrokeCase(geo, peStroke, reporter); 1274 1275 // In general the path effect failure can cause some of the TestCase::compare() tests to fail 1276 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the 1277 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its 1278 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from 1279 // closing it but after the effect fails we can (for the fill+pe case). This causes different 1280 // routes through GrShape to have equivalent but different representations of the path (closed 1281 // or not) but that fill the same. 1282 SkPath a; 1283 SkPath b; 1284 fillCase.appliedPathEffectShape().asPath(&a); 1285 peCase.appliedPathEffectShape().asPath(&b); 1286 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1287 1288 fillCase.appliedFullStyleShape().asPath(&a); 1289 peCase.appliedFullStyleShape().asPath(&b); 1290 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1291 1292 strokeCase.appliedPathEffectShape().asPath(&a); 1293 peStrokeCase.appliedPathEffectShape().asPath(&b); 1294 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1295 1296 strokeCase.appliedFullStyleShape().asPath(&a); 1297 peStrokeCase.appliedFullStyleShape().asPath(&b); 1298 REPORTER_ASSERT(reporter, paths_fill_same(a, b)); 1299} 1300 1301void test_empty_shape(skiatest::Reporter* reporter) { 1302 SkPath emptyPath; 1303 SkPaint fill; 1304 TestCase fillEmptyCase(reporter, emptyPath, fill); 1305 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty()); 1306 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty()); 1307 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty()); 1308 1309 Key emptyKey(fillEmptyCase.baseKey()); 1310 REPORTER_ASSERT(reporter, emptyKey.count()); 1311 TestCase::SelfExpectations expectations; 1312 expectations.fStrokeApplies = false; 1313 expectations.fPEHasEffect = false; 1314 // This will test whether applying style preserves emptiness 1315 fillEmptyCase.testExpectations(reporter, expectations); 1316 1317 // Stroking an empty path should have no effect 1318 SkPath emptyPath2; 1319 SkPaint stroke; 1320 stroke.setStrokeWidth(2.f); 1321 stroke.setStyle(SkPaint::kStroke_Style); 1322 TestCase strokeEmptyCase(reporter, emptyPath2, stroke); 1323 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation); 1324 1325 // Dashing and stroking an empty path should have no effect 1326 SkPath emptyPath3; 1327 SkPaint dashAndStroke; 1328 dashAndStroke.setPathEffect(make_dash()); 1329 dashAndStroke.setStrokeWidth(2.f); 1330 dashAndStroke.setStyle(SkPaint::kStroke_Style); 1331 TestCase dashAndStrokeEmptyCase(reporter, emptyPath3, dashAndStroke); 1332 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase, 1333 TestCase::kAllSame_ComparisonExpecation); 1334 1335 // A shape made from an empty rrect should behave the same as an empty path. 1336 SkRRect emptyRRect = SkRRect::MakeRect(SkRect::MakeEmpty()); 1337 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type); 1338 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke); 1339 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase, 1340 TestCase::kAllSame_ComparisonExpecation); 1341 1342 // Same for a rect. 1343 SkRect emptyRect = SkRect::MakeEmpty(); 1344 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke); 1345 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase, 1346 TestCase::kAllSame_ComparisonExpecation); 1347} 1348 1349// rect and oval types have rrect start indices that collapse to the same point. Here we select the 1350// canonical point in these cases. 1351unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) { 1352 switch (rrect.getType()) { 1353 case SkRRect::kRect_Type: 1354 return (s + 1) & 0b110; 1355 case SkRRect::kOval_Type: 1356 return s & 0b110; 1357 default: 1358 return s; 1359 } 1360} 1361 1362void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) { 1363 enum Style { 1364 kFill, 1365 kStroke, 1366 kHairline, 1367 kStrokeAndFill 1368 }; 1369 1370 // SkStrokeRec has no default cons., so init with kFill before calling the setters below. 1371 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle, 1372 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle}; 1373 strokeRecs[kFill].setFillStyle(); 1374 strokeRecs[kStroke].setStrokeStyle(2.f); 1375 strokeRecs[kHairline].setHairlineStyle(); 1376 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true); 1377 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before 1378 // applyStyle() is called. 1379 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f); 1380 sk_sp<SkPathEffect> dashEffect = make_dash(); 1381 1382 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs)); 1383 1384 auto index = [](bool inverted, 1385 SkPath::Direction dir, 1386 unsigned start, 1387 Style style, 1388 bool dash) -> int { 1389 return inverted * (2 * 8 * kStyleCnt * 2) + 1390 dir * ( 8 * kStyleCnt * 2) + 1391 start * ( kStyleCnt * 2) + 1392 style * ( 2) + 1393 dash; 1394 }; 1395 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1); 1396 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1; 1397 SkAutoTArray<GrShape> shapes(cnt); 1398 for (bool inverted : {false, true}) { 1399 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 1400 for (unsigned start = 0; start < 8; ++start) { 1401 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) { 1402 for (bool dash : {false, true}) { 1403 SkPathEffect* pe = dash ? dashEffect.get() : nullptr; 1404 shapes[index(inverted, dir, start, style, dash)] = 1405 GrShape(rrect, dir, start, SkToBool(inverted), 1406 GrStyle(strokeRecs[style], pe)); 1407 } 1408 } 1409 } 1410 } 1411 } 1412 1413 // Get the keys for some example shape instances that we'll use for comparision against the 1414 // rest. 1415 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction; 1416 static constexpr unsigned kExamplesStart = 0; 1417 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill, 1418 false)]; 1419 Key exampleFillCaseKey; 1420 make_key(&exampleFillCaseKey, exampleFillCase); 1421 1422 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart, 1423 kStrokeAndFill, false)]; 1424 Key exampleStrokeAndFillCaseKey; 1425 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase); 1426 1427 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill, 1428 false)]; 1429 Key exampleInvFillCaseKey; 1430 make_key(&exampleInvFillCaseKey, exampleInvFillCase); 1431 1432 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart, 1433 kStrokeAndFill, false)]; 1434 Key exampleInvStrokeAndFillCaseKey; 1435 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase); 1436 1437 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke, 1438 false)]; 1439 Key exampleStrokeCaseKey; 1440 make_key(&exampleStrokeCaseKey, exampleStrokeCase); 1441 1442 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke, 1443 false)]; 1444 Key exampleInvStrokeCaseKey; 1445 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase); 1446 1447 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart, 1448 kHairline, false)]; 1449 Key exampleHairlineCaseKey; 1450 make_key(&exampleHairlineCaseKey, exampleHairlineCase); 1451 1452 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart, 1453 kHairline, false)]; 1454 Key exampleInvHairlineCaseKey; 1455 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase); 1456 1457 // These are dummy initializations to suppress warnings. 1458 SkRRect queryRR = SkRRect::MakeEmpty(); 1459 SkPath::Direction queryDir = SkPath::kCW_Direction; 1460 unsigned queryStart = ~0U; 1461 bool queryInverted = true; 1462 1463 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted)); 1464 REPORTER_ASSERT(r, queryRR == rrect); 1465 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1466 REPORTER_ASSERT(r, 0 == queryStart); 1467 REPORTER_ASSERT(r, !queryInverted); 1468 1469 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart, 1470 &queryInverted)); 1471 REPORTER_ASSERT(r, queryRR == rrect); 1472 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1473 REPORTER_ASSERT(r, 0 == queryStart); 1474 REPORTER_ASSERT(r, queryInverted); 1475 1476 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart, 1477 &queryInverted)); 1478 REPORTER_ASSERT(r, queryRR == rrect); 1479 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1480 REPORTER_ASSERT(r, 0 == queryStart); 1481 REPORTER_ASSERT(r, !queryInverted); 1482 1483 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart, 1484 &queryInverted)); 1485 REPORTER_ASSERT(r, queryRR == rrect); 1486 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1487 REPORTER_ASSERT(r, 0 == queryStart); 1488 REPORTER_ASSERT(r, queryInverted); 1489 1490 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart, 1491 &queryInverted)); 1492 REPORTER_ASSERT(r, queryRR == rrect); 1493 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1494 REPORTER_ASSERT(r, 0 == queryStart); 1495 REPORTER_ASSERT(r, !queryInverted); 1496 1497 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart, 1498 &queryInverted)); 1499 REPORTER_ASSERT(r, queryRR == rrect); 1500 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1501 REPORTER_ASSERT(r, 0 == queryStart); 1502 REPORTER_ASSERT(r, queryInverted); 1503 1504 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted)); 1505 REPORTER_ASSERT(r, queryRR == rrect); 1506 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1507 REPORTER_ASSERT(r, 0 == queryStart); 1508 REPORTER_ASSERT(r, !queryInverted); 1509 1510 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, 1511 &queryInverted)); 1512 REPORTER_ASSERT(r, queryRR == rrect); 1513 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir); 1514 REPORTER_ASSERT(r, 0 == queryStart); 1515 REPORTER_ASSERT(r, queryInverted); 1516 1517 // Remember that the key reflects the geometry before styling is applied. 1518 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey); 1519 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey); 1520 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey); 1521 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey); 1522 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey); 1523 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey); 1524 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey); 1525 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey); 1526 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey); 1527 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey); 1528 1529 for (bool inverted : {false, true}) { 1530 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 1531 for (unsigned start = 0; start < 8; ++start) { 1532 for (bool dash : {false, true}) { 1533 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)]; 1534 Key fillCaseKey; 1535 make_key(&fillCaseKey, fillCase); 1536 1537 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start, 1538 kStrokeAndFill, dash)]; 1539 Key strokeAndFillCaseKey; 1540 make_key(&strokeAndFillCaseKey, strokeAndFillCase); 1541 1542 // Both fill and stroke-and-fill shapes must respect the inverseness and both 1543 // ignore dashing. 1544 REPORTER_ASSERT(r, !fillCase.style().pathEffect()); 1545 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect()); 1546 TestCase a(fillCase, r); 1547 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r); 1548 TestCase c(strokeAndFillCase, r); 1549 TestCase d(inverted ? exampleInvStrokeAndFillCase 1550 : exampleStrokeAndFillCase, r); 1551 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation); 1552 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation); 1553 1554 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)]; 1555 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline, 1556 dash)]; 1557 1558 TestCase e(strokeCase, r); 1559 TestCase g(hairlineCase, r); 1560 1561 // Both hairline and stroke shapes must respect the dashing. 1562 if (dash) { 1563 // Dashing always ignores the inverseness. skbug.com/5421 1564 TestCase f(exampleStrokeCase, r); 1565 TestCase h(exampleHairlineCase, r); 1566 unsigned expectedStart = canonicalize_rrect_start(start, rrect); 1567 REPORTER_ASSERT(r, strokeCase.style().pathEffect()); 1568 REPORTER_ASSERT(r, hairlineCase.style().pathEffect()); 1569 1570 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart, 1571 &queryInverted)); 1572 REPORTER_ASSERT(r, queryRR == rrect); 1573 REPORTER_ASSERT(r, queryDir == dir); 1574 REPORTER_ASSERT(r, queryStart == expectedStart); 1575 REPORTER_ASSERT(r, !queryInverted); 1576 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart, 1577 &queryInverted)); 1578 REPORTER_ASSERT(r, queryRR == rrect); 1579 REPORTER_ASSERT(r, queryDir == dir); 1580 REPORTER_ASSERT(r, queryStart == expectedStart); 1581 REPORTER_ASSERT(r, !queryInverted); 1582 1583 // The pre-style case for the dash will match the non-dash example iff the 1584 // dir and start match (dir=cw, start=0). 1585 if (0 == expectedStart && SkPath::kCW_Direction == dir) { 1586 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation); 1587 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation); 1588 } else { 1589 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation); 1590 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation); 1591 } 1592 } else { 1593 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r); 1594 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r); 1595 REPORTER_ASSERT(r, !strokeCase.style().pathEffect()); 1596 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect()); 1597 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation); 1598 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation); 1599 } 1600 } 1601 } 1602 } 1603 } 1604} 1605 1606void test_lines(skiatest::Reporter* r) { 1607 static constexpr SkPoint kA { 1, 1}; 1608 static constexpr SkPoint kB { 5, -9}; 1609 static constexpr SkPoint kC {-3, 17}; 1610 1611 SkPath lineAB; 1612 lineAB.moveTo(kA); 1613 lineAB.lineTo(kB); 1614 1615 SkPath lineBA; 1616 lineBA.moveTo(kB); 1617 lineBA.lineTo(kA); 1618 1619 SkPath lineAC; 1620 lineAC.moveTo(kB); 1621 lineAC.lineTo(kC); 1622 1623 SkPath invLineAB = lineAB; 1624 invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType); 1625 1626 SkPaint fill; 1627 SkPaint stroke; 1628 stroke.setStyle(SkPaint::kStroke_Style); 1629 stroke.setStrokeWidth(2.f); 1630 SkPaint hairline; 1631 hairline.setStyle(SkPaint::kStroke_Style); 1632 hairline.setStrokeWidth(0.f); 1633 SkPaint dash = stroke; 1634 dash.setPathEffect(make_dash()); 1635 1636 TestCase fillAB(r, lineAB, fill); 1637 TestCase fillEmpty(r, SkPath(), fill); 1638 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation); 1639 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr)); 1640 1641 TestCase strokeAB(r, lineAB, stroke); 1642 TestCase strokeBA(r, lineBA, stroke); 1643 TestCase strokeAC(r, lineAC, stroke); 1644 1645 TestCase hairlineAB(r, lineAB, hairline); 1646 TestCase hairlineBA(r, lineBA, hairline); 1647 TestCase hairlineAC(r, lineAC, hairline); 1648 1649 TestCase dashAB(r, lineAB, dash); 1650 TestCase dashBA(r, lineBA, dash); 1651 TestCase dashAC(r, lineAC, dash); 1652 1653 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation); 1654 1655 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation); 1656 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation); 1657 1658 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation); 1659 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation); 1660 1661 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation); 1662 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation); 1663 1664 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation); 1665 1666 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how 1667 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed). 1668 bool canonicalizeAsAB; 1669 SkPoint canonicalPts[2] {kA, kB}; 1670 // Init these to suppress warnings. 1671 bool inverted = true; 1672 SkPoint pts[2] {{0, 0}, {0, 0}}; 1673 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted); 1674 if (pts[0] == kA && pts[1] == kB) { 1675 canonicalizeAsAB = true; 1676 } else if (pts[1] == kA && pts[0] == kB) { 1677 canonicalizeAsAB = false; 1678 SkTSwap(canonicalPts[0], canonicalPts[1]); 1679 } else { 1680 ERRORF(r, "Should return pts (a,b) or (b, a)"); 1681 return; 1682 }; 1683 1684 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA, 1685 TestCase::kSameUpToPE_ComparisonExpecation); 1686 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted && 1687 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]); 1688 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted && 1689 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]); 1690 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted && 1691 pts[0] == kA && pts[1] == kB); 1692 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted && 1693 pts[0] == kB && pts[1] == kA); 1694 1695 1696 TestCase strokeInvAB(r, invLineAB, stroke); 1697 TestCase hairlineInvAB(r, invLineAB, hairline); 1698 TestCase dashInvAB(r, invLineAB, dash); 1699 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation); 1700 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation); 1701 // Dashing ignores inverse. 1702 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation); 1703 1704 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted && 1705 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]); 1706 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted && 1707 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]); 1708 // Dashing ignores inverse. 1709 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted && 1710 pts[0] == kA && pts[1] == kB); 1711 1712} 1713 1714static void test_stroked_lines(skiatest::Reporter* r) { 1715 // Paints to try 1716 SkPaint buttCap; 1717 buttCap.setStyle(SkPaint::kStroke_Style); 1718 buttCap.setStrokeWidth(4); 1719 buttCap.setStrokeCap(SkPaint::kButt_Cap); 1720 1721 SkPaint squareCap = buttCap; 1722 squareCap.setStrokeCap(SkPaint::kSquare_Cap); 1723 1724 SkPaint roundCap = buttCap; 1725 roundCap.setStrokeCap(SkPaint::kRound_Cap); 1726 1727 // vertical 1728 SkPath linePath; 1729 linePath.moveTo(4, 4); 1730 linePath.lineTo(4, 5); 1731 1732 SkPaint fill; 1733 1734 TestCase(r, linePath, buttCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill), 1735 TestCase::kAllSame_ComparisonExpecation); 1736 1737 TestCase(r, linePath, squareCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill), 1738 TestCase::kAllSame_ComparisonExpecation); 1739 1740 TestCase(r, linePath, roundCap).compare(r, 1741 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill), 1742 TestCase::kAllSame_ComparisonExpecation); 1743 1744 // horizontal 1745 linePath.reset(); 1746 linePath.moveTo(4, 4); 1747 linePath.lineTo(5, 4); 1748 1749 TestCase(r, linePath, buttCap).compare(r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill), 1750 TestCase::kAllSame_ComparisonExpecation); 1751 TestCase(r, linePath, squareCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill), 1752 TestCase::kAllSame_ComparisonExpecation); 1753 TestCase(r, linePath, roundCap).compare(r, 1754 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill), 1755 TestCase::kAllSame_ComparisonExpecation); 1756 1757 // point 1758 linePath.reset(); 1759 linePath.moveTo(4, 4); 1760 linePath.lineTo(4, 4); 1761 1762 TestCase(r, linePath, buttCap).compare(r, TestCase(r, SkRect::MakeEmpty(), fill), 1763 TestCase::kAllSame_ComparisonExpecation); 1764 TestCase(r, linePath, squareCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill), 1765 TestCase::kAllSame_ComparisonExpecation); 1766 TestCase(r, linePath, roundCap).compare(r, 1767 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill), 1768 TestCase::kAllSame_ComparisonExpecation); 1769} 1770 1771DEF_TEST(GrShape, reporter) { 1772 SkTArray<std::unique_ptr<Geo>> geos; 1773 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos; 1774 1775 for (auto r : { SkRect::MakeWH(10, 20), 1776 SkRect::MakeWH(-10, -20), 1777 SkRect::MakeWH(-10, 20), 1778 SkRect::MakeWH(10, -20)}) { 1779 geos.emplace_back(new RectGeo(r)); 1780 SkPath rectPath; 1781 rectPath.addRect(r); 1782 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes, 1783 PathGeo::Invert::kNo)); 1784 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes, 1785 PathGeo::Invert::kYes)); 1786 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes, 1787 PathGeo::Invert::kNo)); 1788 } 1789 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)), 1790 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4), 1791 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) { 1792 geos.emplace_back(new RRectGeo(rr)); 1793 test_rrect(reporter, rr); 1794 SkPath rectPath; 1795 rectPath.addRRect(rr); 1796 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes, 1797 PathGeo::Invert::kNo)); 1798 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes, 1799 PathGeo::Invert::kYes)); 1800 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr, 1801 RRectPathGeo::RRectForStroke::kYes, 1802 PathGeo::Invert::kNo)); 1803 } 1804 1805 SkPath openRectPath; 1806 openRectPath.moveTo(0, 0); 1807 openRectPath.lineTo(10, 0); 1808 openRectPath.lineTo(10, 10); 1809 openRectPath.lineTo(0, 10); 1810 geos.emplace_back(new RRectPathGeo(openRectPath, SkRect::MakeWH(10, 10), 1811 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo)); 1812 geos.emplace_back(new RRectPathGeo(openRectPath, SkRect::MakeWH(10, 10), 1813 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes)); 1814 rrectPathGeos.emplace_back(new RRectPathGeo(openRectPath, SkRect::MakeWH(10, 10), 1815 RRectPathGeo::RRectForStroke::kNo, 1816 PathGeo::Invert::kNo)); 1817 1818 SkPath quadPath; 1819 quadPath.quadTo(10, 10, 5, 8); 1820 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo)); 1821 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes)); 1822 1823 SkPath linePath; 1824 linePath.lineTo(10, 10); 1825 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo)); 1826 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes)); 1827 1828 // Horizontal and vertical paths become rrects when stroked. 1829 SkPath vLinePath; 1830 vLinePath.lineTo(0, 10); 1831 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo)); 1832 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes)); 1833 1834 SkPath hLinePath; 1835 hLinePath.lineTo(10, 0); 1836 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo)); 1837 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes)); 1838 1839 for (int i = 0; i < geos.count(); ++i) { 1840 test_basic(reporter, *geos[i]); 1841 test_scale(reporter, *geos[i]); 1842 test_dash_fill(reporter, *geos[i]); 1843 test_null_dash(reporter, *geos[i]); 1844 // Test modifying various stroke params. 1845 test_stroke_param<SkScalar>( 1846 reporter, *geos[i], 1847 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, 1848 SkIntToScalar(2), SkIntToScalar(4)); 1849 test_stroke_join(reporter, *geos[i]); 1850 test_stroke_cap(reporter, *geos[i]); 1851 test_miter_limit(reporter, *geos[i]); 1852 test_path_effect_makes_rrect(reporter, *geos[i]); 1853 test_unknown_path_effect(reporter, *geos[i]); 1854 test_path_effect_makes_empty_shape(reporter, *geos[i]); 1855 test_path_effect_fails(reporter, *geos[i]); 1856 test_make_hairline_path_effect(reporter, *geos[i]); 1857 test_volatile_path(reporter, *geos[i]); 1858 } 1859 1860 for (int i = 0; i < rrectPathGeos.count(); ++i) { 1861 const RRectPathGeo& rrgeo = *rrectPathGeos[i]; 1862 SkPaint fillPaint; 1863 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint); 1864 SkRRect rrect; 1865 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) == 1866 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr, 1867 nullptr)); 1868 if (rrgeo.isNonPath(fillPaint)) { 1869 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint); 1870 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect()); 1871 TestCase fillRRectCase(reporter, rrect, fillPaint); 1872 fillPathCase2.compare(reporter, fillRRectCase, 1873 TestCase::kAllSame_ComparisonExpecation); 1874 } 1875 SkPaint strokePaint; 1876 strokePaint.setStrokeWidth(3.f); 1877 strokePaint.setStyle(SkPaint::kStroke_Style); 1878 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint); 1879 if (rrgeo.isNonPath(strokePaint)) { 1880 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr, 1881 nullptr)); 1882 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect()); 1883 TestCase strokeRRectCase(reporter, rrect, strokePaint); 1884 strokePathCase.compare(reporter, strokeRRectCase, 1885 TestCase::kAllSame_ComparisonExpecation); 1886 } 1887 } 1888 1889 // Test a volatile empty path. 1890 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo)); 1891 1892 test_empty_shape(reporter); 1893 1894 test_lines(reporter); 1895 1896 test_stroked_lines(reporter); 1897} 1898 1899#endif 1900