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