blob: 998703c072112724caaa5da949d60b852d353f47 [file] [log] [blame]
bsalomon47cc7692016-04-26 12:56:00 -07001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/core/SkCanvas.h"
9#include "include/core/SkPath.h"
10#include "include/core/SkSurface.h"
11#include "include/effects/SkDashPathEffect.h"
12#include "include/pathops/SkPathOps.h"
13#include "src/core/SkClipOpPriv.h"
Mike Reedec9d0e82021-05-21 17:42:14 -040014#include "src/core/SkPathEffectBase.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050015#include "src/core/SkRectPriv.h"
Michael Ludwig2686d692020-04-17 20:21:37 +000016#include "src/gpu/geometry/GrStyledShape.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050017#include "tests/Test.h"
bsalomon47cc7692016-04-26 12:56:00 -070018
Hal Canary8a001442018-09-19 11:31:27 -040019#include <initializer_list>
20#include <functional>
John Stilesfbd050b2020-08-03 13:21:46 -040021#include <memory>
Ben Wagnerf08d1d02018-06-18 15:11:00 -040022#include <utility>
23
Michael Ludwig2686d692020-04-17 20:21:37 +000024uint32_t GrStyledShape::testingOnly_getOriginalGenerationID() const {
Brian Salomonda6d0722018-01-03 13:54:35 -050025 if (const auto* lp = this->originalPathForListeners()) {
26 return lp->getGenerationID();
27 }
28 return SkPath().getGenerationID();
Brian Osmanf6f7cf62017-09-25 16:49:55 -040029}
30
Michael Ludwig2686d692020-04-17 20:21:37 +000031bool GrStyledShape::testingOnly_isPath() const {
Michael Ludwigf38b7112020-04-30 13:47:00 -040032 return fShape.isPath();
Brian Osmanb379dcd2017-10-04 15:44:05 -040033}
34
Michael Ludwig2686d692020-04-17 20:21:37 +000035bool GrStyledShape::testingOnly_isNonVolatilePath() const {
Michael Ludwigf38b7112020-04-30 13:47:00 -040036 return fShape.isPath() && !fShape.path().isVolatile();
Brian Salomonda6d0722018-01-03 13:54:35 -050037}
38
bsalomon72dc51c2016-04-27 06:46:23 -070039using Key = SkTArray<uint32_t>;
40
Michael Ludwig2686d692020-04-17 20:21:37 +000041static bool make_key(Key* key, const GrStyledShape& shape) {
bsalomon72dc51c2016-04-27 06:46:23 -070042 int size = shape.unstyledKeySize();
43 if (size <= 0) {
44 key->reset(0);
45 return false;
46 }
47 SkASSERT(size);
48 key->reset(size);
49 shape.writeUnstyledKey(key->begin());
50 return true;
51}
52
bsalomonee295642016-06-06 14:01:25 -070053static bool paths_fill_same(const SkPath& a, const SkPath& b) {
54 SkPath pathXor;
55 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
56 return pathXor.isEmpty();
57}
58
bsalomon9fb42032016-05-13 09:23:38 -070059static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
bsalomon164fd9f2016-08-26 06:45:06 -070060 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
61 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
62 // rendering within the bounds (with a tolerance). Then we render the path and check that
63 // everything got clipped out.
bsalomon9fb42032016-05-13 09:23:38 -070064 static constexpr int kRes = 2000;
65 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
Brian Salomone4949402018-04-26 15:22:04 -040066 static constexpr int kTol = 2;
Brian Salomon4dea72a2019-12-18 10:43:10 -050067 static_assert(kRes % 4 == 0);
bsalomon9fb42032016-05-13 09:23:38 -070068 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
69 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
70 surface->getCanvas()->clear(0x0);
71 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
Mike Reed2ac6ce82021-01-15 12:26:22 -050072 SkMatrix matrix = SkMatrix::RectToRect(bounds, clip);
bsalomon9fb42032016-05-13 09:23:38 -070073 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
Mike Reedc1f77742016-12-09 09:00:50 -050074 surface->getCanvas()->clipRect(clip, kDifference_SkClipOp);
bsalomon9fb42032016-05-13 09:23:38 -070075 surface->getCanvas()->concat(matrix);
76 SkPaint whitePaint;
77 whitePaint.setColor(SK_ColorWHITE);
78 surface->getCanvas()->drawPath(path, whitePaint);
79 SkPixmap pixmap;
80 surface->getCanvas()->peekPixels(&pixmap);
81#if defined(SK_BUILD_FOR_WIN)
82 // The static constexpr version in #else causes cl.exe to crash.
83 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
84#else
85 static constexpr uint8_t kZeros[kRes] = {0};
86#endif
bsalomon164fd9f2016-08-26 06:45:06 -070087 for (int y = 0; y < kRes; ++y) {
bsalomon9fb42032016-05-13 09:23:38 -070088 const uint8_t* row = pixmap.addr8(0, y);
89 if (0 != memcmp(kZeros, row, kRes)) {
90 return false;
91 }
92 }
93#ifdef SK_BUILD_FOR_WIN
94 free(const_cast<uint8_t*>(kZeros));
95#endif
96 return true;
97}
bsalomon72dc51c2016-04-27 06:46:23 -070098
Michael Ludwig2686d692020-04-17 20:21:37 +000099static bool can_interchange_winding_and_even_odd_fill(const GrStyledShape& shape) {
Brian Salomon4f40caf2017-09-01 09:00:45 -0400100 SkPath path;
101 shape.asPath(&path);
102 if (shape.style().hasNonDashPathEffect()) {
103 return false;
104 }
105 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
106 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
107 strokeRecStyle == SkStrokeRec::kHairline_Style ||
108 (shape.style().isSimpleFill() && path.isConvex());
109}
110
Michael Ludwig2686d692020-04-17 20:21:37 +0000111static void check_equivalence(skiatest::Reporter* r, const GrStyledShape& a, const GrStyledShape& b,
Brian Salomon4f40caf2017-09-01 09:00:45 -0400112 const Key& keyA, const Key& keyB) {
Michael Ludwig2686d692020-04-17 20:21:37 +0000113 // GrStyledShape only respects the input winding direction and start point for rrect shapes
114 // when there is a path effect. Thus, if there are two GrStyledShapes representing the same
115 // rrect but one has a path effect in its style and the other doesn't then asPath() and the
116 // unstyled key will differ. GrStyledShape will have canonicalized the direction and start point
117 // for the shape without the path effect. If *both* have path effects then they should have both
118 // preserved the direction and starting point.
Brian Salomon4f40caf2017-09-01 09:00:45 -0400119
120 // The asRRect() output params are all initialized just to silence compiler warnings about
121 // uninitialized variables.
122 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
Mike Reed30bc5272019-11-22 18:34:02 +0000123 SkPathDirection dirA = SkPathDirection::kCW, dirB = SkPathDirection::kCW;
Brian Salomon4f40caf2017-09-01 09:00:45 -0400124 unsigned startA = ~0U, startB = ~0U;
125 bool invertedA = true, invertedB = true;
126
127 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
128 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
129 bool aHasPE = a.style().hasPathEffect();
130 bool bHasPE = b.style().hasPathEffect();
131 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
Michael Ludwig2686d692020-04-17 20:21:37 +0000132 // GrStyledShape will close paths with simple fill style.
Brian Salomon4f40caf2017-09-01 09:00:45 -0400133 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
134 SkPath pathA, pathB;
135 a.asPath(&pathA);
136 b.asPath(&pathB);
137
138 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
139 // non-inverse fill type (or vice versa).
140 bool ignoreInversenessDifference = false;
141 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
Michael Ludwig2686d692020-04-17 20:21:37 +0000142 const GrStyledShape* s1 = pathA.isInverseFillType() ? &a : &b;
143 const GrStyledShape* s2 = pathA.isInverseFillType() ? &b : &a;
Brian Salomon4f40caf2017-09-01 09:00:45 -0400144 bool canDropInverse1 = s1->style().isDashed();
145 bool canDropInverse2 = s2->style().isDashed();
146 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
147 }
148 bool ignoreWindingVsEvenOdd = false;
Mike Reedcf0e3c62019-12-03 16:26:15 -0500149 if (SkPathFillType_ConvertToNonInverse(pathA.getFillType()) !=
150 SkPathFillType_ConvertToNonInverse(pathB.getFillType())) {
Brian Salomon4f40caf2017-09-01 09:00:45 -0400151 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
152 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
153 if (aCanChange != bCanChange) {
154 ignoreWindingVsEvenOdd = true;
155 }
156 }
157 if (allowSameRRectButDiffStartAndDir) {
158 REPORTER_ASSERT(r, rrectA == rrectB);
159 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
160 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
161 } else {
162 SkPath pA = pathA;
163 SkPath pB = pathB;
164 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
165 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
166 if (ignoreInversenessDifference) {
Mike Reedcf0e3c62019-12-03 16:26:15 -0500167 pA.setFillType(SkPathFillType_ConvertToNonInverse(pathA.getFillType()));
168 pB.setFillType(SkPathFillType_ConvertToNonInverse(pathB.getFillType()));
Brian Salomon4f40caf2017-09-01 09:00:45 -0400169 }
170 if (ignoreWindingVsEvenOdd) {
Mike Reed7d34dc72019-11-26 12:17:17 -0500171 pA.setFillType(pA.isInverseFillType() ? SkPathFillType::kInverseEvenOdd
172 : SkPathFillType::kEvenOdd);
173 pB.setFillType(pB.isInverseFillType() ? SkPathFillType::kInverseEvenOdd
174 : SkPathFillType::kEvenOdd);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400175 }
176 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
177 REPORTER_ASSERT(r, keyA == keyB);
178 } else {
179 REPORTER_ASSERT(r, keyA != keyB);
180 }
181 if (allowedClosednessDiff) {
Michael Ludwig2686d692020-04-17 20:21:37 +0000182 // GrStyledShape will close paths with simple fill style. Make the non-filled path
183 // closed so that the comparision will succeed. Make sure both are closed before
184 // comparing.
Brian Salomon4f40caf2017-09-01 09:00:45 -0400185 pA.close();
186 pB.close();
187 }
188 REPORTER_ASSERT(r, pA == pB);
189 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
190 if (aIsRRect) {
191 REPORTER_ASSERT(r, rrectA == rrectB);
192 REPORTER_ASSERT(r, dirA == dirB);
193 REPORTER_ASSERT(r, startA == startB);
194 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
195 }
196 }
197 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
198 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
199 // closedness can affect convexity.
200 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
201 if (a.knownToBeConvex()) {
202 REPORTER_ASSERT(r, pathA.isConvex());
203 }
204 if (b.knownToBeConvex()) {
205 REPORTER_ASSERT(r, pathB.isConvex());
206 }
207 REPORTER_ASSERT(r, a.bounds() == b.bounds());
208 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
209 // Init these to suppress warnings.
210 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
211 bool invertedLine[2] {true, true};
212 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
213 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
214 // doesn't (since the PE can set any fill type on its output path).
215 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
216 // then they may disagree about inverseness.
217 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
218 a.style().isDashed() == b.style().isDashed()) {
219 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
220 b.mayBeInverseFilledAfterStyling());
221 }
222 if (a.asLine(nullptr, nullptr)) {
223 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
224 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
225 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
226 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
227 }
228 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
229}
230
Michael Ludwig2686d692020-04-17 20:21:37 +0000231static void check_original_path_ids(skiatest::Reporter* r, const GrStyledShape& base,
232 const GrStyledShape& pe, const GrStyledShape& peStroke,
233 const GrStyledShape& full) {
Brian Salomonda6d0722018-01-03 13:54:35 -0500234 bool baseIsNonVolatilePath = base.testingOnly_isNonVolatilePath();
Brian Osmanb379dcd2017-10-04 15:44:05 -0400235 bool peIsPath = pe.testingOnly_isPath();
236 bool peStrokeIsPath = peStroke.testingOnly_isPath();
237 bool fullIsPath = full.testingOnly_isPath();
238
239 REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath);
240
241 uint32_t baseID = base.testingOnly_getOriginalGenerationID();
242 uint32_t peID = pe.testingOnly_getOriginalGenerationID();
243 uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID();
244 uint32_t fullID = full.testingOnly_getOriginalGenerationID();
245
246 // All empty paths have the same gen ID
247 uint32_t emptyID = SkPath().getGenerationID();
248
249 // If we started with a real path, then our genID should match that path's gen ID (and not be
Brian Salomonda6d0722018-01-03 13:54:35 -0500250 // empty). If we started with a simple shape or a volatile path, our original path should have
251 // been reset.
252 REPORTER_ASSERT(r, baseIsNonVolatilePath == (baseID != emptyID));
Brian Osmanb379dcd2017-10-04 15:44:05 -0400253
254 // For the derived shapes, if they're simple types, their original paths should have been reset
255 REPORTER_ASSERT(r, peIsPath || (peID == emptyID));
256 REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID));
257 REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID));
258
259 if (!peIsPath) {
260 // If the path effect produces a simple shape, then there are no unbroken chains to test
261 return;
262 }
263
264 // From here on, we know that the path effect produced a shape that was a "real" path
265
Brian Salomonda6d0722018-01-03 13:54:35 -0500266 if (baseIsNonVolatilePath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400267 REPORTER_ASSERT(r, baseID == peID);
268 }
269
270 if (peStrokeIsPath) {
271 REPORTER_ASSERT(r, peID == peStrokeID);
272 REPORTER_ASSERT(r, peStrokeID == fullID);
273 }
274
Brian Salomonda6d0722018-01-03 13:54:35 -0500275 if (baseIsNonVolatilePath && peStrokeIsPath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400276 REPORTER_ASSERT(r, baseID == peStrokeID);
277 REPORTER_ASSERT(r, baseID == fullID);
278 }
279}
280
Michael Ludwig2686d692020-04-17 20:21:37 +0000281void test_inversions(skiatest::Reporter* r, const GrStyledShape& shape, const Key& shapeKey) {
282 GrStyledShape preserve = GrStyledShape::MakeFilled(
283 shape, GrStyledShape::FillInversion::kPreserve);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400284 Key preserveKey;
285 make_key(&preserveKey, preserve);
286
Michael Ludwig2686d692020-04-17 20:21:37 +0000287 GrStyledShape flip = GrStyledShape::MakeFilled(shape, GrStyledShape::FillInversion::kFlip);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400288 Key flipKey;
289 make_key(&flipKey, flip);
290
Michael Ludwig2686d692020-04-17 20:21:37 +0000291 GrStyledShape inverted = GrStyledShape::MakeFilled(
292 shape, GrStyledShape::FillInversion::kForceInverted);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400293 Key invertedKey;
294 make_key(&invertedKey, inverted);
295
Michael Ludwig2686d692020-04-17 20:21:37 +0000296 GrStyledShape noninverted = GrStyledShape::MakeFilled(
297 shape, GrStyledShape::FillInversion::kForceNoninverted);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400298 Key noninvertedKey;
299 make_key(&noninvertedKey, noninverted);
300
301 if (invertedKey.count() || noninvertedKey.count()) {
302 REPORTER_ASSERT(r, invertedKey != noninvertedKey);
303 }
304 if (shape.style().isSimpleFill()) {
305 check_equivalence(r, shape, preserve, shapeKey, preserveKey);
306 }
307 if (shape.inverseFilled()) {
308 check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
309 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
310 } else {
311 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
312 check_equivalence(r, flip, inverted, flipKey, invertedKey);
313 }
314
Michael Ludwig2686d692020-04-17 20:21:37 +0000315 GrStyledShape doubleFlip = GrStyledShape::MakeFilled(flip, GrStyledShape::FillInversion::kFlip);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400316 Key doubleFlipKey;
317 make_key(&doubleFlipKey, doubleFlip);
318 // It can be the case that the double flip has no key but preserve does. This happens when the
319 // original shape has an inherited style key. That gets dropped on the first inversion flip.
320 if (preserveKey.count() && !doubleFlipKey.count()) {
321 preserveKey.reset();
322 }
323 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
324}
325
bsalomon9fb42032016-05-13 09:23:38 -0700326namespace {
bsalomona395f7c2016-08-24 17:47:40 -0700327/**
Michael Ludwig2686d692020-04-17 20:21:37 +0000328 * Geo is a factory for creating a GrStyledShape from another representation. It also answers some
329 * questions about expected behavior for GrStyledShape given the inputs.
bsalomona395f7c2016-08-24 17:47:40 -0700330 */
331class Geo {
332public:
Mike Kleinfc6c37b2016-09-27 09:34:10 -0400333 virtual ~Geo() {}
Michael Ludwig2686d692020-04-17 20:21:37 +0000334 virtual GrStyledShape makeShape(const SkPaint&) const = 0;
bsalomona395f7c2016-08-24 17:47:40 -0700335 virtual SkPath path() const = 0;
336 // These functions allow tests to check for special cases where style gets
Michael Ludwig2686d692020-04-17 20:21:37 +0000337 // applied by GrStyledShape in its constructor (without calling GrStyledShape::applyStyle).
338 // These unfortunately rely on knowing details of GrStyledShape's implementation.
bsalomona395f7c2016-08-24 17:47:40 -0700339 // These predicates are factored out here to avoid littering the rest of the
Michael Ludwig2686d692020-04-17 20:21:37 +0000340 // test code with GrStyledShape implementation details.
bsalomona395f7c2016-08-24 17:47:40 -0700341 virtual bool fillChangesGeom() const { return false; }
342 virtual bool strokeIsConvertedToFill() const { return false; }
343 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
Michael Ludwig2686d692020-04-17 20:21:37 +0000344 // Is this something we expect GrStyledShape to recognize as something simpler than a path.
bsalomona395f7c2016-08-24 17:47:40 -0700345 virtual bool isNonPath(const SkPaint& paint) const { return true; }
346};
347
348class RectGeo : public Geo {
349public:
350 RectGeo(const SkRect& rect) : fRect(rect) {}
351
352 SkPath path() const override {
353 SkPath path;
354 path.addRect(fRect);
355 return path;
356 }
357
Michael Ludwig2686d692020-04-17 20:21:37 +0000358 GrStyledShape makeShape(const SkPaint& paint) const override {
359 return GrStyledShape(fRect, paint);
bsalomona395f7c2016-08-24 17:47:40 -0700360 }
361
362 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
363 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
Michael Ludwigf38b7112020-04-30 13:47:00 -0400364 // Converted to an outset rectangle or round rect
365 return (paint.getStrokeJoin() == SkPaint::kMiter_Join &&
366 paint.getStrokeMiter() >= SK_ScalarSqrt2) ||
367 paint.getStrokeJoin() == SkPaint::kRound_Join;
bsalomona395f7c2016-08-24 17:47:40 -0700368 }
369
370private:
371 SkRect fRect;
372};
373
374class RRectGeo : public Geo {
375public:
376 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
377
Michael Ludwig2686d692020-04-17 20:21:37 +0000378 GrStyledShape makeShape(const SkPaint& paint) const override {
379 return GrStyledShape(fRRect, paint);
bsalomona395f7c2016-08-24 17:47:40 -0700380 }
381
382 SkPath path() const override {
383 SkPath path;
384 path.addRRect(fRRect);
385 return path;
386 }
387
388 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
389 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
390 if (fRRect.isRect()) {
391 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
392 }
393 return false;
394 }
395
396private:
397 SkRRect fRRect;
398};
399
Brian Salomone4949402018-04-26 15:22:04 -0400400class ArcGeo : public Geo {
401public:
402 ArcGeo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter)
403 : fOval(oval)
404 , fStartAngle(startAngle)
405 , fSweepAngle(sweepAngle)
406 , fUseCenter(useCenter) {}
407
408 SkPath path() const override {
409 SkPath path;
410 SkPathPriv::CreateDrawArcPath(&path, fOval, fStartAngle, fSweepAngle, fUseCenter, false);
411 return path;
412 }
413
Michael Ludwig2686d692020-04-17 20:21:37 +0000414 GrStyledShape makeShape(const SkPaint& paint) const override {
415 return GrStyledShape::MakeArc(fOval, fStartAngle, fSweepAngle, fUseCenter, GrStyle(paint));
Brian Salomone4949402018-04-26 15:22:04 -0400416 }
417
Michael Ludwig2686d692020-04-17 20:21:37 +0000418 // GrStyledShape specializes when created from arc params but it doesn't recognize arcs from
419 // SkPath.
Brian Salomone4949402018-04-26 15:22:04 -0400420 bool isNonPath(const SkPaint& paint) const override { return false; }
421
422private:
423 SkRect fOval;
424 SkScalar fStartAngle;
425 SkScalar fSweepAngle;
426 bool fUseCenter;
427};
428
bsalomona395f7c2016-08-24 17:47:40 -0700429class PathGeo : public Geo {
430public:
431 enum class Invert { kNo, kYes };
432
433 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
434 SkASSERT(!path.isInverseFillType());
435 if (Invert::kYes == invert) {
Mike Reedcf0e3c62019-12-03 16:26:15 -0500436 if (fPath.getFillType() == SkPathFillType::kEvenOdd) {
Mike Reed7d34dc72019-11-26 12:17:17 -0500437 fPath.setFillType(SkPathFillType::kInverseEvenOdd);
bsalomona395f7c2016-08-24 17:47:40 -0700438 } else {
Mike Reedcf0e3c62019-12-03 16:26:15 -0500439 SkASSERT(fPath.getFillType() == SkPathFillType::kWinding);
Mike Reed7d34dc72019-11-26 12:17:17 -0500440 fPath.setFillType(SkPathFillType::kInverseWinding);
bsalomona395f7c2016-08-24 17:47:40 -0700441 }
442 }
443 }
444
Michael Ludwig2686d692020-04-17 20:21:37 +0000445 GrStyledShape makeShape(const SkPaint& paint) const override {
446 return GrStyledShape(fPath, paint);
bsalomona395f7c2016-08-24 17:47:40 -0700447 }
448
449 SkPath path() const override { return fPath; }
450
451 bool fillChangesGeom() const override {
452 // unclosed rects get closed. Lines get turned into empty geometry
Brian Salomon085c0862017-08-31 15:44:51 -0400453 return this->isUnclosedRect() || fPath.isLine(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -0700454 }
455
456 bool strokeIsConvertedToFill() const override {
457 return this->isAxisAlignedLine();
458 }
459
460 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
461 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
462 if (this->isAxisAlignedLine()) {
463 // The fill is ignored (zero area) and the stroke is converted to a rrect.
464 return true;
465 }
466 SkRect rect;
467 unsigned start;
Mike Reed30bc5272019-11-22 18:34:02 +0000468 SkPathDirection dir;
Chris Dalton34c50522020-09-17 15:45:20 -0600469 if (SkPathPriv::IsSimpleRect(fPath, false, &rect, &dir, &start)) {
bsalomona395f7c2016-08-24 17:47:40 -0700470 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
471 }
472 return false;
473 }
474
475 bool isNonPath(const SkPaint& paint) const override {
476 return fPath.isLine(nullptr) || fPath.isEmpty();
477 }
478
479private:
480 bool isAxisAlignedLine() const {
481 SkPoint pts[2];
482 if (!fPath.isLine(pts)) {
483 return false;
484 }
485 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
486 }
487
488 bool isUnclosedRect() const {
489 bool closed;
490 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
491 }
492
493 SkPath fPath;
494};
495
496class RRectPathGeo : public PathGeo {
497public:
498 enum class RRectForStroke { kNo, kYes };
499
500 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
501 Invert invert)
502 : PathGeo(path, invert)
503 , fRRect(equivalentRRect)
504 , fRRectForStroke(rrectForStroke) {}
505
506 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
507 Invert invert)
508 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
509
510 bool isNonPath(const SkPaint& paint) const override {
511 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
512 return true;
513 }
514 return false;
515 }
516
517 const SkRRect& rrect() const { return fRRect; }
518
519private:
520 SkRRect fRRect;
521 RRectForStroke fRRectForStroke;
522};
523
bsalomon47cc7692016-04-26 12:56:00 -0700524class TestCase {
525public:
bsalomona395f7c2016-08-24 17:47:40 -0700526 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
Brian Salomonc8cdad72018-04-16 09:46:09 -0400527 SkScalar scale = SK_Scalar1)
Michael Ludwig2686d692020-04-17 20:21:37 +0000528 : fBase(new GrStyledShape(geo.makeShape(paint))) {
bsalomon97fd2d42016-05-09 13:02:01 -0700529 this->init(r, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700530 }
531
Brian Salomonc8cdad72018-04-16 09:46:09 -0400532 template <typename... ShapeArgs>
Michael Ludwig2686d692020-04-17 20:21:37 +0000533 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs)
534 : fBase(new GrStyledShape(shapeArgs...)) {
bsalomona395f7c2016-08-24 17:47:40 -0700535 this->init(r, SK_Scalar1);
536 }
537
Michael Ludwig2686d692020-04-17 20:21:37 +0000538 TestCase(const GrStyledShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
539 : fBase(new GrStyledShape(shape)) {
bsalomon70493962016-06-10 08:05:14 -0700540 this->init(r, scale);
541 }
542
bsalomon47cc7692016-04-26 12:56:00 -0700543 struct SelfExpectations {
544 bool fPEHasEffect;
545 bool fPEHasValidKey;
546 bool fStrokeApplies;
547 };
548
549 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
550
551 enum ComparisonExpecation {
552 kAllDifferent_ComparisonExpecation,
553 kSameUpToPE_ComparisonExpecation,
554 kSameUpToStroke_ComparisonExpecation,
555 kAllSame_ComparisonExpecation,
556 };
557
558 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
559
Michael Ludwig2686d692020-04-17 20:21:37 +0000560 const GrStyledShape& baseShape() const { return *fBase; }
561 const GrStyledShape& appliedPathEffectShape() const { return *fAppliedPE; }
562 const GrStyledShape& appliedFullStyleShape() const { return *fAppliedFull; }
bsalomon72dc51c2016-04-27 06:46:23 -0700563
564 // The returned array's count will be 0 if the key shape has no key.
565 const Key& baseKey() const { return fBaseKey; }
566 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
567 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700568 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700569
bsalomon47cc7692016-04-26 12:56:00 -0700570private:
Michael Ludwig2686d692020-04-17 20:21:37 +0000571 static void CheckBounds(skiatest::Reporter* r, const GrStyledShape& shape,
572 const SkRect& bounds) {
bsalomon9fb42032016-05-13 09:23:38 -0700573 SkPath path;
574 shape.asPath(&path);
575 // If the bounds are empty, the path ought to be as well.
bsalomon0ae36a22016-07-18 07:31:13 -0700576 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
bsalomon9fb42032016-05-13 09:23:38 -0700577 REPORTER_ASSERT(r, path.isEmpty());
578 return;
579 }
580 if (path.isEmpty()) {
581 return;
582 }
bsalomon70493962016-06-10 08:05:14 -0700583 // The bounds API explicitly calls out that it does not consider inverseness.
584 SkPath p = path;
Mike Reedcf0e3c62019-12-03 16:26:15 -0500585 p.setFillType(SkPathFillType_ConvertToNonInverse(path.getFillType()));
bsalomon70493962016-06-10 08:05:14 -0700586 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
bsalomon9fb42032016-05-13 09:23:38 -0700587 }
588
bsalomon97fd2d42016-05-09 13:02:01 -0700589 void init(skiatest::Reporter* r, SkScalar scale) {
John Stilesfbd050b2020-08-03 13:21:46 -0400590 fAppliedPE = std::make_unique<GrStyledShape>();
591 fAppliedPEThenStroke = std::make_unique<GrStyledShape>();
592 fAppliedFull = std::make_unique<GrStyledShape>();
bsalomon47cc7692016-04-26 12:56:00 -0700593
Brian Salomonc8cdad72018-04-16 09:46:09 -0400594 *fAppliedPE = fBase->applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
595 *fAppliedPEThenStroke =
596 fAppliedPE->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
597 *fAppliedFull = fBase->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
598
599 make_key(&fBaseKey, *fBase);
600 make_key(&fAppliedPEKey, *fAppliedPE);
601 make_key(&fAppliedPEThenStrokeKey, *fAppliedPEThenStroke);
602 make_key(&fAppliedFullKey, *fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700603
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400604 // All shapes should report the same "original" path, so that path renderers can get to it
605 // if necessary.
Brian Salomonc8cdad72018-04-16 09:46:09 -0400606 check_original_path_ids(r, *fBase, *fAppliedPE, *fAppliedPEThenStroke, *fAppliedFull);
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400607
bsalomonfb083272016-05-04 08:27:41 -0700608 // Applying the path effect and then the stroke should always be the same as applying
609 // both in one go.
610 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
611 SkPath a, b;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400612 fAppliedPEThenStroke->asPath(&a);
613 fAppliedFull->asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700614 // If the output of the path effect is a rrect then it is possible for a and b to be
615 // different paths that fill identically. The reason is that fAppliedFull will do this:
616 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
617 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
618 // now that there is no longer a path effect, the direction and starting index get
619 // canonicalized before the stroke.
Brian Salomonc8cdad72018-04-16 09:46:09 -0400620 if (fAppliedPE->asRRect(nullptr, nullptr, nullptr, nullptr)) {
bsalomonee295642016-06-06 14:01:25 -0700621 REPORTER_ASSERT(r, paths_fill_same(a, b));
622 } else {
623 REPORTER_ASSERT(r, a == b);
624 }
Brian Salomonc8cdad72018-04-16 09:46:09 -0400625 REPORTER_ASSERT(r, fAppliedFull->isEmpty() == fAppliedPEThenStroke->isEmpty());
bsalomon7c73a532016-05-11 15:15:56 -0700626
627 SkPath path;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400628 fBase->asPath(&path);
629 REPORTER_ASSERT(r, path.isEmpty() == fBase->isEmpty());
630 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase->segmentMask());
631 fAppliedPE->asPath(&path);
632 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE->isEmpty());
633 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE->segmentMask());
634 fAppliedFull->asPath(&path);
635 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull->isEmpty());
636 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull->segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700637
Brian Salomonc8cdad72018-04-16 09:46:09 -0400638 CheckBounds(r, *fBase, fBase->bounds());
639 CheckBounds(r, *fAppliedPE, fAppliedPE->bounds());
640 CheckBounds(r, *fAppliedPEThenStroke, fAppliedPEThenStroke->bounds());
641 CheckBounds(r, *fAppliedFull, fAppliedFull->bounds());
642 SkRect styledBounds = fBase->styledBounds();
643 CheckBounds(r, *fAppliedFull, styledBounds);
644 styledBounds = fAppliedPE->styledBounds();
645 CheckBounds(r, *fAppliedFull, styledBounds);
bsalomon9fb42032016-05-13 09:23:38 -0700646
Michael Ludwig2686d692020-04-17 20:21:37 +0000647 // Check that the same path is produced when style is applied by GrStyledShape and GrStyle.
bsalomonfb083272016-05-04 08:27:41 -0700648 SkPath preStyle;
649 SkPath postPathEffect;
650 SkPath postAllStyle;
651
Brian Salomonc8cdad72018-04-16 09:46:09 -0400652 fBase->asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700653 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
Brian Salomonc8cdad72018-04-16 09:46:09 -0400654 if (fBase->style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
655 scale)) {
Michael Ludwig2686d692020-04-17 20:21:37 +0000656 // run postPathEffect through GrStyledShape to get any geometry reductions that would
657 // have occurred to fAppliedPE.
658 GrStyledShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr))
659 .asPath(&postPathEffect);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700660
bsalomonfb083272016-05-04 08:27:41 -0700661 SkPath testPath;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400662 fAppliedPE->asPath(&testPath);
bsalomonfb083272016-05-04 08:27:41 -0700663 REPORTER_ASSERT(r, testPath == postPathEffect);
Brian Salomonc8cdad72018-04-16 09:46:09 -0400664 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE->style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700665 }
666 SkStrokeRec::InitStyle fillOrHairline;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400667 if (fBase->style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomonfb083272016-05-04 08:27:41 -0700668 SkPath testPath;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400669 fAppliedFull->asPath(&testPath);
670 if (fBase->style().hasPathEffect()) {
Michael Ludwig2686d692020-04-17 20:21:37 +0000671 // Because GrStyledShape always does two-stage application when there is a path
672 // effect there may be a reduction/canonicalization step between the path effect and
bsalomon1b28c1a2016-06-20 12:28:17 -0700673 // strokerec not reflected in postAllStyle since it applied both the path effect
674 // and strokerec without analyzing the intermediate path.
675 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
676 } else {
Michael Ludwig2686d692020-04-17 20:21:37 +0000677 // Make sure that postAllStyle sees any reductions/canonicalizations that
678 // GrStyledShape would apply.
679 GrStyledShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
bsalomon1b28c1a2016-06-20 12:28:17 -0700680 REPORTER_ASSERT(r, testPath == postAllStyle);
681 }
682
bsalomonfb083272016-05-04 08:27:41 -0700683 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400684 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleFill());
bsalomonfb083272016-05-04 08:27:41 -0700685 } else {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400686 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleHairline());
bsalomonfb083272016-05-04 08:27:41 -0700687 }
688 }
Brian Salomonc8cdad72018-04-16 09:46:09 -0400689 test_inversions(r, *fBase, fBaseKey);
690 test_inversions(r, *fAppliedPE, fAppliedPEKey);
691 test_inversions(r, *fAppliedFull, fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700692 }
693
Michael Ludwig2686d692020-04-17 20:21:37 +0000694 std::unique_ptr<GrStyledShape> fBase;
695 std::unique_ptr<GrStyledShape> fAppliedPE;
696 std::unique_ptr<GrStyledShape> fAppliedPEThenStroke;
697 std::unique_ptr<GrStyledShape> fAppliedFull;
bsalomon47cc7692016-04-26 12:56:00 -0700698
699 Key fBaseKey;
700 Key fAppliedPEKey;
701 Key fAppliedPEThenStrokeKey;
702 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700703};
704
705void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700706 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700707 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700708 if (expectations.fPEHasEffect) {
709 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700710 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700711 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700712 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700713 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
714 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700715 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700716 }
717 } else {
718 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700719 SkPath a, b;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400720 fBase->asPath(&a);
721 fAppliedPE->asPath(&b);
bsalomon72dc51c2016-04-27 06:46:23 -0700722 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700723 if (expectations.fStrokeApplies) {
724 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
725 } else {
726 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
727 }
728 }
729}
730
bsalomonee295642016-06-06 14:01:25 -0700731void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700732 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700733 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700734 switch (expectation) {
735 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700736 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
737 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
738 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700739 break;
740 case kSameUpToPE_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400741 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
bsalomonee295642016-06-06 14:01:25 -0700742 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
743 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700744 break;
745 case kSameUpToStroke_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400746 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
747 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
bsalomonee295642016-06-06 14:01:25 -0700748 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700749 break;
750 case kAllSame_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400751 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
752 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
753 check_equivalence(r, *fAppliedFull, *that.fAppliedFull, fAppliedFullKey,
bsalomonee295642016-06-06 14:01:25 -0700754 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700755 break;
756 }
757}
758} // namespace
759
760static sk_sp<SkPathEffect> make_dash() {
761 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
762 static const SkScalar kPhase = 0.75;
763 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
764}
765
766static sk_sp<SkPathEffect> make_null_dash() {
767 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
768 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
769}
770
Mike Klein43344282017-08-16 11:56:22 -0400771// We make enough TestCases, and they're large enough, that on Google3 builds we exceed
772// the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
773template <typename... Args>
774static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
John Stilesfbd050b2020-08-03 13:21:46 -0400775 return std::make_unique<TestCase>( std::forward<Args>(args)... );
Mike Klein43344282017-08-16 11:56:22 -0400776}
777
bsalomona395f7c2016-08-24 17:47:40 -0700778static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700779 sk_sp<SkPathEffect> dashPE = make_dash();
780
781 TestCase::SelfExpectations expectations;
782 SkPaint fill;
783
bsalomonfb083272016-05-04 08:27:41 -0700784 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700785 expectations.fPEHasEffect = false;
786 expectations.fPEHasValidKey = false;
787 expectations.fStrokeApplies = false;
788 fillCase.testExpectations(reporter, expectations);
Michael Ludwig2686d692020-04-17 20:21:37 +0000789 // Test that another GrStyledShape instance built from the same primitive is the same.
Mike Klein43344282017-08-16 11:56:22 -0400790 make_TestCase(geo, fill, reporter)
791 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700792
793 SkPaint stroke2RoundBevel;
794 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
795 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
796 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
797 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700798 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700799 expectations.fPEHasValidKey = true;
800 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700801 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomon47cc7692016-04-26 12:56:00 -0700802 stroke2RoundBevelCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400803 make_TestCase(geo, stroke2RoundBevel, reporter)
804 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700805
806 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
807 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700808 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700809 expectations.fPEHasValidKey = true;
810 expectations.fPEHasEffect = true;
811 expectations.fStrokeApplies = true;
812 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400813 make_TestCase(geo, stroke2RoundBevelDash, reporter)
814 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700815
bsalomona395f7c2016-08-24 17:47:40 -0700816 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700817 fillCase.compare(reporter, stroke2RoundBevelCase,
818 TestCase::kAllDifferent_ComparisonExpecation);
819 fillCase.compare(reporter, stroke2RoundBevelDashCase,
820 TestCase::kAllDifferent_ComparisonExpecation);
821 } else {
822 fillCase.compare(reporter, stroke2RoundBevelCase,
823 TestCase::kSameUpToStroke_ComparisonExpecation);
824 fillCase.compare(reporter, stroke2RoundBevelDashCase,
825 TestCase::kSameUpToPE_ComparisonExpecation);
826 }
bsalomona395f7c2016-08-24 17:47:40 -0700827 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700828 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
829 TestCase::kAllDifferent_ComparisonExpecation);
830 } else {
831 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
832 TestCase::kSameUpToPE_ComparisonExpecation);
833 }
bsalomon72dc51c2016-04-27 06:46:23 -0700834
bsalomonf0cf3552016-05-05 08:28:30 -0700835 // Stroke and fill cases
836 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
837 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
838 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
839 expectations.fPEHasValidKey = true;
840 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700841 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700842 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400843 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
844 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700845
846 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
847 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
848 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
849 expectations.fPEHasValidKey = true;
bsalomona0587862016-06-09 06:03:38 -0700850 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700851 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700852 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400853 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
bsalomonf0cf3552016-05-05 08:28:30 -0700854 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomona0587862016-06-09 06:03:38 -0700855 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
856 TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700857
bsalomon72dc51c2016-04-27 06:46:23 -0700858 SkPaint hairline;
859 hairline.setStyle(SkPaint::kStroke_Style);
860 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700861 TestCase hairlineCase(geo, hairline, reporter);
bsalomon487f8d32016-07-20 07:15:44 -0700862 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
863 // in the line and unclosed rect cases).
bsalomona395f7c2016-08-24 17:47:40 -0700864 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -0700865 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
866 } else {
867 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
868 }
bsalomon9ad5d7c2016-05-04 08:44:15 -0700869 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
870 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
871 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700872
bsalomon0ae36a22016-07-18 07:31:13 -0700873}
874
bsalomona395f7c2016-08-24 17:47:40 -0700875static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon97fd2d42016-05-09 13:02:01 -0700876 sk_sp<SkPathEffect> dashPE = make_dash();
877
878 static const SkScalar kS1 = 1.f;
879 static const SkScalar kS2 = 2.f;
880
881 SkPaint fill;
882 TestCase fillCase1(geo, fill, reporter, kS1);
883 TestCase fillCase2(geo, fill, reporter, kS2);
884 // Scale doesn't affect fills.
885 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
886
887 SkPaint hairline;
888 hairline.setStyle(SkPaint::kStroke_Style);
889 hairline.setStrokeWidth(0.f);
890 TestCase hairlineCase1(geo, hairline, reporter, kS1);
891 TestCase hairlineCase2(geo, hairline, reporter, kS2);
892 // Scale doesn't affect hairlines.
893 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
894
895 SkPaint stroke;
896 stroke.setStyle(SkPaint::kStroke_Style);
897 stroke.setStrokeWidth(2.f);
898 TestCase strokeCase1(geo, stroke, reporter, kS1);
899 TestCase strokeCase2(geo, stroke, reporter, kS2);
bsalomon0ae36a22016-07-18 07:31:13 -0700900 // Scale affects the stroke
bsalomona395f7c2016-08-24 17:47:40 -0700901 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700902 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
bsalomon0ae36a22016-07-18 07:31:13 -0700903 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
904 } else {
905 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
906 }
bsalomon97fd2d42016-05-09 13:02:01 -0700907
908 SkPaint strokeDash = stroke;
909 strokeDash.setPathEffect(make_dash());
910 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
911 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
912 // Scale affects the dash and the stroke.
bsalomon487f8d32016-07-20 07:15:44 -0700913 strokeDashCase1.compare(reporter, strokeDashCase2,
914 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700915
916 // Stroke and fill cases
917 SkPaint strokeAndFill = stroke;
918 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
919 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
920 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
bsalomona0587862016-06-09 06:03:38 -0700921 SkPaint strokeAndFillDash = strokeDash;
922 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
923 // Dash is ignored for stroke and fill
924 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
925 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
bsalomon487f8d32016-07-20 07:15:44 -0700926 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
927 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
928 // geometries should agree.
bsalomona395f7c2016-08-24 17:47:40 -0700929 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
bsalomon487f8d32016-07-20 07:15:44 -0700930 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
bsalomon97fd2d42016-05-09 13:02:01 -0700931 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
932 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700933 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
934 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700935 } else {
936 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
937 TestCase::kSameUpToStroke_ComparisonExpecation);
938 }
bsalomona0587862016-06-09 06:03:38 -0700939 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
940 TestCase::kAllSame_ComparisonExpecation);
941 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
942 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700943}
944
bsalomona395f7c2016-08-24 17:47:40 -0700945template <typename T>
946static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700947 std::function<void(SkPaint*, T)> setter, T a, T b,
948 bool paramAffectsStroke,
949 bool paramAffectsDashAndStroke) {
950 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
951 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700952 SkPaint strokeA;
953 strokeA.setStyle(SkPaint::kStroke_Style);
954 strokeA.setStrokeWidth(2.f);
955 setter(&strokeA, a);
956 SkPaint strokeB;
957 strokeB.setStyle(SkPaint::kStroke_Style);
958 strokeB.setStrokeWidth(2.f);
959 setter(&strokeB, b);
960
bsalomonfb083272016-05-04 08:27:41 -0700961 TestCase strokeACase(geo, strokeA, reporter);
962 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700963 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700964 // If stroking is immediately incorporated into a geometric transformation then the base
965 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700966 if (geo.strokeIsConvertedToFill()) {
bsalomon0ae36a22016-07-18 07:31:13 -0700967 strokeACase.compare(reporter, strokeBCase,
968 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700969 } else {
970 strokeACase.compare(reporter, strokeBCase,
971 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700972 }
bsalomon06077562016-05-04 13:50:29 -0700973 } else {
974 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
975 }
bsalomon47cc7692016-04-26 12:56:00 -0700976
bsalomonf0cf3552016-05-05 08:28:30 -0700977 SkPaint strokeAndFillA = strokeA;
978 SkPaint strokeAndFillB = strokeB;
979 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
980 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
981 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
982 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
983 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700984 // If stroking is immediately incorporated into a geometric transformation then the base
985 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700986 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
987 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700988 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700989 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700990 } else {
991 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700992 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700993 }
bsalomonf0cf3552016-05-05 08:28:30 -0700994 } else {
995 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
996 TestCase::kAllSame_ComparisonExpecation);
997 }
998
bsalomon47cc7692016-04-26 12:56:00 -0700999 // Make sure stroking params don't affect fill style.
1000 SkPaint fillA = strokeA, fillB = strokeB;
1001 fillA.setStyle(SkPaint::kFill_Style);
1002 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -07001003 TestCase fillACase(geo, fillA, reporter);
1004 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001005 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
1006
1007 // Make sure just applying the dash but not stroke gives the same key for both stroking
1008 // variations.
1009 SkPaint dashA = strokeA, dashB = strokeB;
1010 dashA.setPathEffect(make_dash());
1011 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -07001012 TestCase dashACase(geo, dashA, reporter);
1013 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -07001014 if (paramAffectsDashAndStroke) {
bsalomon487f8d32016-07-20 07:15:44 -07001015 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon06077562016-05-04 13:50:29 -07001016 } else {
1017 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
1018 }
bsalomon47cc7692016-04-26 12:56:00 -07001019}
1020
bsalomona395f7c2016-08-24 17:47:40 -07001021template <typename T>
1022static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -07001023 std::function<void(SkPaint*, T)> setter, T a, T b) {
1024 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
1025};
1026
bsalomona395f7c2016-08-24 17:47:40 -07001027static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
1028 SkPaint hairline;
1029 hairline.setStrokeWidth(0);
1030 hairline.setStyle(SkPaint::kStroke_Style);
Michael Ludwig2686d692020-04-17 20:21:37 +00001031 GrStyledShape shape = geo.makeShape(hairline);
bsalomon06077562016-05-04 13:50:29 -07001032 // The cap should only affect shapes that may be open.
1033 bool affectsStroke = !shape.knownToBeClosed();
1034 // Dashing adds ends that need caps.
1035 bool affectsDashAndStroke = true;
bsalomona395f7c2016-08-24 17:47:40 -07001036 test_stroke_param_impl<SkPaint::Cap>(
bsalomon06077562016-05-04 13:50:29 -07001037 reporter,
1038 geo,
1039 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
1040 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
1041 affectsStroke,
1042 affectsDashAndStroke);
1043};
1044
Michael Ludwig2686d692020-04-17 20:21:37 +00001045static bool shape_known_not_to_have_joins(const GrStyledShape& shape) {
bsalomon0ae36a22016-07-18 07:31:13 -07001046 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
1047}
1048
bsalomona395f7c2016-08-24 17:47:40 -07001049static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
1050 SkPaint hairline;
1051 hairline.setStrokeWidth(0);
1052 hairline.setStyle(SkPaint::kStroke_Style);
Michael Ludwig2686d692020-04-17 20:21:37 +00001053 GrStyledShape shape = geo.makeShape(hairline);
1054 // GrStyledShape recognizes certain types don't have joins and will prevent the join type from
bsalomon0ae36a22016-07-18 07:31:13 -07001055 // affecting the style key.
Michael Ludwig2686d692020-04-17 20:21:37 +00001056 // Dashing doesn't add additional joins. However, GrStyledShape currently loses track of this
bsalomon0ae36a22016-07-18 07:31:13 -07001057 // after applying the dash.
1058 bool affectsStroke = !shape_known_not_to_have_joins(shape);
bsalomona395f7c2016-08-24 17:47:40 -07001059 test_stroke_param_impl<SkPaint::Join>(
bsalomon0ae36a22016-07-18 07:31:13 -07001060 reporter,
1061 geo,
1062 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
1063 SkPaint::kRound_Join, SkPaint::kBevel_Join,
1064 affectsStroke, true);
1065};
1066
bsalomona395f7c2016-08-24 17:47:40 -07001067static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon06077562016-05-04 13:50:29 -07001068 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1069 p->setStrokeJoin(SkPaint::kMiter_Join);
1070 p->setStrokeMiter(miter);
1071 };
bsalomon47cc7692016-04-26 12:56:00 -07001072
bsalomon06077562016-05-04 13:50:29 -07001073 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1074 p->setStrokeJoin(SkPaint::kRound_Join);
1075 p->setStrokeMiter(miter);
1076 };
bsalomon47cc7692016-04-26 12:56:00 -07001077
bsalomona395f7c2016-08-24 17:47:40 -07001078 SkPaint hairline;
1079 hairline.setStrokeWidth(0);
1080 hairline.setStyle(SkPaint::kStroke_Style);
Michael Ludwig2686d692020-04-17 20:21:37 +00001081 GrStyledShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001082 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
1083
bsalomon06077562016-05-04 13:50:29 -07001084 // The miter limit should affect stroked and dashed-stroked cases when the join type is
1085 // miter.
bsalomona395f7c2016-08-24 17:47:40 -07001086 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001087 reporter,
1088 geo,
1089 setMiterJoinAndLimit,
1090 0.5f, 0.75f,
bsalomon0ae36a22016-07-18 07:31:13 -07001091 mayHaveJoins,
bsalomon06077562016-05-04 13:50:29 -07001092 true);
bsalomon47cc7692016-04-26 12:56:00 -07001093
bsalomon06077562016-05-04 13:50:29 -07001094 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
1095 // not miter.
bsalomona395f7c2016-08-24 17:47:40 -07001096 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001097 reporter,
1098 geo,
1099 setOtherJoinAndLimit,
1100 0.5f, 0.75f,
1101 false,
1102 false);
bsalomon47cc7692016-04-26 12:56:00 -07001103}
1104
bsalomona395f7c2016-08-24 17:47:40 -07001105static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001106 // A dash with no stroke should have no effect
1107 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
1108 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
1109 SkPaint dashFill;
1110 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -07001111 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001112
bsalomonfb083272016-05-04 08:27:41 -07001113 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001114 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
1115 }
1116}
1117
bsalomona395f7c2016-08-24 17:47:40 -07001118void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001119 SkPaint fill;
1120 SkPaint stroke;
1121 stroke.setStyle(SkPaint::kStroke_Style);
1122 stroke.setStrokeWidth(1.f);
1123 SkPaint dash;
1124 dash.setStyle(SkPaint::kStroke_Style);
1125 dash.setStrokeWidth(1.f);
1126 dash.setPathEffect(make_dash());
1127 SkPaint nullDash;
1128 nullDash.setStyle(SkPaint::kStroke_Style);
1129 nullDash.setStrokeWidth(1.f);
1130 nullDash.setPathEffect(make_null_dash());
1131
bsalomonfb083272016-05-04 08:27:41 -07001132 TestCase fillCase(geo, fill, reporter);
1133 TestCase strokeCase(geo, stroke, reporter);
1134 TestCase dashCase(geo, dash, reporter);
1135 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001136
bsalomon487f8d32016-07-20 07:15:44 -07001137 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
bsalomon47cc7692016-04-26 12:56:00 -07001138 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -07001139 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
1140 // on construction in order to determine how to compare the fill and stroke.
bsalomona395f7c2016-08-24 17:47:40 -07001141 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001142 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
1143 } else {
1144 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1145 }
1146 // In the null dash case we may immediately convert to a fill, but not for the normal dash case.
bsalomona395f7c2016-08-24 17:47:40 -07001147 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001148 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
1149 } else {
1150 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
1151 }
bsalomon47cc7692016-04-26 12:56:00 -07001152}
1153
bsalomona395f7c2016-08-24 17:47:40 -07001154void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001155 /**
1156 * This path effect takes any input path and turns it into a rrect. It passes through stroke
1157 * info.
1158 */
Mike Reedec9d0e82021-05-21 17:42:14 -04001159 class RRectPathEffect : SkPathEffectBase {
bsalomon72dc51c2016-04-27 06:46:23 -07001160 public:
1161 static const SkRRect& RRect() {
1162 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1163 return kRRect;
1164 }
1165
Mike Reed6d10f8b2018-08-16 13:22:16 -04001166 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1167 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001168 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001169
1170 protected:
1171 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1172 const SkRect* cullR) const override {
bsalomon72dc51c2016-04-27 06:46:23 -07001173 dst->reset();
1174 dst->addRRect(RRect());
1175 return true;
1176 }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001177
Michael Ludwig4e1c1a72021-05-11 11:39:36 -04001178 bool computeFastBounds(SkRect* bounds) const override {
1179 if (bounds) {
1180 *bounds = RRect().getBounds();
1181 }
1182 return true;
bsalomon72dc51c2016-04-27 06:46:23 -07001183 }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001184
bsalomon72dc51c2016-04-27 06:46:23 -07001185 private:
1186 RRectPathEffect() {}
1187 };
1188
1189 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -07001190 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001191
1192 SkPaint pe;
1193 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001194 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001195
1196 SkPaint peStroke;
1197 peStroke.setPathEffect(RRectPathEffect::Make());
1198 peStroke.setStrokeWidth(2.f);
1199 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001200 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001201
bsalomon487f8d32016-07-20 07:15:44 -07001202 // Check whether constructing the filled case would cause the base shape to have a different
Michael Ludwig2686d692020-04-17 20:21:37 +00001203 // geometry (because of a geometric transformation upon initial GrStyledShape construction).
bsalomona395f7c2016-08-24 17:47:40 -07001204 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -07001205 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1206 fillGeoCase.compare(reporter, geoPEStrokeCase,
1207 TestCase::kAllDifferent_ComparisonExpecation);
1208 } else {
1209 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1210 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1211 }
bsalomon72dc51c2016-04-27 06:46:23 -07001212 geoPECase.compare(reporter, geoPEStrokeCase,
1213 TestCase::kSameUpToStroke_ComparisonExpecation);
1214
bsalomona395f7c2016-08-24 17:47:40 -07001215 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
bsalomon72dc51c2016-04-27 06:46:23 -07001216 SkPaint stroke = peStroke;
1217 stroke.setPathEffect(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -07001218 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001219
1220 SkRRect rrect;
1221 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1222 // geoPECase, so the full style should be the same as just the PE.
bsalomon70493962016-06-10 08:05:14 -07001223 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1224 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001225 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1226 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1227
bsalomon70493962016-06-10 08:05:14 -07001228 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1229 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001230 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1231 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1232
1233 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomon70493962016-06-10 08:05:14 -07001234 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1235 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001236 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1237 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1238
bsalomon70493962016-06-10 08:05:14 -07001239 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1240 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001241 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1242 rrectStrokeCase.appliedFullStyleKey());
1243}
1244
bsalomona395f7c2016-08-24 17:47:40 -07001245void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001246 /**
1247 * This path effect just adds two lineTos to the input path.
1248 */
Mike Reedec9d0e82021-05-21 17:42:14 -04001249 class AddLineTosPathEffect : SkPathEffectBase {
bsalomon72dc51c2016-04-27 06:46:23 -07001250 public:
Mike Reed6d10f8b2018-08-16 13:22:16 -04001251 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1252 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001253 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001254
1255 protected:
1256 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1257 const SkRect* cullR) const override {
bsalomon72dc51c2016-04-27 06:46:23 -07001258 *dst = src;
bsalomon67fa4e32016-09-21 08:26:57 -07001259 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1260 for (int i = 0; i < 100; ++i) {
1261 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1262 }
bsalomon72dc51c2016-04-27 06:46:23 -07001263 return true;
1264 }
Michael Ludwig4e1c1a72021-05-11 11:39:36 -04001265 bool computeFastBounds(SkRect* bounds) const override {
1266 if (bounds) {
1267 SkRectPriv::GrowToInclude(bounds, {0, 0});
1268 SkRectPriv::GrowToInclude(bounds, {100, 100});
1269 }
1270 return true;
bsalomon72dc51c2016-04-27 06:46:23 -07001271 }
bsalomon72dc51c2016-04-27 06:46:23 -07001272 private:
1273 AddLineTosPathEffect() {}
1274 };
1275
bsalomon9ad5d7c2016-05-04 08:44:15 -07001276 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -07001277 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1278 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1279 SkPaint peStroke;
1280 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1281 peStroke.setStrokeWidth(2.f);
1282 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001283 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001284 TestCase::SelfExpectations expectations;
1285 expectations.fPEHasEffect = true;
1286 expectations.fPEHasValidKey = false;
1287 expectations.fStrokeApplies = true;
1288 geoPEStrokeCase.testExpectations(reporter, expectations);
1289}
1290
bsalomona395f7c2016-08-24 17:47:40 -07001291void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon9ad5d7c2016-05-04 08:44:15 -07001292 /**
1293 * This path effect just changes the stroke rec to hairline.
1294 */
Mike Reedec9d0e82021-05-21 17:42:14 -04001295 class MakeHairlinePathEffect : SkPathEffectBase {
bsalomon9ad5d7c2016-05-04 08:44:15 -07001296 public:
bsalomon9ad5d7c2016-05-04 08:44:15 -07001297 static sk_sp<SkPathEffect> Make() {
1298 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1299 }
1300 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001301 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001302
1303 protected:
1304 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1305 const SkRect* cullR) const override {
1306 *dst = src;
1307 strokeRec->setHairlineStyle();
1308 return true;
1309 }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001310 private:
Michael Ludwig4e1c1a72021-05-11 11:39:36 -04001311 bool computeFastBounds(SkRect* bounds) const override { return true; }
1312
bsalomon9ad5d7c2016-05-04 08:44:15 -07001313 MakeHairlinePathEffect() {}
1314 };
1315
1316 SkPaint fill;
1317 SkPaint pe;
1318 pe.setPathEffect(MakeHairlinePathEffect::Make());
1319
1320 TestCase peCase(geo, pe, reporter);
1321
bsalomonee295642016-06-06 14:01:25 -07001322 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -07001323 peCase.baseShape().asPath(&a);
1324 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -07001325 peCase.appliedFullStyleShape().asPath(&c);
bsalomona395f7c2016-08-24 17:47:40 -07001326 if (geo.isNonPath(pe)) {
bsalomonee295642016-06-06 14:01:25 -07001327 // RRect types can have a change in start index or direction after the PE is applied. This
Michael Ludwig2686d692020-04-17 20:21:37 +00001328 // is because once the PE is applied, GrStyledShape may canonicalize the dir and index since
1329 // it is not germane to the styling any longer.
bsalomonee295642016-06-06 14:01:25 -07001330 // Instead we just check that the paths would fill the same both before and after styling.
1331 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1332 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -07001333 } else {
bsalomona4817af2016-06-23 11:48:26 -07001334 // The base shape cannot perform canonicalization on the path's fill type because of an
1335 // unknown path effect. However, after the path effect is applied the resulting hairline
1336 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1337 // don't distinguish between even/odd and non-zero winding.
1338 a.setFillType(b.getFillType());
bsalomonee295642016-06-06 14:01:25 -07001339 REPORTER_ASSERT(reporter, a == b);
1340 REPORTER_ASSERT(reporter, a == c);
bsalomon67fa4e32016-09-21 08:26:57 -07001341 // If the resulting path is small enough then it will have a key.
1342 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1343 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomonaa840642016-09-23 12:09:16 -07001344 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1345 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001346 }
bsalomonee295642016-06-06 14:01:25 -07001347 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1348 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001349}
1350
bsalomona395f7c2016-08-24 17:47:40 -07001351void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1352 SkPath vPath = geo.path();
bsalomon4eeccc92016-04-27 13:30:25 -07001353 vPath.setIsVolatile(true);
1354
1355 SkPaint dashAndStroke;
1356 dashAndStroke.setPathEffect(make_dash());
1357 dashAndStroke.setStrokeWidth(2.f);
1358 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001359 TestCase volatileCase(reporter, vPath, dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001360 // We expect a shape made from a volatile path to have a key iff the shape is recognized
bsalomonaa840642016-09-23 12:09:16 -07001361 // as a specialized geometry.
1362 if (geo.isNonPath(dashAndStroke)) {
bsalomon4eeccc92016-04-27 13:30:25 -07001363 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1364 // In this case all the keys should be identical to the non-volatile case.
bsalomona395f7c2016-08-24 17:47:40 -07001365 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001366 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1367 } else {
1368 // None of the keys should be valid.
1369 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1370 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1371 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1372 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1373 }
1374}
1375
bsalomona395f7c2016-08-24 17:47:40 -07001376void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon409ed732016-04-27 12:36:02 -07001377 /**
Brian Salomon085c0862017-08-31 15:44:51 -04001378 * This path effect returns an empty path (possibly inverted)
bsalomon409ed732016-04-27 12:36:02 -07001379 */
Mike Reedec9d0e82021-05-21 17:42:14 -04001380 class EmptyPathEffect : SkPathEffectBase {
bsalomon409ed732016-04-27 12:36:02 -07001381 public:
Mike Reed6d10f8b2018-08-16 13:22:16 -04001382 static sk_sp<SkPathEffect> Make(bool invert) {
1383 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
1384 }
1385 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001386 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001387 protected:
1388 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1389 const SkRect* cullR) const override {
bsalomon409ed732016-04-27 12:36:02 -07001390 dst->reset();
Brian Salomon085c0862017-08-31 15:44:51 -04001391 if (fInvert) {
1392 dst->toggleInverseFillType();
1393 }
bsalomon409ed732016-04-27 12:36:02 -07001394 return true;
1395 }
Michael Ludwig4e1c1a72021-05-11 11:39:36 -04001396 bool computeFastBounds(SkRect* bounds) const override {
1397 if (bounds) {
1398 *bounds = { 0, 0, 0, 0 };
1399 }
1400 return true;
bsalomon409ed732016-04-27 12:36:02 -07001401 }
bsalomon409ed732016-04-27 12:36:02 -07001402 private:
Brian Salomon085c0862017-08-31 15:44:51 -04001403 bool fInvert;
1404 EmptyPathEffect(bool invert) : fInvert(invert) {}
bsalomon409ed732016-04-27 12:36:02 -07001405 };
1406
1407 SkPath emptyPath;
Michael Ludwig2686d692020-04-17 20:21:37 +00001408 GrStyledShape emptyShape(emptyPath);
bsalomon409ed732016-04-27 12:36:02 -07001409 Key emptyKey;
1410 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -07001411 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001412
Brian Salomon085c0862017-08-31 15:44:51 -04001413 emptyPath.toggleInverseFillType();
Michael Ludwig2686d692020-04-17 20:21:37 +00001414 GrStyledShape invertedEmptyShape(emptyPath);
Brian Salomon085c0862017-08-31 15:44:51 -04001415 Key invertedEmptyKey;
1416 make_key(&invertedEmptyKey, invertedEmptyShape);
1417 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
1418
1419 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
1420
bsalomon409ed732016-04-27 12:36:02 -07001421 SkPaint pe;
Brian Salomon085c0862017-08-31 15:44:51 -04001422 pe.setPathEffect(EmptyPathEffect::Make(false));
1423 TestCase geoPECase(geo, pe, reporter);
1424 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
1425 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
1426 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
1427 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
1428 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
1429 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
1430 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001431
1432 SkPaint peStroke;
Brian Salomon085c0862017-08-31 15:44:51 -04001433 peStroke.setPathEffect(EmptyPathEffect::Make(false));
bsalomon409ed732016-04-27 12:36:02 -07001434 peStroke.setStrokeWidth(2.f);
1435 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001436 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001437 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1438 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1439 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001440 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1441 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001442 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
1443 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
1444 pe.setPathEffect(EmptyPathEffect::Make(true));
1445
1446 TestCase geoPEInvertCase(geo, pe, reporter);
1447 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
1448 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
1449 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1450 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
1451 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
1452 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
1453 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
1454
1455 peStroke.setPathEffect(EmptyPathEffect::Make(true));
1456 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
1457 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
1458 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
1459 REPORTER_ASSERT(reporter,
1460 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1461 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
1462 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
1463 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
1464 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001465}
1466
bsalomona395f7c2016-08-24 17:47:40 -07001467void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
bsalomond6723842016-06-07 12:20:15 -07001468 /**
bsalomon0ae36a22016-07-18 07:31:13 -07001469 * This path effect always fails to apply.
bsalomond6723842016-06-07 12:20:15 -07001470 */
Mike Reedec9d0e82021-05-21 17:42:14 -04001471 class FailurePathEffect : SkPathEffectBase {
bsalomond6723842016-06-07 12:20:15 -07001472 public:
bsalomond6723842016-06-07 12:20:15 -07001473 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1474 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001475 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001476 protected:
1477 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1478 const SkRect* cullR) const override {
1479 return false;
1480 }
bsalomond6723842016-06-07 12:20:15 -07001481 private:
Michael Ludwig4e1c1a72021-05-11 11:39:36 -04001482 bool computeFastBounds(SkRect* bounds) const override { return false; }
1483
bsalomond6723842016-06-07 12:20:15 -07001484 FailurePathEffect() {}
1485 };
1486
1487 SkPaint fill;
1488 TestCase fillCase(geo, fill, reporter);
1489
1490 SkPaint pe;
1491 pe.setPathEffect(FailurePathEffect::Make());
1492 TestCase peCase(geo, pe, reporter);
1493
1494 SkPaint stroke;
1495 stroke.setStrokeWidth(2.f);
1496 stroke.setStyle(SkPaint::kStroke_Style);
1497 TestCase strokeCase(geo, stroke, reporter);
1498
1499 SkPaint peStroke = stroke;
1500 peStroke.setPathEffect(FailurePathEffect::Make());
1501 TestCase peStrokeCase(geo, peStroke, reporter);
1502
1503 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1504 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
Michael Ludwig2686d692020-04-17 20:21:37 +00001505 // path effect, but then when the path effect fails we can key it. 2) GrStyledShape will change
1506 // its mind about whether a unclosed rect is actually rect. The path effect initially bars us
1507 // from closing it but after the effect fails we can (for the fill+pe case). This causes
1508 // different routes through GrStyledShape to have equivalent but different representations of
1509 // the path (closed or not) but that fill the same.
bsalomond6723842016-06-07 12:20:15 -07001510 SkPath a;
1511 SkPath b;
1512 fillCase.appliedPathEffectShape().asPath(&a);
1513 peCase.appliedPathEffectShape().asPath(&b);
1514 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1515
1516 fillCase.appliedFullStyleShape().asPath(&a);
1517 peCase.appliedFullStyleShape().asPath(&b);
1518 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1519
1520 strokeCase.appliedPathEffectShape().asPath(&a);
1521 peStrokeCase.appliedPathEffectShape().asPath(&b);
1522 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1523
1524 strokeCase.appliedFullStyleShape().asPath(&a);
1525 peStrokeCase.appliedFullStyleShape().asPath(&b);
1526 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1527}
1528
Michael Ludwig2686d692020-04-17 20:21:37 +00001529DEF_TEST(GrStyledShape_empty_shape, reporter) {
bsalomon409ed732016-04-27 12:36:02 -07001530 SkPath emptyPath;
Brian Salomon085c0862017-08-31 15:44:51 -04001531 SkPath invertedEmptyPath;
1532 invertedEmptyPath.toggleInverseFillType();
bsalomon409ed732016-04-27 12:36:02 -07001533 SkPaint fill;
bsalomona395f7c2016-08-24 17:47:40 -07001534 TestCase fillEmptyCase(reporter, emptyPath, fill);
bsalomon7c73a532016-05-11 15:15:56 -07001535 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1536 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1537 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001538 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
1539 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
1540 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
1541 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
1542 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
1543 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
1544 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
1545 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
1546 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
1547 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001548
John Stiles31954bf2020-08-07 17:35:54 -04001549 const Key& emptyKey = fillEmptyCase.baseKey();
bsalomon409ed732016-04-27 12:36:02 -07001550 REPORTER_ASSERT(reporter, emptyKey.count());
John Stiles31954bf2020-08-07 17:35:54 -04001551 const Key& inverseEmptyKey = fillInvertedEmptyCase.baseKey();
Brian Salomon085c0862017-08-31 15:44:51 -04001552 REPORTER_ASSERT(reporter, inverseEmptyKey.count());
bsalomon409ed732016-04-27 12:36:02 -07001553 TestCase::SelfExpectations expectations;
1554 expectations.fStrokeApplies = false;
1555 expectations.fPEHasEffect = false;
1556 // This will test whether applying style preserves emptiness
1557 fillEmptyCase.testExpectations(reporter, expectations);
Brian Salomon085c0862017-08-31 15:44:51 -04001558 fillInvertedEmptyCase.testExpectations(reporter, expectations);
bsalomon409ed732016-04-27 12:36:02 -07001559
1560 // Stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001561 SkPaint stroke;
1562 stroke.setStrokeWidth(2.f);
1563 stroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001564 stroke.setStrokeJoin(SkPaint::kRound_Join);
1565 stroke.setStrokeCap(SkPaint::kRound_Cap);
Brian Salomon085c0862017-08-31 15:44:51 -04001566 TestCase strokeEmptyCase(reporter, emptyPath, stroke);
bsalomon409ed732016-04-27 12:36:02 -07001567 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001568 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
1569 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
1570 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001571
1572 // Dashing and stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001573 SkPaint dashAndStroke;
1574 dashAndStroke.setPathEffect(make_dash());
1575 dashAndStroke.setStrokeWidth(2.f);
1576 dashAndStroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon085c0862017-08-31 15:44:51 -04001577 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
bsalomon409ed732016-04-27 12:36:02 -07001578 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1579 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001580 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
1581 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1582 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
1583 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001584
Michael Ludwigf38b7112020-04-30 13:47:00 -04001585 // A shape made from an empty rrect should behave the same as an empty path when filled and
1586 // when stroked. The shape is closed so it does not produce caps when stroked. When dashed there
1587 // is no path to dash along, making it equivalent as well.
Brian Salomon2fad74a2017-12-20 13:28:55 -05001588 SkRRect emptyRRect = SkRRect::MakeEmpty();
bsalomon5e410b42016-04-28 09:30:46 -07001589 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001590
1591 TestCase fillEmptyRRectCase(reporter, emptyRRect, fill);
1592 fillEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1593
1594 TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke);
1595 strokeEmptyRRectCase.compare(reporter, strokeEmptyCase,
Michael Ludwigf38b7112020-04-30 13:47:00 -04001596 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001597
bsalomona395f7c2016-08-24 17:47:40 -07001598 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001599 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1600 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001601
Mike Reed30bc5272019-11-22 18:34:02 +00001602 static constexpr SkPathDirection kDir = SkPathDirection::kCCW;
Brian Salomon085c0862017-08-31 15:44:51 -04001603 static constexpr int kStart = 0;
Brian Salomon2fad74a2017-12-20 13:28:55 -05001604
1605 TestCase fillInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, GrStyle(fill));
1606 fillInvertedEmptyRRectCase.compare(reporter, fillInvertedEmptyCase,
1607 TestCase::kAllSame_ComparisonExpecation);
1608
1609 TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true,
1610 GrStyle(stroke));
1611 strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase,
Michael Ludwigf38b7112020-04-30 13:47:00 -04001612 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001613
Brian Salomon085c0862017-08-31 15:44:51 -04001614 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
1615 GrStyle(dashAndStroke));
Brian Salomon085c0862017-08-31 15:44:51 -04001616 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
1617 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001618
1619 // Same for a rect.
1620 SkRect emptyRect = SkRect::MakeEmpty();
Brian Salomon2fad74a2017-12-20 13:28:55 -05001621 TestCase fillEmptyRectCase(reporter, emptyRect, fill);
1622 fillEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1623
bsalomona395f7c2016-08-24 17:47:40 -07001624 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001625 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1626 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001627
Brian Salomon085c0862017-08-31 15:44:51 -04001628 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
1629 kStart, true, GrStyle(dashAndStroke));
1630 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1631 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
1632 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001633}
1634
bsalomon70493962016-06-10 08:05:14 -07001635// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1636// canonical point in these cases.
1637unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1638 switch (rrect.getType()) {
1639 case SkRRect::kRect_Type:
1640 return (s + 1) & 0b110;
1641 case SkRRect::kOval_Type:
1642 return s & 0b110;
1643 default:
1644 return s;
1645 }
1646}
1647
1648void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
bsalomoncadb5a22016-06-10 18:28:06 -07001649 enum Style {
bsalomon70493962016-06-10 08:05:14 -07001650 kFill,
1651 kStroke,
1652 kHairline,
1653 kStrokeAndFill
1654 };
1655
1656 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1657 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1658 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1659 strokeRecs[kFill].setFillStyle();
1660 strokeRecs[kStroke].setStrokeStyle(2.f);
1661 strokeRecs[kHairline].setHairlineStyle();
1662 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
bsalomon487f8d32016-07-20 07:15:44 -07001663 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1664 // applyStyle() is called.
1665 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
bsalomon70493962016-06-10 08:05:14 -07001666 sk_sp<SkPathEffect> dashEffect = make_dash();
1667
bsalomoncadb5a22016-06-10 18:28:06 -07001668 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1669
1670 auto index = [](bool inverted,
Mike Reed30bc5272019-11-22 18:34:02 +00001671 SkPathDirection dir,
bsalomoncadb5a22016-06-10 18:28:06 -07001672 unsigned start,
1673 Style style,
1674 bool dash) -> int {
1675 return inverted * (2 * 8 * kStyleCnt * 2) +
Mike Reed30bc5272019-11-22 18:34:02 +00001676 (int)dir * ( 8 * kStyleCnt * 2) +
bsalomoncadb5a22016-06-10 18:28:06 -07001677 start * ( kStyleCnt * 2) +
1678 style * ( 2) +
1679 dash;
1680 };
Mike Reed30bc5272019-11-22 18:34:02 +00001681 static const SkPathDirection kSecondDirection = static_cast<SkPathDirection>(1);
bsalomoncadb5a22016-06-10 18:28:06 -07001682 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
Michael Ludwig2686d692020-04-17 20:21:37 +00001683 SkAutoTArray<GrStyledShape> shapes(cnt);
bsalomoncadb5a22016-06-10 18:28:06 -07001684 for (bool inverted : {false, true}) {
Mike Reed30bc5272019-11-22 18:34:02 +00001685 for (SkPathDirection dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
bsalomoncadb5a22016-06-10 18:28:06 -07001686 for (unsigned start = 0; start < 8; ++start) {
1687 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1688 for (bool dash : {false, true}) {
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001689 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
bsalomoncadb5a22016-06-10 18:28:06 -07001690 shapes[index(inverted, dir, start, style, dash)] =
Michael Ludwig2686d692020-04-17 20:21:37 +00001691 GrStyledShape(rrect, dir, start, SkToBool(inverted),
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001692 GrStyle(strokeRecs[style], std::move(pe)));
bsalomon70493962016-06-10 08:05:14 -07001693 }
1694 }
1695 }
1696 }
1697 }
1698
bsalomonfd32df72016-06-14 14:37:21 -07001699 // Get the keys for some example shape instances that we'll use for comparision against the
1700 // rest.
Mike Reed30bc5272019-11-22 18:34:02 +00001701 static constexpr SkPathDirection kExamplesDir = SkPathDirection::kCW;
bsalomonfd32df72016-06-14 14:37:21 -07001702 static constexpr unsigned kExamplesStart = 0;
Michael Ludwig2686d692020-04-17 20:21:37 +00001703 const GrStyledShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
bsalomonfd32df72016-06-14 14:37:21 -07001704 false)];
bsalomon70493962016-06-10 08:05:14 -07001705 Key exampleFillCaseKey;
1706 make_key(&exampleFillCaseKey, exampleFillCase);
1707
Michael Ludwig2686d692020-04-17 20:21:37 +00001708 const GrStyledShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir,
1709 kExamplesStart, kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001710 Key exampleStrokeAndFillCaseKey;
1711 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1712
Michael Ludwig2686d692020-04-17 20:21:37 +00001713 const GrStyledShape& exampleInvFillCase = shapes[index(true, kExamplesDir,
1714 kExamplesStart, kFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001715 Key exampleInvFillCaseKey;
1716 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1717
Michael Ludwig2686d692020-04-17 20:21:37 +00001718 const GrStyledShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir,
1719 kExamplesStart, kStrokeAndFill,
1720 false)];
bsalomon70493962016-06-10 08:05:14 -07001721 Key exampleInvStrokeAndFillCaseKey;
1722 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1723
Michael Ludwig2686d692020-04-17 20:21:37 +00001724 const GrStyledShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart,
1725 kStroke, false)];
bsalomon70493962016-06-10 08:05:14 -07001726 Key exampleStrokeCaseKey;
1727 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1728
Michael Ludwig2686d692020-04-17 20:21:37 +00001729 const GrStyledShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart,
1730 kStroke, false)];
bsalomonfd32df72016-06-14 14:37:21 -07001731 Key exampleInvStrokeCaseKey;
1732 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1733
Michael Ludwig2686d692020-04-17 20:21:37 +00001734 const GrStyledShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
bsalomonfd32df72016-06-14 14:37:21 -07001735 kHairline, false)];
bsalomon70493962016-06-10 08:05:14 -07001736 Key exampleHairlineCaseKey;
1737 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1738
Michael Ludwig2686d692020-04-17 20:21:37 +00001739 const GrStyledShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
bsalomonfd32df72016-06-14 14:37:21 -07001740 kHairline, false)];
1741 Key exampleInvHairlineCaseKey;
1742 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1743
bsalomon70493962016-06-10 08:05:14 -07001744 // These are dummy initializations to suppress warnings.
bsalomoncadb5a22016-06-10 18:28:06 -07001745 SkRRect queryRR = SkRRect::MakeEmpty();
Mike Reed30bc5272019-11-22 18:34:02 +00001746 SkPathDirection queryDir = SkPathDirection::kCW;
bsalomoncadb5a22016-06-10 18:28:06 -07001747 unsigned queryStart = ~0U;
1748 bool queryInverted = true;
bsalomon70493962016-06-10 08:05:14 -07001749
bsalomoncadb5a22016-06-10 18:28:06 -07001750 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1751 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001752 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001753 REPORTER_ASSERT(r, 0 == queryStart);
1754 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001755
bsalomoncadb5a22016-06-10 18:28:06 -07001756 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1757 &queryInverted));
1758 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001759 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001760 REPORTER_ASSERT(r, 0 == queryStart);
1761 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001762
bsalomoncadb5a22016-06-10 18:28:06 -07001763 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1764 &queryInverted));
1765 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001766 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001767 REPORTER_ASSERT(r, 0 == queryStart);
1768 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001769
bsalomoncadb5a22016-06-10 18:28:06 -07001770 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1771 &queryInverted));
1772 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001773 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001774 REPORTER_ASSERT(r, 0 == queryStart);
1775 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001776
bsalomoncadb5a22016-06-10 18:28:06 -07001777 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1778 &queryInverted));
1779 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001780 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001781 REPORTER_ASSERT(r, 0 == queryStart);
1782 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001783
bsalomonfd32df72016-06-14 14:37:21 -07001784 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1785 &queryInverted));
1786 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001787 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomonfd32df72016-06-14 14:37:21 -07001788 REPORTER_ASSERT(r, 0 == queryStart);
1789 REPORTER_ASSERT(r, queryInverted);
1790
bsalomoncadb5a22016-06-10 18:28:06 -07001791 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1792 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001793 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001794 REPORTER_ASSERT(r, 0 == queryStart);
1795 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001796
bsalomonfd32df72016-06-14 14:37:21 -07001797 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1798 &queryInverted));
1799 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001800 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomonfd32df72016-06-14 14:37:21 -07001801 REPORTER_ASSERT(r, 0 == queryStart);
1802 REPORTER_ASSERT(r, queryInverted);
1803
bsalomon70493962016-06-10 08:05:14 -07001804 // Remember that the key reflects the geometry before styling is applied.
1805 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1806 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1807 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1808 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001809 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001810 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001811 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001812 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001813 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1814 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001815
bsalomoncadb5a22016-06-10 18:28:06 -07001816 for (bool inverted : {false, true}) {
Mike Reed30bc5272019-11-22 18:34:02 +00001817 for (SkPathDirection dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
bsalomoncadb5a22016-06-10 18:28:06 -07001818 for (unsigned start = 0; start < 8; ++start) {
1819 for (bool dash : {false, true}) {
Michael Ludwig2686d692020-04-17 20:21:37 +00001820 const GrStyledShape& fillCase = shapes[index(inverted, dir, start, kFill,
1821 dash)];
bsalomon70493962016-06-10 08:05:14 -07001822 Key fillCaseKey;
1823 make_key(&fillCaseKey, fillCase);
1824
Michael Ludwig2686d692020-04-17 20:21:37 +00001825 const GrStyledShape& strokeAndFillCase = shapes[index(inverted, dir, start,
bsalomoncadb5a22016-06-10 18:28:06 -07001826 kStrokeAndFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001827 Key strokeAndFillCaseKey;
1828 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1829
1830 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1831 // ignore dashing.
1832 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1833 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1834 TestCase a(fillCase, r);
1835 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1836 TestCase c(strokeAndFillCase, r);
1837 TestCase d(inverted ? exampleInvStrokeAndFillCase
1838 : exampleStrokeAndFillCase, r);
1839 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1840 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1841
Michael Ludwig2686d692020-04-17 20:21:37 +00001842 const GrStyledShape& strokeCase = shapes[index(inverted, dir, start, kStroke,
1843 dash)];
1844 const GrStyledShape& hairlineCase = shapes[index(inverted, dir, start,
1845 kHairline, dash)];
bsalomon70493962016-06-10 08:05:14 -07001846
1847 TestCase e(strokeCase, r);
bsalomon70493962016-06-10 08:05:14 -07001848 TestCase g(hairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001849
bsalomonfd32df72016-06-14 14:37:21 -07001850 // Both hairline and stroke shapes must respect the dashing.
bsalomon70493962016-06-10 08:05:14 -07001851 if (dash) {
bsalomonfd32df72016-06-14 14:37:21 -07001852 // Dashing always ignores the inverseness. skbug.com/5421
1853 TestCase f(exampleStrokeCase, r);
1854 TestCase h(exampleHairlineCase, r);
bsalomoncadb5a22016-06-10 18:28:06 -07001855 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
bsalomon70493962016-06-10 08:05:14 -07001856 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1857 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1858
bsalomoncadb5a22016-06-10 18:28:06 -07001859 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1860 &queryInverted));
1861 REPORTER_ASSERT(r, queryRR == rrect);
1862 REPORTER_ASSERT(r, queryDir == dir);
1863 REPORTER_ASSERT(r, queryStart == expectedStart);
1864 REPORTER_ASSERT(r, !queryInverted);
1865 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1866 &queryInverted));
1867 REPORTER_ASSERT(r, queryRR == rrect);
1868 REPORTER_ASSERT(r, queryDir == dir);
1869 REPORTER_ASSERT(r, queryStart == expectedStart);
1870 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001871
1872 // The pre-style case for the dash will match the non-dash example iff the
1873 // dir and start match (dir=cw, start=0).
Mike Reed30bc5272019-11-22 18:34:02 +00001874 if (0 == expectedStart && SkPathDirection::kCW == dir) {
bsalomon70493962016-06-10 08:05:14 -07001875 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1876 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1877 } else {
1878 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1879 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1880 }
1881 } else {
bsalomonfd32df72016-06-14 14:37:21 -07001882 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1883 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001884 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1885 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1886 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1887 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1888 }
1889 }
1890 }
1891 }
1892 }
1893}
1894
Michael Ludwig2686d692020-04-17 20:21:37 +00001895DEF_TEST(GrStyledShape_lines, r) {
bsalomon0a0f67e2016-06-28 11:56:42 -07001896 static constexpr SkPoint kA { 1, 1};
1897 static constexpr SkPoint kB { 5, -9};
1898 static constexpr SkPoint kC {-3, 17};
1899
Mike Reed093de4e2020-08-03 16:33:14 -04001900 SkPath lineAB = SkPath::Line(kA, kB);
1901 SkPath lineBA = SkPath::Line(kB, kA);
1902 SkPath lineAC = SkPath::Line(kB, kC);
bsalomon0a0f67e2016-06-28 11:56:42 -07001903 SkPath invLineAB = lineAB;
Mike Reed093de4e2020-08-03 16:33:14 -04001904
Mike Reed7d34dc72019-11-26 12:17:17 -05001905 invLineAB.setFillType(SkPathFillType::kInverseEvenOdd);
bsalomon0a0f67e2016-06-28 11:56:42 -07001906
1907 SkPaint fill;
1908 SkPaint stroke;
1909 stroke.setStyle(SkPaint::kStroke_Style);
1910 stroke.setStrokeWidth(2.f);
1911 SkPaint hairline;
1912 hairline.setStyle(SkPaint::kStroke_Style);
1913 hairline.setStrokeWidth(0.f);
1914 SkPaint dash = stroke;
1915 dash.setPathEffect(make_dash());
1916
bsalomona395f7c2016-08-24 17:47:40 -07001917 TestCase fillAB(r, lineAB, fill);
1918 TestCase fillEmpty(r, SkPath(), fill);
bsalomon0a0f67e2016-06-28 11:56:42 -07001919 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1920 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1921
Brian Salomon085c0862017-08-31 15:44:51 -04001922 SkPath path;
1923 path.toggleInverseFillType();
1924 TestCase fillEmptyInverted(r, path, fill);
1925 TestCase fillABInverted(r, invLineAB, fill);
1926 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
1927 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
1928
bsalomona395f7c2016-08-24 17:47:40 -07001929 TestCase strokeAB(r, lineAB, stroke);
1930 TestCase strokeBA(r, lineBA, stroke);
1931 TestCase strokeAC(r, lineAC, stroke);
bsalomon0a0f67e2016-06-28 11:56:42 -07001932
bsalomona395f7c2016-08-24 17:47:40 -07001933 TestCase hairlineAB(r, lineAB, hairline);
1934 TestCase hairlineBA(r, lineBA, hairline);
1935 TestCase hairlineAC(r, lineAC, hairline);
bsalomon0a0f67e2016-06-28 11:56:42 -07001936
bsalomona395f7c2016-08-24 17:47:40 -07001937 TestCase dashAB(r, lineAB, dash);
1938 TestCase dashBA(r, lineBA, dash);
1939 TestCase dashAC(r, lineAC, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001940
1941 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1942
1943 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1944 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1945
1946 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1947 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1948
1949 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1950 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1951
1952 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1953
1954 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
Michael Ludwig2686d692020-04-17 20:21:37 +00001955 // GrStyledShape canonicalizes line endpoints (when it can, i.e. when not dashed).
bsalomon0a0f67e2016-06-28 11:56:42 -07001956 bool canonicalizeAsAB;
1957 SkPoint canonicalPts[2] {kA, kB};
1958 // Init these to suppress warnings.
1959 bool inverted = true;
1960 SkPoint pts[2] {{0, 0}, {0, 0}};
1961 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1962 if (pts[0] == kA && pts[1] == kB) {
1963 canonicalizeAsAB = true;
1964 } else if (pts[1] == kA && pts[0] == kB) {
1965 canonicalizeAsAB = false;
Ben Wagnerf08d1d02018-06-18 15:11:00 -04001966 using std::swap;
1967 swap(canonicalPts[0], canonicalPts[1]);
bsalomon0a0f67e2016-06-28 11:56:42 -07001968 } else {
1969 ERRORF(r, "Should return pts (a,b) or (b, a)");
1970 return;
Brian Salomon23356442018-11-30 15:33:19 -05001971 }
bsalomon0a0f67e2016-06-28 11:56:42 -07001972
1973 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1974 TestCase::kSameUpToPE_ComparisonExpecation);
1975 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1976 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1977 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1978 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1979 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1980 pts[0] == kA && pts[1] == kB);
1981 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1982 pts[0] == kB && pts[1] == kA);
1983
1984
bsalomona395f7c2016-08-24 17:47:40 -07001985 TestCase strokeInvAB(r, invLineAB, stroke);
1986 TestCase hairlineInvAB(r, invLineAB, hairline);
1987 TestCase dashInvAB(r, invLineAB, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001988 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1989 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1990 // Dashing ignores inverse.
1991 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1992
1993 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1994 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1995 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1996 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1997 // Dashing ignores inverse.
1998 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1999 pts[0] == kA && pts[1] == kB);
2000
2001}
2002
Michael Ludwig2686d692020-04-17 20:21:37 +00002003DEF_TEST(GrStyledShape_stroked_lines, r) {
Brian Salomon72f78c32017-12-21 11:56:42 -05002004 static constexpr SkScalar kIntervals1[] = {1.f, 0.f};
2005 auto dash1 = SkDashPathEffect::Make(kIntervals1, SK_ARRAY_COUNT(kIntervals1), 0.f);
2006 REPORTER_ASSERT(r, dash1);
2007 static constexpr SkScalar kIntervals2[] = {10.f, 0.f, 5.f, 0.f};
2008 auto dash2 = SkDashPathEffect::Make(kIntervals2, SK_ARRAY_COUNT(kIntervals2), 10.f);
2009 REPORTER_ASSERT(r, dash2);
bsalomon0ae36a22016-07-18 07:31:13 -07002010
Brian Salomon72f78c32017-12-21 11:56:42 -05002011 sk_sp<SkPathEffect> pathEffects[] = {nullptr, std::move(dash1), std::move(dash2)};
bsalomon0ae36a22016-07-18 07:31:13 -07002012
Brian Salomon72f78c32017-12-21 11:56:42 -05002013 for (const auto& pe : pathEffects) {
2014 // Paints to try
2015 SkPaint buttCap;
2016 buttCap.setStyle(SkPaint::kStroke_Style);
2017 buttCap.setStrokeWidth(4);
2018 buttCap.setStrokeCap(SkPaint::kButt_Cap);
2019 buttCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07002020
Brian Salomon72f78c32017-12-21 11:56:42 -05002021 SkPaint squareCap = buttCap;
2022 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
2023 squareCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07002024
Brian Salomon72f78c32017-12-21 11:56:42 -05002025 SkPaint roundCap = buttCap;
2026 roundCap.setStrokeCap(SkPaint::kRound_Cap);
2027 roundCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07002028
Brian Salomon72f78c32017-12-21 11:56:42 -05002029 // vertical
2030 SkPath linePath;
2031 linePath.moveTo(4, 4);
2032 linePath.lineTo(4, 5);
bsalomon0ae36a22016-07-18 07:31:13 -07002033
Brian Salomon72f78c32017-12-21 11:56:42 -05002034 SkPaint fill;
bsalomon0ae36a22016-07-18 07:31:13 -07002035
Brian Salomon72f78c32017-12-21 11:56:42 -05002036 make_TestCase(r, linePath, buttCap)->compare(
2037 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
2038 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002039
Brian Salomon72f78c32017-12-21 11:56:42 -05002040 make_TestCase(r, linePath, squareCap)->compare(
2041 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
2042 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002043
Brian Salomon72f78c32017-12-21 11:56:42 -05002044 make_TestCase(r, linePath, roundCap)->compare(r,
2045 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
2046 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002047
Brian Salomon72f78c32017-12-21 11:56:42 -05002048 // horizontal
2049 linePath.reset();
2050 linePath.moveTo(4, 4);
2051 linePath.lineTo(5, 4);
bsalomon0ae36a22016-07-18 07:31:13 -07002052
Brian Salomon72f78c32017-12-21 11:56:42 -05002053 make_TestCase(r, linePath, buttCap)->compare(
2054 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
2055 TestCase::kAllSame_ComparisonExpecation);
2056 make_TestCase(r, linePath, squareCap)->compare(
2057 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
2058 TestCase::kAllSame_ComparisonExpecation);
2059 make_TestCase(r, linePath, roundCap)->compare(
2060 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
2061 TestCase::kAllSame_ComparisonExpecation);
2062
2063 // point
2064 linePath.reset();
2065 linePath.moveTo(4, 4);
2066 linePath.lineTo(4, 4);
2067
2068 make_TestCase(r, linePath, buttCap)->compare(
2069 r, TestCase(r, SkRect::MakeEmpty(), fill),
2070 TestCase::kAllSame_ComparisonExpecation);
2071 make_TestCase(r, linePath, squareCap)->compare(
2072 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
2073 TestCase::kAllSame_ComparisonExpecation);
2074 make_TestCase(r, linePath, roundCap)->compare(
2075 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
2076 TestCase::kAllSame_ComparisonExpecation);
2077 }
bsalomon0ae36a22016-07-18 07:31:13 -07002078}
2079
Michael Ludwig2686d692020-04-17 20:21:37 +00002080DEF_TEST(GrStyledShape_short_path_keys, r) {
bsalomon67fa4e32016-09-21 08:26:57 -07002081 SkPaint paints[4];
2082 paints[1].setStyle(SkPaint::kStroke_Style);
2083 paints[1].setStrokeWidth(5.f);
2084 paints[2].setStyle(SkPaint::kStroke_Style);
2085 paints[2].setStrokeWidth(0.f);
2086 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
2087 paints[3].setStrokeWidth(5.f);
2088
bsalomonaa840642016-09-23 12:09:16 -07002089 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
bsalomon67fa4e32016-09-21 08:26:57 -07002090 TestCase::ComparisonExpecation expectation) {
bsalomonaa840642016-09-23 12:09:16 -07002091 SkPath volatileA = pathA;
2092 SkPath volatileB = pathB;
2093 volatileA.setIsVolatile(true);
2094 volatileB.setIsVolatile(true);
bsalomon67fa4e32016-09-21 08:26:57 -07002095 for (const SkPaint& paint : paints) {
Michael Ludwig2686d692020-04-17 20:21:37 +00002096 REPORTER_ASSERT(r, !GrStyledShape(volatileA, paint).hasUnstyledKey());
2097 REPORTER_ASSERT(r, !GrStyledShape(volatileB, paint).hasUnstyledKey());
bsalomon67fa4e32016-09-21 08:26:57 -07002098 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
bsalomonaa840642016-09-23 12:09:16 -07002099 TestCase caseA(PathGeo(pathA, invert), paint, r);
2100 TestCase caseB(PathGeo(pathB, invert), paint, r);
2101 caseA.compare(r, caseB, expectation);
bsalomon67fa4e32016-09-21 08:26:57 -07002102 }
2103 }
2104 };
2105
2106 SkPath pathA;
2107 SkPath pathB;
2108
2109 // Two identical paths
2110 pathA.lineTo(10.f, 10.f);
2111 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2112
2113 pathB.lineTo(10.f, 10.f);
2114 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002115 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002116
2117 // Give path b a different point
2118 pathB.reset();
2119 pathB.lineTo(10.f, 10.f);
2120 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002121 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002122
2123 // Give path b a different conic weight
2124 pathB.reset();
2125 pathB.lineTo(10.f, 10.f);
2126 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
bsalomonaa840642016-09-23 12:09:16 -07002127 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002128
2129 // Give path b an extra lineTo verb
2130 pathB.reset();
2131 pathB.lineTo(10.f, 10.f);
2132 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2133 pathB.lineTo(50.f, 50.f);
bsalomonaa840642016-09-23 12:09:16 -07002134 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002135
2136 // Give path b a close
2137 pathB.reset();
2138 pathB.lineTo(10.f, 10.f);
2139 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2140 pathB.close();
bsalomonaa840642016-09-23 12:09:16 -07002141 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002142}
2143
Michael Ludwig2686d692020-04-17 20:21:37 +00002144DEF_TEST(GrStyledShape, reporter) {
bsalomona395f7c2016-08-24 17:47:40 -07002145 SkTArray<std::unique_ptr<Geo>> geos;
2146 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
2147
bsalomonee295642016-06-06 14:01:25 -07002148 for (auto r : { SkRect::MakeWH(10, 20),
2149 SkRect::MakeWH(-10, -20),
2150 SkRect::MakeWH(-10, 20),
2151 SkRect::MakeWH(10, -20)}) {
bsalomona395f7c2016-08-24 17:47:40 -07002152 geos.emplace_back(new RectGeo(r));
2153 SkPath rectPath;
2154 rectPath.addRect(r);
2155 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2156 PathGeo::Invert::kNo));
2157 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2158 PathGeo::Invert::kYes));
2159 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2160 PathGeo::Invert::kNo));
bsalomonee295642016-06-06 14:01:25 -07002161 }
bsalomon47cc7692016-04-26 12:56:00 -07002162 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -07002163 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
2164 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomona395f7c2016-08-24 17:47:40 -07002165 geos.emplace_back(new RRectGeo(rr));
bsalomon70493962016-06-10 08:05:14 -07002166 test_rrect(reporter, rr);
bsalomona395f7c2016-08-24 17:47:40 -07002167 SkPath rectPath;
2168 rectPath.addRRect(rr);
2169 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2170 PathGeo::Invert::kNo));
2171 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2172 PathGeo::Invert::kYes));
2173 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
2174 RRectPathGeo::RRectForStroke::kYes,
2175 PathGeo::Invert::kNo));
bsalomon72dc51c2016-04-27 06:46:23 -07002176 }
2177
Brian Salomone4949402018-04-26 15:22:04 -04002178 // Arcs
2179 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, false));
2180 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, true));
2181
Mike Klein43344282017-08-16 11:56:22 -04002182 {
2183 SkPath openRectPath;
2184 openRectPath.moveTo(0, 0);
2185 openRectPath.lineTo(10, 0);
2186 openRectPath.lineTo(10, 10);
2187 openRectPath.lineTo(0, 10);
2188 geos.emplace_back(new RRectPathGeo(
2189 openRectPath, SkRect::MakeWH(10, 10),
2190 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2191 geos.emplace_back(new RRectPathGeo(
2192 openRectPath, SkRect::MakeWH(10, 10),
2193 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
2194 rrectPathGeos.emplace_back(new RRectPathGeo(
2195 openRectPath, SkRect::MakeWH(10, 10),
2196 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2197 }
bsalomon72dc51c2016-04-27 06:46:23 -07002198
Mike Klein43344282017-08-16 11:56:22 -04002199 {
2200 SkPath quadPath;
2201 quadPath.quadTo(10, 10, 5, 8);
2202 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
2203 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
2204 }
bsalomon398e3f42016-06-13 10:22:48 -07002205
Mike Klein43344282017-08-16 11:56:22 -04002206 {
2207 SkPath linePath;
2208 linePath.lineTo(10, 10);
2209 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
2210 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
2211 }
bsalomon72dc51c2016-04-27 06:46:23 -07002212
bsalomon0ae36a22016-07-18 07:31:13 -07002213 // Horizontal and vertical paths become rrects when stroked.
Mike Klein43344282017-08-16 11:56:22 -04002214 {
2215 SkPath vLinePath;
2216 vLinePath.lineTo(0, 10);
2217 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
2218 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
2219 }
bsalomon0ae36a22016-07-18 07:31:13 -07002220
Mike Klein43344282017-08-16 11:56:22 -04002221 {
2222 SkPath hLinePath;
2223 hLinePath.lineTo(10, 0);
2224 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
2225 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
2226 }
bsalomon0ae36a22016-07-18 07:31:13 -07002227
bsalomona395f7c2016-08-24 17:47:40 -07002228 for (int i = 0; i < geos.count(); ++i) {
2229 test_basic(reporter, *geos[i]);
2230 test_scale(reporter, *geos[i]);
2231 test_dash_fill(reporter, *geos[i]);
2232 test_null_dash(reporter, *geos[i]);
2233 // Test modifying various stroke params.
2234 test_stroke_param<SkScalar>(
2235 reporter, *geos[i],
bsalomon70493962016-06-10 08:05:14 -07002236 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
2237 SkIntToScalar(2), SkIntToScalar(4));
bsalomona395f7c2016-08-24 17:47:40 -07002238 test_stroke_join(reporter, *geos[i]);
2239 test_stroke_cap(reporter, *geos[i]);
2240 test_miter_limit(reporter, *geos[i]);
2241 test_path_effect_makes_rrect(reporter, *geos[i]);
2242 test_unknown_path_effect(reporter, *geos[i]);
2243 test_path_effect_makes_empty_shape(reporter, *geos[i]);
2244 test_path_effect_fails(reporter, *geos[i]);
2245 test_make_hairline_path_effect(reporter, *geos[i]);
2246 test_volatile_path(reporter, *geos[i]);
bsalomon70493962016-06-10 08:05:14 -07002247 }
bsalomonfd32df72016-06-14 14:37:21 -07002248
bsalomona395f7c2016-08-24 17:47:40 -07002249 for (int i = 0; i < rrectPathGeos.count(); ++i) {
2250 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
bsalomon72dc51c2016-04-27 06:46:23 -07002251 SkPaint fillPaint;
bsalomona395f7c2016-08-24 17:47:40 -07002252 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002253 SkRRect rrect;
bsalomona395f7c2016-08-24 17:47:40 -07002254 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
bsalomon70493962016-06-10 08:05:14 -07002255 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2256 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002257 if (rrgeo.isNonPath(fillPaint)) {
2258 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
2259 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2260 TestCase fillRRectCase(reporter, rrect, fillPaint);
bsalomon70493962016-06-10 08:05:14 -07002261 fillPathCase2.compare(reporter, fillRRectCase,
2262 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002263 }
bsalomon72dc51c2016-04-27 06:46:23 -07002264 SkPaint strokePaint;
2265 strokePaint.setStrokeWidth(3.f);
2266 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07002267 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
2268 if (rrgeo.isNonPath(strokePaint)) {
bsalomon0ae36a22016-07-18 07:31:13 -07002269 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2270 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002271 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2272 TestCase strokeRRectCase(reporter, rrect, strokePaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002273 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07002274 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002275 }
bsalomon47cc7692016-04-26 12:56:00 -07002276 }
bsalomon409ed732016-04-27 12:36:02 -07002277
bsalomon4eeccc92016-04-27 13:30:25 -07002278 // Test a volatile empty path.
bsalomona395f7c2016-08-24 17:47:40 -07002279 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
bsalomon47cc7692016-04-26 12:56:00 -07002280}
2281
Michael Ludwig2686d692020-04-17 20:21:37 +00002282DEF_TEST(GrStyledShape_arcs, reporter) {
Brian Salomone4949402018-04-26 15:22:04 -04002283 SkStrokeRec roundStroke(SkStrokeRec::kFill_InitStyle);
2284 roundStroke.setStrokeStyle(2.f);
2285 roundStroke.setStrokeParams(SkPaint::kRound_Cap, SkPaint::kRound_Join, 1.f);
2286
2287 SkStrokeRec squareStroke(roundStroke);
2288 squareStroke.setStrokeParams(SkPaint::kSquare_Cap, SkPaint::kRound_Join, 1.f);
2289
2290 SkStrokeRec roundStrokeAndFill(roundStroke);
2291 roundStrokeAndFill.setStrokeStyle(2.f, true);
2292
2293 static constexpr SkScalar kIntervals[] = {1, 2};
2294 auto dash = SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), 1.5f);
2295
2296 SkTArray<GrStyle> styles;
2297 styles.push_back(GrStyle::SimpleFill());
2298 styles.push_back(GrStyle::SimpleHairline());
2299 styles.push_back(GrStyle(roundStroke, nullptr));
2300 styles.push_back(GrStyle(squareStroke, nullptr));
2301 styles.push_back(GrStyle(roundStrokeAndFill, nullptr));
2302 styles.push_back(GrStyle(roundStroke, dash));
2303
2304 for (const auto& style : styles) {
2305 // An empty rect never draws anything according to SkCanvas::drawArc() docs.
Michael Ludwig2686d692020-04-17 20:21:37 +00002306 TestCase emptyArc(GrStyledShape::MakeArc(SkRect::MakeEmpty(), 0, 90.f, false, style),
2307 reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002308 TestCase emptyPath(reporter, SkPath(), style);
2309 emptyArc.compare(reporter, emptyPath, TestCase::kAllSame_ComparisonExpecation);
2310
2311 static constexpr SkRect kOval1{0, 0, 50, 50};
2312 static constexpr SkRect kOval2{50, 0, 100, 50};
2313 // Test that swapping starting and ending angle doesn't change the shape unless the arc
2314 // has a path effect. Also test that different ovals produce different shapes.
Michael Ludwig2686d692020-04-17 20:21:37 +00002315 TestCase arc1CW(GrStyledShape::MakeArc(kOval1, 0, 90.f, false, style), reporter);
2316 TestCase arc1CCW(GrStyledShape::MakeArc(kOval1, 90.f, -90.f, false, style), reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002317
Michael Ludwig2686d692020-04-17 20:21:37 +00002318 TestCase arc1CWWithCenter(GrStyledShape::MakeArc(kOval1, 0, 90.f, true, style), reporter);
2319 TestCase arc1CCWWithCenter(GrStyledShape::MakeArc(kOval1, 90.f, -90.f, true, style),
2320 reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002321
Michael Ludwig2686d692020-04-17 20:21:37 +00002322 TestCase arc2CW(GrStyledShape::MakeArc(kOval2, 0, 90.f, false, style), reporter);
2323 TestCase arc2CWWithCenter(GrStyledShape::MakeArc(kOval2, 0, 90.f, true, style), reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002324
2325 auto reversedExepectations = style.hasPathEffect()
2326 ? TestCase::kAllDifferent_ComparisonExpecation
2327 : TestCase::kAllSame_ComparisonExpecation;
2328 arc1CW.compare(reporter, arc1CCW, reversedExepectations);
2329 arc1CWWithCenter.compare(reporter, arc1CCWWithCenter, reversedExepectations);
2330 arc1CW.compare(reporter, arc2CW, TestCase::kAllDifferent_ComparisonExpecation);
2331 arc1CW.compare(reporter, arc1CWWithCenter, TestCase::kAllDifferent_ComparisonExpecation);
2332 arc1CWWithCenter.compare(reporter, arc2CWWithCenter,
2333 TestCase::kAllDifferent_ComparisonExpecation);
2334
2335 // Test that two arcs that start at the same angle but specified differently are equivalent.
Michael Ludwig2686d692020-04-17 20:21:37 +00002336 TestCase arc3A(GrStyledShape::MakeArc(kOval1, 224.f, 73.f, false, style), reporter);
2337 TestCase arc3B(GrStyledShape::MakeArc(kOval1, 224.f - 360.f, 73.f, false, style), reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002338 arc3A.compare(reporter, arc3B, TestCase::kAllDifferent_ComparisonExpecation);
2339
2340 // Test that an arc that traverses the entire oval (and then some) is equivalent to the
2341 // oval itself unless there is a path effect.
Michael Ludwig2686d692020-04-17 20:21:37 +00002342 TestCase ovalArc(GrStyledShape::MakeArc(kOval1, 150.f, -790.f, false, style), reporter);
2343 TestCase oval(GrStyledShape(SkRRect::MakeOval(kOval1)), reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002344 auto ovalExpectations = style.hasPathEffect() ? TestCase::kAllDifferent_ComparisonExpecation
2345 : TestCase::kAllSame_ComparisonExpecation;
2346 if (style.strokeRec().getWidth() >= 0 && style.strokeRec().getCap() != SkPaint::kButt_Cap) {
2347 ovalExpectations = TestCase::kAllDifferent_ComparisonExpecation;
2348 }
2349 ovalArc.compare(reporter, oval, ovalExpectations);
2350
2351 // If the the arc starts/ends at the center then it is then equivalent to the oval only for
2352 // simple fills.
Michael Ludwig2686d692020-04-17 20:21:37 +00002353 TestCase ovalArcWithCenter(GrStyledShape::MakeArc(kOval1, 304.f, 1225.f, true, style),
2354 reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002355 ovalExpectations = style.isSimpleFill() ? TestCase::kAllSame_ComparisonExpecation
2356 : TestCase::kAllDifferent_ComparisonExpecation;
2357 ovalArcWithCenter.compare(reporter, oval, ovalExpectations);
2358 }
2359}
Michael Ludwig80e3d422020-08-04 10:43:56 -04002360
2361DEF_TEST(GrShapeInversion, r) {
2362 SkPath path;
Michael Ludwig21efb7c2020-08-04 11:38:28 -04002363 SkScalar radii[] = {10.f, 10.f, 10.f, 10.f,
2364 10.f, 10.f, 10.f, 10.f};
Michael Ludwig80e3d422020-08-04 10:43:56 -04002365 path.addRoundRect(SkRect::MakeWH(50, 50), radii);
2366 path.toggleInverseFillType();
2367
2368 GrShape inverseRRect(path);
2369 GrShape rrect(inverseRRect);
2370 rrect.setInverted(false);
2371
2372 REPORTER_ASSERT(r, inverseRRect.inverted() && inverseRRect.isPath());
2373 REPORTER_ASSERT(r, !rrect.inverted() && rrect.isPath());
2374
2375 // Invertedness should be preserved after simplification
2376 inverseRRect.simplify();
2377 rrect.simplify();
2378
2379 REPORTER_ASSERT(r, inverseRRect.inverted() && inverseRRect.isRRect());
2380 REPORTER_ASSERT(r, !rrect.inverted() && rrect.isRRect());
2381
2382 // Invertedness should be reset when calling reset().
2383 inverseRRect.reset();
2384 REPORTER_ASSERT(r, !inverseRRect.inverted() && inverseRRect.isEmpty());
2385 inverseRRect.setPath(path);
2386 inverseRRect.reset();
2387 REPORTER_ASSERT(r, !inverseRRect.inverted() && inverseRRect.isEmpty());
2388}