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