blob: 42b7a2c9633ca14e8063002081b9b97eee637617 [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"
14#include "src/core/SkRectPriv.h"
Michael Ludwig2686d692020-04-17 20:21:37 +000015#include "src/gpu/geometry/GrStyledShape.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050016#include "tests/Test.h"
bsalomon47cc7692016-04-26 12:56:00 -070017
Hal Canary8a001442018-09-19 11:31:27 -040018#include <initializer_list>
19#include <functional>
John Stilesfbd050b2020-08-03 13:21:46 -040020#include <memory>
Ben Wagnerf08d1d02018-06-18 15:11:00 -040021#include <utility>
22
Michael Ludwig2686d692020-04-17 20:21:37 +000023uint32_t GrStyledShape::testingOnly_getOriginalGenerationID() const {
Brian Salomonda6d0722018-01-03 13:54:35 -050024 if (const auto* lp = this->originalPathForListeners()) {
25 return lp->getGenerationID();
26 }
27 return SkPath().getGenerationID();
Brian Osmanf6f7cf62017-09-25 16:49:55 -040028}
29
Michael Ludwig2686d692020-04-17 20:21:37 +000030bool GrStyledShape::testingOnly_isPath() const {
Michael Ludwigf38b7112020-04-30 13:47:00 -040031 return fShape.isPath();
Brian Osmanb379dcd2017-10-04 15:44:05 -040032}
33
Michael Ludwig2686d692020-04-17 20:21:37 +000034bool GrStyledShape::testingOnly_isNonVolatilePath() const {
Michael Ludwigf38b7112020-04-30 13:47:00 -040035 return fShape.isPath() && !fShape.path().isVolatile();
Brian Salomonda6d0722018-01-03 13:54:35 -050036}
37
bsalomon72dc51c2016-04-27 06:46:23 -070038using Key = SkTArray<uint32_t>;
39
Michael Ludwig2686d692020-04-17 20:21:37 +000040static bool make_key(Key* key, const GrStyledShape& shape) {
bsalomon72dc51c2016-04-27 06:46:23 -070041 int size = shape.unstyledKeySize();
42 if (size <= 0) {
43 key->reset(0);
44 return false;
45 }
46 SkASSERT(size);
47 key->reset(size);
48 shape.writeUnstyledKey(key->begin());
49 return true;
50}
51
bsalomonee295642016-06-06 14:01:25 -070052static bool paths_fill_same(const SkPath& a, const SkPath& b) {
53 SkPath pathXor;
54 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
55 return pathXor.isEmpty();
56}
57
bsalomon9fb42032016-05-13 09:23:38 -070058static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
bsalomon164fd9f2016-08-26 06:45:06 -070059 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
60 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
61 // rendering within the bounds (with a tolerance). Then we render the path and check that
62 // everything got clipped out.
bsalomon9fb42032016-05-13 09:23:38 -070063 static constexpr int kRes = 2000;
64 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
Brian Salomone4949402018-04-26 15:22:04 -040065 static constexpr int kTol = 2;
Brian Salomon4dea72a2019-12-18 10:43:10 -050066 static_assert(kRes % 4 == 0);
bsalomon9fb42032016-05-13 09:23:38 -070067 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
68 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
69 surface->getCanvas()->clear(0x0);
70 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
Mike Reed2ac6ce82021-01-15 12:26:22 -050071 SkMatrix matrix = SkMatrix::RectToRect(bounds, clip);
bsalomon9fb42032016-05-13 09:23:38 -070072 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
Mike Reedc1f77742016-12-09 09:00:50 -050073 surface->getCanvas()->clipRect(clip, kDifference_SkClipOp);
bsalomon9fb42032016-05-13 09:23:38 -070074 surface->getCanvas()->concat(matrix);
75 SkPaint whitePaint;
76 whitePaint.setColor(SK_ColorWHITE);
77 surface->getCanvas()->drawPath(path, whitePaint);
78 SkPixmap pixmap;
79 surface->getCanvas()->peekPixels(&pixmap);
80#if defined(SK_BUILD_FOR_WIN)
81 // The static constexpr version in #else causes cl.exe to crash.
82 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
83#else
84 static constexpr uint8_t kZeros[kRes] = {0};
85#endif
bsalomon164fd9f2016-08-26 06:45:06 -070086 for (int y = 0; y < kRes; ++y) {
bsalomon9fb42032016-05-13 09:23:38 -070087 const uint8_t* row = pixmap.addr8(0, y);
88 if (0 != memcmp(kZeros, row, kRes)) {
89 return false;
90 }
91 }
92#ifdef SK_BUILD_FOR_WIN
93 free(const_cast<uint8_t*>(kZeros));
94#endif
95 return true;
96}
bsalomon72dc51c2016-04-27 06:46:23 -070097
Michael Ludwig2686d692020-04-17 20:21:37 +000098static bool can_interchange_winding_and_even_odd_fill(const GrStyledShape& shape) {
Brian Salomon4f40caf2017-09-01 09:00:45 -040099 SkPath path;
100 shape.asPath(&path);
101 if (shape.style().hasNonDashPathEffect()) {
102 return false;
103 }
104 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
105 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
106 strokeRecStyle == SkStrokeRec::kHairline_Style ||
107 (shape.style().isSimpleFill() && path.isConvex());
108}
109
Michael Ludwig2686d692020-04-17 20:21:37 +0000110static void check_equivalence(skiatest::Reporter* r, const GrStyledShape& a, const GrStyledShape& b,
Brian Salomon4f40caf2017-09-01 09:00:45 -0400111 const Key& keyA, const Key& keyB) {
Michael Ludwig2686d692020-04-17 20:21:37 +0000112 // GrStyledShape only respects the input winding direction and start point for rrect shapes
113 // when there is a path effect. Thus, if there are two GrStyledShapes representing the same
114 // rrect but one has a path effect in its style and the other doesn't then asPath() and the
115 // unstyled key will differ. GrStyledShape will have canonicalized the direction and start point
116 // for the shape without the path effect. If *both* have path effects then they should have both
117 // preserved the direction and starting point.
Brian Salomon4f40caf2017-09-01 09:00:45 -0400118
119 // The asRRect() output params are all initialized just to silence compiler warnings about
120 // uninitialized variables.
121 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
Mike Reed30bc5272019-11-22 18:34:02 +0000122 SkPathDirection dirA = SkPathDirection::kCW, dirB = SkPathDirection::kCW;
Brian Salomon4f40caf2017-09-01 09:00:45 -0400123 unsigned startA = ~0U, startB = ~0U;
124 bool invertedA = true, invertedB = true;
125
126 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
127 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
128 bool aHasPE = a.style().hasPathEffect();
129 bool bHasPE = b.style().hasPathEffect();
130 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
Michael Ludwig2686d692020-04-17 20:21:37 +0000131 // GrStyledShape will close paths with simple fill style.
Brian Salomon4f40caf2017-09-01 09:00:45 -0400132 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
133 SkPath pathA, pathB;
134 a.asPath(&pathA);
135 b.asPath(&pathB);
136
137 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
138 // non-inverse fill type (or vice versa).
139 bool ignoreInversenessDifference = false;
140 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
Michael Ludwig2686d692020-04-17 20:21:37 +0000141 const GrStyledShape* s1 = pathA.isInverseFillType() ? &a : &b;
142 const GrStyledShape* s2 = pathA.isInverseFillType() ? &b : &a;
Brian Salomon4f40caf2017-09-01 09:00:45 -0400143 bool canDropInverse1 = s1->style().isDashed();
144 bool canDropInverse2 = s2->style().isDashed();
145 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
146 }
147 bool ignoreWindingVsEvenOdd = false;
Mike Reedcf0e3c62019-12-03 16:26:15 -0500148 if (SkPathFillType_ConvertToNonInverse(pathA.getFillType()) !=
149 SkPathFillType_ConvertToNonInverse(pathB.getFillType())) {
Brian Salomon4f40caf2017-09-01 09:00:45 -0400150 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
151 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
152 if (aCanChange != bCanChange) {
153 ignoreWindingVsEvenOdd = true;
154 }
155 }
156 if (allowSameRRectButDiffStartAndDir) {
157 REPORTER_ASSERT(r, rrectA == rrectB);
158 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
159 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
160 } else {
161 SkPath pA = pathA;
162 SkPath pB = pathB;
163 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
164 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
165 if (ignoreInversenessDifference) {
Mike Reedcf0e3c62019-12-03 16:26:15 -0500166 pA.setFillType(SkPathFillType_ConvertToNonInverse(pathA.getFillType()));
167 pB.setFillType(SkPathFillType_ConvertToNonInverse(pathB.getFillType()));
Brian Salomon4f40caf2017-09-01 09:00:45 -0400168 }
169 if (ignoreWindingVsEvenOdd) {
Mike Reed7d34dc72019-11-26 12:17:17 -0500170 pA.setFillType(pA.isInverseFillType() ? SkPathFillType::kInverseEvenOdd
171 : SkPathFillType::kEvenOdd);
172 pB.setFillType(pB.isInverseFillType() ? SkPathFillType::kInverseEvenOdd
173 : SkPathFillType::kEvenOdd);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400174 }
175 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
176 REPORTER_ASSERT(r, keyA == keyB);
177 } else {
178 REPORTER_ASSERT(r, keyA != keyB);
179 }
180 if (allowedClosednessDiff) {
Michael Ludwig2686d692020-04-17 20:21:37 +0000181 // GrStyledShape will close paths with simple fill style. Make the non-filled path
182 // closed so that the comparision will succeed. Make sure both are closed before
183 // comparing.
Brian Salomon4f40caf2017-09-01 09:00:45 -0400184 pA.close();
185 pB.close();
186 }
187 REPORTER_ASSERT(r, pA == pB);
188 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
189 if (aIsRRect) {
190 REPORTER_ASSERT(r, rrectA == rrectB);
191 REPORTER_ASSERT(r, dirA == dirB);
192 REPORTER_ASSERT(r, startA == startB);
193 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
194 }
195 }
196 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
197 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
198 // closedness can affect convexity.
199 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
200 if (a.knownToBeConvex()) {
201 REPORTER_ASSERT(r, pathA.isConvex());
202 }
203 if (b.knownToBeConvex()) {
204 REPORTER_ASSERT(r, pathB.isConvex());
205 }
206 REPORTER_ASSERT(r, a.bounds() == b.bounds());
207 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
208 // Init these to suppress warnings.
209 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
210 bool invertedLine[2] {true, true};
211 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
212 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
213 // doesn't (since the PE can set any fill type on its output path).
214 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
215 // then they may disagree about inverseness.
216 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
217 a.style().isDashed() == b.style().isDashed()) {
218 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
219 b.mayBeInverseFilledAfterStyling());
220 }
221 if (a.asLine(nullptr, nullptr)) {
222 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
223 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
224 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
225 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
226 }
227 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
228}
229
Michael Ludwig2686d692020-04-17 20:21:37 +0000230static void check_original_path_ids(skiatest::Reporter* r, const GrStyledShape& base,
231 const GrStyledShape& pe, const GrStyledShape& peStroke,
232 const GrStyledShape& full) {
Brian Salomonda6d0722018-01-03 13:54:35 -0500233 bool baseIsNonVolatilePath = base.testingOnly_isNonVolatilePath();
Brian Osmanb379dcd2017-10-04 15:44:05 -0400234 bool peIsPath = pe.testingOnly_isPath();
235 bool peStrokeIsPath = peStroke.testingOnly_isPath();
236 bool fullIsPath = full.testingOnly_isPath();
237
238 REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath);
239
240 uint32_t baseID = base.testingOnly_getOriginalGenerationID();
241 uint32_t peID = pe.testingOnly_getOriginalGenerationID();
242 uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID();
243 uint32_t fullID = full.testingOnly_getOriginalGenerationID();
244
245 // All empty paths have the same gen ID
246 uint32_t emptyID = SkPath().getGenerationID();
247
248 // 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 -0500249 // empty). If we started with a simple shape or a volatile path, our original path should have
250 // been reset.
251 REPORTER_ASSERT(r, baseIsNonVolatilePath == (baseID != emptyID));
Brian Osmanb379dcd2017-10-04 15:44:05 -0400252
253 // For the derived shapes, if they're simple types, their original paths should have been reset
254 REPORTER_ASSERT(r, peIsPath || (peID == emptyID));
255 REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID));
256 REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID));
257
258 if (!peIsPath) {
259 // If the path effect produces a simple shape, then there are no unbroken chains to test
260 return;
261 }
262
263 // From here on, we know that the path effect produced a shape that was a "real" path
264
Brian Salomonda6d0722018-01-03 13:54:35 -0500265 if (baseIsNonVolatilePath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400266 REPORTER_ASSERT(r, baseID == peID);
267 }
268
269 if (peStrokeIsPath) {
270 REPORTER_ASSERT(r, peID == peStrokeID);
271 REPORTER_ASSERT(r, peStrokeID == fullID);
272 }
273
Brian Salomonda6d0722018-01-03 13:54:35 -0500274 if (baseIsNonVolatilePath && peStrokeIsPath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400275 REPORTER_ASSERT(r, baseID == peStrokeID);
276 REPORTER_ASSERT(r, baseID == fullID);
277 }
278}
279
Michael Ludwig2686d692020-04-17 20:21:37 +0000280void test_inversions(skiatest::Reporter* r, const GrStyledShape& shape, const Key& shapeKey) {
281 GrStyledShape preserve = GrStyledShape::MakeFilled(
282 shape, GrStyledShape::FillInversion::kPreserve);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400283 Key preserveKey;
284 make_key(&preserveKey, preserve);
285
Michael Ludwig2686d692020-04-17 20:21:37 +0000286 GrStyledShape flip = GrStyledShape::MakeFilled(shape, GrStyledShape::FillInversion::kFlip);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400287 Key flipKey;
288 make_key(&flipKey, flip);
289
Michael Ludwig2686d692020-04-17 20:21:37 +0000290 GrStyledShape inverted = GrStyledShape::MakeFilled(
291 shape, GrStyledShape::FillInversion::kForceInverted);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400292 Key invertedKey;
293 make_key(&invertedKey, inverted);
294
Michael Ludwig2686d692020-04-17 20:21:37 +0000295 GrStyledShape noninverted = GrStyledShape::MakeFilled(
296 shape, GrStyledShape::FillInversion::kForceNoninverted);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400297 Key noninvertedKey;
298 make_key(&noninvertedKey, noninverted);
299
300 if (invertedKey.count() || noninvertedKey.count()) {
301 REPORTER_ASSERT(r, invertedKey != noninvertedKey);
302 }
303 if (shape.style().isSimpleFill()) {
304 check_equivalence(r, shape, preserve, shapeKey, preserveKey);
305 }
306 if (shape.inverseFilled()) {
307 check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
308 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
309 } else {
310 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
311 check_equivalence(r, flip, inverted, flipKey, invertedKey);
312 }
313
Michael Ludwig2686d692020-04-17 20:21:37 +0000314 GrStyledShape doubleFlip = GrStyledShape::MakeFilled(flip, GrStyledShape::FillInversion::kFlip);
Brian Salomon4f40caf2017-09-01 09:00:45 -0400315 Key doubleFlipKey;
316 make_key(&doubleFlipKey, doubleFlip);
317 // It can be the case that the double flip has no key but preserve does. This happens when the
318 // original shape has an inherited style key. That gets dropped on the first inversion flip.
319 if (preserveKey.count() && !doubleFlipKey.count()) {
320 preserveKey.reset();
321 }
322 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
323}
324
bsalomon9fb42032016-05-13 09:23:38 -0700325namespace {
bsalomona395f7c2016-08-24 17:47:40 -0700326/**
Michael Ludwig2686d692020-04-17 20:21:37 +0000327 * Geo is a factory for creating a GrStyledShape from another representation. It also answers some
328 * questions about expected behavior for GrStyledShape given the inputs.
bsalomona395f7c2016-08-24 17:47:40 -0700329 */
330class Geo {
331public:
Mike Kleinfc6c37b2016-09-27 09:34:10 -0400332 virtual ~Geo() {}
Michael Ludwig2686d692020-04-17 20:21:37 +0000333 virtual GrStyledShape makeShape(const SkPaint&) const = 0;
bsalomona395f7c2016-08-24 17:47:40 -0700334 virtual SkPath path() const = 0;
335 // These functions allow tests to check for special cases where style gets
Michael Ludwig2686d692020-04-17 20:21:37 +0000336 // applied by GrStyledShape in its constructor (without calling GrStyledShape::applyStyle).
337 // These unfortunately rely on knowing details of GrStyledShape's implementation.
bsalomona395f7c2016-08-24 17:47:40 -0700338 // These predicates are factored out here to avoid littering the rest of the
Michael Ludwig2686d692020-04-17 20:21:37 +0000339 // test code with GrStyledShape implementation details.
bsalomona395f7c2016-08-24 17:47:40 -0700340 virtual bool fillChangesGeom() const { return false; }
341 virtual bool strokeIsConvertedToFill() const { return false; }
342 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
Michael Ludwig2686d692020-04-17 20:21:37 +0000343 // Is this something we expect GrStyledShape to recognize as something simpler than a path.
bsalomona395f7c2016-08-24 17:47:40 -0700344 virtual bool isNonPath(const SkPaint& paint) const { return true; }
345};
346
347class RectGeo : public Geo {
348public:
349 RectGeo(const SkRect& rect) : fRect(rect) {}
350
351 SkPath path() const override {
352 SkPath path;
353 path.addRect(fRect);
354 return path;
355 }
356
Michael Ludwig2686d692020-04-17 20:21:37 +0000357 GrStyledShape makeShape(const SkPaint& paint) const override {
358 return GrStyledShape(fRect, paint);
bsalomona395f7c2016-08-24 17:47:40 -0700359 }
360
361 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
362 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
Michael Ludwigf38b7112020-04-30 13:47:00 -0400363 // Converted to an outset rectangle or round rect
364 return (paint.getStrokeJoin() == SkPaint::kMiter_Join &&
365 paint.getStrokeMiter() >= SK_ScalarSqrt2) ||
366 paint.getStrokeJoin() == SkPaint::kRound_Join;
bsalomona395f7c2016-08-24 17:47:40 -0700367 }
368
369private:
370 SkRect fRect;
371};
372
373class RRectGeo : public Geo {
374public:
375 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
376
Michael Ludwig2686d692020-04-17 20:21:37 +0000377 GrStyledShape makeShape(const SkPaint& paint) const override {
378 return GrStyledShape(fRRect, paint);
bsalomona395f7c2016-08-24 17:47:40 -0700379 }
380
381 SkPath path() const override {
382 SkPath path;
383 path.addRRect(fRRect);
384 return path;
385 }
386
387 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
388 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
389 if (fRRect.isRect()) {
390 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
391 }
392 return false;
393 }
394
395private:
396 SkRRect fRRect;
397};
398
Brian Salomone4949402018-04-26 15:22:04 -0400399class ArcGeo : public Geo {
400public:
401 ArcGeo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter)
402 : fOval(oval)
403 , fStartAngle(startAngle)
404 , fSweepAngle(sweepAngle)
405 , fUseCenter(useCenter) {}
406
407 SkPath path() const override {
408 SkPath path;
409 SkPathPriv::CreateDrawArcPath(&path, fOval, fStartAngle, fSweepAngle, fUseCenter, false);
410 return path;
411 }
412
Michael Ludwig2686d692020-04-17 20:21:37 +0000413 GrStyledShape makeShape(const SkPaint& paint) const override {
414 return GrStyledShape::MakeArc(fOval, fStartAngle, fSweepAngle, fUseCenter, GrStyle(paint));
Brian Salomone4949402018-04-26 15:22:04 -0400415 }
416
Michael Ludwig2686d692020-04-17 20:21:37 +0000417 // GrStyledShape specializes when created from arc params but it doesn't recognize arcs from
418 // SkPath.
Brian Salomone4949402018-04-26 15:22:04 -0400419 bool isNonPath(const SkPaint& paint) const override { return false; }
420
421private:
422 SkRect fOval;
423 SkScalar fStartAngle;
424 SkScalar fSweepAngle;
425 bool fUseCenter;
426};
427
bsalomona395f7c2016-08-24 17:47:40 -0700428class PathGeo : public Geo {
429public:
430 enum class Invert { kNo, kYes };
431
432 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
433 SkASSERT(!path.isInverseFillType());
434 if (Invert::kYes == invert) {
Mike Reedcf0e3c62019-12-03 16:26:15 -0500435 if (fPath.getFillType() == SkPathFillType::kEvenOdd) {
Mike Reed7d34dc72019-11-26 12:17:17 -0500436 fPath.setFillType(SkPathFillType::kInverseEvenOdd);
bsalomona395f7c2016-08-24 17:47:40 -0700437 } else {
Mike Reedcf0e3c62019-12-03 16:26:15 -0500438 SkASSERT(fPath.getFillType() == SkPathFillType::kWinding);
Mike Reed7d34dc72019-11-26 12:17:17 -0500439 fPath.setFillType(SkPathFillType::kInverseWinding);
bsalomona395f7c2016-08-24 17:47:40 -0700440 }
441 }
442 }
443
Michael Ludwig2686d692020-04-17 20:21:37 +0000444 GrStyledShape makeShape(const SkPaint& paint) const override {
445 return GrStyledShape(fPath, paint);
bsalomona395f7c2016-08-24 17:47:40 -0700446 }
447
448 SkPath path() const override { return fPath; }
449
450 bool fillChangesGeom() const override {
451 // unclosed rects get closed. Lines get turned into empty geometry
Brian Salomon085c0862017-08-31 15:44:51 -0400452 return this->isUnclosedRect() || fPath.isLine(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -0700453 }
454
455 bool strokeIsConvertedToFill() const override {
456 return this->isAxisAlignedLine();
457 }
458
459 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
460 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
461 if (this->isAxisAlignedLine()) {
462 // The fill is ignored (zero area) and the stroke is converted to a rrect.
463 return true;
464 }
465 SkRect rect;
466 unsigned start;
Mike Reed30bc5272019-11-22 18:34:02 +0000467 SkPathDirection dir;
Chris Dalton34c50522020-09-17 15:45:20 -0600468 if (SkPathPriv::IsSimpleRect(fPath, false, &rect, &dir, &start)) {
bsalomona395f7c2016-08-24 17:47:40 -0700469 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
470 }
471 return false;
472 }
473
474 bool isNonPath(const SkPaint& paint) const override {
475 return fPath.isLine(nullptr) || fPath.isEmpty();
476 }
477
478private:
479 bool isAxisAlignedLine() const {
480 SkPoint pts[2];
481 if (!fPath.isLine(pts)) {
482 return false;
483 }
484 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
485 }
486
487 bool isUnclosedRect() const {
488 bool closed;
489 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
490 }
491
492 SkPath fPath;
493};
494
495class RRectPathGeo : public PathGeo {
496public:
497 enum class RRectForStroke { kNo, kYes };
498
499 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
500 Invert invert)
501 : PathGeo(path, invert)
502 , fRRect(equivalentRRect)
503 , fRRectForStroke(rrectForStroke) {}
504
505 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
506 Invert invert)
507 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
508
509 bool isNonPath(const SkPaint& paint) const override {
510 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
511 return true;
512 }
513 return false;
514 }
515
516 const SkRRect& rrect() const { return fRRect; }
517
518private:
519 SkRRect fRRect;
520 RRectForStroke fRRectForStroke;
521};
522
bsalomon47cc7692016-04-26 12:56:00 -0700523class TestCase {
524public:
bsalomona395f7c2016-08-24 17:47:40 -0700525 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
Brian Salomonc8cdad72018-04-16 09:46:09 -0400526 SkScalar scale = SK_Scalar1)
Michael Ludwig2686d692020-04-17 20:21:37 +0000527 : fBase(new GrStyledShape(geo.makeShape(paint))) {
bsalomon97fd2d42016-05-09 13:02:01 -0700528 this->init(r, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700529 }
530
Brian Salomonc8cdad72018-04-16 09:46:09 -0400531 template <typename... ShapeArgs>
Michael Ludwig2686d692020-04-17 20:21:37 +0000532 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs)
533 : fBase(new GrStyledShape(shapeArgs...)) {
bsalomona395f7c2016-08-24 17:47:40 -0700534 this->init(r, SK_Scalar1);
535 }
536
Michael Ludwig2686d692020-04-17 20:21:37 +0000537 TestCase(const GrStyledShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
538 : fBase(new GrStyledShape(shape)) {
bsalomon70493962016-06-10 08:05:14 -0700539 this->init(r, scale);
540 }
541
bsalomon47cc7692016-04-26 12:56:00 -0700542 struct SelfExpectations {
543 bool fPEHasEffect;
544 bool fPEHasValidKey;
545 bool fStrokeApplies;
546 };
547
548 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
549
550 enum ComparisonExpecation {
551 kAllDifferent_ComparisonExpecation,
552 kSameUpToPE_ComparisonExpecation,
553 kSameUpToStroke_ComparisonExpecation,
554 kAllSame_ComparisonExpecation,
555 };
556
557 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
558
Michael Ludwig2686d692020-04-17 20:21:37 +0000559 const GrStyledShape& baseShape() const { return *fBase; }
560 const GrStyledShape& appliedPathEffectShape() const { return *fAppliedPE; }
561 const GrStyledShape& appliedFullStyleShape() const { return *fAppliedFull; }
bsalomon72dc51c2016-04-27 06:46:23 -0700562
563 // The returned array's count will be 0 if the key shape has no key.
564 const Key& baseKey() const { return fBaseKey; }
565 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
566 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700567 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700568
bsalomon47cc7692016-04-26 12:56:00 -0700569private:
Michael Ludwig2686d692020-04-17 20:21:37 +0000570 static void CheckBounds(skiatest::Reporter* r, const GrStyledShape& shape,
571 const SkRect& bounds) {
bsalomon9fb42032016-05-13 09:23:38 -0700572 SkPath path;
573 shape.asPath(&path);
574 // If the bounds are empty, the path ought to be as well.
bsalomon0ae36a22016-07-18 07:31:13 -0700575 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
bsalomon9fb42032016-05-13 09:23:38 -0700576 REPORTER_ASSERT(r, path.isEmpty());
577 return;
578 }
579 if (path.isEmpty()) {
580 return;
581 }
bsalomon70493962016-06-10 08:05:14 -0700582 // The bounds API explicitly calls out that it does not consider inverseness.
583 SkPath p = path;
Mike Reedcf0e3c62019-12-03 16:26:15 -0500584 p.setFillType(SkPathFillType_ConvertToNonInverse(path.getFillType()));
bsalomon70493962016-06-10 08:05:14 -0700585 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
bsalomon9fb42032016-05-13 09:23:38 -0700586 }
587
bsalomon97fd2d42016-05-09 13:02:01 -0700588 void init(skiatest::Reporter* r, SkScalar scale) {
John Stilesfbd050b2020-08-03 13:21:46 -0400589 fAppliedPE = std::make_unique<GrStyledShape>();
590 fAppliedPEThenStroke = std::make_unique<GrStyledShape>();
591 fAppliedFull = std::make_unique<GrStyledShape>();
bsalomon47cc7692016-04-26 12:56:00 -0700592
Brian Salomonc8cdad72018-04-16 09:46:09 -0400593 *fAppliedPE = fBase->applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
594 *fAppliedPEThenStroke =
595 fAppliedPE->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
596 *fAppliedFull = fBase->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
597
598 make_key(&fBaseKey, *fBase);
599 make_key(&fAppliedPEKey, *fAppliedPE);
600 make_key(&fAppliedPEThenStrokeKey, *fAppliedPEThenStroke);
601 make_key(&fAppliedFullKey, *fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700602
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400603 // All shapes should report the same "original" path, so that path renderers can get to it
604 // if necessary.
Brian Salomonc8cdad72018-04-16 09:46:09 -0400605 check_original_path_ids(r, *fBase, *fAppliedPE, *fAppliedPEThenStroke, *fAppliedFull);
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400606
bsalomonfb083272016-05-04 08:27:41 -0700607 // Applying the path effect and then the stroke should always be the same as applying
608 // both in one go.
609 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
610 SkPath a, b;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400611 fAppliedPEThenStroke->asPath(&a);
612 fAppliedFull->asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700613 // If the output of the path effect is a rrect then it is possible for a and b to be
614 // different paths that fill identically. The reason is that fAppliedFull will do this:
615 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
616 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
617 // now that there is no longer a path effect, the direction and starting index get
618 // canonicalized before the stroke.
Brian Salomonc8cdad72018-04-16 09:46:09 -0400619 if (fAppliedPE->asRRect(nullptr, nullptr, nullptr, nullptr)) {
bsalomonee295642016-06-06 14:01:25 -0700620 REPORTER_ASSERT(r, paths_fill_same(a, b));
621 } else {
622 REPORTER_ASSERT(r, a == b);
623 }
Brian Salomonc8cdad72018-04-16 09:46:09 -0400624 REPORTER_ASSERT(r, fAppliedFull->isEmpty() == fAppliedPEThenStroke->isEmpty());
bsalomon7c73a532016-05-11 15:15:56 -0700625
626 SkPath path;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400627 fBase->asPath(&path);
628 REPORTER_ASSERT(r, path.isEmpty() == fBase->isEmpty());
629 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase->segmentMask());
630 fAppliedPE->asPath(&path);
631 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE->isEmpty());
632 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE->segmentMask());
633 fAppliedFull->asPath(&path);
634 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull->isEmpty());
635 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull->segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700636
Brian Salomonc8cdad72018-04-16 09:46:09 -0400637 CheckBounds(r, *fBase, fBase->bounds());
638 CheckBounds(r, *fAppliedPE, fAppliedPE->bounds());
639 CheckBounds(r, *fAppliedPEThenStroke, fAppliedPEThenStroke->bounds());
640 CheckBounds(r, *fAppliedFull, fAppliedFull->bounds());
641 SkRect styledBounds = fBase->styledBounds();
642 CheckBounds(r, *fAppliedFull, styledBounds);
643 styledBounds = fAppliedPE->styledBounds();
644 CheckBounds(r, *fAppliedFull, styledBounds);
bsalomon9fb42032016-05-13 09:23:38 -0700645
Michael Ludwig2686d692020-04-17 20:21:37 +0000646 // Check that the same path is produced when style is applied by GrStyledShape and GrStyle.
bsalomonfb083272016-05-04 08:27:41 -0700647 SkPath preStyle;
648 SkPath postPathEffect;
649 SkPath postAllStyle;
650
Brian Salomonc8cdad72018-04-16 09:46:09 -0400651 fBase->asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700652 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
Brian Salomonc8cdad72018-04-16 09:46:09 -0400653 if (fBase->style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
654 scale)) {
Michael Ludwig2686d692020-04-17 20:21:37 +0000655 // run postPathEffect through GrStyledShape to get any geometry reductions that would
656 // have occurred to fAppliedPE.
657 GrStyledShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr))
658 .asPath(&postPathEffect);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700659
bsalomonfb083272016-05-04 08:27:41 -0700660 SkPath testPath;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400661 fAppliedPE->asPath(&testPath);
bsalomonfb083272016-05-04 08:27:41 -0700662 REPORTER_ASSERT(r, testPath == postPathEffect);
Brian Salomonc8cdad72018-04-16 09:46:09 -0400663 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE->style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700664 }
665 SkStrokeRec::InitStyle fillOrHairline;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400666 if (fBase->style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomonfb083272016-05-04 08:27:41 -0700667 SkPath testPath;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400668 fAppliedFull->asPath(&testPath);
669 if (fBase->style().hasPathEffect()) {
Michael Ludwig2686d692020-04-17 20:21:37 +0000670 // Because GrStyledShape always does two-stage application when there is a path
671 // effect there may be a reduction/canonicalization step between the path effect and
bsalomon1b28c1a2016-06-20 12:28:17 -0700672 // strokerec not reflected in postAllStyle since it applied both the path effect
673 // and strokerec without analyzing the intermediate path.
674 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
675 } else {
Michael Ludwig2686d692020-04-17 20:21:37 +0000676 // Make sure that postAllStyle sees any reductions/canonicalizations that
677 // GrStyledShape would apply.
678 GrStyledShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
bsalomon1b28c1a2016-06-20 12:28:17 -0700679 REPORTER_ASSERT(r, testPath == postAllStyle);
680 }
681
bsalomonfb083272016-05-04 08:27:41 -0700682 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400683 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleFill());
bsalomonfb083272016-05-04 08:27:41 -0700684 } else {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400685 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleHairline());
bsalomonfb083272016-05-04 08:27:41 -0700686 }
687 }
Brian Salomonc8cdad72018-04-16 09:46:09 -0400688 test_inversions(r, *fBase, fBaseKey);
689 test_inversions(r, *fAppliedPE, fAppliedPEKey);
690 test_inversions(r, *fAppliedFull, fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700691 }
692
Michael Ludwig2686d692020-04-17 20:21:37 +0000693 std::unique_ptr<GrStyledShape> fBase;
694 std::unique_ptr<GrStyledShape> fAppliedPE;
695 std::unique_ptr<GrStyledShape> fAppliedPEThenStroke;
696 std::unique_ptr<GrStyledShape> fAppliedFull;
bsalomon47cc7692016-04-26 12:56:00 -0700697
698 Key fBaseKey;
699 Key fAppliedPEKey;
700 Key fAppliedPEThenStrokeKey;
701 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700702};
703
704void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700705 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700706 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700707 if (expectations.fPEHasEffect) {
708 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700709 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700710 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700711 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700712 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
713 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700714 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700715 }
716 } else {
717 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700718 SkPath a, b;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400719 fBase->asPath(&a);
720 fAppliedPE->asPath(&b);
bsalomon72dc51c2016-04-27 06:46:23 -0700721 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700722 if (expectations.fStrokeApplies) {
723 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
724 } else {
725 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
726 }
727 }
728}
729
bsalomonee295642016-06-06 14:01:25 -0700730void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700731 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700732 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700733 switch (expectation) {
734 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700735 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
736 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
737 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700738 break;
739 case kSameUpToPE_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400740 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
bsalomonee295642016-06-06 14:01:25 -0700741 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
742 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700743 break;
744 case kSameUpToStroke_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400745 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
746 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
bsalomonee295642016-06-06 14:01:25 -0700747 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700748 break;
749 case kAllSame_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400750 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
751 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
752 check_equivalence(r, *fAppliedFull, *that.fAppliedFull, fAppliedFullKey,
bsalomonee295642016-06-06 14:01:25 -0700753 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700754 break;
755 }
756}
757} // namespace
758
759static sk_sp<SkPathEffect> make_dash() {
760 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
761 static const SkScalar kPhase = 0.75;
762 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
763}
764
765static sk_sp<SkPathEffect> make_null_dash() {
766 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
767 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
768}
769
Mike Klein43344282017-08-16 11:56:22 -0400770// We make enough TestCases, and they're large enough, that on Google3 builds we exceed
771// the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
772template <typename... Args>
773static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
John Stilesfbd050b2020-08-03 13:21:46 -0400774 return std::make_unique<TestCase>( std::forward<Args>(args)... );
Mike Klein43344282017-08-16 11:56:22 -0400775}
776
bsalomona395f7c2016-08-24 17:47:40 -0700777static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700778 sk_sp<SkPathEffect> dashPE = make_dash();
779
780 TestCase::SelfExpectations expectations;
781 SkPaint fill;
782
bsalomonfb083272016-05-04 08:27:41 -0700783 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700784 expectations.fPEHasEffect = false;
785 expectations.fPEHasValidKey = false;
786 expectations.fStrokeApplies = false;
787 fillCase.testExpectations(reporter, expectations);
Michael Ludwig2686d692020-04-17 20:21:37 +0000788 // Test that another GrStyledShape instance built from the same primitive is the same.
Mike Klein43344282017-08-16 11:56:22 -0400789 make_TestCase(geo, fill, reporter)
790 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700791
792 SkPaint stroke2RoundBevel;
793 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
794 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
795 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
796 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700797 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700798 expectations.fPEHasValidKey = true;
799 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700800 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomon47cc7692016-04-26 12:56:00 -0700801 stroke2RoundBevelCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400802 make_TestCase(geo, stroke2RoundBevel, reporter)
803 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700804
805 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
806 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700807 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700808 expectations.fPEHasValidKey = true;
809 expectations.fPEHasEffect = true;
810 expectations.fStrokeApplies = true;
811 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400812 make_TestCase(geo, stroke2RoundBevelDash, reporter)
813 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700814
bsalomona395f7c2016-08-24 17:47:40 -0700815 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700816 fillCase.compare(reporter, stroke2RoundBevelCase,
817 TestCase::kAllDifferent_ComparisonExpecation);
818 fillCase.compare(reporter, stroke2RoundBevelDashCase,
819 TestCase::kAllDifferent_ComparisonExpecation);
820 } else {
821 fillCase.compare(reporter, stroke2RoundBevelCase,
822 TestCase::kSameUpToStroke_ComparisonExpecation);
823 fillCase.compare(reporter, stroke2RoundBevelDashCase,
824 TestCase::kSameUpToPE_ComparisonExpecation);
825 }
bsalomona395f7c2016-08-24 17:47:40 -0700826 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700827 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
828 TestCase::kAllDifferent_ComparisonExpecation);
829 } else {
830 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
831 TestCase::kSameUpToPE_ComparisonExpecation);
832 }
bsalomon72dc51c2016-04-27 06:46:23 -0700833
bsalomonf0cf3552016-05-05 08:28:30 -0700834 // Stroke and fill cases
835 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
836 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
837 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
838 expectations.fPEHasValidKey = true;
839 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700840 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700841 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400842 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
843 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700844
845 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
846 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
847 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
848 expectations.fPEHasValidKey = true;
bsalomona0587862016-06-09 06:03:38 -0700849 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700850 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700851 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400852 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
bsalomonf0cf3552016-05-05 08:28:30 -0700853 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomona0587862016-06-09 06:03:38 -0700854 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
855 TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700856
bsalomon72dc51c2016-04-27 06:46:23 -0700857 SkPaint hairline;
858 hairline.setStyle(SkPaint::kStroke_Style);
859 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700860 TestCase hairlineCase(geo, hairline, reporter);
bsalomon487f8d32016-07-20 07:15:44 -0700861 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
862 // in the line and unclosed rect cases).
bsalomona395f7c2016-08-24 17:47:40 -0700863 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -0700864 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
865 } else {
866 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
867 }
bsalomon9ad5d7c2016-05-04 08:44:15 -0700868 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
869 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
870 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700871
bsalomon0ae36a22016-07-18 07:31:13 -0700872}
873
bsalomona395f7c2016-08-24 17:47:40 -0700874static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon97fd2d42016-05-09 13:02:01 -0700875 sk_sp<SkPathEffect> dashPE = make_dash();
876
877 static const SkScalar kS1 = 1.f;
878 static const SkScalar kS2 = 2.f;
879
880 SkPaint fill;
881 TestCase fillCase1(geo, fill, reporter, kS1);
882 TestCase fillCase2(geo, fill, reporter, kS2);
883 // Scale doesn't affect fills.
884 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
885
886 SkPaint hairline;
887 hairline.setStyle(SkPaint::kStroke_Style);
888 hairline.setStrokeWidth(0.f);
889 TestCase hairlineCase1(geo, hairline, reporter, kS1);
890 TestCase hairlineCase2(geo, hairline, reporter, kS2);
891 // Scale doesn't affect hairlines.
892 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
893
894 SkPaint stroke;
895 stroke.setStyle(SkPaint::kStroke_Style);
896 stroke.setStrokeWidth(2.f);
897 TestCase strokeCase1(geo, stroke, reporter, kS1);
898 TestCase strokeCase2(geo, stroke, reporter, kS2);
bsalomon0ae36a22016-07-18 07:31:13 -0700899 // Scale affects the stroke
bsalomona395f7c2016-08-24 17:47:40 -0700900 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700901 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
bsalomon0ae36a22016-07-18 07:31:13 -0700902 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
903 } else {
904 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
905 }
bsalomon97fd2d42016-05-09 13:02:01 -0700906
907 SkPaint strokeDash = stroke;
908 strokeDash.setPathEffect(make_dash());
909 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
910 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
911 // Scale affects the dash and the stroke.
bsalomon487f8d32016-07-20 07:15:44 -0700912 strokeDashCase1.compare(reporter, strokeDashCase2,
913 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700914
915 // Stroke and fill cases
916 SkPaint strokeAndFill = stroke;
917 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
918 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
919 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
bsalomona0587862016-06-09 06:03:38 -0700920 SkPaint strokeAndFillDash = strokeDash;
921 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
922 // Dash is ignored for stroke and fill
923 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
924 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
bsalomon487f8d32016-07-20 07:15:44 -0700925 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
926 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
927 // geometries should agree.
bsalomona395f7c2016-08-24 17:47:40 -0700928 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
bsalomon487f8d32016-07-20 07:15:44 -0700929 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
bsalomon97fd2d42016-05-09 13:02:01 -0700930 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
931 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700932 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
933 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700934 } else {
935 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
936 TestCase::kSameUpToStroke_ComparisonExpecation);
937 }
bsalomona0587862016-06-09 06:03:38 -0700938 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
939 TestCase::kAllSame_ComparisonExpecation);
940 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
941 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700942}
943
bsalomona395f7c2016-08-24 17:47:40 -0700944template <typename T>
945static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700946 std::function<void(SkPaint*, T)> setter, T a, T b,
947 bool paramAffectsStroke,
948 bool paramAffectsDashAndStroke) {
949 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
950 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700951 SkPaint strokeA;
952 strokeA.setStyle(SkPaint::kStroke_Style);
953 strokeA.setStrokeWidth(2.f);
954 setter(&strokeA, a);
955 SkPaint strokeB;
956 strokeB.setStyle(SkPaint::kStroke_Style);
957 strokeB.setStrokeWidth(2.f);
958 setter(&strokeB, b);
959
bsalomonfb083272016-05-04 08:27:41 -0700960 TestCase strokeACase(geo, strokeA, reporter);
961 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700962 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700963 // If stroking is immediately incorporated into a geometric transformation then the base
964 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700965 if (geo.strokeIsConvertedToFill()) {
bsalomon0ae36a22016-07-18 07:31:13 -0700966 strokeACase.compare(reporter, strokeBCase,
967 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700968 } else {
969 strokeACase.compare(reporter, strokeBCase,
970 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700971 }
bsalomon06077562016-05-04 13:50:29 -0700972 } else {
973 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
974 }
bsalomon47cc7692016-04-26 12:56:00 -0700975
bsalomonf0cf3552016-05-05 08:28:30 -0700976 SkPaint strokeAndFillA = strokeA;
977 SkPaint strokeAndFillB = strokeB;
978 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
979 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
980 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
981 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
982 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700983 // If stroking is immediately incorporated into a geometric transformation then the base
984 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700985 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
986 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700987 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700988 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700989 } else {
990 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700991 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700992 }
bsalomonf0cf3552016-05-05 08:28:30 -0700993 } else {
994 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
995 TestCase::kAllSame_ComparisonExpecation);
996 }
997
bsalomon47cc7692016-04-26 12:56:00 -0700998 // Make sure stroking params don't affect fill style.
999 SkPaint fillA = strokeA, fillB = strokeB;
1000 fillA.setStyle(SkPaint::kFill_Style);
1001 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -07001002 TestCase fillACase(geo, fillA, reporter);
1003 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001004 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
1005
1006 // Make sure just applying the dash but not stroke gives the same key for both stroking
1007 // variations.
1008 SkPaint dashA = strokeA, dashB = strokeB;
1009 dashA.setPathEffect(make_dash());
1010 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -07001011 TestCase dashACase(geo, dashA, reporter);
1012 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -07001013 if (paramAffectsDashAndStroke) {
bsalomon487f8d32016-07-20 07:15:44 -07001014 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon06077562016-05-04 13:50:29 -07001015 } else {
1016 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
1017 }
bsalomon47cc7692016-04-26 12:56:00 -07001018}
1019
bsalomona395f7c2016-08-24 17:47:40 -07001020template <typename T>
1021static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -07001022 std::function<void(SkPaint*, T)> setter, T a, T b) {
1023 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
1024};
1025
bsalomona395f7c2016-08-24 17:47:40 -07001026static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
1027 SkPaint hairline;
1028 hairline.setStrokeWidth(0);
1029 hairline.setStyle(SkPaint::kStroke_Style);
Michael Ludwig2686d692020-04-17 20:21:37 +00001030 GrStyledShape shape = geo.makeShape(hairline);
bsalomon06077562016-05-04 13:50:29 -07001031 // The cap should only affect shapes that may be open.
1032 bool affectsStroke = !shape.knownToBeClosed();
1033 // Dashing adds ends that need caps.
1034 bool affectsDashAndStroke = true;
bsalomona395f7c2016-08-24 17:47:40 -07001035 test_stroke_param_impl<SkPaint::Cap>(
bsalomon06077562016-05-04 13:50:29 -07001036 reporter,
1037 geo,
1038 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
1039 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
1040 affectsStroke,
1041 affectsDashAndStroke);
1042};
1043
Michael Ludwig2686d692020-04-17 20:21:37 +00001044static bool shape_known_not_to_have_joins(const GrStyledShape& shape) {
bsalomon0ae36a22016-07-18 07:31:13 -07001045 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
1046}
1047
bsalomona395f7c2016-08-24 17:47:40 -07001048static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
1049 SkPaint hairline;
1050 hairline.setStrokeWidth(0);
1051 hairline.setStyle(SkPaint::kStroke_Style);
Michael Ludwig2686d692020-04-17 20:21:37 +00001052 GrStyledShape shape = geo.makeShape(hairline);
1053 // GrStyledShape recognizes certain types don't have joins and will prevent the join type from
bsalomon0ae36a22016-07-18 07:31:13 -07001054 // affecting the style key.
Michael Ludwig2686d692020-04-17 20:21:37 +00001055 // Dashing doesn't add additional joins. However, GrStyledShape currently loses track of this
bsalomon0ae36a22016-07-18 07:31:13 -07001056 // after applying the dash.
1057 bool affectsStroke = !shape_known_not_to_have_joins(shape);
bsalomona395f7c2016-08-24 17:47:40 -07001058 test_stroke_param_impl<SkPaint::Join>(
bsalomon0ae36a22016-07-18 07:31:13 -07001059 reporter,
1060 geo,
1061 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
1062 SkPaint::kRound_Join, SkPaint::kBevel_Join,
1063 affectsStroke, true);
1064};
1065
bsalomona395f7c2016-08-24 17:47:40 -07001066static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon06077562016-05-04 13:50:29 -07001067 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1068 p->setStrokeJoin(SkPaint::kMiter_Join);
1069 p->setStrokeMiter(miter);
1070 };
bsalomon47cc7692016-04-26 12:56:00 -07001071
bsalomon06077562016-05-04 13:50:29 -07001072 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1073 p->setStrokeJoin(SkPaint::kRound_Join);
1074 p->setStrokeMiter(miter);
1075 };
bsalomon47cc7692016-04-26 12:56:00 -07001076
bsalomona395f7c2016-08-24 17:47:40 -07001077 SkPaint hairline;
1078 hairline.setStrokeWidth(0);
1079 hairline.setStyle(SkPaint::kStroke_Style);
Michael Ludwig2686d692020-04-17 20:21:37 +00001080 GrStyledShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001081 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
1082
bsalomon06077562016-05-04 13:50:29 -07001083 // The miter limit should affect stroked and dashed-stroked cases when the join type is
1084 // miter.
bsalomona395f7c2016-08-24 17:47:40 -07001085 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001086 reporter,
1087 geo,
1088 setMiterJoinAndLimit,
1089 0.5f, 0.75f,
bsalomon0ae36a22016-07-18 07:31:13 -07001090 mayHaveJoins,
bsalomon06077562016-05-04 13:50:29 -07001091 true);
bsalomon47cc7692016-04-26 12:56:00 -07001092
bsalomon06077562016-05-04 13:50:29 -07001093 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
1094 // not miter.
bsalomona395f7c2016-08-24 17:47:40 -07001095 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001096 reporter,
1097 geo,
1098 setOtherJoinAndLimit,
1099 0.5f, 0.75f,
1100 false,
1101 false);
bsalomon47cc7692016-04-26 12:56:00 -07001102}
1103
bsalomona395f7c2016-08-24 17:47:40 -07001104static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001105 // A dash with no stroke should have no effect
1106 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
1107 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
1108 SkPaint dashFill;
1109 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -07001110 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001111
bsalomonfb083272016-05-04 08:27:41 -07001112 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001113 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
1114 }
1115}
1116
bsalomona395f7c2016-08-24 17:47:40 -07001117void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001118 SkPaint fill;
1119 SkPaint stroke;
1120 stroke.setStyle(SkPaint::kStroke_Style);
1121 stroke.setStrokeWidth(1.f);
1122 SkPaint dash;
1123 dash.setStyle(SkPaint::kStroke_Style);
1124 dash.setStrokeWidth(1.f);
1125 dash.setPathEffect(make_dash());
1126 SkPaint nullDash;
1127 nullDash.setStyle(SkPaint::kStroke_Style);
1128 nullDash.setStrokeWidth(1.f);
1129 nullDash.setPathEffect(make_null_dash());
1130
bsalomonfb083272016-05-04 08:27:41 -07001131 TestCase fillCase(geo, fill, reporter);
1132 TestCase strokeCase(geo, stroke, reporter);
1133 TestCase dashCase(geo, dash, reporter);
1134 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001135
bsalomon487f8d32016-07-20 07:15:44 -07001136 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
bsalomon47cc7692016-04-26 12:56:00 -07001137 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -07001138 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
1139 // on construction in order to determine how to compare the fill and stroke.
bsalomona395f7c2016-08-24 17:47:40 -07001140 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001141 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
1142 } else {
1143 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1144 }
1145 // 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 -07001146 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001147 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
1148 } else {
1149 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
1150 }
bsalomon47cc7692016-04-26 12:56:00 -07001151}
1152
bsalomona395f7c2016-08-24 17:47:40 -07001153void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001154 /**
1155 * This path effect takes any input path and turns it into a rrect. It passes through stroke
1156 * info.
1157 */
1158 class RRectPathEffect : SkPathEffect {
1159 public:
1160 static const SkRRect& RRect() {
1161 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1162 return kRRect;
1163 }
1164
Mike Reed6d10f8b2018-08-16 13:22:16 -04001165 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1166 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001167 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001168
1169 protected:
1170 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1171 const SkRect* cullR) const override {
bsalomon72dc51c2016-04-27 06:46:23 -07001172 dst->reset();
1173 dst->addRRect(RRect());
1174 return true;
1175 }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001176
Michael Ludwig4e1c1a72021-05-11 11:39:36 -04001177 bool computeFastBounds(SkRect* bounds) const override {
1178 if (bounds) {
1179 *bounds = RRect().getBounds();
1180 }
1181 return true;
bsalomon72dc51c2016-04-27 06:46:23 -07001182 }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001183
bsalomon72dc51c2016-04-27 06:46:23 -07001184 private:
1185 RRectPathEffect() {}
1186 };
1187
1188 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -07001189 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001190
1191 SkPaint pe;
1192 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001193 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001194
1195 SkPaint peStroke;
1196 peStroke.setPathEffect(RRectPathEffect::Make());
1197 peStroke.setStrokeWidth(2.f);
1198 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001199 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001200
bsalomon487f8d32016-07-20 07:15:44 -07001201 // Check whether constructing the filled case would cause the base shape to have a different
Michael Ludwig2686d692020-04-17 20:21:37 +00001202 // geometry (because of a geometric transformation upon initial GrStyledShape construction).
bsalomona395f7c2016-08-24 17:47:40 -07001203 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -07001204 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1205 fillGeoCase.compare(reporter, geoPEStrokeCase,
1206 TestCase::kAllDifferent_ComparisonExpecation);
1207 } else {
1208 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1209 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1210 }
bsalomon72dc51c2016-04-27 06:46:23 -07001211 geoPECase.compare(reporter, geoPEStrokeCase,
1212 TestCase::kSameUpToStroke_ComparisonExpecation);
1213
bsalomona395f7c2016-08-24 17:47:40 -07001214 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
bsalomon72dc51c2016-04-27 06:46:23 -07001215 SkPaint stroke = peStroke;
1216 stroke.setPathEffect(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -07001217 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001218
1219 SkRRect rrect;
1220 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1221 // geoPECase, so the full style should be the same as just the PE.
bsalomon70493962016-06-10 08:05:14 -07001222 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1223 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001224 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1225 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1226
bsalomon70493962016-06-10 08:05:14 -07001227 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1228 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001229 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1230 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1231
1232 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomon70493962016-06-10 08:05:14 -07001233 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1234 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001235 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1236 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1237
bsalomon70493962016-06-10 08:05:14 -07001238 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1239 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001240 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1241 rrectStrokeCase.appliedFullStyleKey());
1242}
1243
bsalomona395f7c2016-08-24 17:47:40 -07001244void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001245 /**
1246 * This path effect just adds two lineTos to the input path.
1247 */
1248 class AddLineTosPathEffect : SkPathEffect {
1249 public:
Mike Reed6d10f8b2018-08-16 13:22:16 -04001250 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1251 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001252 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001253
1254 protected:
1255 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1256 const SkRect* cullR) const override {
bsalomon72dc51c2016-04-27 06:46:23 -07001257 *dst = src;
bsalomon67fa4e32016-09-21 08:26:57 -07001258 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1259 for (int i = 0; i < 100; ++i) {
1260 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1261 }
bsalomon72dc51c2016-04-27 06:46:23 -07001262 return true;
1263 }
Michael Ludwig4e1c1a72021-05-11 11:39:36 -04001264 bool computeFastBounds(SkRect* bounds) const override {
1265 if (bounds) {
1266 SkRectPriv::GrowToInclude(bounds, {0, 0});
1267 SkRectPriv::GrowToInclude(bounds, {100, 100});
1268 }
1269 return true;
bsalomon72dc51c2016-04-27 06:46:23 -07001270 }
bsalomon72dc51c2016-04-27 06:46:23 -07001271 private:
1272 AddLineTosPathEffect() {}
1273 };
1274
bsalomon9ad5d7c2016-05-04 08:44:15 -07001275 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -07001276 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1277 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1278 SkPaint peStroke;
1279 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1280 peStroke.setStrokeWidth(2.f);
1281 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001282 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001283 TestCase::SelfExpectations expectations;
1284 expectations.fPEHasEffect = true;
1285 expectations.fPEHasValidKey = false;
1286 expectations.fStrokeApplies = true;
1287 geoPEStrokeCase.testExpectations(reporter, expectations);
1288}
1289
bsalomona395f7c2016-08-24 17:47:40 -07001290void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon9ad5d7c2016-05-04 08:44:15 -07001291 /**
1292 * This path effect just changes the stroke rec to hairline.
1293 */
1294 class MakeHairlinePathEffect : SkPathEffect {
1295 public:
bsalomon9ad5d7c2016-05-04 08:44:15 -07001296 static sk_sp<SkPathEffect> Make() {
1297 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1298 }
1299 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001300 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001301
1302 protected:
1303 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1304 const SkRect* cullR) const override {
1305 *dst = src;
1306 strokeRec->setHairlineStyle();
1307 return true;
1308 }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001309 private:
Michael Ludwig4e1c1a72021-05-11 11:39:36 -04001310 bool computeFastBounds(SkRect* bounds) const override { return true; }
1311
bsalomon9ad5d7c2016-05-04 08:44:15 -07001312 MakeHairlinePathEffect() {}
1313 };
1314
1315 SkPaint fill;
1316 SkPaint pe;
1317 pe.setPathEffect(MakeHairlinePathEffect::Make());
1318
1319 TestCase peCase(geo, pe, reporter);
1320
bsalomonee295642016-06-06 14:01:25 -07001321 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -07001322 peCase.baseShape().asPath(&a);
1323 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -07001324 peCase.appliedFullStyleShape().asPath(&c);
bsalomona395f7c2016-08-24 17:47:40 -07001325 if (geo.isNonPath(pe)) {
bsalomonee295642016-06-06 14:01:25 -07001326 // RRect types can have a change in start index or direction after the PE is applied. This
Michael Ludwig2686d692020-04-17 20:21:37 +00001327 // is because once the PE is applied, GrStyledShape may canonicalize the dir and index since
1328 // it is not germane to the styling any longer.
bsalomonee295642016-06-06 14:01:25 -07001329 // Instead we just check that the paths would fill the same both before and after styling.
1330 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1331 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -07001332 } else {
bsalomona4817af2016-06-23 11:48:26 -07001333 // The base shape cannot perform canonicalization on the path's fill type because of an
1334 // unknown path effect. However, after the path effect is applied the resulting hairline
1335 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1336 // don't distinguish between even/odd and non-zero winding.
1337 a.setFillType(b.getFillType());
bsalomonee295642016-06-06 14:01:25 -07001338 REPORTER_ASSERT(reporter, a == b);
1339 REPORTER_ASSERT(reporter, a == c);
bsalomon67fa4e32016-09-21 08:26:57 -07001340 // If the resulting path is small enough then it will have a key.
1341 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1342 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomonaa840642016-09-23 12:09:16 -07001343 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1344 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001345 }
bsalomonee295642016-06-06 14:01:25 -07001346 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1347 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001348}
1349
bsalomona395f7c2016-08-24 17:47:40 -07001350void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1351 SkPath vPath = geo.path();
bsalomon4eeccc92016-04-27 13:30:25 -07001352 vPath.setIsVolatile(true);
1353
1354 SkPaint dashAndStroke;
1355 dashAndStroke.setPathEffect(make_dash());
1356 dashAndStroke.setStrokeWidth(2.f);
1357 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001358 TestCase volatileCase(reporter, vPath, dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001359 // We expect a shape made from a volatile path to have a key iff the shape is recognized
bsalomonaa840642016-09-23 12:09:16 -07001360 // as a specialized geometry.
1361 if (geo.isNonPath(dashAndStroke)) {
bsalomon4eeccc92016-04-27 13:30:25 -07001362 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1363 // In this case all the keys should be identical to the non-volatile case.
bsalomona395f7c2016-08-24 17:47:40 -07001364 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001365 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1366 } else {
1367 // None of the keys should be valid.
1368 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1369 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1370 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1371 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1372 }
1373}
1374
bsalomona395f7c2016-08-24 17:47:40 -07001375void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon409ed732016-04-27 12:36:02 -07001376 /**
Brian Salomon085c0862017-08-31 15:44:51 -04001377 * This path effect returns an empty path (possibly inverted)
bsalomon409ed732016-04-27 12:36:02 -07001378 */
1379 class EmptyPathEffect : SkPathEffect {
1380 public:
Mike Reed6d10f8b2018-08-16 13:22:16 -04001381 static sk_sp<SkPathEffect> Make(bool invert) {
1382 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
1383 }
1384 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001385 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001386 protected:
1387 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1388 const SkRect* cullR) const override {
bsalomon409ed732016-04-27 12:36:02 -07001389 dst->reset();
Brian Salomon085c0862017-08-31 15:44:51 -04001390 if (fInvert) {
1391 dst->toggleInverseFillType();
1392 }
bsalomon409ed732016-04-27 12:36:02 -07001393 return true;
1394 }
Michael Ludwig4e1c1a72021-05-11 11:39:36 -04001395 bool computeFastBounds(SkRect* bounds) const override {
1396 if (bounds) {
1397 *bounds = { 0, 0, 0, 0 };
1398 }
1399 return true;
bsalomon409ed732016-04-27 12:36:02 -07001400 }
bsalomon409ed732016-04-27 12:36:02 -07001401 private:
Brian Salomon085c0862017-08-31 15:44:51 -04001402 bool fInvert;
1403 EmptyPathEffect(bool invert) : fInvert(invert) {}
bsalomon409ed732016-04-27 12:36:02 -07001404 };
1405
1406 SkPath emptyPath;
Michael Ludwig2686d692020-04-17 20:21:37 +00001407 GrStyledShape emptyShape(emptyPath);
bsalomon409ed732016-04-27 12:36:02 -07001408 Key emptyKey;
1409 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -07001410 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001411
Brian Salomon085c0862017-08-31 15:44:51 -04001412 emptyPath.toggleInverseFillType();
Michael Ludwig2686d692020-04-17 20:21:37 +00001413 GrStyledShape invertedEmptyShape(emptyPath);
Brian Salomon085c0862017-08-31 15:44:51 -04001414 Key invertedEmptyKey;
1415 make_key(&invertedEmptyKey, invertedEmptyShape);
1416 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
1417
1418 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
1419
bsalomon409ed732016-04-27 12:36:02 -07001420 SkPaint pe;
Brian Salomon085c0862017-08-31 15:44:51 -04001421 pe.setPathEffect(EmptyPathEffect::Make(false));
1422 TestCase geoPECase(geo, pe, reporter);
1423 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
1424 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
1425 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
1426 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
1427 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
1428 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
1429 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001430
1431 SkPaint peStroke;
Brian Salomon085c0862017-08-31 15:44:51 -04001432 peStroke.setPathEffect(EmptyPathEffect::Make(false));
bsalomon409ed732016-04-27 12:36:02 -07001433 peStroke.setStrokeWidth(2.f);
1434 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001435 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001436 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1437 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1438 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001439 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1440 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001441 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
1442 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
1443 pe.setPathEffect(EmptyPathEffect::Make(true));
1444
1445 TestCase geoPEInvertCase(geo, pe, reporter);
1446 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
1447 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
1448 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1449 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
1450 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
1451 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
1452 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
1453
1454 peStroke.setPathEffect(EmptyPathEffect::Make(true));
1455 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
1456 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
1457 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
1458 REPORTER_ASSERT(reporter,
1459 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1460 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
1461 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
1462 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
1463 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001464}
1465
bsalomona395f7c2016-08-24 17:47:40 -07001466void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
bsalomond6723842016-06-07 12:20:15 -07001467 /**
bsalomon0ae36a22016-07-18 07:31:13 -07001468 * This path effect always fails to apply.
bsalomond6723842016-06-07 12:20:15 -07001469 */
1470 class FailurePathEffect : SkPathEffect {
1471 public:
bsalomond6723842016-06-07 12:20:15 -07001472 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1473 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001474 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001475 protected:
1476 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1477 const SkRect* cullR) const override {
1478 return false;
1479 }
bsalomond6723842016-06-07 12:20:15 -07001480 private:
Michael Ludwig4e1c1a72021-05-11 11:39:36 -04001481 bool computeFastBounds(SkRect* bounds) const override { return false; }
1482
bsalomond6723842016-06-07 12:20:15 -07001483 FailurePathEffect() {}
1484 };
1485
1486 SkPaint fill;
1487 TestCase fillCase(geo, fill, reporter);
1488
1489 SkPaint pe;
1490 pe.setPathEffect(FailurePathEffect::Make());
1491 TestCase peCase(geo, pe, reporter);
1492
1493 SkPaint stroke;
1494 stroke.setStrokeWidth(2.f);
1495 stroke.setStyle(SkPaint::kStroke_Style);
1496 TestCase strokeCase(geo, stroke, reporter);
1497
1498 SkPaint peStroke = stroke;
1499 peStroke.setPathEffect(FailurePathEffect::Make());
1500 TestCase peStrokeCase(geo, peStroke, reporter);
1501
1502 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1503 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
Michael Ludwig2686d692020-04-17 20:21:37 +00001504 // path effect, but then when the path effect fails we can key it. 2) GrStyledShape will change
1505 // its mind about whether a unclosed rect is actually rect. The path effect initially bars us
1506 // from closing it but after the effect fails we can (for the fill+pe case). This causes
1507 // different routes through GrStyledShape to have equivalent but different representations of
1508 // the path (closed or not) but that fill the same.
bsalomond6723842016-06-07 12:20:15 -07001509 SkPath a;
1510 SkPath b;
1511 fillCase.appliedPathEffectShape().asPath(&a);
1512 peCase.appliedPathEffectShape().asPath(&b);
1513 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1514
1515 fillCase.appliedFullStyleShape().asPath(&a);
1516 peCase.appliedFullStyleShape().asPath(&b);
1517 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1518
1519 strokeCase.appliedPathEffectShape().asPath(&a);
1520 peStrokeCase.appliedPathEffectShape().asPath(&b);
1521 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1522
1523 strokeCase.appliedFullStyleShape().asPath(&a);
1524 peStrokeCase.appliedFullStyleShape().asPath(&b);
1525 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1526}
1527
Michael Ludwig2686d692020-04-17 20:21:37 +00001528DEF_TEST(GrStyledShape_empty_shape, reporter) {
bsalomon409ed732016-04-27 12:36:02 -07001529 SkPath emptyPath;
Brian Salomon085c0862017-08-31 15:44:51 -04001530 SkPath invertedEmptyPath;
1531 invertedEmptyPath.toggleInverseFillType();
bsalomon409ed732016-04-27 12:36:02 -07001532 SkPaint fill;
bsalomona395f7c2016-08-24 17:47:40 -07001533 TestCase fillEmptyCase(reporter, emptyPath, fill);
bsalomon7c73a532016-05-11 15:15:56 -07001534 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1535 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1536 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001537 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
1538 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
1539 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
1540 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
1541 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
1542 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
1543 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
1544 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
1545 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
1546 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001547
John Stiles31954bf2020-08-07 17:35:54 -04001548 const Key& emptyKey = fillEmptyCase.baseKey();
bsalomon409ed732016-04-27 12:36:02 -07001549 REPORTER_ASSERT(reporter, emptyKey.count());
John Stiles31954bf2020-08-07 17:35:54 -04001550 const Key& inverseEmptyKey = fillInvertedEmptyCase.baseKey();
Brian Salomon085c0862017-08-31 15:44:51 -04001551 REPORTER_ASSERT(reporter, inverseEmptyKey.count());
bsalomon409ed732016-04-27 12:36:02 -07001552 TestCase::SelfExpectations expectations;
1553 expectations.fStrokeApplies = false;
1554 expectations.fPEHasEffect = false;
1555 // This will test whether applying style preserves emptiness
1556 fillEmptyCase.testExpectations(reporter, expectations);
Brian Salomon085c0862017-08-31 15:44:51 -04001557 fillInvertedEmptyCase.testExpectations(reporter, expectations);
bsalomon409ed732016-04-27 12:36:02 -07001558
1559 // Stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001560 SkPaint stroke;
1561 stroke.setStrokeWidth(2.f);
1562 stroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001563 stroke.setStrokeJoin(SkPaint::kRound_Join);
1564 stroke.setStrokeCap(SkPaint::kRound_Cap);
Brian Salomon085c0862017-08-31 15:44:51 -04001565 TestCase strokeEmptyCase(reporter, emptyPath, stroke);
bsalomon409ed732016-04-27 12:36:02 -07001566 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001567 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
1568 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
1569 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001570
1571 // Dashing and stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001572 SkPaint dashAndStroke;
1573 dashAndStroke.setPathEffect(make_dash());
1574 dashAndStroke.setStrokeWidth(2.f);
1575 dashAndStroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon085c0862017-08-31 15:44:51 -04001576 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
bsalomon409ed732016-04-27 12:36:02 -07001577 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1578 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001579 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
1580 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1581 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
1582 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001583
Michael Ludwigf38b7112020-04-30 13:47:00 -04001584 // A shape made from an empty rrect should behave the same as an empty path when filled and
1585 // when stroked. The shape is closed so it does not produce caps when stroked. When dashed there
1586 // is no path to dash along, making it equivalent as well.
Brian Salomon2fad74a2017-12-20 13:28:55 -05001587 SkRRect emptyRRect = SkRRect::MakeEmpty();
bsalomon5e410b42016-04-28 09:30:46 -07001588 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001589
1590 TestCase fillEmptyRRectCase(reporter, emptyRRect, fill);
1591 fillEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1592
1593 TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke);
1594 strokeEmptyRRectCase.compare(reporter, strokeEmptyCase,
Michael Ludwigf38b7112020-04-30 13:47:00 -04001595 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001596
bsalomona395f7c2016-08-24 17:47:40 -07001597 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001598 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1599 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001600
Mike Reed30bc5272019-11-22 18:34:02 +00001601 static constexpr SkPathDirection kDir = SkPathDirection::kCCW;
Brian Salomon085c0862017-08-31 15:44:51 -04001602 static constexpr int kStart = 0;
Brian Salomon2fad74a2017-12-20 13:28:55 -05001603
1604 TestCase fillInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, GrStyle(fill));
1605 fillInvertedEmptyRRectCase.compare(reporter, fillInvertedEmptyCase,
1606 TestCase::kAllSame_ComparisonExpecation);
1607
1608 TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true,
1609 GrStyle(stroke));
1610 strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase,
Michael Ludwigf38b7112020-04-30 13:47:00 -04001611 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001612
Brian Salomon085c0862017-08-31 15:44:51 -04001613 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
1614 GrStyle(dashAndStroke));
Brian Salomon085c0862017-08-31 15:44:51 -04001615 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
1616 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001617
1618 // Same for a rect.
1619 SkRect emptyRect = SkRect::MakeEmpty();
Brian Salomon2fad74a2017-12-20 13:28:55 -05001620 TestCase fillEmptyRectCase(reporter, emptyRect, fill);
1621 fillEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1622
bsalomona395f7c2016-08-24 17:47:40 -07001623 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001624 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1625 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001626
Brian Salomon085c0862017-08-31 15:44:51 -04001627 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
1628 kStart, true, GrStyle(dashAndStroke));
1629 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1630 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
1631 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001632}
1633
bsalomon70493962016-06-10 08:05:14 -07001634// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1635// canonical point in these cases.
1636unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1637 switch (rrect.getType()) {
1638 case SkRRect::kRect_Type:
1639 return (s + 1) & 0b110;
1640 case SkRRect::kOval_Type:
1641 return s & 0b110;
1642 default:
1643 return s;
1644 }
1645}
1646
1647void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
bsalomoncadb5a22016-06-10 18:28:06 -07001648 enum Style {
bsalomon70493962016-06-10 08:05:14 -07001649 kFill,
1650 kStroke,
1651 kHairline,
1652 kStrokeAndFill
1653 };
1654
1655 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1656 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1657 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1658 strokeRecs[kFill].setFillStyle();
1659 strokeRecs[kStroke].setStrokeStyle(2.f);
1660 strokeRecs[kHairline].setHairlineStyle();
1661 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
bsalomon487f8d32016-07-20 07:15:44 -07001662 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1663 // applyStyle() is called.
1664 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
bsalomon70493962016-06-10 08:05:14 -07001665 sk_sp<SkPathEffect> dashEffect = make_dash();
1666
bsalomoncadb5a22016-06-10 18:28:06 -07001667 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1668
1669 auto index = [](bool inverted,
Mike Reed30bc5272019-11-22 18:34:02 +00001670 SkPathDirection dir,
bsalomoncadb5a22016-06-10 18:28:06 -07001671 unsigned start,
1672 Style style,
1673 bool dash) -> int {
1674 return inverted * (2 * 8 * kStyleCnt * 2) +
Mike Reed30bc5272019-11-22 18:34:02 +00001675 (int)dir * ( 8 * kStyleCnt * 2) +
bsalomoncadb5a22016-06-10 18:28:06 -07001676 start * ( kStyleCnt * 2) +
1677 style * ( 2) +
1678 dash;
1679 };
Mike Reed30bc5272019-11-22 18:34:02 +00001680 static const SkPathDirection kSecondDirection = static_cast<SkPathDirection>(1);
bsalomoncadb5a22016-06-10 18:28:06 -07001681 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
Michael Ludwig2686d692020-04-17 20:21:37 +00001682 SkAutoTArray<GrStyledShape> shapes(cnt);
bsalomoncadb5a22016-06-10 18:28:06 -07001683 for (bool inverted : {false, true}) {
Mike Reed30bc5272019-11-22 18:34:02 +00001684 for (SkPathDirection dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
bsalomoncadb5a22016-06-10 18:28:06 -07001685 for (unsigned start = 0; start < 8; ++start) {
1686 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1687 for (bool dash : {false, true}) {
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001688 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
bsalomoncadb5a22016-06-10 18:28:06 -07001689 shapes[index(inverted, dir, start, style, dash)] =
Michael Ludwig2686d692020-04-17 20:21:37 +00001690 GrStyledShape(rrect, dir, start, SkToBool(inverted),
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001691 GrStyle(strokeRecs[style], std::move(pe)));
bsalomon70493962016-06-10 08:05:14 -07001692 }
1693 }
1694 }
1695 }
1696 }
1697
bsalomonfd32df72016-06-14 14:37:21 -07001698 // Get the keys for some example shape instances that we'll use for comparision against the
1699 // rest.
Mike Reed30bc5272019-11-22 18:34:02 +00001700 static constexpr SkPathDirection kExamplesDir = SkPathDirection::kCW;
bsalomonfd32df72016-06-14 14:37:21 -07001701 static constexpr unsigned kExamplesStart = 0;
Michael Ludwig2686d692020-04-17 20:21:37 +00001702 const GrStyledShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
bsalomonfd32df72016-06-14 14:37:21 -07001703 false)];
bsalomon70493962016-06-10 08:05:14 -07001704 Key exampleFillCaseKey;
1705 make_key(&exampleFillCaseKey, exampleFillCase);
1706
Michael Ludwig2686d692020-04-17 20:21:37 +00001707 const GrStyledShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir,
1708 kExamplesStart, kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001709 Key exampleStrokeAndFillCaseKey;
1710 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1711
Michael Ludwig2686d692020-04-17 20:21:37 +00001712 const GrStyledShape& exampleInvFillCase = shapes[index(true, kExamplesDir,
1713 kExamplesStart, kFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001714 Key exampleInvFillCaseKey;
1715 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1716
Michael Ludwig2686d692020-04-17 20:21:37 +00001717 const GrStyledShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir,
1718 kExamplesStart, kStrokeAndFill,
1719 false)];
bsalomon70493962016-06-10 08:05:14 -07001720 Key exampleInvStrokeAndFillCaseKey;
1721 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1722
Michael Ludwig2686d692020-04-17 20:21:37 +00001723 const GrStyledShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart,
1724 kStroke, false)];
bsalomon70493962016-06-10 08:05:14 -07001725 Key exampleStrokeCaseKey;
1726 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1727
Michael Ludwig2686d692020-04-17 20:21:37 +00001728 const GrStyledShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart,
1729 kStroke, false)];
bsalomonfd32df72016-06-14 14:37:21 -07001730 Key exampleInvStrokeCaseKey;
1731 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1732
Michael Ludwig2686d692020-04-17 20:21:37 +00001733 const GrStyledShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
bsalomonfd32df72016-06-14 14:37:21 -07001734 kHairline, false)];
bsalomon70493962016-06-10 08:05:14 -07001735 Key exampleHairlineCaseKey;
1736 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1737
Michael Ludwig2686d692020-04-17 20:21:37 +00001738 const GrStyledShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
bsalomonfd32df72016-06-14 14:37:21 -07001739 kHairline, false)];
1740 Key exampleInvHairlineCaseKey;
1741 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1742
bsalomon70493962016-06-10 08:05:14 -07001743 // These are dummy initializations to suppress warnings.
bsalomoncadb5a22016-06-10 18:28:06 -07001744 SkRRect queryRR = SkRRect::MakeEmpty();
Mike Reed30bc5272019-11-22 18:34:02 +00001745 SkPathDirection queryDir = SkPathDirection::kCW;
bsalomoncadb5a22016-06-10 18:28:06 -07001746 unsigned queryStart = ~0U;
1747 bool queryInverted = true;
bsalomon70493962016-06-10 08:05:14 -07001748
bsalomoncadb5a22016-06-10 18:28:06 -07001749 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1750 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001751 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001752 REPORTER_ASSERT(r, 0 == queryStart);
1753 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001754
bsalomoncadb5a22016-06-10 18:28:06 -07001755 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1756 &queryInverted));
1757 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001758 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001759 REPORTER_ASSERT(r, 0 == queryStart);
1760 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001761
bsalomoncadb5a22016-06-10 18:28:06 -07001762 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1763 &queryInverted));
1764 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001765 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001766 REPORTER_ASSERT(r, 0 == queryStart);
1767 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001768
bsalomoncadb5a22016-06-10 18:28:06 -07001769 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1770 &queryInverted));
1771 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001772 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001773 REPORTER_ASSERT(r, 0 == queryStart);
1774 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001775
bsalomoncadb5a22016-06-10 18:28:06 -07001776 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1777 &queryInverted));
1778 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001779 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001780 REPORTER_ASSERT(r, 0 == queryStart);
1781 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001782
bsalomonfd32df72016-06-14 14:37:21 -07001783 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1784 &queryInverted));
1785 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001786 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomonfd32df72016-06-14 14:37:21 -07001787 REPORTER_ASSERT(r, 0 == queryStart);
1788 REPORTER_ASSERT(r, queryInverted);
1789
bsalomoncadb5a22016-06-10 18:28:06 -07001790 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1791 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001792 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001793 REPORTER_ASSERT(r, 0 == queryStart);
1794 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001795
bsalomonfd32df72016-06-14 14:37:21 -07001796 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1797 &queryInverted));
1798 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001799 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomonfd32df72016-06-14 14:37:21 -07001800 REPORTER_ASSERT(r, 0 == queryStart);
1801 REPORTER_ASSERT(r, queryInverted);
1802
bsalomon70493962016-06-10 08:05:14 -07001803 // Remember that the key reflects the geometry before styling is applied.
1804 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1805 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1806 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1807 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001808 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001809 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001810 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001811 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001812 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1813 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001814
bsalomoncadb5a22016-06-10 18:28:06 -07001815 for (bool inverted : {false, true}) {
Mike Reed30bc5272019-11-22 18:34:02 +00001816 for (SkPathDirection dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
bsalomoncadb5a22016-06-10 18:28:06 -07001817 for (unsigned start = 0; start < 8; ++start) {
1818 for (bool dash : {false, true}) {
Michael Ludwig2686d692020-04-17 20:21:37 +00001819 const GrStyledShape& fillCase = shapes[index(inverted, dir, start, kFill,
1820 dash)];
bsalomon70493962016-06-10 08:05:14 -07001821 Key fillCaseKey;
1822 make_key(&fillCaseKey, fillCase);
1823
Michael Ludwig2686d692020-04-17 20:21:37 +00001824 const GrStyledShape& strokeAndFillCase = shapes[index(inverted, dir, start,
bsalomoncadb5a22016-06-10 18:28:06 -07001825 kStrokeAndFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001826 Key strokeAndFillCaseKey;
1827 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1828
1829 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1830 // ignore dashing.
1831 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1832 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1833 TestCase a(fillCase, r);
1834 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1835 TestCase c(strokeAndFillCase, r);
1836 TestCase d(inverted ? exampleInvStrokeAndFillCase
1837 : exampleStrokeAndFillCase, r);
1838 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1839 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1840
Michael Ludwig2686d692020-04-17 20:21:37 +00001841 const GrStyledShape& strokeCase = shapes[index(inverted, dir, start, kStroke,
1842 dash)];
1843 const GrStyledShape& hairlineCase = shapes[index(inverted, dir, start,
1844 kHairline, dash)];
bsalomon70493962016-06-10 08:05:14 -07001845
1846 TestCase e(strokeCase, r);
bsalomon70493962016-06-10 08:05:14 -07001847 TestCase g(hairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001848
bsalomonfd32df72016-06-14 14:37:21 -07001849 // Both hairline and stroke shapes must respect the dashing.
bsalomon70493962016-06-10 08:05:14 -07001850 if (dash) {
bsalomonfd32df72016-06-14 14:37:21 -07001851 // Dashing always ignores the inverseness. skbug.com/5421
1852 TestCase f(exampleStrokeCase, r);
1853 TestCase h(exampleHairlineCase, r);
bsalomoncadb5a22016-06-10 18:28:06 -07001854 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
bsalomon70493962016-06-10 08:05:14 -07001855 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1856 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1857
bsalomoncadb5a22016-06-10 18:28:06 -07001858 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1859 &queryInverted));
1860 REPORTER_ASSERT(r, queryRR == rrect);
1861 REPORTER_ASSERT(r, queryDir == dir);
1862 REPORTER_ASSERT(r, queryStart == expectedStart);
1863 REPORTER_ASSERT(r, !queryInverted);
1864 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1865 &queryInverted));
1866 REPORTER_ASSERT(r, queryRR == rrect);
1867 REPORTER_ASSERT(r, queryDir == dir);
1868 REPORTER_ASSERT(r, queryStart == expectedStart);
1869 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001870
1871 // The pre-style case for the dash will match the non-dash example iff the
1872 // dir and start match (dir=cw, start=0).
Mike Reed30bc5272019-11-22 18:34:02 +00001873 if (0 == expectedStart && SkPathDirection::kCW == dir) {
bsalomon70493962016-06-10 08:05:14 -07001874 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1875 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1876 } else {
1877 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1878 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1879 }
1880 } else {
bsalomonfd32df72016-06-14 14:37:21 -07001881 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1882 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001883 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1884 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1885 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1886 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1887 }
1888 }
1889 }
1890 }
1891 }
1892}
1893
Michael Ludwig2686d692020-04-17 20:21:37 +00001894DEF_TEST(GrStyledShape_lines, r) {
bsalomon0a0f67e2016-06-28 11:56:42 -07001895 static constexpr SkPoint kA { 1, 1};
1896 static constexpr SkPoint kB { 5, -9};
1897 static constexpr SkPoint kC {-3, 17};
1898
Mike Reed093de4e2020-08-03 16:33:14 -04001899 SkPath lineAB = SkPath::Line(kA, kB);
1900 SkPath lineBA = SkPath::Line(kB, kA);
1901 SkPath lineAC = SkPath::Line(kB, kC);
bsalomon0a0f67e2016-06-28 11:56:42 -07001902 SkPath invLineAB = lineAB;
Mike Reed093de4e2020-08-03 16:33:14 -04001903
Mike Reed7d34dc72019-11-26 12:17:17 -05001904 invLineAB.setFillType(SkPathFillType::kInverseEvenOdd);
bsalomon0a0f67e2016-06-28 11:56:42 -07001905
1906 SkPaint fill;
1907 SkPaint stroke;
1908 stroke.setStyle(SkPaint::kStroke_Style);
1909 stroke.setStrokeWidth(2.f);
1910 SkPaint hairline;
1911 hairline.setStyle(SkPaint::kStroke_Style);
1912 hairline.setStrokeWidth(0.f);
1913 SkPaint dash = stroke;
1914 dash.setPathEffect(make_dash());
1915
bsalomona395f7c2016-08-24 17:47:40 -07001916 TestCase fillAB(r, lineAB, fill);
1917 TestCase fillEmpty(r, SkPath(), fill);
bsalomon0a0f67e2016-06-28 11:56:42 -07001918 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1919 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1920
Brian Salomon085c0862017-08-31 15:44:51 -04001921 SkPath path;
1922 path.toggleInverseFillType();
1923 TestCase fillEmptyInverted(r, path, fill);
1924 TestCase fillABInverted(r, invLineAB, fill);
1925 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
1926 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
1927
bsalomona395f7c2016-08-24 17:47:40 -07001928 TestCase strokeAB(r, lineAB, stroke);
1929 TestCase strokeBA(r, lineBA, stroke);
1930 TestCase strokeAC(r, lineAC, stroke);
bsalomon0a0f67e2016-06-28 11:56:42 -07001931
bsalomona395f7c2016-08-24 17:47:40 -07001932 TestCase hairlineAB(r, lineAB, hairline);
1933 TestCase hairlineBA(r, lineBA, hairline);
1934 TestCase hairlineAC(r, lineAC, hairline);
bsalomon0a0f67e2016-06-28 11:56:42 -07001935
bsalomona395f7c2016-08-24 17:47:40 -07001936 TestCase dashAB(r, lineAB, dash);
1937 TestCase dashBA(r, lineBA, dash);
1938 TestCase dashAC(r, lineAC, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001939
1940 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1941
1942 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1943 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1944
1945 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1946 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1947
1948 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1949 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1950
1951 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1952
1953 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
Michael Ludwig2686d692020-04-17 20:21:37 +00001954 // GrStyledShape canonicalizes line endpoints (when it can, i.e. when not dashed).
bsalomon0a0f67e2016-06-28 11:56:42 -07001955 bool canonicalizeAsAB;
1956 SkPoint canonicalPts[2] {kA, kB};
1957 // Init these to suppress warnings.
1958 bool inverted = true;
1959 SkPoint pts[2] {{0, 0}, {0, 0}};
1960 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1961 if (pts[0] == kA && pts[1] == kB) {
1962 canonicalizeAsAB = true;
1963 } else if (pts[1] == kA && pts[0] == kB) {
1964 canonicalizeAsAB = false;
Ben Wagnerf08d1d02018-06-18 15:11:00 -04001965 using std::swap;
1966 swap(canonicalPts[0], canonicalPts[1]);
bsalomon0a0f67e2016-06-28 11:56:42 -07001967 } else {
1968 ERRORF(r, "Should return pts (a,b) or (b, a)");
1969 return;
Brian Salomon23356442018-11-30 15:33:19 -05001970 }
bsalomon0a0f67e2016-06-28 11:56:42 -07001971
1972 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1973 TestCase::kSameUpToPE_ComparisonExpecation);
1974 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1975 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1976 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1977 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1978 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1979 pts[0] == kA && pts[1] == kB);
1980 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1981 pts[0] == kB && pts[1] == kA);
1982
1983
bsalomona395f7c2016-08-24 17:47:40 -07001984 TestCase strokeInvAB(r, invLineAB, stroke);
1985 TestCase hairlineInvAB(r, invLineAB, hairline);
1986 TestCase dashInvAB(r, invLineAB, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001987 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1988 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1989 // Dashing ignores inverse.
1990 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1991
1992 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1993 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1994 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1995 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1996 // Dashing ignores inverse.
1997 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1998 pts[0] == kA && pts[1] == kB);
1999
2000}
2001
Michael Ludwig2686d692020-04-17 20:21:37 +00002002DEF_TEST(GrStyledShape_stroked_lines, r) {
Brian Salomon72f78c32017-12-21 11:56:42 -05002003 static constexpr SkScalar kIntervals1[] = {1.f, 0.f};
2004 auto dash1 = SkDashPathEffect::Make(kIntervals1, SK_ARRAY_COUNT(kIntervals1), 0.f);
2005 REPORTER_ASSERT(r, dash1);
2006 static constexpr SkScalar kIntervals2[] = {10.f, 0.f, 5.f, 0.f};
2007 auto dash2 = SkDashPathEffect::Make(kIntervals2, SK_ARRAY_COUNT(kIntervals2), 10.f);
2008 REPORTER_ASSERT(r, dash2);
bsalomon0ae36a22016-07-18 07:31:13 -07002009
Brian Salomon72f78c32017-12-21 11:56:42 -05002010 sk_sp<SkPathEffect> pathEffects[] = {nullptr, std::move(dash1), std::move(dash2)};
bsalomon0ae36a22016-07-18 07:31:13 -07002011
Brian Salomon72f78c32017-12-21 11:56:42 -05002012 for (const auto& pe : pathEffects) {
2013 // Paints to try
2014 SkPaint buttCap;
2015 buttCap.setStyle(SkPaint::kStroke_Style);
2016 buttCap.setStrokeWidth(4);
2017 buttCap.setStrokeCap(SkPaint::kButt_Cap);
2018 buttCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07002019
Brian Salomon72f78c32017-12-21 11:56:42 -05002020 SkPaint squareCap = buttCap;
2021 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
2022 squareCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07002023
Brian Salomon72f78c32017-12-21 11:56:42 -05002024 SkPaint roundCap = buttCap;
2025 roundCap.setStrokeCap(SkPaint::kRound_Cap);
2026 roundCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07002027
Brian Salomon72f78c32017-12-21 11:56:42 -05002028 // vertical
2029 SkPath linePath;
2030 linePath.moveTo(4, 4);
2031 linePath.lineTo(4, 5);
bsalomon0ae36a22016-07-18 07:31:13 -07002032
Brian Salomon72f78c32017-12-21 11:56:42 -05002033 SkPaint fill;
bsalomon0ae36a22016-07-18 07:31:13 -07002034
Brian Salomon72f78c32017-12-21 11:56:42 -05002035 make_TestCase(r, linePath, buttCap)->compare(
2036 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
2037 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002038
Brian Salomon72f78c32017-12-21 11:56:42 -05002039 make_TestCase(r, linePath, squareCap)->compare(
2040 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
2041 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002042
Brian Salomon72f78c32017-12-21 11:56:42 -05002043 make_TestCase(r, linePath, roundCap)->compare(r,
2044 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
2045 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002046
Brian Salomon72f78c32017-12-21 11:56:42 -05002047 // horizontal
2048 linePath.reset();
2049 linePath.moveTo(4, 4);
2050 linePath.lineTo(5, 4);
bsalomon0ae36a22016-07-18 07:31:13 -07002051
Brian Salomon72f78c32017-12-21 11:56:42 -05002052 make_TestCase(r, linePath, buttCap)->compare(
2053 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
2054 TestCase::kAllSame_ComparisonExpecation);
2055 make_TestCase(r, linePath, squareCap)->compare(
2056 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
2057 TestCase::kAllSame_ComparisonExpecation);
2058 make_TestCase(r, linePath, roundCap)->compare(
2059 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
2060 TestCase::kAllSame_ComparisonExpecation);
2061
2062 // point
2063 linePath.reset();
2064 linePath.moveTo(4, 4);
2065 linePath.lineTo(4, 4);
2066
2067 make_TestCase(r, linePath, buttCap)->compare(
2068 r, TestCase(r, SkRect::MakeEmpty(), fill),
2069 TestCase::kAllSame_ComparisonExpecation);
2070 make_TestCase(r, linePath, squareCap)->compare(
2071 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
2072 TestCase::kAllSame_ComparisonExpecation);
2073 make_TestCase(r, linePath, roundCap)->compare(
2074 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
2075 TestCase::kAllSame_ComparisonExpecation);
2076 }
bsalomon0ae36a22016-07-18 07:31:13 -07002077}
2078
Michael Ludwig2686d692020-04-17 20:21:37 +00002079DEF_TEST(GrStyledShape_short_path_keys, r) {
bsalomon67fa4e32016-09-21 08:26:57 -07002080 SkPaint paints[4];
2081 paints[1].setStyle(SkPaint::kStroke_Style);
2082 paints[1].setStrokeWidth(5.f);
2083 paints[2].setStyle(SkPaint::kStroke_Style);
2084 paints[2].setStrokeWidth(0.f);
2085 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
2086 paints[3].setStrokeWidth(5.f);
2087
bsalomonaa840642016-09-23 12:09:16 -07002088 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
bsalomon67fa4e32016-09-21 08:26:57 -07002089 TestCase::ComparisonExpecation expectation) {
bsalomonaa840642016-09-23 12:09:16 -07002090 SkPath volatileA = pathA;
2091 SkPath volatileB = pathB;
2092 volatileA.setIsVolatile(true);
2093 volatileB.setIsVolatile(true);
bsalomon67fa4e32016-09-21 08:26:57 -07002094 for (const SkPaint& paint : paints) {
Michael Ludwig2686d692020-04-17 20:21:37 +00002095 REPORTER_ASSERT(r, !GrStyledShape(volatileA, paint).hasUnstyledKey());
2096 REPORTER_ASSERT(r, !GrStyledShape(volatileB, paint).hasUnstyledKey());
bsalomon67fa4e32016-09-21 08:26:57 -07002097 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
bsalomonaa840642016-09-23 12:09:16 -07002098 TestCase caseA(PathGeo(pathA, invert), paint, r);
2099 TestCase caseB(PathGeo(pathB, invert), paint, r);
2100 caseA.compare(r, caseB, expectation);
bsalomon67fa4e32016-09-21 08:26:57 -07002101 }
2102 }
2103 };
2104
2105 SkPath pathA;
2106 SkPath pathB;
2107
2108 // Two identical paths
2109 pathA.lineTo(10.f, 10.f);
2110 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2111
2112 pathB.lineTo(10.f, 10.f);
2113 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002114 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002115
2116 // Give path b a different point
2117 pathB.reset();
2118 pathB.lineTo(10.f, 10.f);
2119 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002120 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002121
2122 // Give path b a different conic weight
2123 pathB.reset();
2124 pathB.lineTo(10.f, 10.f);
2125 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
bsalomonaa840642016-09-23 12:09:16 -07002126 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002127
2128 // Give path b an extra lineTo verb
2129 pathB.reset();
2130 pathB.lineTo(10.f, 10.f);
2131 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2132 pathB.lineTo(50.f, 50.f);
bsalomonaa840642016-09-23 12:09:16 -07002133 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002134
2135 // Give path b a close
2136 pathB.reset();
2137 pathB.lineTo(10.f, 10.f);
2138 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2139 pathB.close();
bsalomonaa840642016-09-23 12:09:16 -07002140 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002141}
2142
Michael Ludwig2686d692020-04-17 20:21:37 +00002143DEF_TEST(GrStyledShape, reporter) {
bsalomona395f7c2016-08-24 17:47:40 -07002144 SkTArray<std::unique_ptr<Geo>> geos;
2145 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
2146
bsalomonee295642016-06-06 14:01:25 -07002147 for (auto r : { SkRect::MakeWH(10, 20),
2148 SkRect::MakeWH(-10, -20),
2149 SkRect::MakeWH(-10, 20),
2150 SkRect::MakeWH(10, -20)}) {
bsalomona395f7c2016-08-24 17:47:40 -07002151 geos.emplace_back(new RectGeo(r));
2152 SkPath rectPath;
2153 rectPath.addRect(r);
2154 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2155 PathGeo::Invert::kNo));
2156 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2157 PathGeo::Invert::kYes));
2158 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2159 PathGeo::Invert::kNo));
bsalomonee295642016-06-06 14:01:25 -07002160 }
bsalomon47cc7692016-04-26 12:56:00 -07002161 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -07002162 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
2163 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomona395f7c2016-08-24 17:47:40 -07002164 geos.emplace_back(new RRectGeo(rr));
bsalomon70493962016-06-10 08:05:14 -07002165 test_rrect(reporter, rr);
bsalomona395f7c2016-08-24 17:47:40 -07002166 SkPath rectPath;
2167 rectPath.addRRect(rr);
2168 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2169 PathGeo::Invert::kNo));
2170 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2171 PathGeo::Invert::kYes));
2172 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
2173 RRectPathGeo::RRectForStroke::kYes,
2174 PathGeo::Invert::kNo));
bsalomon72dc51c2016-04-27 06:46:23 -07002175 }
2176
Brian Salomone4949402018-04-26 15:22:04 -04002177 // Arcs
2178 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, false));
2179 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, true));
2180
Mike Klein43344282017-08-16 11:56:22 -04002181 {
2182 SkPath openRectPath;
2183 openRectPath.moveTo(0, 0);
2184 openRectPath.lineTo(10, 0);
2185 openRectPath.lineTo(10, 10);
2186 openRectPath.lineTo(0, 10);
2187 geos.emplace_back(new RRectPathGeo(
2188 openRectPath, SkRect::MakeWH(10, 10),
2189 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2190 geos.emplace_back(new RRectPathGeo(
2191 openRectPath, SkRect::MakeWH(10, 10),
2192 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
2193 rrectPathGeos.emplace_back(new RRectPathGeo(
2194 openRectPath, SkRect::MakeWH(10, 10),
2195 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2196 }
bsalomon72dc51c2016-04-27 06:46:23 -07002197
Mike Klein43344282017-08-16 11:56:22 -04002198 {
2199 SkPath quadPath;
2200 quadPath.quadTo(10, 10, 5, 8);
2201 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
2202 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
2203 }
bsalomon398e3f42016-06-13 10:22:48 -07002204
Mike Klein43344282017-08-16 11:56:22 -04002205 {
2206 SkPath linePath;
2207 linePath.lineTo(10, 10);
2208 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
2209 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
2210 }
bsalomon72dc51c2016-04-27 06:46:23 -07002211
bsalomon0ae36a22016-07-18 07:31:13 -07002212 // Horizontal and vertical paths become rrects when stroked.
Mike Klein43344282017-08-16 11:56:22 -04002213 {
2214 SkPath vLinePath;
2215 vLinePath.lineTo(0, 10);
2216 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
2217 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
2218 }
bsalomon0ae36a22016-07-18 07:31:13 -07002219
Mike Klein43344282017-08-16 11:56:22 -04002220 {
2221 SkPath hLinePath;
2222 hLinePath.lineTo(10, 0);
2223 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
2224 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
2225 }
bsalomon0ae36a22016-07-18 07:31:13 -07002226
bsalomona395f7c2016-08-24 17:47:40 -07002227 for (int i = 0; i < geos.count(); ++i) {
2228 test_basic(reporter, *geos[i]);
2229 test_scale(reporter, *geos[i]);
2230 test_dash_fill(reporter, *geos[i]);
2231 test_null_dash(reporter, *geos[i]);
2232 // Test modifying various stroke params.
2233 test_stroke_param<SkScalar>(
2234 reporter, *geos[i],
bsalomon70493962016-06-10 08:05:14 -07002235 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
2236 SkIntToScalar(2), SkIntToScalar(4));
bsalomona395f7c2016-08-24 17:47:40 -07002237 test_stroke_join(reporter, *geos[i]);
2238 test_stroke_cap(reporter, *geos[i]);
2239 test_miter_limit(reporter, *geos[i]);
2240 test_path_effect_makes_rrect(reporter, *geos[i]);
2241 test_unknown_path_effect(reporter, *geos[i]);
2242 test_path_effect_makes_empty_shape(reporter, *geos[i]);
2243 test_path_effect_fails(reporter, *geos[i]);
2244 test_make_hairline_path_effect(reporter, *geos[i]);
2245 test_volatile_path(reporter, *geos[i]);
bsalomon70493962016-06-10 08:05:14 -07002246 }
bsalomonfd32df72016-06-14 14:37:21 -07002247
bsalomona395f7c2016-08-24 17:47:40 -07002248 for (int i = 0; i < rrectPathGeos.count(); ++i) {
2249 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
bsalomon72dc51c2016-04-27 06:46:23 -07002250 SkPaint fillPaint;
bsalomona395f7c2016-08-24 17:47:40 -07002251 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002252 SkRRect rrect;
bsalomona395f7c2016-08-24 17:47:40 -07002253 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
bsalomon70493962016-06-10 08:05:14 -07002254 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2255 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002256 if (rrgeo.isNonPath(fillPaint)) {
2257 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
2258 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2259 TestCase fillRRectCase(reporter, rrect, fillPaint);
bsalomon70493962016-06-10 08:05:14 -07002260 fillPathCase2.compare(reporter, fillRRectCase,
2261 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002262 }
bsalomon72dc51c2016-04-27 06:46:23 -07002263 SkPaint strokePaint;
2264 strokePaint.setStrokeWidth(3.f);
2265 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07002266 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
2267 if (rrgeo.isNonPath(strokePaint)) {
bsalomon0ae36a22016-07-18 07:31:13 -07002268 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2269 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002270 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2271 TestCase strokeRRectCase(reporter, rrect, strokePaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002272 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07002273 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002274 }
bsalomon47cc7692016-04-26 12:56:00 -07002275 }
bsalomon409ed732016-04-27 12:36:02 -07002276
bsalomon4eeccc92016-04-27 13:30:25 -07002277 // Test a volatile empty path.
bsalomona395f7c2016-08-24 17:47:40 -07002278 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
bsalomon47cc7692016-04-26 12:56:00 -07002279}
2280
Michael Ludwig2686d692020-04-17 20:21:37 +00002281DEF_TEST(GrStyledShape_arcs, reporter) {
Brian Salomone4949402018-04-26 15:22:04 -04002282 SkStrokeRec roundStroke(SkStrokeRec::kFill_InitStyle);
2283 roundStroke.setStrokeStyle(2.f);
2284 roundStroke.setStrokeParams(SkPaint::kRound_Cap, SkPaint::kRound_Join, 1.f);
2285
2286 SkStrokeRec squareStroke(roundStroke);
2287 squareStroke.setStrokeParams(SkPaint::kSquare_Cap, SkPaint::kRound_Join, 1.f);
2288
2289 SkStrokeRec roundStrokeAndFill(roundStroke);
2290 roundStrokeAndFill.setStrokeStyle(2.f, true);
2291
2292 static constexpr SkScalar kIntervals[] = {1, 2};
2293 auto dash = SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), 1.5f);
2294
2295 SkTArray<GrStyle> styles;
2296 styles.push_back(GrStyle::SimpleFill());
2297 styles.push_back(GrStyle::SimpleHairline());
2298 styles.push_back(GrStyle(roundStroke, nullptr));
2299 styles.push_back(GrStyle(squareStroke, nullptr));
2300 styles.push_back(GrStyle(roundStrokeAndFill, nullptr));
2301 styles.push_back(GrStyle(roundStroke, dash));
2302
2303 for (const auto& style : styles) {
2304 // An empty rect never draws anything according to SkCanvas::drawArc() docs.
Michael Ludwig2686d692020-04-17 20:21:37 +00002305 TestCase emptyArc(GrStyledShape::MakeArc(SkRect::MakeEmpty(), 0, 90.f, false, style),
2306 reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002307 TestCase emptyPath(reporter, SkPath(), style);
2308 emptyArc.compare(reporter, emptyPath, TestCase::kAllSame_ComparisonExpecation);
2309
2310 static constexpr SkRect kOval1{0, 0, 50, 50};
2311 static constexpr SkRect kOval2{50, 0, 100, 50};
2312 // Test that swapping starting and ending angle doesn't change the shape unless the arc
2313 // has a path effect. Also test that different ovals produce different shapes.
Michael Ludwig2686d692020-04-17 20:21:37 +00002314 TestCase arc1CW(GrStyledShape::MakeArc(kOval1, 0, 90.f, false, style), reporter);
2315 TestCase arc1CCW(GrStyledShape::MakeArc(kOval1, 90.f, -90.f, false, style), reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002316
Michael Ludwig2686d692020-04-17 20:21:37 +00002317 TestCase arc1CWWithCenter(GrStyledShape::MakeArc(kOval1, 0, 90.f, true, style), reporter);
2318 TestCase arc1CCWWithCenter(GrStyledShape::MakeArc(kOval1, 90.f, -90.f, true, style),
2319 reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002320
Michael Ludwig2686d692020-04-17 20:21:37 +00002321 TestCase arc2CW(GrStyledShape::MakeArc(kOval2, 0, 90.f, false, style), reporter);
2322 TestCase arc2CWWithCenter(GrStyledShape::MakeArc(kOval2, 0, 90.f, true, style), reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002323
2324 auto reversedExepectations = style.hasPathEffect()
2325 ? TestCase::kAllDifferent_ComparisonExpecation
2326 : TestCase::kAllSame_ComparisonExpecation;
2327 arc1CW.compare(reporter, arc1CCW, reversedExepectations);
2328 arc1CWWithCenter.compare(reporter, arc1CCWWithCenter, reversedExepectations);
2329 arc1CW.compare(reporter, arc2CW, TestCase::kAllDifferent_ComparisonExpecation);
2330 arc1CW.compare(reporter, arc1CWWithCenter, TestCase::kAllDifferent_ComparisonExpecation);
2331 arc1CWWithCenter.compare(reporter, arc2CWWithCenter,
2332 TestCase::kAllDifferent_ComparisonExpecation);
2333
2334 // Test that two arcs that start at the same angle but specified differently are equivalent.
Michael Ludwig2686d692020-04-17 20:21:37 +00002335 TestCase arc3A(GrStyledShape::MakeArc(kOval1, 224.f, 73.f, false, style), reporter);
2336 TestCase arc3B(GrStyledShape::MakeArc(kOval1, 224.f - 360.f, 73.f, false, style), reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002337 arc3A.compare(reporter, arc3B, TestCase::kAllDifferent_ComparisonExpecation);
2338
2339 // Test that an arc that traverses the entire oval (and then some) is equivalent to the
2340 // oval itself unless there is a path effect.
Michael Ludwig2686d692020-04-17 20:21:37 +00002341 TestCase ovalArc(GrStyledShape::MakeArc(kOval1, 150.f, -790.f, false, style), reporter);
2342 TestCase oval(GrStyledShape(SkRRect::MakeOval(kOval1)), reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002343 auto ovalExpectations = style.hasPathEffect() ? TestCase::kAllDifferent_ComparisonExpecation
2344 : TestCase::kAllSame_ComparisonExpecation;
2345 if (style.strokeRec().getWidth() >= 0 && style.strokeRec().getCap() != SkPaint::kButt_Cap) {
2346 ovalExpectations = TestCase::kAllDifferent_ComparisonExpecation;
2347 }
2348 ovalArc.compare(reporter, oval, ovalExpectations);
2349
2350 // If the the arc starts/ends at the center then it is then equivalent to the oval only for
2351 // simple fills.
Michael Ludwig2686d692020-04-17 20:21:37 +00002352 TestCase ovalArcWithCenter(GrStyledShape::MakeArc(kOval1, 304.f, 1225.f, true, style),
2353 reporter);
Brian Salomone4949402018-04-26 15:22:04 -04002354 ovalExpectations = style.isSimpleFill() ? TestCase::kAllSame_ComparisonExpecation
2355 : TestCase::kAllDifferent_ComparisonExpecation;
2356 ovalArcWithCenter.compare(reporter, oval, ovalExpectations);
2357 }
2358}
Michael Ludwig80e3d422020-08-04 10:43:56 -04002359
2360DEF_TEST(GrShapeInversion, r) {
2361 SkPath path;
Michael Ludwig21efb7c2020-08-04 11:38:28 -04002362 SkScalar radii[] = {10.f, 10.f, 10.f, 10.f,
2363 10.f, 10.f, 10.f, 10.f};
Michael Ludwig80e3d422020-08-04 10:43:56 -04002364 path.addRoundRect(SkRect::MakeWH(50, 50), radii);
2365 path.toggleInverseFillType();
2366
2367 GrShape inverseRRect(path);
2368 GrShape rrect(inverseRRect);
2369 rrect.setInverted(false);
2370
2371 REPORTER_ASSERT(r, inverseRRect.inverted() && inverseRRect.isPath());
2372 REPORTER_ASSERT(r, !rrect.inverted() && rrect.isPath());
2373
2374 // Invertedness should be preserved after simplification
2375 inverseRRect.simplify();
2376 rrect.simplify();
2377
2378 REPORTER_ASSERT(r, inverseRRect.inverted() && inverseRRect.isRRect());
2379 REPORTER_ASSERT(r, !rrect.inverted() && rrect.isRRect());
2380
2381 // Invertedness should be reset when calling reset().
2382 inverseRRect.reset();
2383 REPORTER_ASSERT(r, !inverseRRect.inverted() && inverseRRect.isEmpty());
2384 inverseRRect.setPath(path);
2385 inverseRRect.reset();
2386 REPORTER_ASSERT(r, !inverseRRect.inverted() && inverseRRect.isEmpty());
2387}