blob: a01540254ea1f5ae9e2fcd5e2e8f6f24680b4913 [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 Ludwig663afe52019-06-03 16:46:19 -040015#include "src/gpu/geometry/GrShape.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>
Ben Wagnerf08d1d02018-06-18 15:11:00 -040020#include <utility>
21
Brian Osmanf6f7cf62017-09-25 16:49:55 -040022uint32_t GrShape::testingOnly_getOriginalGenerationID() const {
Brian Salomonda6d0722018-01-03 13:54:35 -050023 if (const auto* lp = this->originalPathForListeners()) {
24 return lp->getGenerationID();
25 }
26 return SkPath().getGenerationID();
Brian Osmanf6f7cf62017-09-25 16:49:55 -040027}
28
Brian Osmanb379dcd2017-10-04 15:44:05 -040029bool GrShape::testingOnly_isPath() const {
30 return Type::kPath == fType;
31}
32
Brian Salomonda6d0722018-01-03 13:54:35 -050033bool GrShape::testingOnly_isNonVolatilePath() const {
34 return Type::kPath == fType && !fPathData.fPath.isVolatile();
35}
36
bsalomon72dc51c2016-04-27 06:46:23 -070037using Key = SkTArray<uint32_t>;
38
39static bool make_key(Key* key, const GrShape& shape) {
40 int size = shape.unstyledKeySize();
41 if (size <= 0) {
42 key->reset(0);
43 return false;
44 }
45 SkASSERT(size);
46 key->reset(size);
47 shape.writeUnstyledKey(key->begin());
48 return true;
49}
50
bsalomonee295642016-06-06 14:01:25 -070051static bool paths_fill_same(const SkPath& a, const SkPath& b) {
52 SkPath pathXor;
53 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
54 return pathXor.isEmpty();
55}
56
bsalomon9fb42032016-05-13 09:23:38 -070057static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
bsalomon164fd9f2016-08-26 06:45:06 -070058 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
59 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
60 // rendering within the bounds (with a tolerance). Then we render the path and check that
61 // everything got clipped out.
bsalomon9fb42032016-05-13 09:23:38 -070062 static constexpr int kRes = 2000;
63 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
Brian Salomone4949402018-04-26 15:22:04 -040064 static constexpr int kTol = 2;
Brian Salomon4dea72a2019-12-18 10:43:10 -050065 static_assert(kRes % 4 == 0);
bsalomon9fb42032016-05-13 09:23:38 -070066 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
67 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
68 surface->getCanvas()->clear(0x0);
69 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
70 SkMatrix matrix;
71 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit);
72 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
Brian Salomon4f40caf2017-09-01 09:00:45 -040098static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
99 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
110static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
111 const Key& keyA, const Key& keyB) {
112 // GrShape only respects the input winding direction and start point for rrect shapes
113 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
114 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
115 // key will differ. GrShape will have canonicalized the direction and start point for the shape
116 // without the path effect. If *both* have path effects then they should have both preserved
117 // the direction and starting point.
118
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);
131 // GrShape will close paths with simple fill style.
132 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()) {
141 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
142 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
143 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) {
181 // GrShape will close paths with simple fill style. Make the non-filled path closed
182 // so that the comparision will succeed. Make sure both are closed before comparing.
183 pA.close();
184 pB.close();
185 }
186 REPORTER_ASSERT(r, pA == pB);
187 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
188 if (aIsRRect) {
189 REPORTER_ASSERT(r, rrectA == rrectB);
190 REPORTER_ASSERT(r, dirA == dirB);
191 REPORTER_ASSERT(r, startA == startB);
192 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
193 }
194 }
195 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
196 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
197 // closedness can affect convexity.
198 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
199 if (a.knownToBeConvex()) {
200 REPORTER_ASSERT(r, pathA.isConvex());
201 }
202 if (b.knownToBeConvex()) {
203 REPORTER_ASSERT(r, pathB.isConvex());
204 }
205 REPORTER_ASSERT(r, a.bounds() == b.bounds());
206 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
207 // Init these to suppress warnings.
208 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
209 bool invertedLine[2] {true, true};
210 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
211 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
212 // doesn't (since the PE can set any fill type on its output path).
213 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
214 // then they may disagree about inverseness.
215 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
216 a.style().isDashed() == b.style().isDashed()) {
217 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
218 b.mayBeInverseFilledAfterStyling());
219 }
220 if (a.asLine(nullptr, nullptr)) {
221 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
222 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
223 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
224 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
225 }
226 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
227}
228
Brian Osmanb379dcd2017-10-04 15:44:05 -0400229static void check_original_path_ids(skiatest::Reporter* r, const GrShape& base, const GrShape& pe,
230 const GrShape& peStroke, const GrShape& full) {
Brian Salomonda6d0722018-01-03 13:54:35 -0500231 bool baseIsNonVolatilePath = base.testingOnly_isNonVolatilePath();
Brian Osmanb379dcd2017-10-04 15:44:05 -0400232 bool peIsPath = pe.testingOnly_isPath();
233 bool peStrokeIsPath = peStroke.testingOnly_isPath();
234 bool fullIsPath = full.testingOnly_isPath();
235
236 REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath);
237
238 uint32_t baseID = base.testingOnly_getOriginalGenerationID();
239 uint32_t peID = pe.testingOnly_getOriginalGenerationID();
240 uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID();
241 uint32_t fullID = full.testingOnly_getOriginalGenerationID();
242
243 // All empty paths have the same gen ID
244 uint32_t emptyID = SkPath().getGenerationID();
245
246 // 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 -0500247 // empty). If we started with a simple shape or a volatile path, our original path should have
248 // been reset.
249 REPORTER_ASSERT(r, baseIsNonVolatilePath == (baseID != emptyID));
Brian Osmanb379dcd2017-10-04 15:44:05 -0400250
251 // For the derived shapes, if they're simple types, their original paths should have been reset
252 REPORTER_ASSERT(r, peIsPath || (peID == emptyID));
253 REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID));
254 REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID));
255
256 if (!peIsPath) {
257 // If the path effect produces a simple shape, then there are no unbroken chains to test
258 return;
259 }
260
261 // From here on, we know that the path effect produced a shape that was a "real" path
262
Brian Salomonda6d0722018-01-03 13:54:35 -0500263 if (baseIsNonVolatilePath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400264 REPORTER_ASSERT(r, baseID == peID);
265 }
266
267 if (peStrokeIsPath) {
268 REPORTER_ASSERT(r, peID == peStrokeID);
269 REPORTER_ASSERT(r, peStrokeID == fullID);
270 }
271
Brian Salomonda6d0722018-01-03 13:54:35 -0500272 if (baseIsNonVolatilePath && peStrokeIsPath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400273 REPORTER_ASSERT(r, baseID == peStrokeID);
274 REPORTER_ASSERT(r, baseID == fullID);
275 }
276}
277
Brian Salomon4f40caf2017-09-01 09:00:45 -0400278void test_inversions(skiatest::Reporter* r, const GrShape& shape, const Key& shapeKey) {
279 GrShape preserve = GrShape::MakeFilled(shape, GrShape::FillInversion::kPreserve);
280 Key preserveKey;
281 make_key(&preserveKey, preserve);
282
283 GrShape flip = GrShape::MakeFilled(shape, GrShape::FillInversion::kFlip);
284 Key flipKey;
285 make_key(&flipKey, flip);
286
287 GrShape inverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceInverted);
288 Key invertedKey;
289 make_key(&invertedKey, inverted);
290
291 GrShape noninverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceNoninverted);
292 Key noninvertedKey;
293 make_key(&noninvertedKey, noninverted);
294
295 if (invertedKey.count() || noninvertedKey.count()) {
296 REPORTER_ASSERT(r, invertedKey != noninvertedKey);
297 }
298 if (shape.style().isSimpleFill()) {
299 check_equivalence(r, shape, preserve, shapeKey, preserveKey);
300 }
301 if (shape.inverseFilled()) {
302 check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
303 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
304 } else {
305 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
306 check_equivalence(r, flip, inverted, flipKey, invertedKey);
307 }
308
309 GrShape doubleFlip = GrShape::MakeFilled(flip, GrShape::FillInversion::kFlip);
310 Key doubleFlipKey;
311 make_key(&doubleFlipKey, doubleFlip);
312 // It can be the case that the double flip has no key but preserve does. This happens when the
313 // original shape has an inherited style key. That gets dropped on the first inversion flip.
314 if (preserveKey.count() && !doubleFlipKey.count()) {
315 preserveKey.reset();
316 }
317 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
318}
319
bsalomon9fb42032016-05-13 09:23:38 -0700320namespace {
bsalomona395f7c2016-08-24 17:47:40 -0700321/**
322 * Geo is a factory for creating a GrShape from another representation. It also answers some
323 * questions about expected behavior for GrShape given the inputs.
324 */
325class Geo {
326public:
Mike Kleinfc6c37b2016-09-27 09:34:10 -0400327 virtual ~Geo() {}
bsalomona395f7c2016-08-24 17:47:40 -0700328 virtual GrShape makeShape(const SkPaint&) const = 0;
329 virtual SkPath path() const = 0;
330 // These functions allow tests to check for special cases where style gets
331 // applied by GrShape in its constructor (without calling GrShape::applyStyle).
332 // These unfortunately rely on knowing details of GrShape's implementation.
333 // These predicates are factored out here to avoid littering the rest of the
334 // test code with GrShape implementation details.
335 virtual bool fillChangesGeom() const { return false; }
336 virtual bool strokeIsConvertedToFill() const { return false; }
337 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
338 // Is this something we expect GrShape to recognize as something simpler than a path.
339 virtual bool isNonPath(const SkPaint& paint) const { return true; }
340};
341
342class RectGeo : public Geo {
343public:
344 RectGeo(const SkRect& rect) : fRect(rect) {}
345
346 SkPath path() const override {
347 SkPath path;
348 path.addRect(fRect);
349 return path;
350 }
351
352 GrShape makeShape(const SkPaint& paint) const override {
353 return GrShape(fRect, paint);
354 }
355
356 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
357 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
358 // Converted to an outset rectangle.
359 return paint.getStrokeJoin() == SkPaint::kMiter_Join &&
360 paint.getStrokeMiter() >= SK_ScalarSqrt2;
361 }
362
363private:
364 SkRect fRect;
365};
366
367class RRectGeo : public Geo {
368public:
369 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
370
371 GrShape makeShape(const SkPaint& paint) const override {
372 return GrShape(fRRect, paint);
373 }
374
375 SkPath path() const override {
376 SkPath path;
377 path.addRRect(fRRect);
378 return path;
379 }
380
381 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
382 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
383 if (fRRect.isRect()) {
384 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
385 }
386 return false;
387 }
388
389private:
390 SkRRect fRRect;
391};
392
Brian Salomone4949402018-04-26 15:22:04 -0400393class ArcGeo : public Geo {
394public:
395 ArcGeo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter)
396 : fOval(oval)
397 , fStartAngle(startAngle)
398 , fSweepAngle(sweepAngle)
399 , fUseCenter(useCenter) {}
400
401 SkPath path() const override {
402 SkPath path;
403 SkPathPriv::CreateDrawArcPath(&path, fOval, fStartAngle, fSweepAngle, fUseCenter, false);
404 return path;
405 }
406
407 GrShape makeShape(const SkPaint& paint) const override {
408 return GrShape::MakeArc(fOval, fStartAngle, fSweepAngle, fUseCenter, GrStyle(paint));
409 }
410
411 // GrShape specializes when created from arc params but it doesn't recognize arcs from SkPath.
412 bool isNonPath(const SkPaint& paint) const override { return false; }
413
414private:
415 SkRect fOval;
416 SkScalar fStartAngle;
417 SkScalar fSweepAngle;
418 bool fUseCenter;
419};
420
bsalomona395f7c2016-08-24 17:47:40 -0700421class PathGeo : public Geo {
422public:
423 enum class Invert { kNo, kYes };
424
425 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
426 SkASSERT(!path.isInverseFillType());
427 if (Invert::kYes == invert) {
Mike Reedcf0e3c62019-12-03 16:26:15 -0500428 if (fPath.getFillType() == SkPathFillType::kEvenOdd) {
Mike Reed7d34dc72019-11-26 12:17:17 -0500429 fPath.setFillType(SkPathFillType::kInverseEvenOdd);
bsalomona395f7c2016-08-24 17:47:40 -0700430 } else {
Mike Reedcf0e3c62019-12-03 16:26:15 -0500431 SkASSERT(fPath.getFillType() == SkPathFillType::kWinding);
Mike Reed7d34dc72019-11-26 12:17:17 -0500432 fPath.setFillType(SkPathFillType::kInverseWinding);
bsalomona395f7c2016-08-24 17:47:40 -0700433 }
434 }
435 }
436
437 GrShape makeShape(const SkPaint& paint) const override {
438 return GrShape(fPath, paint);
439 }
440
441 SkPath path() const override { return fPath; }
442
443 bool fillChangesGeom() const override {
444 // unclosed rects get closed. Lines get turned into empty geometry
Brian Salomon085c0862017-08-31 15:44:51 -0400445 return this->isUnclosedRect() || fPath.isLine(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -0700446 }
447
448 bool strokeIsConvertedToFill() const override {
449 return this->isAxisAlignedLine();
450 }
451
452 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
453 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
454 if (this->isAxisAlignedLine()) {
455 // The fill is ignored (zero area) and the stroke is converted to a rrect.
456 return true;
457 }
458 SkRect rect;
459 unsigned start;
Mike Reed30bc5272019-11-22 18:34:02 +0000460 SkPathDirection dir;
bsalomona395f7c2016-08-24 17:47:40 -0700461 if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
462 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
463 }
464 return false;
465 }
466
467 bool isNonPath(const SkPaint& paint) const override {
468 return fPath.isLine(nullptr) || fPath.isEmpty();
469 }
470
471private:
472 bool isAxisAlignedLine() const {
473 SkPoint pts[2];
474 if (!fPath.isLine(pts)) {
475 return false;
476 }
477 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
478 }
479
480 bool isUnclosedRect() const {
481 bool closed;
482 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
483 }
484
485 SkPath fPath;
486};
487
488class RRectPathGeo : public PathGeo {
489public:
490 enum class RRectForStroke { kNo, kYes };
491
492 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
493 Invert invert)
494 : PathGeo(path, invert)
495 , fRRect(equivalentRRect)
496 , fRRectForStroke(rrectForStroke) {}
497
498 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
499 Invert invert)
500 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
501
502 bool isNonPath(const SkPaint& paint) const override {
503 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
504 return true;
505 }
506 return false;
507 }
508
509 const SkRRect& rrect() const { return fRRect; }
510
511private:
512 SkRRect fRRect;
513 RRectForStroke fRRectForStroke;
514};
515
bsalomon47cc7692016-04-26 12:56:00 -0700516class TestCase {
517public:
bsalomona395f7c2016-08-24 17:47:40 -0700518 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
Brian Salomonc8cdad72018-04-16 09:46:09 -0400519 SkScalar scale = SK_Scalar1)
520 : fBase(new GrShape(geo.makeShape(paint))) {
bsalomon97fd2d42016-05-09 13:02:01 -0700521 this->init(r, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700522 }
523
Brian Salomonc8cdad72018-04-16 09:46:09 -0400524 template <typename... ShapeArgs>
525 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs) : fBase(new GrShape(shapeArgs...)) {
bsalomona395f7c2016-08-24 17:47:40 -0700526 this->init(r, SK_Scalar1);
527 }
528
bsalomon70493962016-06-10 08:05:14 -0700529 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
Brian Salomonc8cdad72018-04-16 09:46:09 -0400530 : fBase(new GrShape(shape)) {
bsalomon70493962016-06-10 08:05:14 -0700531 this->init(r, scale);
532 }
533
bsalomon47cc7692016-04-26 12:56:00 -0700534 struct SelfExpectations {
535 bool fPEHasEffect;
536 bool fPEHasValidKey;
537 bool fStrokeApplies;
538 };
539
540 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
541
542 enum ComparisonExpecation {
543 kAllDifferent_ComparisonExpecation,
544 kSameUpToPE_ComparisonExpecation,
545 kSameUpToStroke_ComparisonExpecation,
546 kAllSame_ComparisonExpecation,
547 };
548
549 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
550
Brian Salomonc8cdad72018-04-16 09:46:09 -0400551 const GrShape& baseShape() const { return *fBase; }
552 const GrShape& appliedPathEffectShape() const { return *fAppliedPE; }
553 const GrShape& appliedFullStyleShape() const { return *fAppliedFull; }
bsalomon72dc51c2016-04-27 06:46:23 -0700554
555 // The returned array's count will be 0 if the key shape has no key.
556 const Key& baseKey() const { return fBaseKey; }
557 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
558 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700559 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700560
bsalomon47cc7692016-04-26 12:56:00 -0700561private:
bsalomon9fb42032016-05-13 09:23:38 -0700562 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
563 SkPath path;
564 shape.asPath(&path);
565 // If the bounds are empty, the path ought to be as well.
bsalomon0ae36a22016-07-18 07:31:13 -0700566 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
bsalomon9fb42032016-05-13 09:23:38 -0700567 REPORTER_ASSERT(r, path.isEmpty());
568 return;
569 }
570 if (path.isEmpty()) {
571 return;
572 }
bsalomon70493962016-06-10 08:05:14 -0700573 // The bounds API explicitly calls out that it does not consider inverseness.
574 SkPath p = path;
Mike Reedcf0e3c62019-12-03 16:26:15 -0500575 p.setFillType(SkPathFillType_ConvertToNonInverse(path.getFillType()));
bsalomon70493962016-06-10 08:05:14 -0700576 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
bsalomon9fb42032016-05-13 09:23:38 -0700577 }
578
bsalomon97fd2d42016-05-09 13:02:01 -0700579 void init(skiatest::Reporter* r, SkScalar scale) {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400580 fAppliedPE.reset(new GrShape);
581 fAppliedPEThenStroke.reset(new GrShape);
582 fAppliedFull.reset(new GrShape);
bsalomon47cc7692016-04-26 12:56:00 -0700583
Brian Salomonc8cdad72018-04-16 09:46:09 -0400584 *fAppliedPE = fBase->applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
585 *fAppliedPEThenStroke =
586 fAppliedPE->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
587 *fAppliedFull = fBase->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
588
589 make_key(&fBaseKey, *fBase);
590 make_key(&fAppliedPEKey, *fAppliedPE);
591 make_key(&fAppliedPEThenStrokeKey, *fAppliedPEThenStroke);
592 make_key(&fAppliedFullKey, *fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700593
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400594 // All shapes should report the same "original" path, so that path renderers can get to it
595 // if necessary.
Brian Salomonc8cdad72018-04-16 09:46:09 -0400596 check_original_path_ids(r, *fBase, *fAppliedPE, *fAppliedPEThenStroke, *fAppliedFull);
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400597
bsalomonfb083272016-05-04 08:27:41 -0700598 // Applying the path effect and then the stroke should always be the same as applying
599 // both in one go.
600 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
601 SkPath a, b;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400602 fAppliedPEThenStroke->asPath(&a);
603 fAppliedFull->asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700604 // If the output of the path effect is a rrect then it is possible for a and b to be
605 // different paths that fill identically. The reason is that fAppliedFull will do this:
606 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
607 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
608 // now that there is no longer a path effect, the direction and starting index get
609 // canonicalized before the stroke.
Brian Salomonc8cdad72018-04-16 09:46:09 -0400610 if (fAppliedPE->asRRect(nullptr, nullptr, nullptr, nullptr)) {
bsalomonee295642016-06-06 14:01:25 -0700611 REPORTER_ASSERT(r, paths_fill_same(a, b));
612 } else {
613 REPORTER_ASSERT(r, a == b);
614 }
Brian Salomonc8cdad72018-04-16 09:46:09 -0400615 REPORTER_ASSERT(r, fAppliedFull->isEmpty() == fAppliedPEThenStroke->isEmpty());
bsalomon7c73a532016-05-11 15:15:56 -0700616
617 SkPath path;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400618 fBase->asPath(&path);
619 REPORTER_ASSERT(r, path.isEmpty() == fBase->isEmpty());
620 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase->segmentMask());
621 fAppliedPE->asPath(&path);
622 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE->isEmpty());
623 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE->segmentMask());
624 fAppliedFull->asPath(&path);
625 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull->isEmpty());
626 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull->segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700627
Brian Salomonc8cdad72018-04-16 09:46:09 -0400628 CheckBounds(r, *fBase, fBase->bounds());
629 CheckBounds(r, *fAppliedPE, fAppliedPE->bounds());
630 CheckBounds(r, *fAppliedPEThenStroke, fAppliedPEThenStroke->bounds());
631 CheckBounds(r, *fAppliedFull, fAppliedFull->bounds());
632 SkRect styledBounds = fBase->styledBounds();
633 CheckBounds(r, *fAppliedFull, styledBounds);
634 styledBounds = fAppliedPE->styledBounds();
635 CheckBounds(r, *fAppliedFull, styledBounds);
bsalomon9fb42032016-05-13 09:23:38 -0700636
bsalomonfb083272016-05-04 08:27:41 -0700637 // Check that the same path is produced when style is applied by GrShape and GrStyle.
638 SkPath preStyle;
639 SkPath postPathEffect;
640 SkPath postAllStyle;
641
Brian Salomonc8cdad72018-04-16 09:46:09 -0400642 fBase->asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700643 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
Brian Salomonc8cdad72018-04-16 09:46:09 -0400644 if (fBase->style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
645 scale)) {
bsalomon1a0b9ed2016-05-06 11:07:03 -0700646 // run postPathEffect through GrShape to get any geometry reductions that would have
647 // occurred to fAppliedPE.
648 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
649
bsalomonfb083272016-05-04 08:27:41 -0700650 SkPath testPath;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400651 fAppliedPE->asPath(&testPath);
bsalomonfb083272016-05-04 08:27:41 -0700652 REPORTER_ASSERT(r, testPath == postPathEffect);
Brian Salomonc8cdad72018-04-16 09:46:09 -0400653 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE->style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700654 }
655 SkStrokeRec::InitStyle fillOrHairline;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400656 if (fBase->style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomonfb083272016-05-04 08:27:41 -0700657 SkPath testPath;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400658 fAppliedFull->asPath(&testPath);
659 if (fBase->style().hasPathEffect()) {
bsalomon1b28c1a2016-06-20 12:28:17 -0700660 // Because GrShape always does two-stage application when there is a path effect
661 // there may be a reduction/canonicalization step between the path effect and
662 // strokerec not reflected in postAllStyle since it applied both the path effect
663 // and strokerec without analyzing the intermediate path.
664 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
665 } else {
666 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape
667 // would apply.
668 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
669 REPORTER_ASSERT(r, testPath == postAllStyle);
670 }
671
bsalomonfb083272016-05-04 08:27:41 -0700672 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400673 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleFill());
bsalomonfb083272016-05-04 08:27:41 -0700674 } else {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400675 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleHairline());
bsalomonfb083272016-05-04 08:27:41 -0700676 }
677 }
Brian Salomonc8cdad72018-04-16 09:46:09 -0400678 test_inversions(r, *fBase, fBaseKey);
679 test_inversions(r, *fAppliedPE, fAppliedPEKey);
680 test_inversions(r, *fAppliedFull, fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700681 }
682
Brian Salomonc8cdad72018-04-16 09:46:09 -0400683 std::unique_ptr<GrShape> fBase;
684 std::unique_ptr<GrShape> fAppliedPE;
685 std::unique_ptr<GrShape> fAppliedPEThenStroke;
686 std::unique_ptr<GrShape> fAppliedFull;
bsalomon47cc7692016-04-26 12:56:00 -0700687
688 Key fBaseKey;
689 Key fAppliedPEKey;
690 Key fAppliedPEThenStrokeKey;
691 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700692};
693
694void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700695 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700696 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700697 if (expectations.fPEHasEffect) {
698 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700699 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700700 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700701 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700702 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
703 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700704 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700705 }
706 } else {
707 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700708 SkPath a, b;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400709 fBase->asPath(&a);
710 fAppliedPE->asPath(&b);
bsalomon72dc51c2016-04-27 06:46:23 -0700711 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700712 if (expectations.fStrokeApplies) {
713 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
714 } else {
715 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
716 }
717 }
718}
719
bsalomonee295642016-06-06 14:01:25 -0700720void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700721 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700722 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700723 switch (expectation) {
724 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700725 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
726 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
727 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700728 break;
729 case kSameUpToPE_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400730 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
bsalomonee295642016-06-06 14:01:25 -0700731 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
732 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700733 break;
734 case kSameUpToStroke_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400735 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
736 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
bsalomonee295642016-06-06 14:01:25 -0700737 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700738 break;
739 case kAllSame_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400740 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
741 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
742 check_equivalence(r, *fAppliedFull, *that.fAppliedFull, fAppliedFullKey,
bsalomonee295642016-06-06 14:01:25 -0700743 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700744 break;
745 }
746}
747} // namespace
748
749static sk_sp<SkPathEffect> make_dash() {
750 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
751 static const SkScalar kPhase = 0.75;
752 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
753}
754
755static sk_sp<SkPathEffect> make_null_dash() {
756 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
757 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
758}
759
Mike Klein43344282017-08-16 11:56:22 -0400760// We make enough TestCases, and they're large enough, that on Google3 builds we exceed
761// the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
762template <typename... Args>
763static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
764 return std::unique_ptr<TestCase>{ new TestCase(std::forward<Args>(args)...) };
765}
766
bsalomona395f7c2016-08-24 17:47:40 -0700767static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700768 sk_sp<SkPathEffect> dashPE = make_dash();
769
770 TestCase::SelfExpectations expectations;
771 SkPaint fill;
772
bsalomonfb083272016-05-04 08:27:41 -0700773 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700774 expectations.fPEHasEffect = false;
775 expectations.fPEHasValidKey = false;
776 expectations.fStrokeApplies = false;
777 fillCase.testExpectations(reporter, expectations);
778 // Test that another GrShape instance built from the same primitive is the same.
Mike Klein43344282017-08-16 11:56:22 -0400779 make_TestCase(geo, fill, reporter)
780 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700781
782 SkPaint stroke2RoundBevel;
783 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
784 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
785 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
786 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700787 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700788 expectations.fPEHasValidKey = true;
789 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700790 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomon47cc7692016-04-26 12:56:00 -0700791 stroke2RoundBevelCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400792 make_TestCase(geo, stroke2RoundBevel, reporter)
793 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700794
795 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
796 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700797 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700798 expectations.fPEHasValidKey = true;
799 expectations.fPEHasEffect = true;
800 expectations.fStrokeApplies = true;
801 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400802 make_TestCase(geo, stroke2RoundBevelDash, reporter)
803 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700804
bsalomona395f7c2016-08-24 17:47:40 -0700805 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700806 fillCase.compare(reporter, stroke2RoundBevelCase,
807 TestCase::kAllDifferent_ComparisonExpecation);
808 fillCase.compare(reporter, stroke2RoundBevelDashCase,
809 TestCase::kAllDifferent_ComparisonExpecation);
810 } else {
811 fillCase.compare(reporter, stroke2RoundBevelCase,
812 TestCase::kSameUpToStroke_ComparisonExpecation);
813 fillCase.compare(reporter, stroke2RoundBevelDashCase,
814 TestCase::kSameUpToPE_ComparisonExpecation);
815 }
bsalomona395f7c2016-08-24 17:47:40 -0700816 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700817 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
818 TestCase::kAllDifferent_ComparisonExpecation);
819 } else {
820 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
821 TestCase::kSameUpToPE_ComparisonExpecation);
822 }
bsalomon72dc51c2016-04-27 06:46:23 -0700823
bsalomonf0cf3552016-05-05 08:28:30 -0700824 // Stroke and fill cases
825 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
826 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
827 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
828 expectations.fPEHasValidKey = true;
829 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700830 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700831 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400832 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
833 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700834
835 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
836 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
837 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
838 expectations.fPEHasValidKey = true;
bsalomona0587862016-06-09 06:03:38 -0700839 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700840 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700841 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400842 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
bsalomonf0cf3552016-05-05 08:28:30 -0700843 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomona0587862016-06-09 06:03:38 -0700844 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
845 TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700846
bsalomon72dc51c2016-04-27 06:46:23 -0700847 SkPaint hairline;
848 hairline.setStyle(SkPaint::kStroke_Style);
849 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700850 TestCase hairlineCase(geo, hairline, reporter);
bsalomon487f8d32016-07-20 07:15:44 -0700851 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
852 // in the line and unclosed rect cases).
bsalomona395f7c2016-08-24 17:47:40 -0700853 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -0700854 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
855 } else {
856 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
857 }
bsalomon9ad5d7c2016-05-04 08:44:15 -0700858 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
859 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
860 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700861
bsalomon0ae36a22016-07-18 07:31:13 -0700862}
863
bsalomona395f7c2016-08-24 17:47:40 -0700864static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon97fd2d42016-05-09 13:02:01 -0700865 sk_sp<SkPathEffect> dashPE = make_dash();
866
867 static const SkScalar kS1 = 1.f;
868 static const SkScalar kS2 = 2.f;
869
870 SkPaint fill;
871 TestCase fillCase1(geo, fill, reporter, kS1);
872 TestCase fillCase2(geo, fill, reporter, kS2);
873 // Scale doesn't affect fills.
874 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
875
876 SkPaint hairline;
877 hairline.setStyle(SkPaint::kStroke_Style);
878 hairline.setStrokeWidth(0.f);
879 TestCase hairlineCase1(geo, hairline, reporter, kS1);
880 TestCase hairlineCase2(geo, hairline, reporter, kS2);
881 // Scale doesn't affect hairlines.
882 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
883
884 SkPaint stroke;
885 stroke.setStyle(SkPaint::kStroke_Style);
886 stroke.setStrokeWidth(2.f);
887 TestCase strokeCase1(geo, stroke, reporter, kS1);
888 TestCase strokeCase2(geo, stroke, reporter, kS2);
bsalomon0ae36a22016-07-18 07:31:13 -0700889 // Scale affects the stroke
bsalomona395f7c2016-08-24 17:47:40 -0700890 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700891 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
bsalomon0ae36a22016-07-18 07:31:13 -0700892 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
893 } else {
894 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
895 }
bsalomon97fd2d42016-05-09 13:02:01 -0700896
897 SkPaint strokeDash = stroke;
898 strokeDash.setPathEffect(make_dash());
899 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
900 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
901 // Scale affects the dash and the stroke.
bsalomon487f8d32016-07-20 07:15:44 -0700902 strokeDashCase1.compare(reporter, strokeDashCase2,
903 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700904
905 // Stroke and fill cases
906 SkPaint strokeAndFill = stroke;
907 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
908 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
909 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
bsalomona0587862016-06-09 06:03:38 -0700910 SkPaint strokeAndFillDash = strokeDash;
911 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
912 // Dash is ignored for stroke and fill
913 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
914 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
bsalomon487f8d32016-07-20 07:15:44 -0700915 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
916 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
917 // geometries should agree.
bsalomona395f7c2016-08-24 17:47:40 -0700918 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
bsalomon487f8d32016-07-20 07:15:44 -0700919 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
bsalomon97fd2d42016-05-09 13:02:01 -0700920 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
921 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700922 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
923 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700924 } else {
925 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
926 TestCase::kSameUpToStroke_ComparisonExpecation);
927 }
bsalomona0587862016-06-09 06:03:38 -0700928 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
929 TestCase::kAllSame_ComparisonExpecation);
930 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
931 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700932}
933
bsalomona395f7c2016-08-24 17:47:40 -0700934template <typename T>
935static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700936 std::function<void(SkPaint*, T)> setter, T a, T b,
937 bool paramAffectsStroke,
938 bool paramAffectsDashAndStroke) {
939 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
940 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700941 SkPaint strokeA;
942 strokeA.setStyle(SkPaint::kStroke_Style);
943 strokeA.setStrokeWidth(2.f);
944 setter(&strokeA, a);
945 SkPaint strokeB;
946 strokeB.setStyle(SkPaint::kStroke_Style);
947 strokeB.setStrokeWidth(2.f);
948 setter(&strokeB, b);
949
bsalomonfb083272016-05-04 08:27:41 -0700950 TestCase strokeACase(geo, strokeA, reporter);
951 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700952 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700953 // If stroking is immediately incorporated into a geometric transformation then the base
954 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700955 if (geo.strokeIsConvertedToFill()) {
bsalomon0ae36a22016-07-18 07:31:13 -0700956 strokeACase.compare(reporter, strokeBCase,
957 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700958 } else {
959 strokeACase.compare(reporter, strokeBCase,
960 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700961 }
bsalomon06077562016-05-04 13:50:29 -0700962 } else {
963 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
964 }
bsalomon47cc7692016-04-26 12:56:00 -0700965
bsalomonf0cf3552016-05-05 08:28:30 -0700966 SkPaint strokeAndFillA = strokeA;
967 SkPaint strokeAndFillB = strokeB;
968 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
969 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
970 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
971 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
972 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700973 // If stroking is immediately incorporated into a geometric transformation then the base
974 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700975 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
976 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700977 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700978 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700979 } else {
980 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700981 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700982 }
bsalomonf0cf3552016-05-05 08:28:30 -0700983 } else {
984 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
985 TestCase::kAllSame_ComparisonExpecation);
986 }
987
bsalomon47cc7692016-04-26 12:56:00 -0700988 // Make sure stroking params don't affect fill style.
989 SkPaint fillA = strokeA, fillB = strokeB;
990 fillA.setStyle(SkPaint::kFill_Style);
991 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -0700992 TestCase fillACase(geo, fillA, reporter);
993 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700994 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
995
996 // Make sure just applying the dash but not stroke gives the same key for both stroking
997 // variations.
998 SkPaint dashA = strokeA, dashB = strokeB;
999 dashA.setPathEffect(make_dash());
1000 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -07001001 TestCase dashACase(geo, dashA, reporter);
1002 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -07001003 if (paramAffectsDashAndStroke) {
bsalomon487f8d32016-07-20 07:15:44 -07001004 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon06077562016-05-04 13:50:29 -07001005 } else {
1006 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
1007 }
bsalomon47cc7692016-04-26 12:56:00 -07001008}
1009
bsalomona395f7c2016-08-24 17:47:40 -07001010template <typename T>
1011static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -07001012 std::function<void(SkPaint*, T)> setter, T a, T b) {
1013 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
1014};
1015
bsalomona395f7c2016-08-24 17:47:40 -07001016static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
1017 SkPaint hairline;
1018 hairline.setStrokeWidth(0);
1019 hairline.setStyle(SkPaint::kStroke_Style);
1020 GrShape shape = geo.makeShape(hairline);
bsalomon06077562016-05-04 13:50:29 -07001021 // The cap should only affect shapes that may be open.
1022 bool affectsStroke = !shape.knownToBeClosed();
1023 // Dashing adds ends that need caps.
1024 bool affectsDashAndStroke = true;
bsalomona395f7c2016-08-24 17:47:40 -07001025 test_stroke_param_impl<SkPaint::Cap>(
bsalomon06077562016-05-04 13:50:29 -07001026 reporter,
1027 geo,
1028 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
1029 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
1030 affectsStroke,
1031 affectsDashAndStroke);
1032};
1033
bsalomon0ae36a22016-07-18 07:31:13 -07001034static bool shape_known_not_to_have_joins(const GrShape& shape) {
1035 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
1036}
1037
bsalomona395f7c2016-08-24 17:47:40 -07001038static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
1039 SkPaint hairline;
1040 hairline.setStrokeWidth(0);
1041 hairline.setStyle(SkPaint::kStroke_Style);
1042 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001043 // GrShape recognizes certain types don't have joins and will prevent the join type from
1044 // affecting the style key.
1045 // Dashing doesn't add additional joins. However, GrShape currently loses track of this
1046 // after applying the dash.
1047 bool affectsStroke = !shape_known_not_to_have_joins(shape);
bsalomona395f7c2016-08-24 17:47:40 -07001048 test_stroke_param_impl<SkPaint::Join>(
bsalomon0ae36a22016-07-18 07:31:13 -07001049 reporter,
1050 geo,
1051 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
1052 SkPaint::kRound_Join, SkPaint::kBevel_Join,
1053 affectsStroke, true);
1054};
1055
bsalomona395f7c2016-08-24 17:47:40 -07001056static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon06077562016-05-04 13:50:29 -07001057 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1058 p->setStrokeJoin(SkPaint::kMiter_Join);
1059 p->setStrokeMiter(miter);
1060 };
bsalomon47cc7692016-04-26 12:56:00 -07001061
bsalomon06077562016-05-04 13:50:29 -07001062 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1063 p->setStrokeJoin(SkPaint::kRound_Join);
1064 p->setStrokeMiter(miter);
1065 };
bsalomon47cc7692016-04-26 12:56:00 -07001066
bsalomona395f7c2016-08-24 17:47:40 -07001067 SkPaint hairline;
1068 hairline.setStrokeWidth(0);
1069 hairline.setStyle(SkPaint::kStroke_Style);
1070 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001071 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
1072
bsalomon06077562016-05-04 13:50:29 -07001073 // The miter limit should affect stroked and dashed-stroked cases when the join type is
1074 // miter.
bsalomona395f7c2016-08-24 17:47:40 -07001075 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001076 reporter,
1077 geo,
1078 setMiterJoinAndLimit,
1079 0.5f, 0.75f,
bsalomon0ae36a22016-07-18 07:31:13 -07001080 mayHaveJoins,
bsalomon06077562016-05-04 13:50:29 -07001081 true);
bsalomon47cc7692016-04-26 12:56:00 -07001082
bsalomon06077562016-05-04 13:50:29 -07001083 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
1084 // not miter.
bsalomona395f7c2016-08-24 17:47:40 -07001085 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001086 reporter,
1087 geo,
1088 setOtherJoinAndLimit,
1089 0.5f, 0.75f,
1090 false,
1091 false);
bsalomon47cc7692016-04-26 12:56:00 -07001092}
1093
bsalomona395f7c2016-08-24 17:47:40 -07001094static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001095 // A dash with no stroke should have no effect
1096 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
1097 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
1098 SkPaint dashFill;
1099 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -07001100 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001101
bsalomonfb083272016-05-04 08:27:41 -07001102 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001103 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
1104 }
1105}
1106
bsalomona395f7c2016-08-24 17:47:40 -07001107void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001108 SkPaint fill;
1109 SkPaint stroke;
1110 stroke.setStyle(SkPaint::kStroke_Style);
1111 stroke.setStrokeWidth(1.f);
1112 SkPaint dash;
1113 dash.setStyle(SkPaint::kStroke_Style);
1114 dash.setStrokeWidth(1.f);
1115 dash.setPathEffect(make_dash());
1116 SkPaint nullDash;
1117 nullDash.setStyle(SkPaint::kStroke_Style);
1118 nullDash.setStrokeWidth(1.f);
1119 nullDash.setPathEffect(make_null_dash());
1120
bsalomonfb083272016-05-04 08:27:41 -07001121 TestCase fillCase(geo, fill, reporter);
1122 TestCase strokeCase(geo, stroke, reporter);
1123 TestCase dashCase(geo, dash, reporter);
1124 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001125
bsalomon487f8d32016-07-20 07:15:44 -07001126 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
bsalomon47cc7692016-04-26 12:56:00 -07001127 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -07001128 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
1129 // on construction in order to determine how to compare the fill and stroke.
bsalomona395f7c2016-08-24 17:47:40 -07001130 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001131 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
1132 } else {
1133 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1134 }
1135 // 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 -07001136 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001137 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
1138 } else {
1139 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
1140 }
bsalomon47cc7692016-04-26 12:56:00 -07001141}
1142
bsalomona395f7c2016-08-24 17:47:40 -07001143void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001144 /**
1145 * This path effect takes any input path and turns it into a rrect. It passes through stroke
1146 * info.
1147 */
1148 class RRectPathEffect : SkPathEffect {
1149 public:
1150 static const SkRRect& RRect() {
1151 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1152 return kRRect;
1153 }
1154
Mike Reed6d10f8b2018-08-16 13:22:16 -04001155 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1156 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001157 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001158
1159 protected:
1160 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1161 const SkRect* cullR) const override {
bsalomon72dc51c2016-04-27 06:46:23 -07001162 dst->reset();
1163 dst->addRRect(RRect());
1164 return true;
1165 }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001166
1167 SkRect onComputeFastBounds(const SkRect& src) const override {
1168 return RRect().getBounds();
bsalomon72dc51c2016-04-27 06:46:23 -07001169 }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001170
bsalomon72dc51c2016-04-27 06:46:23 -07001171 private:
1172 RRectPathEffect() {}
1173 };
1174
1175 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -07001176 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001177
1178 SkPaint pe;
1179 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001180 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001181
1182 SkPaint peStroke;
1183 peStroke.setPathEffect(RRectPathEffect::Make());
1184 peStroke.setStrokeWidth(2.f);
1185 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001186 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001187
bsalomon487f8d32016-07-20 07:15:44 -07001188 // Check whether constructing the filled case would cause the base shape to have a different
1189 // geometry (because of a geometric transformation upon initial GrShape construction).
bsalomona395f7c2016-08-24 17:47:40 -07001190 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -07001191 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1192 fillGeoCase.compare(reporter, geoPEStrokeCase,
1193 TestCase::kAllDifferent_ComparisonExpecation);
1194 } else {
1195 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1196 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1197 }
bsalomon72dc51c2016-04-27 06:46:23 -07001198 geoPECase.compare(reporter, geoPEStrokeCase,
1199 TestCase::kSameUpToStroke_ComparisonExpecation);
1200
bsalomona395f7c2016-08-24 17:47:40 -07001201 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
bsalomon72dc51c2016-04-27 06:46:23 -07001202 SkPaint stroke = peStroke;
1203 stroke.setPathEffect(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -07001204 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001205
1206 SkRRect rrect;
1207 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1208 // geoPECase, so the full style should be the same as just the PE.
bsalomon70493962016-06-10 08:05:14 -07001209 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1210 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001211 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1212 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1213
bsalomon70493962016-06-10 08:05:14 -07001214 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1215 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001216 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1217 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1218
1219 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomon70493962016-06-10 08:05:14 -07001220 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1221 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001222 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1223 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1224
bsalomon70493962016-06-10 08:05:14 -07001225 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1226 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001227 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1228 rrectStrokeCase.appliedFullStyleKey());
1229}
1230
bsalomona395f7c2016-08-24 17:47:40 -07001231void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001232 /**
1233 * This path effect just adds two lineTos to the input path.
1234 */
1235 class AddLineTosPathEffect : SkPathEffect {
1236 public:
Mike Reed6d10f8b2018-08-16 13:22:16 -04001237 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1238 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001239 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001240
1241 protected:
1242 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1243 const SkRect* cullR) const override {
bsalomon72dc51c2016-04-27 06:46:23 -07001244 *dst = src;
bsalomon67fa4e32016-09-21 08:26:57 -07001245 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1246 for (int i = 0; i < 100; ++i) {
1247 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1248 }
bsalomon72dc51c2016-04-27 06:46:23 -07001249 return true;
1250 }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001251 SkRect onComputeFastBounds(const SkRect& src) const override {
1252 SkRect dst = src;
1253 SkRectPriv::GrowToInclude(&dst, {0, 0});
1254 SkRectPriv::GrowToInclude(&dst, {100, 100});
1255 return dst;
bsalomon72dc51c2016-04-27 06:46:23 -07001256 }
bsalomon72dc51c2016-04-27 06:46:23 -07001257 private:
1258 AddLineTosPathEffect() {}
1259 };
1260
bsalomon9ad5d7c2016-05-04 08:44:15 -07001261 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -07001262 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1263 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1264 SkPaint peStroke;
1265 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1266 peStroke.setStrokeWidth(2.f);
1267 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001268 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001269 TestCase::SelfExpectations expectations;
1270 expectations.fPEHasEffect = true;
1271 expectations.fPEHasValidKey = false;
1272 expectations.fStrokeApplies = true;
1273 geoPEStrokeCase.testExpectations(reporter, expectations);
1274}
1275
bsalomona395f7c2016-08-24 17:47:40 -07001276void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon9ad5d7c2016-05-04 08:44:15 -07001277 /**
1278 * This path effect just changes the stroke rec to hairline.
1279 */
1280 class MakeHairlinePathEffect : SkPathEffect {
1281 public:
bsalomon9ad5d7c2016-05-04 08:44:15 -07001282 static sk_sp<SkPathEffect> Make() {
1283 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1284 }
1285 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001286 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001287
1288 protected:
1289 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1290 const SkRect* cullR) const override {
1291 *dst = src;
1292 strokeRec->setHairlineStyle();
1293 return true;
1294 }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001295 private:
1296 MakeHairlinePathEffect() {}
1297 };
1298
1299 SkPaint fill;
1300 SkPaint pe;
1301 pe.setPathEffect(MakeHairlinePathEffect::Make());
1302
1303 TestCase peCase(geo, pe, reporter);
1304
bsalomonee295642016-06-06 14:01:25 -07001305 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -07001306 peCase.baseShape().asPath(&a);
1307 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -07001308 peCase.appliedFullStyleShape().asPath(&c);
bsalomona395f7c2016-08-24 17:47:40 -07001309 if (geo.isNonPath(pe)) {
bsalomonee295642016-06-06 14:01:25 -07001310 // RRect types can have a change in start index or direction after the PE is applied. This
1311 // is because once the PE is applied, GrShape may canonicalize the dir and index since it
1312 // is not germane to the styling any longer.
1313 // Instead we just check that the paths would fill the same both before and after styling.
1314 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1315 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -07001316 } else {
bsalomona4817af2016-06-23 11:48:26 -07001317 // The base shape cannot perform canonicalization on the path's fill type because of an
1318 // unknown path effect. However, after the path effect is applied the resulting hairline
1319 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1320 // don't distinguish between even/odd and non-zero winding.
1321 a.setFillType(b.getFillType());
bsalomonee295642016-06-06 14:01:25 -07001322 REPORTER_ASSERT(reporter, a == b);
1323 REPORTER_ASSERT(reporter, a == c);
bsalomon67fa4e32016-09-21 08:26:57 -07001324 // If the resulting path is small enough then it will have a key.
1325 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1326 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomonaa840642016-09-23 12:09:16 -07001327 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1328 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001329 }
bsalomonee295642016-06-06 14:01:25 -07001330 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1331 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001332}
1333
bsalomona395f7c2016-08-24 17:47:40 -07001334void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1335 SkPath vPath = geo.path();
bsalomon4eeccc92016-04-27 13:30:25 -07001336 vPath.setIsVolatile(true);
1337
1338 SkPaint dashAndStroke;
1339 dashAndStroke.setPathEffect(make_dash());
1340 dashAndStroke.setStrokeWidth(2.f);
1341 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001342 TestCase volatileCase(reporter, vPath, dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001343 // We expect a shape made from a volatile path to have a key iff the shape is recognized
bsalomonaa840642016-09-23 12:09:16 -07001344 // as a specialized geometry.
1345 if (geo.isNonPath(dashAndStroke)) {
bsalomon4eeccc92016-04-27 13:30:25 -07001346 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1347 // In this case all the keys should be identical to the non-volatile case.
bsalomona395f7c2016-08-24 17:47:40 -07001348 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001349 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1350 } else {
1351 // None of the keys should be valid.
1352 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1353 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1354 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1355 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1356 }
1357}
1358
bsalomona395f7c2016-08-24 17:47:40 -07001359void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon409ed732016-04-27 12:36:02 -07001360 /**
Brian Salomon085c0862017-08-31 15:44:51 -04001361 * This path effect returns an empty path (possibly inverted)
bsalomon409ed732016-04-27 12:36:02 -07001362 */
1363 class EmptyPathEffect : SkPathEffect {
1364 public:
Mike Reed6d10f8b2018-08-16 13:22:16 -04001365 static sk_sp<SkPathEffect> Make(bool invert) {
1366 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
1367 }
1368 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001369 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001370 protected:
1371 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1372 const SkRect* cullR) const override {
bsalomon409ed732016-04-27 12:36:02 -07001373 dst->reset();
Brian Salomon085c0862017-08-31 15:44:51 -04001374 if (fInvert) {
1375 dst->toggleInverseFillType();
1376 }
bsalomon409ed732016-04-27 12:36:02 -07001377 return true;
1378 }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001379 SkRect onComputeFastBounds(const SkRect& src) const override {
1380 return { 0, 0, 0, 0 };
bsalomon409ed732016-04-27 12:36:02 -07001381 }
bsalomon409ed732016-04-27 12:36:02 -07001382 private:
Brian Salomon085c0862017-08-31 15:44:51 -04001383 bool fInvert;
1384 EmptyPathEffect(bool invert) : fInvert(invert) {}
bsalomon409ed732016-04-27 12:36:02 -07001385 };
1386
1387 SkPath emptyPath;
1388 GrShape emptyShape(emptyPath);
1389 Key emptyKey;
1390 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -07001391 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001392
Brian Salomon085c0862017-08-31 15:44:51 -04001393 emptyPath.toggleInverseFillType();
1394 GrShape invertedEmptyShape(emptyPath);
1395 Key invertedEmptyKey;
1396 make_key(&invertedEmptyKey, invertedEmptyShape);
1397 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
1398
1399 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
1400
bsalomon409ed732016-04-27 12:36:02 -07001401 SkPaint pe;
Brian Salomon085c0862017-08-31 15:44:51 -04001402 pe.setPathEffect(EmptyPathEffect::Make(false));
1403 TestCase geoPECase(geo, pe, reporter);
1404 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
1405 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
1406 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
1407 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
1408 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
1409 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
1410 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001411
1412 SkPaint peStroke;
Brian Salomon085c0862017-08-31 15:44:51 -04001413 peStroke.setPathEffect(EmptyPathEffect::Make(false));
bsalomon409ed732016-04-27 12:36:02 -07001414 peStroke.setStrokeWidth(2.f);
1415 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001416 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001417 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1418 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1419 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001420 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1421 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001422 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
1423 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
1424 pe.setPathEffect(EmptyPathEffect::Make(true));
1425
1426 TestCase geoPEInvertCase(geo, pe, reporter);
1427 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
1428 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
1429 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1430 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
1431 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
1432 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
1433 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
1434
1435 peStroke.setPathEffect(EmptyPathEffect::Make(true));
1436 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
1437 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
1438 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
1439 REPORTER_ASSERT(reporter,
1440 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1441 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
1442 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
1443 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
1444 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001445}
1446
bsalomona395f7c2016-08-24 17:47:40 -07001447void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
bsalomond6723842016-06-07 12:20:15 -07001448 /**
bsalomon0ae36a22016-07-18 07:31:13 -07001449 * This path effect always fails to apply.
bsalomond6723842016-06-07 12:20:15 -07001450 */
1451 class FailurePathEffect : SkPathEffect {
1452 public:
bsalomond6723842016-06-07 12:20:15 -07001453 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1454 Factory getFactory() const override { return nullptr; }
Mike Klein4fee3232018-10-18 17:27:16 -04001455 const char* getTypeName() const override { return nullptr; }
Mike Reed6d10f8b2018-08-16 13:22:16 -04001456 protected:
1457 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1458 const SkRect* cullR) const override {
1459 return false;
1460 }
bsalomond6723842016-06-07 12:20:15 -07001461 private:
1462 FailurePathEffect() {}
1463 };
1464
1465 SkPaint fill;
1466 TestCase fillCase(geo, fill, reporter);
1467
1468 SkPaint pe;
1469 pe.setPathEffect(FailurePathEffect::Make());
1470 TestCase peCase(geo, pe, reporter);
1471
1472 SkPaint stroke;
1473 stroke.setStrokeWidth(2.f);
1474 stroke.setStyle(SkPaint::kStroke_Style);
1475 TestCase strokeCase(geo, stroke, reporter);
1476
1477 SkPaint peStroke = stroke;
1478 peStroke.setPathEffect(FailurePathEffect::Make());
1479 TestCase peStrokeCase(geo, peStroke, reporter);
1480
1481 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1482 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1483 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its
1484 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from
1485 // closing it but after the effect fails we can (for the fill+pe case). This causes different
1486 // routes through GrShape to have equivalent but different representations of the path (closed
1487 // or not) but that fill the same.
1488 SkPath a;
1489 SkPath b;
1490 fillCase.appliedPathEffectShape().asPath(&a);
1491 peCase.appliedPathEffectShape().asPath(&b);
1492 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1493
1494 fillCase.appliedFullStyleShape().asPath(&a);
1495 peCase.appliedFullStyleShape().asPath(&b);
1496 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1497
1498 strokeCase.appliedPathEffectShape().asPath(&a);
1499 peStrokeCase.appliedPathEffectShape().asPath(&b);
1500 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1501
1502 strokeCase.appliedFullStyleShape().asPath(&a);
1503 peStrokeCase.appliedFullStyleShape().asPath(&b);
1504 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1505}
1506
Mike Klein43344282017-08-16 11:56:22 -04001507DEF_TEST(GrShape_empty_shape, reporter) {
bsalomon409ed732016-04-27 12:36:02 -07001508 SkPath emptyPath;
Brian Salomon085c0862017-08-31 15:44:51 -04001509 SkPath invertedEmptyPath;
1510 invertedEmptyPath.toggleInverseFillType();
bsalomon409ed732016-04-27 12:36:02 -07001511 SkPaint fill;
bsalomona395f7c2016-08-24 17:47:40 -07001512 TestCase fillEmptyCase(reporter, emptyPath, fill);
bsalomon7c73a532016-05-11 15:15:56 -07001513 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1514 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1515 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001516 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
1517 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
1518 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
1519 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
1520 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
1521 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
1522 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
1523 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
1524 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
1525 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001526
1527 Key emptyKey(fillEmptyCase.baseKey());
1528 REPORTER_ASSERT(reporter, emptyKey.count());
Brian Salomon085c0862017-08-31 15:44:51 -04001529 Key inverseEmptyKey(fillInvertedEmptyCase.baseKey());
1530 REPORTER_ASSERT(reporter, inverseEmptyKey.count());
bsalomon409ed732016-04-27 12:36:02 -07001531 TestCase::SelfExpectations expectations;
1532 expectations.fStrokeApplies = false;
1533 expectations.fPEHasEffect = false;
1534 // This will test whether applying style preserves emptiness
1535 fillEmptyCase.testExpectations(reporter, expectations);
Brian Salomon085c0862017-08-31 15:44:51 -04001536 fillInvertedEmptyCase.testExpectations(reporter, expectations);
bsalomon409ed732016-04-27 12:36:02 -07001537
1538 // Stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001539 SkPaint stroke;
1540 stroke.setStrokeWidth(2.f);
1541 stroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001542 stroke.setStrokeJoin(SkPaint::kRound_Join);
1543 stroke.setStrokeCap(SkPaint::kRound_Cap);
Brian Salomon085c0862017-08-31 15:44:51 -04001544 TestCase strokeEmptyCase(reporter, emptyPath, stroke);
bsalomon409ed732016-04-27 12:36:02 -07001545 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001546 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
1547 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
1548 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001549
1550 // Dashing and stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001551 SkPaint dashAndStroke;
1552 dashAndStroke.setPathEffect(make_dash());
1553 dashAndStroke.setStrokeWidth(2.f);
1554 dashAndStroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon085c0862017-08-31 15:44:51 -04001555 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
bsalomon409ed732016-04-27 12:36:02 -07001556 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1557 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001558 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
1559 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1560 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
1561 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001562
Brian Salomon2fad74a2017-12-20 13:28:55 -05001563 // A shape made from an empty rrect should behave the same as an empty path when filled but not
1564 // when stroked. However, dashing an empty rrect produces an empty path leaving nothing to
1565 // stroke - so equivalent to filling an empty path.
1566 SkRRect emptyRRect = SkRRect::MakeEmpty();
bsalomon5e410b42016-04-28 09:30:46 -07001567 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001568
1569 TestCase fillEmptyRRectCase(reporter, emptyRRect, fill);
1570 fillEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1571
1572 TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke);
1573 strokeEmptyRRectCase.compare(reporter, strokeEmptyCase,
1574 TestCase::kAllDifferent_ComparisonExpecation);
1575
bsalomona395f7c2016-08-24 17:47:40 -07001576 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001577 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1578 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001579
Mike Reed30bc5272019-11-22 18:34:02 +00001580 static constexpr SkPathDirection kDir = SkPathDirection::kCCW;
Brian Salomon085c0862017-08-31 15:44:51 -04001581 static constexpr int kStart = 0;
Brian Salomon2fad74a2017-12-20 13:28:55 -05001582
1583 TestCase fillInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, GrStyle(fill));
1584 fillInvertedEmptyRRectCase.compare(reporter, fillInvertedEmptyCase,
1585 TestCase::kAllSame_ComparisonExpecation);
1586
1587 TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true,
1588 GrStyle(stroke));
1589 strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase,
1590 TestCase::kAllDifferent_ComparisonExpecation);
1591
Brian Salomon085c0862017-08-31 15:44:51 -04001592 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
1593 GrStyle(dashAndStroke));
Brian Salomon085c0862017-08-31 15:44:51 -04001594 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
1595 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001596
1597 // Same for a rect.
1598 SkRect emptyRect = SkRect::MakeEmpty();
Brian Salomon2fad74a2017-12-20 13:28:55 -05001599 TestCase fillEmptyRectCase(reporter, emptyRect, fill);
1600 fillEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1601
bsalomona395f7c2016-08-24 17:47:40 -07001602 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001603 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1604 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001605
Brian Salomon085c0862017-08-31 15:44:51 -04001606 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
1607 kStart, true, GrStyle(dashAndStroke));
1608 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1609 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
1610 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001611}
1612
bsalomon70493962016-06-10 08:05:14 -07001613// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1614// canonical point in these cases.
1615unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1616 switch (rrect.getType()) {
1617 case SkRRect::kRect_Type:
1618 return (s + 1) & 0b110;
1619 case SkRRect::kOval_Type:
1620 return s & 0b110;
1621 default:
1622 return s;
1623 }
1624}
1625
1626void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
bsalomoncadb5a22016-06-10 18:28:06 -07001627 enum Style {
bsalomon70493962016-06-10 08:05:14 -07001628 kFill,
1629 kStroke,
1630 kHairline,
1631 kStrokeAndFill
1632 };
1633
1634 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1635 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1636 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1637 strokeRecs[kFill].setFillStyle();
1638 strokeRecs[kStroke].setStrokeStyle(2.f);
1639 strokeRecs[kHairline].setHairlineStyle();
1640 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
bsalomon487f8d32016-07-20 07:15:44 -07001641 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1642 // applyStyle() is called.
1643 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
bsalomon70493962016-06-10 08:05:14 -07001644 sk_sp<SkPathEffect> dashEffect = make_dash();
1645
bsalomoncadb5a22016-06-10 18:28:06 -07001646 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1647
1648 auto index = [](bool inverted,
Mike Reed30bc5272019-11-22 18:34:02 +00001649 SkPathDirection dir,
bsalomoncadb5a22016-06-10 18:28:06 -07001650 unsigned start,
1651 Style style,
1652 bool dash) -> int {
1653 return inverted * (2 * 8 * kStyleCnt * 2) +
Mike Reed30bc5272019-11-22 18:34:02 +00001654 (int)dir * ( 8 * kStyleCnt * 2) +
bsalomoncadb5a22016-06-10 18:28:06 -07001655 start * ( kStyleCnt * 2) +
1656 style * ( 2) +
1657 dash;
1658 };
Mike Reed30bc5272019-11-22 18:34:02 +00001659 static const SkPathDirection kSecondDirection = static_cast<SkPathDirection>(1);
bsalomoncadb5a22016-06-10 18:28:06 -07001660 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1661 SkAutoTArray<GrShape> shapes(cnt);
1662 for (bool inverted : {false, true}) {
Mike Reed30bc5272019-11-22 18:34:02 +00001663 for (SkPathDirection dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
bsalomoncadb5a22016-06-10 18:28:06 -07001664 for (unsigned start = 0; start < 8; ++start) {
1665 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1666 for (bool dash : {false, true}) {
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001667 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
bsalomoncadb5a22016-06-10 18:28:06 -07001668 shapes[index(inverted, dir, start, style, dash)] =
1669 GrShape(rrect, dir, start, SkToBool(inverted),
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001670 GrStyle(strokeRecs[style], std::move(pe)));
bsalomon70493962016-06-10 08:05:14 -07001671 }
1672 }
1673 }
1674 }
1675 }
1676
bsalomonfd32df72016-06-14 14:37:21 -07001677 // Get the keys for some example shape instances that we'll use for comparision against the
1678 // rest.
Mike Reed30bc5272019-11-22 18:34:02 +00001679 static constexpr SkPathDirection kExamplesDir = SkPathDirection::kCW;
bsalomonfd32df72016-06-14 14:37:21 -07001680 static constexpr unsigned kExamplesStart = 0;
1681 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1682 false)];
bsalomon70493962016-06-10 08:05:14 -07001683 Key exampleFillCaseKey;
1684 make_key(&exampleFillCaseKey, exampleFillCase);
1685
bsalomonfd32df72016-06-14 14:37:21 -07001686 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart,
1687 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001688 Key exampleStrokeAndFillCaseKey;
1689 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1690
bsalomonfd32df72016-06-14 14:37:21 -07001691 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill,
1692 false)];
bsalomon70493962016-06-10 08:05:14 -07001693 Key exampleInvFillCaseKey;
1694 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1695
bsalomonfd32df72016-06-14 14:37:21 -07001696 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart,
1697 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001698 Key exampleInvStrokeAndFillCaseKey;
1699 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1700
bsalomonfd32df72016-06-14 14:37:21 -07001701 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke,
1702 false)];
bsalomon70493962016-06-10 08:05:14 -07001703 Key exampleStrokeCaseKey;
1704 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1705
bsalomonfd32df72016-06-14 14:37:21 -07001706 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke,
1707 false)];
1708 Key exampleInvStrokeCaseKey;
1709 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1710
1711 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1712 kHairline, false)];
bsalomon70493962016-06-10 08:05:14 -07001713 Key exampleHairlineCaseKey;
1714 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1715
bsalomonfd32df72016-06-14 14:37:21 -07001716 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1717 kHairline, false)];
1718 Key exampleInvHairlineCaseKey;
1719 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1720
bsalomon70493962016-06-10 08:05:14 -07001721 // These are dummy initializations to suppress warnings.
bsalomoncadb5a22016-06-10 18:28:06 -07001722 SkRRect queryRR = SkRRect::MakeEmpty();
Mike Reed30bc5272019-11-22 18:34:02 +00001723 SkPathDirection queryDir = SkPathDirection::kCW;
bsalomoncadb5a22016-06-10 18:28:06 -07001724 unsigned queryStart = ~0U;
1725 bool queryInverted = true;
bsalomon70493962016-06-10 08:05:14 -07001726
bsalomoncadb5a22016-06-10 18:28:06 -07001727 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1728 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001729 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001730 REPORTER_ASSERT(r, 0 == queryStart);
1731 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001732
bsalomoncadb5a22016-06-10 18:28:06 -07001733 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1734 &queryInverted));
1735 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001736 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001737 REPORTER_ASSERT(r, 0 == queryStart);
1738 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001739
bsalomoncadb5a22016-06-10 18:28:06 -07001740 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1741 &queryInverted));
1742 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001743 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001744 REPORTER_ASSERT(r, 0 == queryStart);
1745 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001746
bsalomoncadb5a22016-06-10 18:28:06 -07001747 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1748 &queryInverted));
1749 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001750 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001751 REPORTER_ASSERT(r, 0 == queryStart);
1752 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001753
bsalomoncadb5a22016-06-10 18:28:06 -07001754 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1755 &queryInverted));
1756 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001757 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001758 REPORTER_ASSERT(r, 0 == queryStart);
1759 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001760
bsalomonfd32df72016-06-14 14:37:21 -07001761 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1762 &queryInverted));
1763 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001764 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomonfd32df72016-06-14 14:37:21 -07001765 REPORTER_ASSERT(r, 0 == queryStart);
1766 REPORTER_ASSERT(r, queryInverted);
1767
bsalomoncadb5a22016-06-10 18:28:06 -07001768 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1769 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001770 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomoncadb5a22016-06-10 18:28:06 -07001771 REPORTER_ASSERT(r, 0 == queryStart);
1772 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001773
bsalomonfd32df72016-06-14 14:37:21 -07001774 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1775 &queryInverted));
1776 REPORTER_ASSERT(r, queryRR == rrect);
Mike Reed30bc5272019-11-22 18:34:02 +00001777 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
bsalomonfd32df72016-06-14 14:37:21 -07001778 REPORTER_ASSERT(r, 0 == queryStart);
1779 REPORTER_ASSERT(r, queryInverted);
1780
bsalomon70493962016-06-10 08:05:14 -07001781 // Remember that the key reflects the geometry before styling is applied.
1782 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1783 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1784 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1785 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001786 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001787 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001788 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001789 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001790 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1791 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001792
bsalomoncadb5a22016-06-10 18:28:06 -07001793 for (bool inverted : {false, true}) {
Mike Reed30bc5272019-11-22 18:34:02 +00001794 for (SkPathDirection dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
bsalomoncadb5a22016-06-10 18:28:06 -07001795 for (unsigned start = 0; start < 8; ++start) {
1796 for (bool dash : {false, true}) {
1797 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001798 Key fillCaseKey;
1799 make_key(&fillCaseKey, fillCase);
1800
bsalomoncadb5a22016-06-10 18:28:06 -07001801 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1802 kStrokeAndFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001803 Key strokeAndFillCaseKey;
1804 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1805
1806 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1807 // ignore dashing.
1808 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1809 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1810 TestCase a(fillCase, r);
1811 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1812 TestCase c(strokeAndFillCase, r);
1813 TestCase d(inverted ? exampleInvStrokeAndFillCase
1814 : exampleStrokeAndFillCase, r);
1815 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1816 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1817
bsalomoncadb5a22016-06-10 18:28:06 -07001818 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)];
1819 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline,
1820 dash)];
bsalomon70493962016-06-10 08:05:14 -07001821
1822 TestCase e(strokeCase, r);
bsalomon70493962016-06-10 08:05:14 -07001823 TestCase g(hairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001824
bsalomonfd32df72016-06-14 14:37:21 -07001825 // Both hairline and stroke shapes must respect the dashing.
bsalomon70493962016-06-10 08:05:14 -07001826 if (dash) {
bsalomonfd32df72016-06-14 14:37:21 -07001827 // Dashing always ignores the inverseness. skbug.com/5421
1828 TestCase f(exampleStrokeCase, r);
1829 TestCase h(exampleHairlineCase, r);
bsalomoncadb5a22016-06-10 18:28:06 -07001830 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
bsalomon70493962016-06-10 08:05:14 -07001831 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1832 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1833
bsalomoncadb5a22016-06-10 18:28:06 -07001834 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1835 &queryInverted));
1836 REPORTER_ASSERT(r, queryRR == rrect);
1837 REPORTER_ASSERT(r, queryDir == dir);
1838 REPORTER_ASSERT(r, queryStart == expectedStart);
1839 REPORTER_ASSERT(r, !queryInverted);
1840 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1841 &queryInverted));
1842 REPORTER_ASSERT(r, queryRR == rrect);
1843 REPORTER_ASSERT(r, queryDir == dir);
1844 REPORTER_ASSERT(r, queryStart == expectedStart);
1845 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001846
1847 // The pre-style case for the dash will match the non-dash example iff the
1848 // dir and start match (dir=cw, start=0).
Mike Reed30bc5272019-11-22 18:34:02 +00001849 if (0 == expectedStart && SkPathDirection::kCW == dir) {
bsalomon70493962016-06-10 08:05:14 -07001850 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1851 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1852 } else {
1853 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1854 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1855 }
1856 } else {
bsalomonfd32df72016-06-14 14:37:21 -07001857 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1858 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001859 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1860 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1861 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1862 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1863 }
1864 }
1865 }
1866 }
1867 }
1868}
1869
Mike Klein43344282017-08-16 11:56:22 -04001870DEF_TEST(GrShape_lines, r) {
bsalomon0a0f67e2016-06-28 11:56:42 -07001871 static constexpr SkPoint kA { 1, 1};
1872 static constexpr SkPoint kB { 5, -9};
1873 static constexpr SkPoint kC {-3, 17};
1874
1875 SkPath lineAB;
1876 lineAB.moveTo(kA);
1877 lineAB.lineTo(kB);
1878
1879 SkPath lineBA;
1880 lineBA.moveTo(kB);
1881 lineBA.lineTo(kA);
1882
1883 SkPath lineAC;
1884 lineAC.moveTo(kB);
1885 lineAC.lineTo(kC);
1886
1887 SkPath invLineAB = lineAB;
Mike Reed7d34dc72019-11-26 12:17:17 -05001888 invLineAB.setFillType(SkPathFillType::kInverseEvenOdd);
bsalomon0a0f67e2016-06-28 11:56:42 -07001889
1890 SkPaint fill;
1891 SkPaint stroke;
1892 stroke.setStyle(SkPaint::kStroke_Style);
1893 stroke.setStrokeWidth(2.f);
1894 SkPaint hairline;
1895 hairline.setStyle(SkPaint::kStroke_Style);
1896 hairline.setStrokeWidth(0.f);
1897 SkPaint dash = stroke;
1898 dash.setPathEffect(make_dash());
1899
bsalomona395f7c2016-08-24 17:47:40 -07001900 TestCase fillAB(r, lineAB, fill);
1901 TestCase fillEmpty(r, SkPath(), fill);
bsalomon0a0f67e2016-06-28 11:56:42 -07001902 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1903 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1904
Brian Salomon085c0862017-08-31 15:44:51 -04001905 SkPath path;
1906 path.toggleInverseFillType();
1907 TestCase fillEmptyInverted(r, path, fill);
1908 TestCase fillABInverted(r, invLineAB, fill);
1909 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
1910 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
1911
bsalomona395f7c2016-08-24 17:47:40 -07001912 TestCase strokeAB(r, lineAB, stroke);
1913 TestCase strokeBA(r, lineBA, stroke);
1914 TestCase strokeAC(r, lineAC, stroke);
bsalomon0a0f67e2016-06-28 11:56:42 -07001915
bsalomona395f7c2016-08-24 17:47:40 -07001916 TestCase hairlineAB(r, lineAB, hairline);
1917 TestCase hairlineBA(r, lineBA, hairline);
1918 TestCase hairlineAC(r, lineAC, hairline);
bsalomon0a0f67e2016-06-28 11:56:42 -07001919
bsalomona395f7c2016-08-24 17:47:40 -07001920 TestCase dashAB(r, lineAB, dash);
1921 TestCase dashBA(r, lineBA, dash);
1922 TestCase dashAC(r, lineAC, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001923
1924 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1925
1926 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1927 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1928
1929 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1930 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1931
1932 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1933 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1934
1935 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1936
1937 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1938 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1939 bool canonicalizeAsAB;
1940 SkPoint canonicalPts[2] {kA, kB};
1941 // Init these to suppress warnings.
1942 bool inverted = true;
1943 SkPoint pts[2] {{0, 0}, {0, 0}};
1944 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1945 if (pts[0] == kA && pts[1] == kB) {
1946 canonicalizeAsAB = true;
1947 } else if (pts[1] == kA && pts[0] == kB) {
1948 canonicalizeAsAB = false;
Ben Wagnerf08d1d02018-06-18 15:11:00 -04001949 using std::swap;
1950 swap(canonicalPts[0], canonicalPts[1]);
bsalomon0a0f67e2016-06-28 11:56:42 -07001951 } else {
1952 ERRORF(r, "Should return pts (a,b) or (b, a)");
1953 return;
Brian Salomon23356442018-11-30 15:33:19 -05001954 }
bsalomon0a0f67e2016-06-28 11:56:42 -07001955
1956 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1957 TestCase::kSameUpToPE_ComparisonExpecation);
1958 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1959 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1960 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1961 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1962 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1963 pts[0] == kA && pts[1] == kB);
1964 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1965 pts[0] == kB && pts[1] == kA);
1966
1967
bsalomona395f7c2016-08-24 17:47:40 -07001968 TestCase strokeInvAB(r, invLineAB, stroke);
1969 TestCase hairlineInvAB(r, invLineAB, hairline);
1970 TestCase dashInvAB(r, invLineAB, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001971 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1972 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1973 // Dashing ignores inverse.
1974 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1975
1976 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1977 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1978 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1979 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1980 // Dashing ignores inverse.
1981 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1982 pts[0] == kA && pts[1] == kB);
1983
1984}
1985
Mike Klein43344282017-08-16 11:56:22 -04001986DEF_TEST(GrShape_stroked_lines, r) {
Brian Salomon72f78c32017-12-21 11:56:42 -05001987 static constexpr SkScalar kIntervals1[] = {1.f, 0.f};
1988 auto dash1 = SkDashPathEffect::Make(kIntervals1, SK_ARRAY_COUNT(kIntervals1), 0.f);
1989 REPORTER_ASSERT(r, dash1);
1990 static constexpr SkScalar kIntervals2[] = {10.f, 0.f, 5.f, 0.f};
1991 auto dash2 = SkDashPathEffect::Make(kIntervals2, SK_ARRAY_COUNT(kIntervals2), 10.f);
1992 REPORTER_ASSERT(r, dash2);
bsalomon0ae36a22016-07-18 07:31:13 -07001993
Brian Salomon72f78c32017-12-21 11:56:42 -05001994 sk_sp<SkPathEffect> pathEffects[] = {nullptr, std::move(dash1), std::move(dash2)};
bsalomon0ae36a22016-07-18 07:31:13 -07001995
Brian Salomon72f78c32017-12-21 11:56:42 -05001996 for (const auto& pe : pathEffects) {
1997 // Paints to try
1998 SkPaint buttCap;
1999 buttCap.setStyle(SkPaint::kStroke_Style);
2000 buttCap.setStrokeWidth(4);
2001 buttCap.setStrokeCap(SkPaint::kButt_Cap);
2002 buttCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07002003
Brian Salomon72f78c32017-12-21 11:56:42 -05002004 SkPaint squareCap = buttCap;
2005 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
2006 squareCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07002007
Brian Salomon72f78c32017-12-21 11:56:42 -05002008 SkPaint roundCap = buttCap;
2009 roundCap.setStrokeCap(SkPaint::kRound_Cap);
2010 roundCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07002011
Brian Salomon72f78c32017-12-21 11:56:42 -05002012 // vertical
2013 SkPath linePath;
2014 linePath.moveTo(4, 4);
2015 linePath.lineTo(4, 5);
bsalomon0ae36a22016-07-18 07:31:13 -07002016
Brian Salomon72f78c32017-12-21 11:56:42 -05002017 SkPaint fill;
bsalomon0ae36a22016-07-18 07:31:13 -07002018
Brian Salomon72f78c32017-12-21 11:56:42 -05002019 make_TestCase(r, linePath, buttCap)->compare(
2020 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
2021 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002022
Brian Salomon72f78c32017-12-21 11:56:42 -05002023 make_TestCase(r, linePath, squareCap)->compare(
2024 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
2025 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002026
Brian Salomon72f78c32017-12-21 11:56:42 -05002027 make_TestCase(r, linePath, roundCap)->compare(r,
2028 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
2029 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002030
Brian Salomon72f78c32017-12-21 11:56:42 -05002031 // horizontal
2032 linePath.reset();
2033 linePath.moveTo(4, 4);
2034 linePath.lineTo(5, 4);
bsalomon0ae36a22016-07-18 07:31:13 -07002035
Brian Salomon72f78c32017-12-21 11:56:42 -05002036 make_TestCase(r, linePath, buttCap)->compare(
2037 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
2038 TestCase::kAllSame_ComparisonExpecation);
2039 make_TestCase(r, linePath, squareCap)->compare(
2040 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
2041 TestCase::kAllSame_ComparisonExpecation);
2042 make_TestCase(r, linePath, roundCap)->compare(
2043 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
2044 TestCase::kAllSame_ComparisonExpecation);
2045
2046 // point
2047 linePath.reset();
2048 linePath.moveTo(4, 4);
2049 linePath.lineTo(4, 4);
2050
2051 make_TestCase(r, linePath, buttCap)->compare(
2052 r, TestCase(r, SkRect::MakeEmpty(), fill),
2053 TestCase::kAllSame_ComparisonExpecation);
2054 make_TestCase(r, linePath, squareCap)->compare(
2055 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
2056 TestCase::kAllSame_ComparisonExpecation);
2057 make_TestCase(r, linePath, roundCap)->compare(
2058 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
2059 TestCase::kAllSame_ComparisonExpecation);
2060 }
bsalomon0ae36a22016-07-18 07:31:13 -07002061}
2062
Mike Klein43344282017-08-16 11:56:22 -04002063DEF_TEST(GrShape_short_path_keys, r) {
bsalomon67fa4e32016-09-21 08:26:57 -07002064 SkPaint paints[4];
2065 paints[1].setStyle(SkPaint::kStroke_Style);
2066 paints[1].setStrokeWidth(5.f);
2067 paints[2].setStyle(SkPaint::kStroke_Style);
2068 paints[2].setStrokeWidth(0.f);
2069 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
2070 paints[3].setStrokeWidth(5.f);
2071
bsalomonaa840642016-09-23 12:09:16 -07002072 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
bsalomon67fa4e32016-09-21 08:26:57 -07002073 TestCase::ComparisonExpecation expectation) {
bsalomonaa840642016-09-23 12:09:16 -07002074 SkPath volatileA = pathA;
2075 SkPath volatileB = pathB;
2076 volatileA.setIsVolatile(true);
2077 volatileB.setIsVolatile(true);
bsalomon67fa4e32016-09-21 08:26:57 -07002078 for (const SkPaint& paint : paints) {
bsalomonaa840642016-09-23 12:09:16 -07002079 REPORTER_ASSERT(r, !GrShape(volatileA, paint).hasUnstyledKey());
2080 REPORTER_ASSERT(r, !GrShape(volatileB, paint).hasUnstyledKey());
bsalomon67fa4e32016-09-21 08:26:57 -07002081 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
bsalomonaa840642016-09-23 12:09:16 -07002082 TestCase caseA(PathGeo(pathA, invert), paint, r);
2083 TestCase caseB(PathGeo(pathB, invert), paint, r);
2084 caseA.compare(r, caseB, expectation);
bsalomon67fa4e32016-09-21 08:26:57 -07002085 }
2086 }
2087 };
2088
2089 SkPath pathA;
2090 SkPath pathB;
2091
2092 // Two identical paths
2093 pathA.lineTo(10.f, 10.f);
2094 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2095
2096 pathB.lineTo(10.f, 10.f);
2097 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002098 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002099
2100 // Give path b a different point
2101 pathB.reset();
2102 pathB.lineTo(10.f, 10.f);
2103 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002104 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002105
2106 // Give path b a different conic weight
2107 pathB.reset();
2108 pathB.lineTo(10.f, 10.f);
2109 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
bsalomonaa840642016-09-23 12:09:16 -07002110 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002111
2112 // Give path b an extra lineTo verb
2113 pathB.reset();
2114 pathB.lineTo(10.f, 10.f);
2115 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2116 pathB.lineTo(50.f, 50.f);
bsalomonaa840642016-09-23 12:09:16 -07002117 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002118
2119 // Give path b a close
2120 pathB.reset();
2121 pathB.lineTo(10.f, 10.f);
2122 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2123 pathB.close();
bsalomonaa840642016-09-23 12:09:16 -07002124 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002125}
2126
bsalomon47cc7692016-04-26 12:56:00 -07002127DEF_TEST(GrShape, reporter) {
bsalomona395f7c2016-08-24 17:47:40 -07002128 SkTArray<std::unique_ptr<Geo>> geos;
2129 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
2130
bsalomonee295642016-06-06 14:01:25 -07002131 for (auto r : { SkRect::MakeWH(10, 20),
2132 SkRect::MakeWH(-10, -20),
2133 SkRect::MakeWH(-10, 20),
2134 SkRect::MakeWH(10, -20)}) {
bsalomona395f7c2016-08-24 17:47:40 -07002135 geos.emplace_back(new RectGeo(r));
2136 SkPath rectPath;
2137 rectPath.addRect(r);
2138 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2139 PathGeo::Invert::kNo));
2140 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2141 PathGeo::Invert::kYes));
2142 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2143 PathGeo::Invert::kNo));
bsalomonee295642016-06-06 14:01:25 -07002144 }
bsalomon47cc7692016-04-26 12:56:00 -07002145 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -07002146 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
2147 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomona395f7c2016-08-24 17:47:40 -07002148 geos.emplace_back(new RRectGeo(rr));
bsalomon70493962016-06-10 08:05:14 -07002149 test_rrect(reporter, rr);
bsalomona395f7c2016-08-24 17:47:40 -07002150 SkPath rectPath;
2151 rectPath.addRRect(rr);
2152 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2153 PathGeo::Invert::kNo));
2154 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2155 PathGeo::Invert::kYes));
2156 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
2157 RRectPathGeo::RRectForStroke::kYes,
2158 PathGeo::Invert::kNo));
bsalomon72dc51c2016-04-27 06:46:23 -07002159 }
2160
Brian Salomone4949402018-04-26 15:22:04 -04002161 // Arcs
2162 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, false));
2163 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, true));
2164
Mike Klein43344282017-08-16 11:56:22 -04002165 {
2166 SkPath openRectPath;
2167 openRectPath.moveTo(0, 0);
2168 openRectPath.lineTo(10, 0);
2169 openRectPath.lineTo(10, 10);
2170 openRectPath.lineTo(0, 10);
2171 geos.emplace_back(new RRectPathGeo(
2172 openRectPath, SkRect::MakeWH(10, 10),
2173 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2174 geos.emplace_back(new RRectPathGeo(
2175 openRectPath, SkRect::MakeWH(10, 10),
2176 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
2177 rrectPathGeos.emplace_back(new RRectPathGeo(
2178 openRectPath, SkRect::MakeWH(10, 10),
2179 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2180 }
bsalomon72dc51c2016-04-27 06:46:23 -07002181
Mike Klein43344282017-08-16 11:56:22 -04002182 {
2183 SkPath quadPath;
2184 quadPath.quadTo(10, 10, 5, 8);
2185 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
2186 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
2187 }
bsalomon398e3f42016-06-13 10:22:48 -07002188
Mike Klein43344282017-08-16 11:56:22 -04002189 {
2190 SkPath linePath;
2191 linePath.lineTo(10, 10);
2192 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
2193 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
2194 }
bsalomon72dc51c2016-04-27 06:46:23 -07002195
bsalomon0ae36a22016-07-18 07:31:13 -07002196 // Horizontal and vertical paths become rrects when stroked.
Mike Klein43344282017-08-16 11:56:22 -04002197 {
2198 SkPath vLinePath;
2199 vLinePath.lineTo(0, 10);
2200 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
2201 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
2202 }
bsalomon0ae36a22016-07-18 07:31:13 -07002203
Mike Klein43344282017-08-16 11:56:22 -04002204 {
2205 SkPath hLinePath;
2206 hLinePath.lineTo(10, 0);
2207 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
2208 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
2209 }
bsalomon0ae36a22016-07-18 07:31:13 -07002210
bsalomona395f7c2016-08-24 17:47:40 -07002211 for (int i = 0; i < geos.count(); ++i) {
2212 test_basic(reporter, *geos[i]);
2213 test_scale(reporter, *geos[i]);
2214 test_dash_fill(reporter, *geos[i]);
2215 test_null_dash(reporter, *geos[i]);
2216 // Test modifying various stroke params.
2217 test_stroke_param<SkScalar>(
2218 reporter, *geos[i],
bsalomon70493962016-06-10 08:05:14 -07002219 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
2220 SkIntToScalar(2), SkIntToScalar(4));
bsalomona395f7c2016-08-24 17:47:40 -07002221 test_stroke_join(reporter, *geos[i]);
2222 test_stroke_cap(reporter, *geos[i]);
2223 test_miter_limit(reporter, *geos[i]);
2224 test_path_effect_makes_rrect(reporter, *geos[i]);
2225 test_unknown_path_effect(reporter, *geos[i]);
2226 test_path_effect_makes_empty_shape(reporter, *geos[i]);
2227 test_path_effect_fails(reporter, *geos[i]);
2228 test_make_hairline_path_effect(reporter, *geos[i]);
2229 test_volatile_path(reporter, *geos[i]);
bsalomon70493962016-06-10 08:05:14 -07002230 }
bsalomonfd32df72016-06-14 14:37:21 -07002231
bsalomona395f7c2016-08-24 17:47:40 -07002232 for (int i = 0; i < rrectPathGeos.count(); ++i) {
2233 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
bsalomon72dc51c2016-04-27 06:46:23 -07002234 SkPaint fillPaint;
bsalomona395f7c2016-08-24 17:47:40 -07002235 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002236 SkRRect rrect;
bsalomona395f7c2016-08-24 17:47:40 -07002237 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
bsalomon70493962016-06-10 08:05:14 -07002238 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2239 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002240 if (rrgeo.isNonPath(fillPaint)) {
2241 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
2242 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2243 TestCase fillRRectCase(reporter, rrect, fillPaint);
bsalomon70493962016-06-10 08:05:14 -07002244 fillPathCase2.compare(reporter, fillRRectCase,
2245 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002246 }
bsalomon72dc51c2016-04-27 06:46:23 -07002247 SkPaint strokePaint;
2248 strokePaint.setStrokeWidth(3.f);
2249 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07002250 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
2251 if (rrgeo.isNonPath(strokePaint)) {
bsalomon0ae36a22016-07-18 07:31:13 -07002252 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2253 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002254 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2255 TestCase strokeRRectCase(reporter, rrect, strokePaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002256 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07002257 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002258 }
bsalomon47cc7692016-04-26 12:56:00 -07002259 }
bsalomon409ed732016-04-27 12:36:02 -07002260
bsalomon4eeccc92016-04-27 13:30:25 -07002261 // Test a volatile empty path.
bsalomona395f7c2016-08-24 17:47:40 -07002262 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
bsalomon47cc7692016-04-26 12:56:00 -07002263}
2264
Brian Salomone4949402018-04-26 15:22:04 -04002265DEF_TEST(GrShape_arcs, reporter) {
2266 SkStrokeRec roundStroke(SkStrokeRec::kFill_InitStyle);
2267 roundStroke.setStrokeStyle(2.f);
2268 roundStroke.setStrokeParams(SkPaint::kRound_Cap, SkPaint::kRound_Join, 1.f);
2269
2270 SkStrokeRec squareStroke(roundStroke);
2271 squareStroke.setStrokeParams(SkPaint::kSquare_Cap, SkPaint::kRound_Join, 1.f);
2272
2273 SkStrokeRec roundStrokeAndFill(roundStroke);
2274 roundStrokeAndFill.setStrokeStyle(2.f, true);
2275
2276 static constexpr SkScalar kIntervals[] = {1, 2};
2277 auto dash = SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), 1.5f);
2278
2279 SkTArray<GrStyle> styles;
2280 styles.push_back(GrStyle::SimpleFill());
2281 styles.push_back(GrStyle::SimpleHairline());
2282 styles.push_back(GrStyle(roundStroke, nullptr));
2283 styles.push_back(GrStyle(squareStroke, nullptr));
2284 styles.push_back(GrStyle(roundStrokeAndFill, nullptr));
2285 styles.push_back(GrStyle(roundStroke, dash));
2286
2287 for (const auto& style : styles) {
2288 // An empty rect never draws anything according to SkCanvas::drawArc() docs.
2289 TestCase emptyArc(GrShape::MakeArc(SkRect::MakeEmpty(), 0, 90.f, false, style), reporter);
2290 TestCase emptyPath(reporter, SkPath(), style);
2291 emptyArc.compare(reporter, emptyPath, TestCase::kAllSame_ComparisonExpecation);
2292
2293 static constexpr SkRect kOval1{0, 0, 50, 50};
2294 static constexpr SkRect kOval2{50, 0, 100, 50};
2295 // Test that swapping starting and ending angle doesn't change the shape unless the arc
2296 // has a path effect. Also test that different ovals produce different shapes.
2297 TestCase arc1CW(GrShape::MakeArc(kOval1, 0, 90.f, false, style), reporter);
2298 TestCase arc1CCW(GrShape::MakeArc(kOval1, 90.f, -90.f, false, style), reporter);
2299
2300 TestCase arc1CWWithCenter(GrShape::MakeArc(kOval1, 0, 90.f, true, style), reporter);
2301 TestCase arc1CCWWithCenter(GrShape::MakeArc(kOval1, 90.f, -90.f, true, style), reporter);
2302
2303 TestCase arc2CW(GrShape::MakeArc(kOval2, 0, 90.f, false, style), reporter);
2304 TestCase arc2CWWithCenter(GrShape::MakeArc(kOval2, 0, 90.f, true, style), reporter);
2305
2306 auto reversedExepectations = style.hasPathEffect()
2307 ? TestCase::kAllDifferent_ComparisonExpecation
2308 : TestCase::kAllSame_ComparisonExpecation;
2309 arc1CW.compare(reporter, arc1CCW, reversedExepectations);
2310 arc1CWWithCenter.compare(reporter, arc1CCWWithCenter, reversedExepectations);
2311 arc1CW.compare(reporter, arc2CW, TestCase::kAllDifferent_ComparisonExpecation);
2312 arc1CW.compare(reporter, arc1CWWithCenter, TestCase::kAllDifferent_ComparisonExpecation);
2313 arc1CWWithCenter.compare(reporter, arc2CWWithCenter,
2314 TestCase::kAllDifferent_ComparisonExpecation);
2315
2316 // Test that two arcs that start at the same angle but specified differently are equivalent.
2317 TestCase arc3A(GrShape::MakeArc(kOval1, 224.f, 73.f, false, style), reporter);
2318 TestCase arc3B(GrShape::MakeArc(kOval1, 224.f - 360.f, 73.f, false, style), reporter);
2319 arc3A.compare(reporter, arc3B, TestCase::kAllDifferent_ComparisonExpecation);
2320
2321 // Test that an arc that traverses the entire oval (and then some) is equivalent to the
2322 // oval itself unless there is a path effect.
2323 TestCase ovalArc(GrShape::MakeArc(kOval1, 150.f, -790.f, false, style), reporter);
2324 TestCase oval(GrShape(SkRRect::MakeOval(kOval1)), reporter);
2325 auto ovalExpectations = style.hasPathEffect() ? TestCase::kAllDifferent_ComparisonExpecation
2326 : TestCase::kAllSame_ComparisonExpecation;
2327 if (style.strokeRec().getWidth() >= 0 && style.strokeRec().getCap() != SkPaint::kButt_Cap) {
2328 ovalExpectations = TestCase::kAllDifferent_ComparisonExpecation;
2329 }
2330 ovalArc.compare(reporter, oval, ovalExpectations);
2331
2332 // If the the arc starts/ends at the center then it is then equivalent to the oval only for
2333 // simple fills.
2334 TestCase ovalArcWithCenter(GrShape::MakeArc(kOval1, 304.f, 1225.f, true, style), reporter);
2335 ovalExpectations = style.isSimpleFill() ? TestCase::kAllSame_ComparisonExpecation
2336 : TestCase::kAllDifferent_ComparisonExpecation;
2337 ovalArcWithCenter.compare(reporter, oval, ovalExpectations);
2338 }
2339}