blob: 930012c33f728d12621d44d99507b998bf423c69 [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
8#include <initializer_list>
9#include <functional>
10#include "Test.h"
bsalomon47cc7692016-04-26 12:56:00 -070011#include "GrShape.h"
bsalomon9fb42032016-05-13 09:23:38 -070012#include "SkCanvas.h"
bsalomon47cc7692016-04-26 12:56:00 -070013#include "SkDashPathEffect.h"
bsalomon9fb42032016-05-13 09:23:38 -070014#include "SkPath.h"
bsalomonee295642016-06-06 14:01:25 -070015#include "SkPathOps.h"
Mike Reed185ffe92018-01-08 17:09:54 -050016#include "SkRectPriv.h"
bsalomon9fb42032016-05-13 09:23:38 -070017#include "SkSurface.h"
Mike Reedebfce6d2016-12-12 10:02:12 -050018#include "SkClipOpPriv.h"
bsalomon47cc7692016-04-26 12:56:00 -070019
Brian Osmanf6f7cf62017-09-25 16:49:55 -040020uint32_t GrShape::testingOnly_getOriginalGenerationID() const {
Brian Salomonda6d0722018-01-03 13:54:35 -050021 if (const auto* lp = this->originalPathForListeners()) {
22 return lp->getGenerationID();
23 }
24 return SkPath().getGenerationID();
Brian Osmanf6f7cf62017-09-25 16:49:55 -040025}
26
Brian Osmanb379dcd2017-10-04 15:44:05 -040027bool GrShape::testingOnly_isPath() const {
28 return Type::kPath == fType;
29}
30
Brian Salomonda6d0722018-01-03 13:54:35 -050031bool GrShape::testingOnly_isNonVolatilePath() const {
32 return Type::kPath == fType && !fPathData.fPath.isVolatile();
33}
34
bsalomon72dc51c2016-04-27 06:46:23 -070035using Key = SkTArray<uint32_t>;
36
37static bool make_key(Key* key, const GrShape& shape) {
38 int size = shape.unstyledKeySize();
39 if (size <= 0) {
40 key->reset(0);
41 return false;
42 }
43 SkASSERT(size);
44 key->reset(size);
45 shape.writeUnstyledKey(key->begin());
46 return true;
47}
48
bsalomonee295642016-06-06 14:01:25 -070049static bool paths_fill_same(const SkPath& a, const SkPath& b) {
50 SkPath pathXor;
51 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
52 return pathXor.isEmpty();
53}
54
bsalomon9fb42032016-05-13 09:23:38 -070055static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
bsalomon164fd9f2016-08-26 06:45:06 -070056 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
57 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
58 // rendering within the bounds (with a tolerance). Then we render the path and check that
59 // everything got clipped out.
bsalomon9fb42032016-05-13 09:23:38 -070060 static constexpr int kRes = 2000;
61 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
Brian Salomone4949402018-04-26 15:22:04 -040062 static constexpr int kTol = 2;
bsalomon9fb42032016-05-13 09:23:38 -070063 GR_STATIC_ASSERT(kRes % 4 == 0);
64 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
65 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
66 surface->getCanvas()->clear(0x0);
67 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
68 SkMatrix matrix;
69 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit);
70 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
Mike Reedc1f77742016-12-09 09:00:50 -050071 surface->getCanvas()->clipRect(clip, kDifference_SkClipOp);
bsalomon9fb42032016-05-13 09:23:38 -070072 surface->getCanvas()->concat(matrix);
73 SkPaint whitePaint;
74 whitePaint.setColor(SK_ColorWHITE);
75 surface->getCanvas()->drawPath(path, whitePaint);
76 SkPixmap pixmap;
77 surface->getCanvas()->peekPixels(&pixmap);
78#if defined(SK_BUILD_FOR_WIN)
79 // The static constexpr version in #else causes cl.exe to crash.
80 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
81#else
82 static constexpr uint8_t kZeros[kRes] = {0};
83#endif
bsalomon164fd9f2016-08-26 06:45:06 -070084 for (int y = 0; y < kRes; ++y) {
bsalomon9fb42032016-05-13 09:23:38 -070085 const uint8_t* row = pixmap.addr8(0, y);
86 if (0 != memcmp(kZeros, row, kRes)) {
87 return false;
88 }
89 }
90#ifdef SK_BUILD_FOR_WIN
91 free(const_cast<uint8_t*>(kZeros));
92#endif
93 return true;
94}
bsalomon72dc51c2016-04-27 06:46:23 -070095
Brian Salomon4f40caf2017-09-01 09:00:45 -040096static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
97 SkPath path;
98 shape.asPath(&path);
99 if (shape.style().hasNonDashPathEffect()) {
100 return false;
101 }
102 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
103 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
104 strokeRecStyle == SkStrokeRec::kHairline_Style ||
105 (shape.style().isSimpleFill() && path.isConvex());
106}
107
108static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
109 const Key& keyA, const Key& keyB) {
110 // GrShape only respects the input winding direction and start point for rrect shapes
111 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
112 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
113 // key will differ. GrShape will have canonicalized the direction and start point for the shape
114 // without the path effect. If *both* have path effects then they should have both preserved
115 // the direction and starting point.
116
117 // The asRRect() output params are all initialized just to silence compiler warnings about
118 // uninitialized variables.
119 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
120 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
121 unsigned startA = ~0U, startB = ~0U;
122 bool invertedA = true, invertedB = true;
123
124 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
125 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
126 bool aHasPE = a.style().hasPathEffect();
127 bool bHasPE = b.style().hasPathEffect();
128 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
129 // GrShape will close paths with simple fill style.
130 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
131 SkPath pathA, pathB;
132 a.asPath(&pathA);
133 b.asPath(&pathB);
134
135 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
136 // non-inverse fill type (or vice versa).
137 bool ignoreInversenessDifference = false;
138 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
139 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
140 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
141 bool canDropInverse1 = s1->style().isDashed();
142 bool canDropInverse2 = s2->style().isDashed();
143 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
144 }
145 bool ignoreWindingVsEvenOdd = false;
146 if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
147 SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
148 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
149 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
150 if (aCanChange != bCanChange) {
151 ignoreWindingVsEvenOdd = true;
152 }
153 }
154 if (allowSameRRectButDiffStartAndDir) {
155 REPORTER_ASSERT(r, rrectA == rrectB);
156 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
157 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
158 } else {
159 SkPath pA = pathA;
160 SkPath pB = pathB;
161 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
162 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
163 if (ignoreInversenessDifference) {
164 pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
165 pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
166 }
167 if (ignoreWindingVsEvenOdd) {
168 pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
169 : SkPath::kEvenOdd_FillType);
170 pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
171 : SkPath::kEvenOdd_FillType);
172 }
173 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
174 REPORTER_ASSERT(r, keyA == keyB);
175 } else {
176 REPORTER_ASSERT(r, keyA != keyB);
177 }
178 if (allowedClosednessDiff) {
179 // GrShape will close paths with simple fill style. Make the non-filled path closed
180 // so that the comparision will succeed. Make sure both are closed before comparing.
181 pA.close();
182 pB.close();
183 }
184 REPORTER_ASSERT(r, pA == pB);
185 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
186 if (aIsRRect) {
187 REPORTER_ASSERT(r, rrectA == rrectB);
188 REPORTER_ASSERT(r, dirA == dirB);
189 REPORTER_ASSERT(r, startA == startB);
190 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
191 }
192 }
193 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
194 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
195 // closedness can affect convexity.
196 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
197 if (a.knownToBeConvex()) {
198 REPORTER_ASSERT(r, pathA.isConvex());
199 }
200 if (b.knownToBeConvex()) {
201 REPORTER_ASSERT(r, pathB.isConvex());
202 }
203 REPORTER_ASSERT(r, a.bounds() == b.bounds());
204 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
205 // Init these to suppress warnings.
206 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
207 bool invertedLine[2] {true, true};
208 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
209 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
210 // doesn't (since the PE can set any fill type on its output path).
211 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
212 // then they may disagree about inverseness.
213 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
214 a.style().isDashed() == b.style().isDashed()) {
215 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
216 b.mayBeInverseFilledAfterStyling());
217 }
218 if (a.asLine(nullptr, nullptr)) {
219 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
220 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
221 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
222 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
223 }
224 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
225}
226
Brian Osmanb379dcd2017-10-04 15:44:05 -0400227static void check_original_path_ids(skiatest::Reporter* r, const GrShape& base, const GrShape& pe,
228 const GrShape& peStroke, const GrShape& full) {
Brian Salomonda6d0722018-01-03 13:54:35 -0500229 bool baseIsNonVolatilePath = base.testingOnly_isNonVolatilePath();
Brian Osmanb379dcd2017-10-04 15:44:05 -0400230 bool peIsPath = pe.testingOnly_isPath();
231 bool peStrokeIsPath = peStroke.testingOnly_isPath();
232 bool fullIsPath = full.testingOnly_isPath();
233
234 REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath);
235
236 uint32_t baseID = base.testingOnly_getOriginalGenerationID();
237 uint32_t peID = pe.testingOnly_getOriginalGenerationID();
238 uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID();
239 uint32_t fullID = full.testingOnly_getOriginalGenerationID();
240
241 // All empty paths have the same gen ID
242 uint32_t emptyID = SkPath().getGenerationID();
243
244 // 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 -0500245 // empty). If we started with a simple shape or a volatile path, our original path should have
246 // been reset.
247 REPORTER_ASSERT(r, baseIsNonVolatilePath == (baseID != emptyID));
Brian Osmanb379dcd2017-10-04 15:44:05 -0400248
249 // For the derived shapes, if they're simple types, their original paths should have been reset
250 REPORTER_ASSERT(r, peIsPath || (peID == emptyID));
251 REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID));
252 REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID));
253
254 if (!peIsPath) {
255 // If the path effect produces a simple shape, then there are no unbroken chains to test
256 return;
257 }
258
259 // From here on, we know that the path effect produced a shape that was a "real" path
260
Brian Salomonda6d0722018-01-03 13:54:35 -0500261 if (baseIsNonVolatilePath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400262 REPORTER_ASSERT(r, baseID == peID);
263 }
264
265 if (peStrokeIsPath) {
266 REPORTER_ASSERT(r, peID == peStrokeID);
267 REPORTER_ASSERT(r, peStrokeID == fullID);
268 }
269
Brian Salomonda6d0722018-01-03 13:54:35 -0500270 if (baseIsNonVolatilePath && peStrokeIsPath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400271 REPORTER_ASSERT(r, baseID == peStrokeID);
272 REPORTER_ASSERT(r, baseID == fullID);
273 }
274}
275
Brian Salomon4f40caf2017-09-01 09:00:45 -0400276void test_inversions(skiatest::Reporter* r, const GrShape& shape, const Key& shapeKey) {
277 GrShape preserve = GrShape::MakeFilled(shape, GrShape::FillInversion::kPreserve);
278 Key preserveKey;
279 make_key(&preserveKey, preserve);
280
281 GrShape flip = GrShape::MakeFilled(shape, GrShape::FillInversion::kFlip);
282 Key flipKey;
283 make_key(&flipKey, flip);
284
285 GrShape inverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceInverted);
286 Key invertedKey;
287 make_key(&invertedKey, inverted);
288
289 GrShape noninverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceNoninverted);
290 Key noninvertedKey;
291 make_key(&noninvertedKey, noninverted);
292
293 if (invertedKey.count() || noninvertedKey.count()) {
294 REPORTER_ASSERT(r, invertedKey != noninvertedKey);
295 }
296 if (shape.style().isSimpleFill()) {
297 check_equivalence(r, shape, preserve, shapeKey, preserveKey);
298 }
299 if (shape.inverseFilled()) {
300 check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
301 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
302 } else {
303 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
304 check_equivalence(r, flip, inverted, flipKey, invertedKey);
305 }
306
307 GrShape doubleFlip = GrShape::MakeFilled(flip, GrShape::FillInversion::kFlip);
308 Key doubleFlipKey;
309 make_key(&doubleFlipKey, doubleFlip);
310 // It can be the case that the double flip has no key but preserve does. This happens when the
311 // original shape has an inherited style key. That gets dropped on the first inversion flip.
312 if (preserveKey.count() && !doubleFlipKey.count()) {
313 preserveKey.reset();
314 }
315 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
316}
317
bsalomon9fb42032016-05-13 09:23:38 -0700318namespace {
bsalomona395f7c2016-08-24 17:47:40 -0700319/**
320 * Geo is a factory for creating a GrShape from another representation. It also answers some
321 * questions about expected behavior for GrShape given the inputs.
322 */
323class Geo {
324public:
Mike Kleinfc6c37b2016-09-27 09:34:10 -0400325 virtual ~Geo() {}
bsalomona395f7c2016-08-24 17:47:40 -0700326 virtual GrShape makeShape(const SkPaint&) const = 0;
327 virtual SkPath path() const = 0;
328 // These functions allow tests to check for special cases where style gets
329 // applied by GrShape in its constructor (without calling GrShape::applyStyle).
330 // These unfortunately rely on knowing details of GrShape's implementation.
331 // These predicates are factored out here to avoid littering the rest of the
332 // test code with GrShape implementation details.
333 virtual bool fillChangesGeom() const { return false; }
334 virtual bool strokeIsConvertedToFill() const { return false; }
335 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
336 // Is this something we expect GrShape to recognize as something simpler than a path.
337 virtual bool isNonPath(const SkPaint& paint) const { return true; }
338};
339
340class RectGeo : public Geo {
341public:
342 RectGeo(const SkRect& rect) : fRect(rect) {}
343
344 SkPath path() const override {
345 SkPath path;
346 path.addRect(fRect);
347 return path;
348 }
349
350 GrShape makeShape(const SkPaint& paint) const override {
351 return GrShape(fRect, paint);
352 }
353
354 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
355 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
356 // Converted to an outset rectangle.
357 return paint.getStrokeJoin() == SkPaint::kMiter_Join &&
358 paint.getStrokeMiter() >= SK_ScalarSqrt2;
359 }
360
361private:
362 SkRect fRect;
363};
364
365class RRectGeo : public Geo {
366public:
367 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
368
369 GrShape makeShape(const SkPaint& paint) const override {
370 return GrShape(fRRect, paint);
371 }
372
373 SkPath path() const override {
374 SkPath path;
375 path.addRRect(fRRect);
376 return path;
377 }
378
379 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
380 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
381 if (fRRect.isRect()) {
382 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
383 }
384 return false;
385 }
386
387private:
388 SkRRect fRRect;
389};
390
Brian Salomone4949402018-04-26 15:22:04 -0400391class ArcGeo : public Geo {
392public:
393 ArcGeo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter)
394 : fOval(oval)
395 , fStartAngle(startAngle)
396 , fSweepAngle(sweepAngle)
397 , fUseCenter(useCenter) {}
398
399 SkPath path() const override {
400 SkPath path;
401 SkPathPriv::CreateDrawArcPath(&path, fOval, fStartAngle, fSweepAngle, fUseCenter, false);
402 return path;
403 }
404
405 GrShape makeShape(const SkPaint& paint) const override {
406 return GrShape::MakeArc(fOval, fStartAngle, fSweepAngle, fUseCenter, GrStyle(paint));
407 }
408
409 // GrShape specializes when created from arc params but it doesn't recognize arcs from SkPath.
410 bool isNonPath(const SkPaint& paint) const override { return false; }
411
412private:
413 SkRect fOval;
414 SkScalar fStartAngle;
415 SkScalar fSweepAngle;
416 bool fUseCenter;
417};
418
bsalomona395f7c2016-08-24 17:47:40 -0700419class PathGeo : public Geo {
420public:
421 enum class Invert { kNo, kYes };
422
423 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
424 SkASSERT(!path.isInverseFillType());
425 if (Invert::kYes == invert) {
426 if (fPath.getFillType() == SkPath::kEvenOdd_FillType) {
427 fPath.setFillType(SkPath::kInverseEvenOdd_FillType);
428 } else {
429 SkASSERT(fPath.getFillType() == SkPath::kWinding_FillType);
430 fPath.setFillType(SkPath::kInverseWinding_FillType);
431 }
432 }
433 }
434
435 GrShape makeShape(const SkPaint& paint) const override {
436 return GrShape(fPath, paint);
437 }
438
439 SkPath path() const override { return fPath; }
440
441 bool fillChangesGeom() const override {
442 // unclosed rects get closed. Lines get turned into empty geometry
Brian Salomon085c0862017-08-31 15:44:51 -0400443 return this->isUnclosedRect() || fPath.isLine(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -0700444 }
445
446 bool strokeIsConvertedToFill() const override {
447 return this->isAxisAlignedLine();
448 }
449
450 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
451 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
452 if (this->isAxisAlignedLine()) {
453 // The fill is ignored (zero area) and the stroke is converted to a rrect.
454 return true;
455 }
456 SkRect rect;
457 unsigned start;
458 SkPath::Direction dir;
459 if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
460 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
461 }
462 return false;
463 }
464
465 bool isNonPath(const SkPaint& paint) const override {
466 return fPath.isLine(nullptr) || fPath.isEmpty();
467 }
468
469private:
470 bool isAxisAlignedLine() const {
471 SkPoint pts[2];
472 if (!fPath.isLine(pts)) {
473 return false;
474 }
475 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
476 }
477
478 bool isUnclosedRect() const {
479 bool closed;
480 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
481 }
482
483 SkPath fPath;
484};
485
486class RRectPathGeo : public PathGeo {
487public:
488 enum class RRectForStroke { kNo, kYes };
489
490 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
491 Invert invert)
492 : PathGeo(path, invert)
493 , fRRect(equivalentRRect)
494 , fRRectForStroke(rrectForStroke) {}
495
496 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
497 Invert invert)
498 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
499
500 bool isNonPath(const SkPaint& paint) const override {
501 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
502 return true;
503 }
504 return false;
505 }
506
507 const SkRRect& rrect() const { return fRRect; }
508
509private:
510 SkRRect fRRect;
511 RRectForStroke fRRectForStroke;
512};
513
bsalomon47cc7692016-04-26 12:56:00 -0700514class TestCase {
515public:
bsalomona395f7c2016-08-24 17:47:40 -0700516 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
Brian Salomonc8cdad72018-04-16 09:46:09 -0400517 SkScalar scale = SK_Scalar1)
518 : fBase(new GrShape(geo.makeShape(paint))) {
bsalomon97fd2d42016-05-09 13:02:01 -0700519 this->init(r, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700520 }
521
Brian Salomonc8cdad72018-04-16 09:46:09 -0400522 template <typename... ShapeArgs>
523 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs) : fBase(new GrShape(shapeArgs...)) {
bsalomona395f7c2016-08-24 17:47:40 -0700524 this->init(r, SK_Scalar1);
525 }
526
bsalomon70493962016-06-10 08:05:14 -0700527 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
Brian Salomonc8cdad72018-04-16 09:46:09 -0400528 : fBase(new GrShape(shape)) {
bsalomon70493962016-06-10 08:05:14 -0700529 this->init(r, scale);
530 }
531
bsalomon47cc7692016-04-26 12:56:00 -0700532 struct SelfExpectations {
533 bool fPEHasEffect;
534 bool fPEHasValidKey;
535 bool fStrokeApplies;
536 };
537
538 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
539
540 enum ComparisonExpecation {
541 kAllDifferent_ComparisonExpecation,
542 kSameUpToPE_ComparisonExpecation,
543 kSameUpToStroke_ComparisonExpecation,
544 kAllSame_ComparisonExpecation,
545 };
546
547 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
548
Brian Salomonc8cdad72018-04-16 09:46:09 -0400549 const GrShape& baseShape() const { return *fBase; }
550 const GrShape& appliedPathEffectShape() const { return *fAppliedPE; }
551 const GrShape& appliedFullStyleShape() const { return *fAppliedFull; }
bsalomon72dc51c2016-04-27 06:46:23 -0700552
553 // The returned array's count will be 0 if the key shape has no key.
554 const Key& baseKey() const { return fBaseKey; }
555 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
556 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700557 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700558
bsalomon47cc7692016-04-26 12:56:00 -0700559private:
bsalomon9fb42032016-05-13 09:23:38 -0700560 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
561 SkPath path;
562 shape.asPath(&path);
563 // If the bounds are empty, the path ought to be as well.
bsalomon0ae36a22016-07-18 07:31:13 -0700564 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
bsalomon9fb42032016-05-13 09:23:38 -0700565 REPORTER_ASSERT(r, path.isEmpty());
566 return;
567 }
568 if (path.isEmpty()) {
569 return;
570 }
bsalomon70493962016-06-10 08:05:14 -0700571 // The bounds API explicitly calls out that it does not consider inverseness.
572 SkPath p = path;
573 p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType()));
574 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
bsalomon9fb42032016-05-13 09:23:38 -0700575 }
576
bsalomon97fd2d42016-05-09 13:02:01 -0700577 void init(skiatest::Reporter* r, SkScalar scale) {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400578 fAppliedPE.reset(new GrShape);
579 fAppliedPEThenStroke.reset(new GrShape);
580 fAppliedFull.reset(new GrShape);
bsalomon47cc7692016-04-26 12:56:00 -0700581
Brian Salomonc8cdad72018-04-16 09:46:09 -0400582 *fAppliedPE = fBase->applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
583 *fAppliedPEThenStroke =
584 fAppliedPE->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
585 *fAppliedFull = fBase->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
586
587 make_key(&fBaseKey, *fBase);
588 make_key(&fAppliedPEKey, *fAppliedPE);
589 make_key(&fAppliedPEThenStrokeKey, *fAppliedPEThenStroke);
590 make_key(&fAppliedFullKey, *fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700591
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400592 // All shapes should report the same "original" path, so that path renderers can get to it
593 // if necessary.
Brian Salomonc8cdad72018-04-16 09:46:09 -0400594 check_original_path_ids(r, *fBase, *fAppliedPE, *fAppliedPEThenStroke, *fAppliedFull);
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400595
bsalomonfb083272016-05-04 08:27:41 -0700596 // Applying the path effect and then the stroke should always be the same as applying
597 // both in one go.
598 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
599 SkPath a, b;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400600 fAppliedPEThenStroke->asPath(&a);
601 fAppliedFull->asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700602 // If the output of the path effect is a rrect then it is possible for a and b to be
603 // different paths that fill identically. The reason is that fAppliedFull will do this:
604 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
605 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
606 // now that there is no longer a path effect, the direction and starting index get
607 // canonicalized before the stroke.
Brian Salomonc8cdad72018-04-16 09:46:09 -0400608 if (fAppliedPE->asRRect(nullptr, nullptr, nullptr, nullptr)) {
bsalomonee295642016-06-06 14:01:25 -0700609 REPORTER_ASSERT(r, paths_fill_same(a, b));
610 } else {
611 REPORTER_ASSERT(r, a == b);
612 }
Brian Salomonc8cdad72018-04-16 09:46:09 -0400613 REPORTER_ASSERT(r, fAppliedFull->isEmpty() == fAppliedPEThenStroke->isEmpty());
bsalomon7c73a532016-05-11 15:15:56 -0700614
615 SkPath path;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400616 fBase->asPath(&path);
617 REPORTER_ASSERT(r, path.isEmpty() == fBase->isEmpty());
618 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase->segmentMask());
619 fAppliedPE->asPath(&path);
620 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE->isEmpty());
621 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE->segmentMask());
622 fAppliedFull->asPath(&path);
623 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull->isEmpty());
624 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull->segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700625
Brian Salomonc8cdad72018-04-16 09:46:09 -0400626 CheckBounds(r, *fBase, fBase->bounds());
627 CheckBounds(r, *fAppliedPE, fAppliedPE->bounds());
628 CheckBounds(r, *fAppliedPEThenStroke, fAppliedPEThenStroke->bounds());
629 CheckBounds(r, *fAppliedFull, fAppliedFull->bounds());
630 SkRect styledBounds = fBase->styledBounds();
631 CheckBounds(r, *fAppliedFull, styledBounds);
632 styledBounds = fAppliedPE->styledBounds();
633 CheckBounds(r, *fAppliedFull, styledBounds);
bsalomon9fb42032016-05-13 09:23:38 -0700634
bsalomonfb083272016-05-04 08:27:41 -0700635 // Check that the same path is produced when style is applied by GrShape and GrStyle.
636 SkPath preStyle;
637 SkPath postPathEffect;
638 SkPath postAllStyle;
639
Brian Salomonc8cdad72018-04-16 09:46:09 -0400640 fBase->asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700641 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
Brian Salomonc8cdad72018-04-16 09:46:09 -0400642 if (fBase->style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
643 scale)) {
bsalomon1a0b9ed2016-05-06 11:07:03 -0700644 // run postPathEffect through GrShape to get any geometry reductions that would have
645 // occurred to fAppliedPE.
646 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
647
bsalomonfb083272016-05-04 08:27:41 -0700648 SkPath testPath;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400649 fAppliedPE->asPath(&testPath);
bsalomonfb083272016-05-04 08:27:41 -0700650 REPORTER_ASSERT(r, testPath == postPathEffect);
Brian Salomonc8cdad72018-04-16 09:46:09 -0400651 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE->style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700652 }
653 SkStrokeRec::InitStyle fillOrHairline;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400654 if (fBase->style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomonfb083272016-05-04 08:27:41 -0700655 SkPath testPath;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400656 fAppliedFull->asPath(&testPath);
657 if (fBase->style().hasPathEffect()) {
bsalomon1b28c1a2016-06-20 12:28:17 -0700658 // Because GrShape always does two-stage application when there is a path effect
659 // there may be a reduction/canonicalization step between the path effect and
660 // strokerec not reflected in postAllStyle since it applied both the path effect
661 // and strokerec without analyzing the intermediate path.
662 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
663 } else {
664 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape
665 // would apply.
666 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
667 REPORTER_ASSERT(r, testPath == postAllStyle);
668 }
669
bsalomonfb083272016-05-04 08:27:41 -0700670 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400671 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleFill());
bsalomonfb083272016-05-04 08:27:41 -0700672 } else {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400673 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleHairline());
bsalomonfb083272016-05-04 08:27:41 -0700674 }
675 }
Brian Salomonc8cdad72018-04-16 09:46:09 -0400676 test_inversions(r, *fBase, fBaseKey);
677 test_inversions(r, *fAppliedPE, fAppliedPEKey);
678 test_inversions(r, *fAppliedFull, fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700679 }
680
Brian Salomonc8cdad72018-04-16 09:46:09 -0400681 std::unique_ptr<GrShape> fBase;
682 std::unique_ptr<GrShape> fAppliedPE;
683 std::unique_ptr<GrShape> fAppliedPEThenStroke;
684 std::unique_ptr<GrShape> fAppliedFull;
bsalomon47cc7692016-04-26 12:56:00 -0700685
686 Key fBaseKey;
687 Key fAppliedPEKey;
688 Key fAppliedPEThenStrokeKey;
689 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700690};
691
692void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700693 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700694 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700695 if (expectations.fPEHasEffect) {
696 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700697 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700698 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700699 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700700 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
701 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700702 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700703 }
704 } else {
705 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700706 SkPath a, b;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400707 fBase->asPath(&a);
708 fAppliedPE->asPath(&b);
bsalomon72dc51c2016-04-27 06:46:23 -0700709 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700710 if (expectations.fStrokeApplies) {
711 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
712 } else {
713 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
714 }
715 }
716}
717
bsalomonee295642016-06-06 14:01:25 -0700718void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700719 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700720 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700721 switch (expectation) {
722 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700723 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
724 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
725 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700726 break;
727 case kSameUpToPE_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400728 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
bsalomonee295642016-06-06 14:01:25 -0700729 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
730 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700731 break;
732 case kSameUpToStroke_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400733 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
734 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
bsalomonee295642016-06-06 14:01:25 -0700735 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700736 break;
737 case kAllSame_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400738 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
739 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
740 check_equivalence(r, *fAppliedFull, *that.fAppliedFull, fAppliedFullKey,
bsalomonee295642016-06-06 14:01:25 -0700741 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700742 break;
743 }
744}
745} // namespace
746
747static sk_sp<SkPathEffect> make_dash() {
748 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
749 static const SkScalar kPhase = 0.75;
750 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
751}
752
753static sk_sp<SkPathEffect> make_null_dash() {
754 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
755 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
756}
757
Mike Klein43344282017-08-16 11:56:22 -0400758// We make enough TestCases, and they're large enough, that on Google3 builds we exceed
759// the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
760template <typename... Args>
761static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
762 return std::unique_ptr<TestCase>{ new TestCase(std::forward<Args>(args)...) };
763}
764
bsalomona395f7c2016-08-24 17:47:40 -0700765static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700766 sk_sp<SkPathEffect> dashPE = make_dash();
767
768 TestCase::SelfExpectations expectations;
769 SkPaint fill;
770
bsalomonfb083272016-05-04 08:27:41 -0700771 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700772 expectations.fPEHasEffect = false;
773 expectations.fPEHasValidKey = false;
774 expectations.fStrokeApplies = false;
775 fillCase.testExpectations(reporter, expectations);
776 // Test that another GrShape instance built from the same primitive is the same.
Mike Klein43344282017-08-16 11:56:22 -0400777 make_TestCase(geo, fill, reporter)
778 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700779
780 SkPaint stroke2RoundBevel;
781 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
782 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
783 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
784 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700785 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700786 expectations.fPEHasValidKey = true;
787 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700788 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomon47cc7692016-04-26 12:56:00 -0700789 stroke2RoundBevelCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400790 make_TestCase(geo, stroke2RoundBevel, reporter)
791 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700792
793 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
794 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700795 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700796 expectations.fPEHasValidKey = true;
797 expectations.fPEHasEffect = true;
798 expectations.fStrokeApplies = true;
799 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400800 make_TestCase(geo, stroke2RoundBevelDash, reporter)
801 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700802
bsalomona395f7c2016-08-24 17:47:40 -0700803 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700804 fillCase.compare(reporter, stroke2RoundBevelCase,
805 TestCase::kAllDifferent_ComparisonExpecation);
806 fillCase.compare(reporter, stroke2RoundBevelDashCase,
807 TestCase::kAllDifferent_ComparisonExpecation);
808 } else {
809 fillCase.compare(reporter, stroke2RoundBevelCase,
810 TestCase::kSameUpToStroke_ComparisonExpecation);
811 fillCase.compare(reporter, stroke2RoundBevelDashCase,
812 TestCase::kSameUpToPE_ComparisonExpecation);
813 }
bsalomona395f7c2016-08-24 17:47:40 -0700814 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700815 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
816 TestCase::kAllDifferent_ComparisonExpecation);
817 } else {
818 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
819 TestCase::kSameUpToPE_ComparisonExpecation);
820 }
bsalomon72dc51c2016-04-27 06:46:23 -0700821
bsalomonf0cf3552016-05-05 08:28:30 -0700822 // Stroke and fill cases
823 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
824 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
825 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
826 expectations.fPEHasValidKey = true;
827 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700828 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700829 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400830 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
831 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700832
833 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
834 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
835 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
836 expectations.fPEHasValidKey = true;
bsalomona0587862016-06-09 06:03:38 -0700837 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700838 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700839 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400840 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
bsalomonf0cf3552016-05-05 08:28:30 -0700841 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomona0587862016-06-09 06:03:38 -0700842 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
843 TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700844
bsalomon72dc51c2016-04-27 06:46:23 -0700845 SkPaint hairline;
846 hairline.setStyle(SkPaint::kStroke_Style);
847 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700848 TestCase hairlineCase(geo, hairline, reporter);
bsalomon487f8d32016-07-20 07:15:44 -0700849 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
850 // in the line and unclosed rect cases).
bsalomona395f7c2016-08-24 17:47:40 -0700851 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -0700852 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
853 } else {
854 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
855 }
bsalomon9ad5d7c2016-05-04 08:44:15 -0700856 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
857 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
858 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700859
bsalomon0ae36a22016-07-18 07:31:13 -0700860}
861
bsalomona395f7c2016-08-24 17:47:40 -0700862static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon97fd2d42016-05-09 13:02:01 -0700863 sk_sp<SkPathEffect> dashPE = make_dash();
864
865 static const SkScalar kS1 = 1.f;
866 static const SkScalar kS2 = 2.f;
867
868 SkPaint fill;
869 TestCase fillCase1(geo, fill, reporter, kS1);
870 TestCase fillCase2(geo, fill, reporter, kS2);
871 // Scale doesn't affect fills.
872 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
873
874 SkPaint hairline;
875 hairline.setStyle(SkPaint::kStroke_Style);
876 hairline.setStrokeWidth(0.f);
877 TestCase hairlineCase1(geo, hairline, reporter, kS1);
878 TestCase hairlineCase2(geo, hairline, reporter, kS2);
879 // Scale doesn't affect hairlines.
880 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
881
882 SkPaint stroke;
883 stroke.setStyle(SkPaint::kStroke_Style);
884 stroke.setStrokeWidth(2.f);
885 TestCase strokeCase1(geo, stroke, reporter, kS1);
886 TestCase strokeCase2(geo, stroke, reporter, kS2);
bsalomon0ae36a22016-07-18 07:31:13 -0700887 // Scale affects the stroke
bsalomona395f7c2016-08-24 17:47:40 -0700888 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700889 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
bsalomon0ae36a22016-07-18 07:31:13 -0700890 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
891 } else {
892 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
893 }
bsalomon97fd2d42016-05-09 13:02:01 -0700894
895 SkPaint strokeDash = stroke;
896 strokeDash.setPathEffect(make_dash());
897 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
898 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
899 // Scale affects the dash and the stroke.
bsalomon487f8d32016-07-20 07:15:44 -0700900 strokeDashCase1.compare(reporter, strokeDashCase2,
901 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700902
903 // Stroke and fill cases
904 SkPaint strokeAndFill = stroke;
905 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
906 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
907 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
bsalomona0587862016-06-09 06:03:38 -0700908 SkPaint strokeAndFillDash = strokeDash;
909 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
910 // Dash is ignored for stroke and fill
911 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
912 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
bsalomon487f8d32016-07-20 07:15:44 -0700913 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
914 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
915 // geometries should agree.
bsalomona395f7c2016-08-24 17:47:40 -0700916 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
bsalomon487f8d32016-07-20 07:15:44 -0700917 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
bsalomon97fd2d42016-05-09 13:02:01 -0700918 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
919 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700920 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
921 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700922 } else {
923 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
924 TestCase::kSameUpToStroke_ComparisonExpecation);
925 }
bsalomona0587862016-06-09 06:03:38 -0700926 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
927 TestCase::kAllSame_ComparisonExpecation);
928 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
929 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700930}
931
bsalomona395f7c2016-08-24 17:47:40 -0700932template <typename T>
933static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700934 std::function<void(SkPaint*, T)> setter, T a, T b,
935 bool paramAffectsStroke,
936 bool paramAffectsDashAndStroke) {
937 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
938 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700939 SkPaint strokeA;
940 strokeA.setStyle(SkPaint::kStroke_Style);
941 strokeA.setStrokeWidth(2.f);
942 setter(&strokeA, a);
943 SkPaint strokeB;
944 strokeB.setStyle(SkPaint::kStroke_Style);
945 strokeB.setStrokeWidth(2.f);
946 setter(&strokeB, b);
947
bsalomonfb083272016-05-04 08:27:41 -0700948 TestCase strokeACase(geo, strokeA, reporter);
949 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700950 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700951 // If stroking is immediately incorporated into a geometric transformation then the base
952 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700953 if (geo.strokeIsConvertedToFill()) {
bsalomon0ae36a22016-07-18 07:31:13 -0700954 strokeACase.compare(reporter, strokeBCase,
955 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700956 } else {
957 strokeACase.compare(reporter, strokeBCase,
958 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700959 }
bsalomon06077562016-05-04 13:50:29 -0700960 } else {
961 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
962 }
bsalomon47cc7692016-04-26 12:56:00 -0700963
bsalomonf0cf3552016-05-05 08:28:30 -0700964 SkPaint strokeAndFillA = strokeA;
965 SkPaint strokeAndFillB = strokeB;
966 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
967 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
968 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
969 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
970 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700971 // If stroking is immediately incorporated into a geometric transformation then the base
972 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700973 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
974 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700975 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700976 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700977 } else {
978 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700979 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700980 }
bsalomonf0cf3552016-05-05 08:28:30 -0700981 } else {
982 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
983 TestCase::kAllSame_ComparisonExpecation);
984 }
985
bsalomon47cc7692016-04-26 12:56:00 -0700986 // Make sure stroking params don't affect fill style.
987 SkPaint fillA = strokeA, fillB = strokeB;
988 fillA.setStyle(SkPaint::kFill_Style);
989 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -0700990 TestCase fillACase(geo, fillA, reporter);
991 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700992 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
993
994 // Make sure just applying the dash but not stroke gives the same key for both stroking
995 // variations.
996 SkPaint dashA = strokeA, dashB = strokeB;
997 dashA.setPathEffect(make_dash());
998 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700999 TestCase dashACase(geo, dashA, reporter);
1000 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -07001001 if (paramAffectsDashAndStroke) {
bsalomon487f8d32016-07-20 07:15:44 -07001002 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon06077562016-05-04 13:50:29 -07001003 } else {
1004 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
1005 }
bsalomon47cc7692016-04-26 12:56:00 -07001006}
1007
bsalomona395f7c2016-08-24 17:47:40 -07001008template <typename T>
1009static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -07001010 std::function<void(SkPaint*, T)> setter, T a, T b) {
1011 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
1012};
1013
bsalomona395f7c2016-08-24 17:47:40 -07001014static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
1015 SkPaint hairline;
1016 hairline.setStrokeWidth(0);
1017 hairline.setStyle(SkPaint::kStroke_Style);
1018 GrShape shape = geo.makeShape(hairline);
bsalomon06077562016-05-04 13:50:29 -07001019 // The cap should only affect shapes that may be open.
1020 bool affectsStroke = !shape.knownToBeClosed();
1021 // Dashing adds ends that need caps.
1022 bool affectsDashAndStroke = true;
bsalomona395f7c2016-08-24 17:47:40 -07001023 test_stroke_param_impl<SkPaint::Cap>(
bsalomon06077562016-05-04 13:50:29 -07001024 reporter,
1025 geo,
1026 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
1027 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
1028 affectsStroke,
1029 affectsDashAndStroke);
1030};
1031
bsalomon0ae36a22016-07-18 07:31:13 -07001032static bool shape_known_not_to_have_joins(const GrShape& shape) {
1033 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
1034}
1035
bsalomona395f7c2016-08-24 17:47:40 -07001036static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
1037 SkPaint hairline;
1038 hairline.setStrokeWidth(0);
1039 hairline.setStyle(SkPaint::kStroke_Style);
1040 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001041 // GrShape recognizes certain types don't have joins and will prevent the join type from
1042 // affecting the style key.
1043 // Dashing doesn't add additional joins. However, GrShape currently loses track of this
1044 // after applying the dash.
1045 bool affectsStroke = !shape_known_not_to_have_joins(shape);
bsalomona395f7c2016-08-24 17:47:40 -07001046 test_stroke_param_impl<SkPaint::Join>(
bsalomon0ae36a22016-07-18 07:31:13 -07001047 reporter,
1048 geo,
1049 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
1050 SkPaint::kRound_Join, SkPaint::kBevel_Join,
1051 affectsStroke, true);
1052};
1053
bsalomona395f7c2016-08-24 17:47:40 -07001054static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon06077562016-05-04 13:50:29 -07001055 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1056 p->setStrokeJoin(SkPaint::kMiter_Join);
1057 p->setStrokeMiter(miter);
1058 };
bsalomon47cc7692016-04-26 12:56:00 -07001059
bsalomon06077562016-05-04 13:50:29 -07001060 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1061 p->setStrokeJoin(SkPaint::kRound_Join);
1062 p->setStrokeMiter(miter);
1063 };
bsalomon47cc7692016-04-26 12:56:00 -07001064
bsalomona395f7c2016-08-24 17:47:40 -07001065 SkPaint hairline;
1066 hairline.setStrokeWidth(0);
1067 hairline.setStyle(SkPaint::kStroke_Style);
1068 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001069 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
1070
bsalomon06077562016-05-04 13:50:29 -07001071 // The miter limit should affect stroked and dashed-stroked cases when the join type is
1072 // miter.
bsalomona395f7c2016-08-24 17:47:40 -07001073 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001074 reporter,
1075 geo,
1076 setMiterJoinAndLimit,
1077 0.5f, 0.75f,
bsalomon0ae36a22016-07-18 07:31:13 -07001078 mayHaveJoins,
bsalomon06077562016-05-04 13:50:29 -07001079 true);
bsalomon47cc7692016-04-26 12:56:00 -07001080
bsalomon06077562016-05-04 13:50:29 -07001081 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
1082 // not miter.
bsalomona395f7c2016-08-24 17:47:40 -07001083 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001084 reporter,
1085 geo,
1086 setOtherJoinAndLimit,
1087 0.5f, 0.75f,
1088 false,
1089 false);
bsalomon47cc7692016-04-26 12:56:00 -07001090}
1091
bsalomona395f7c2016-08-24 17:47:40 -07001092static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001093 // A dash with no stroke should have no effect
1094 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
1095 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
1096 SkPaint dashFill;
1097 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -07001098 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001099
bsalomonfb083272016-05-04 08:27:41 -07001100 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001101 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
1102 }
1103}
1104
bsalomona395f7c2016-08-24 17:47:40 -07001105void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001106 SkPaint fill;
1107 SkPaint stroke;
1108 stroke.setStyle(SkPaint::kStroke_Style);
1109 stroke.setStrokeWidth(1.f);
1110 SkPaint dash;
1111 dash.setStyle(SkPaint::kStroke_Style);
1112 dash.setStrokeWidth(1.f);
1113 dash.setPathEffect(make_dash());
1114 SkPaint nullDash;
1115 nullDash.setStyle(SkPaint::kStroke_Style);
1116 nullDash.setStrokeWidth(1.f);
1117 nullDash.setPathEffect(make_null_dash());
1118
bsalomonfb083272016-05-04 08:27:41 -07001119 TestCase fillCase(geo, fill, reporter);
1120 TestCase strokeCase(geo, stroke, reporter);
1121 TestCase dashCase(geo, dash, reporter);
1122 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001123
bsalomon487f8d32016-07-20 07:15:44 -07001124 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
bsalomon47cc7692016-04-26 12:56:00 -07001125 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -07001126 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
1127 // on construction in order to determine how to compare the fill and stroke.
bsalomona395f7c2016-08-24 17:47:40 -07001128 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001129 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
1130 } else {
1131 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1132 }
1133 // 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 -07001134 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001135 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
1136 } else {
1137 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
1138 }
bsalomon47cc7692016-04-26 12:56:00 -07001139}
1140
bsalomona395f7c2016-08-24 17:47:40 -07001141void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001142 /**
1143 * This path effect takes any input path and turns it into a rrect. It passes through stroke
1144 * info.
1145 */
1146 class RRectPathEffect : SkPathEffect {
1147 public:
1148 static const SkRRect& RRect() {
1149 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1150 return kRRect;
1151 }
1152
1153 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1154 const SkRect* cullR) const override {
1155 dst->reset();
1156 dst->addRRect(RRect());
1157 return true;
1158 }
1159 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1160 *dst = RRect().getBounds();
1161 }
1162 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1163 Factory getFactory() const override { return nullptr; }
bsalomon72dc51c2016-04-27 06:46:23 -07001164 private:
1165 RRectPathEffect() {}
1166 };
1167
1168 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -07001169 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001170
1171 SkPaint pe;
1172 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001173 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001174
1175 SkPaint peStroke;
1176 peStroke.setPathEffect(RRectPathEffect::Make());
1177 peStroke.setStrokeWidth(2.f);
1178 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001179 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001180
bsalomon487f8d32016-07-20 07:15:44 -07001181 // Check whether constructing the filled case would cause the base shape to have a different
1182 // geometry (because of a geometric transformation upon initial GrShape construction).
bsalomona395f7c2016-08-24 17:47:40 -07001183 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -07001184 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1185 fillGeoCase.compare(reporter, geoPEStrokeCase,
1186 TestCase::kAllDifferent_ComparisonExpecation);
1187 } else {
1188 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1189 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1190 }
bsalomon72dc51c2016-04-27 06:46:23 -07001191 geoPECase.compare(reporter, geoPEStrokeCase,
1192 TestCase::kSameUpToStroke_ComparisonExpecation);
1193
bsalomona395f7c2016-08-24 17:47:40 -07001194 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
bsalomon72dc51c2016-04-27 06:46:23 -07001195 SkPaint stroke = peStroke;
1196 stroke.setPathEffect(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -07001197 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001198
1199 SkRRect rrect;
1200 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1201 // geoPECase, so the full style should be the same as just the PE.
bsalomon70493962016-06-10 08:05:14 -07001202 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1203 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001204 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1205 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1206
bsalomon70493962016-06-10 08:05:14 -07001207 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1208 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001209 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1210 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1211
1212 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomon70493962016-06-10 08:05:14 -07001213 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1214 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001215 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1216 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1217
bsalomon70493962016-06-10 08:05:14 -07001218 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1219 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001220 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1221 rrectStrokeCase.appliedFullStyleKey());
1222}
1223
bsalomona395f7c2016-08-24 17:47:40 -07001224void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001225 /**
1226 * This path effect just adds two lineTos to the input path.
1227 */
1228 class AddLineTosPathEffect : SkPathEffect {
1229 public:
1230 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1231 const SkRect* cullR) const override {
1232 *dst = src;
bsalomon67fa4e32016-09-21 08:26:57 -07001233 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1234 for (int i = 0; i < 100; ++i) {
1235 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1236 }
bsalomon72dc51c2016-04-27 06:46:23 -07001237 return true;
1238 }
1239 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1240 *dst = src;
Mike Reed185ffe92018-01-08 17:09:54 -05001241 SkRectPriv::GrowToInclude(dst, {0, 0});
1242 SkRectPriv::GrowToInclude(dst, {100, 100});
bsalomon72dc51c2016-04-27 06:46:23 -07001243 }
1244 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1245 Factory getFactory() const override { return nullptr; }
bsalomon72dc51c2016-04-27 06:46:23 -07001246 private:
1247 AddLineTosPathEffect() {}
1248 };
1249
bsalomon9ad5d7c2016-05-04 08:44:15 -07001250 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -07001251 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1252 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1253 SkPaint peStroke;
1254 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1255 peStroke.setStrokeWidth(2.f);
1256 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001257 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001258 TestCase::SelfExpectations expectations;
1259 expectations.fPEHasEffect = true;
1260 expectations.fPEHasValidKey = false;
1261 expectations.fStrokeApplies = true;
1262 geoPEStrokeCase.testExpectations(reporter, expectations);
1263}
1264
bsalomona395f7c2016-08-24 17:47:40 -07001265void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon9ad5d7c2016-05-04 08:44:15 -07001266 /**
1267 * This path effect just changes the stroke rec to hairline.
1268 */
1269 class MakeHairlinePathEffect : SkPathEffect {
1270 public:
1271 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1272 const SkRect* cullR) const override {
1273 *dst = src;
1274 strokeRec->setHairlineStyle();
1275 return true;
1276 }
bsalomon70493962016-06-10 08:05:14 -07001277 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001278 static sk_sp<SkPathEffect> Make() {
1279 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1280 }
1281 Factory getFactory() const override { return nullptr; }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001282 private:
1283 MakeHairlinePathEffect() {}
1284 };
1285
1286 SkPaint fill;
1287 SkPaint pe;
1288 pe.setPathEffect(MakeHairlinePathEffect::Make());
1289
1290 TestCase peCase(geo, pe, reporter);
1291
bsalomonee295642016-06-06 14:01:25 -07001292 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -07001293 peCase.baseShape().asPath(&a);
1294 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -07001295 peCase.appliedFullStyleShape().asPath(&c);
bsalomona395f7c2016-08-24 17:47:40 -07001296 if (geo.isNonPath(pe)) {
bsalomonee295642016-06-06 14:01:25 -07001297 // RRect types can have a change in start index or direction after the PE is applied. This
1298 // is because once the PE is applied, GrShape may canonicalize the dir and index since it
1299 // is not germane to the styling any longer.
1300 // Instead we just check that the paths would fill the same both before and after styling.
1301 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1302 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -07001303 } else {
bsalomona4817af2016-06-23 11:48:26 -07001304 // The base shape cannot perform canonicalization on the path's fill type because of an
1305 // unknown path effect. However, after the path effect is applied the resulting hairline
1306 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1307 // don't distinguish between even/odd and non-zero winding.
1308 a.setFillType(b.getFillType());
bsalomonee295642016-06-06 14:01:25 -07001309 REPORTER_ASSERT(reporter, a == b);
1310 REPORTER_ASSERT(reporter, a == c);
bsalomon67fa4e32016-09-21 08:26:57 -07001311 // If the resulting path is small enough then it will have a key.
1312 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1313 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomonaa840642016-09-23 12:09:16 -07001314 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1315 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001316 }
bsalomonee295642016-06-06 14:01:25 -07001317 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1318 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001319}
1320
bsalomona395f7c2016-08-24 17:47:40 -07001321void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1322 SkPath vPath = geo.path();
bsalomon4eeccc92016-04-27 13:30:25 -07001323 vPath.setIsVolatile(true);
1324
1325 SkPaint dashAndStroke;
1326 dashAndStroke.setPathEffect(make_dash());
1327 dashAndStroke.setStrokeWidth(2.f);
1328 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001329 TestCase volatileCase(reporter, vPath, dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001330 // We expect a shape made from a volatile path to have a key iff the shape is recognized
bsalomonaa840642016-09-23 12:09:16 -07001331 // as a specialized geometry.
1332 if (geo.isNonPath(dashAndStroke)) {
bsalomon4eeccc92016-04-27 13:30:25 -07001333 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1334 // In this case all the keys should be identical to the non-volatile case.
bsalomona395f7c2016-08-24 17:47:40 -07001335 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001336 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1337 } else {
1338 // None of the keys should be valid.
1339 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1340 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1341 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1342 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1343 }
1344}
1345
bsalomona395f7c2016-08-24 17:47:40 -07001346void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon409ed732016-04-27 12:36:02 -07001347 /**
Brian Salomon085c0862017-08-31 15:44:51 -04001348 * This path effect returns an empty path (possibly inverted)
bsalomon409ed732016-04-27 12:36:02 -07001349 */
1350 class EmptyPathEffect : SkPathEffect {
1351 public:
1352 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1353 const SkRect* cullR) const override {
1354 dst->reset();
Brian Salomon085c0862017-08-31 15:44:51 -04001355 if (fInvert) {
1356 dst->toggleInverseFillType();
1357 }
bsalomon409ed732016-04-27 12:36:02 -07001358 return true;
1359 }
1360 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1361 dst->setEmpty();
1362 }
Brian Salomon085c0862017-08-31 15:44:51 -04001363 static sk_sp<SkPathEffect> Make(bool invert) {
1364 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
1365 }
bsalomon409ed732016-04-27 12:36:02 -07001366 Factory getFactory() const override { return nullptr; }
bsalomon409ed732016-04-27 12:36:02 -07001367 private:
Brian Salomon085c0862017-08-31 15:44:51 -04001368 bool fInvert;
1369 EmptyPathEffect(bool invert) : fInvert(invert) {}
bsalomon409ed732016-04-27 12:36:02 -07001370 };
1371
1372 SkPath emptyPath;
1373 GrShape emptyShape(emptyPath);
1374 Key emptyKey;
1375 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -07001376 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001377
Brian Salomon085c0862017-08-31 15:44:51 -04001378 emptyPath.toggleInverseFillType();
1379 GrShape invertedEmptyShape(emptyPath);
1380 Key invertedEmptyKey;
1381 make_key(&invertedEmptyKey, invertedEmptyShape);
1382 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
1383
1384 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
1385
bsalomon409ed732016-04-27 12:36:02 -07001386 SkPaint pe;
Brian Salomon085c0862017-08-31 15:44:51 -04001387 pe.setPathEffect(EmptyPathEffect::Make(false));
1388 TestCase geoPECase(geo, pe, reporter);
1389 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
1390 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
1391 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
1392 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
1393 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
1394 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
1395 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001396
1397 SkPaint peStroke;
Brian Salomon085c0862017-08-31 15:44:51 -04001398 peStroke.setPathEffect(EmptyPathEffect::Make(false));
bsalomon409ed732016-04-27 12:36:02 -07001399 peStroke.setStrokeWidth(2.f);
1400 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001401 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001402 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1403 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1404 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001405 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1406 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001407 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
1408 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
1409 pe.setPathEffect(EmptyPathEffect::Make(true));
1410
1411 TestCase geoPEInvertCase(geo, pe, reporter);
1412 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
1413 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
1414 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1415 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
1416 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
1417 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
1418 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
1419
1420 peStroke.setPathEffect(EmptyPathEffect::Make(true));
1421 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
1422 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
1423 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
1424 REPORTER_ASSERT(reporter,
1425 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1426 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
1427 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
1428 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
1429 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001430}
1431
bsalomona395f7c2016-08-24 17:47:40 -07001432void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
bsalomond6723842016-06-07 12:20:15 -07001433 /**
bsalomon0ae36a22016-07-18 07:31:13 -07001434 * This path effect always fails to apply.
bsalomond6723842016-06-07 12:20:15 -07001435 */
1436 class FailurePathEffect : SkPathEffect {
1437 public:
1438 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1439 const SkRect* cullR) const override {
1440 return false;
1441 }
1442 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1443 *dst = src;
1444 }
1445 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1446 Factory getFactory() const override { return nullptr; }
bsalomond6723842016-06-07 12:20:15 -07001447 private:
1448 FailurePathEffect() {}
1449 };
1450
1451 SkPaint fill;
1452 TestCase fillCase(geo, fill, reporter);
1453
1454 SkPaint pe;
1455 pe.setPathEffect(FailurePathEffect::Make());
1456 TestCase peCase(geo, pe, reporter);
1457
1458 SkPaint stroke;
1459 stroke.setStrokeWidth(2.f);
1460 stroke.setStyle(SkPaint::kStroke_Style);
1461 TestCase strokeCase(geo, stroke, reporter);
1462
1463 SkPaint peStroke = stroke;
1464 peStroke.setPathEffect(FailurePathEffect::Make());
1465 TestCase peStrokeCase(geo, peStroke, reporter);
1466
1467 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1468 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1469 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its
1470 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from
1471 // closing it but after the effect fails we can (for the fill+pe case). This causes different
1472 // routes through GrShape to have equivalent but different representations of the path (closed
1473 // or not) but that fill the same.
1474 SkPath a;
1475 SkPath b;
1476 fillCase.appliedPathEffectShape().asPath(&a);
1477 peCase.appliedPathEffectShape().asPath(&b);
1478 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1479
1480 fillCase.appliedFullStyleShape().asPath(&a);
1481 peCase.appliedFullStyleShape().asPath(&b);
1482 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1483
1484 strokeCase.appliedPathEffectShape().asPath(&a);
1485 peStrokeCase.appliedPathEffectShape().asPath(&b);
1486 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1487
1488 strokeCase.appliedFullStyleShape().asPath(&a);
1489 peStrokeCase.appliedFullStyleShape().asPath(&b);
1490 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1491}
1492
Mike Klein43344282017-08-16 11:56:22 -04001493DEF_TEST(GrShape_empty_shape, reporter) {
bsalomon409ed732016-04-27 12:36:02 -07001494 SkPath emptyPath;
Brian Salomon085c0862017-08-31 15:44:51 -04001495 SkPath invertedEmptyPath;
1496 invertedEmptyPath.toggleInverseFillType();
bsalomon409ed732016-04-27 12:36:02 -07001497 SkPaint fill;
bsalomona395f7c2016-08-24 17:47:40 -07001498 TestCase fillEmptyCase(reporter, emptyPath, fill);
bsalomon7c73a532016-05-11 15:15:56 -07001499 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1500 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1501 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001502 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
1503 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
1504 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
1505 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
1506 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
1507 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
1508 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
1509 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
1510 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
1511 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001512
1513 Key emptyKey(fillEmptyCase.baseKey());
1514 REPORTER_ASSERT(reporter, emptyKey.count());
Brian Salomon085c0862017-08-31 15:44:51 -04001515 Key inverseEmptyKey(fillInvertedEmptyCase.baseKey());
1516 REPORTER_ASSERT(reporter, inverseEmptyKey.count());
bsalomon409ed732016-04-27 12:36:02 -07001517 TestCase::SelfExpectations expectations;
1518 expectations.fStrokeApplies = false;
1519 expectations.fPEHasEffect = false;
1520 // This will test whether applying style preserves emptiness
1521 fillEmptyCase.testExpectations(reporter, expectations);
Brian Salomon085c0862017-08-31 15:44:51 -04001522 fillInvertedEmptyCase.testExpectations(reporter, expectations);
bsalomon409ed732016-04-27 12:36:02 -07001523
1524 // Stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001525 SkPaint stroke;
1526 stroke.setStrokeWidth(2.f);
1527 stroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001528 stroke.setStrokeJoin(SkPaint::kRound_Join);
1529 stroke.setStrokeCap(SkPaint::kRound_Cap);
Brian Salomon085c0862017-08-31 15:44:51 -04001530 TestCase strokeEmptyCase(reporter, emptyPath, stroke);
bsalomon409ed732016-04-27 12:36:02 -07001531 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001532 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
1533 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
1534 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001535
1536 // Dashing and stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001537 SkPaint dashAndStroke;
1538 dashAndStroke.setPathEffect(make_dash());
1539 dashAndStroke.setStrokeWidth(2.f);
1540 dashAndStroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon085c0862017-08-31 15:44:51 -04001541 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
bsalomon409ed732016-04-27 12:36:02 -07001542 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1543 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001544 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
1545 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1546 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
1547 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001548
Brian Salomon2fad74a2017-12-20 13:28:55 -05001549 // A shape made from an empty rrect should behave the same as an empty path when filled but not
1550 // when stroked. However, dashing an empty rrect produces an empty path leaving nothing to
1551 // stroke - so equivalent to filling an empty path.
1552 SkRRect emptyRRect = SkRRect::MakeEmpty();
bsalomon5e410b42016-04-28 09:30:46 -07001553 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001554
1555 TestCase fillEmptyRRectCase(reporter, emptyRRect, fill);
1556 fillEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1557
1558 TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke);
1559 strokeEmptyRRectCase.compare(reporter, strokeEmptyCase,
1560 TestCase::kAllDifferent_ComparisonExpecation);
1561
bsalomona395f7c2016-08-24 17:47:40 -07001562 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001563 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1564 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001565
Brian Salomon085c0862017-08-31 15:44:51 -04001566 static constexpr SkPath::Direction kDir = SkPath::kCCW_Direction;
1567 static constexpr int kStart = 0;
Brian Salomon2fad74a2017-12-20 13:28:55 -05001568
1569 TestCase fillInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, GrStyle(fill));
1570 fillInvertedEmptyRRectCase.compare(reporter, fillInvertedEmptyCase,
1571 TestCase::kAllSame_ComparisonExpecation);
1572
1573 TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true,
1574 GrStyle(stroke));
1575 strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase,
1576 TestCase::kAllDifferent_ComparisonExpecation);
1577
Brian Salomon085c0862017-08-31 15:44:51 -04001578 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
1579 GrStyle(dashAndStroke));
Brian Salomon085c0862017-08-31 15:44:51 -04001580 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
1581 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001582
1583 // Same for a rect.
1584 SkRect emptyRect = SkRect::MakeEmpty();
Brian Salomon2fad74a2017-12-20 13:28:55 -05001585 TestCase fillEmptyRectCase(reporter, emptyRect, fill);
1586 fillEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1587
bsalomona395f7c2016-08-24 17:47:40 -07001588 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001589 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1590 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001591
Brian Salomon085c0862017-08-31 15:44:51 -04001592 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
1593 kStart, true, GrStyle(dashAndStroke));
1594 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1595 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
1596 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001597}
1598
bsalomon70493962016-06-10 08:05:14 -07001599// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1600// canonical point in these cases.
1601unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1602 switch (rrect.getType()) {
1603 case SkRRect::kRect_Type:
1604 return (s + 1) & 0b110;
1605 case SkRRect::kOval_Type:
1606 return s & 0b110;
1607 default:
1608 return s;
1609 }
1610}
1611
1612void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
bsalomoncadb5a22016-06-10 18:28:06 -07001613 enum Style {
bsalomon70493962016-06-10 08:05:14 -07001614 kFill,
1615 kStroke,
1616 kHairline,
1617 kStrokeAndFill
1618 };
1619
1620 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1621 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1622 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1623 strokeRecs[kFill].setFillStyle();
1624 strokeRecs[kStroke].setStrokeStyle(2.f);
1625 strokeRecs[kHairline].setHairlineStyle();
1626 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
bsalomon487f8d32016-07-20 07:15:44 -07001627 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1628 // applyStyle() is called.
1629 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
bsalomon70493962016-06-10 08:05:14 -07001630 sk_sp<SkPathEffect> dashEffect = make_dash();
1631
bsalomoncadb5a22016-06-10 18:28:06 -07001632 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1633
1634 auto index = [](bool inverted,
1635 SkPath::Direction dir,
1636 unsigned start,
1637 Style style,
1638 bool dash) -> int {
1639 return inverted * (2 * 8 * kStyleCnt * 2) +
1640 dir * ( 8 * kStyleCnt * 2) +
1641 start * ( kStyleCnt * 2) +
1642 style * ( 2) +
1643 dash;
1644 };
1645 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1);
1646 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1647 SkAutoTArray<GrShape> shapes(cnt);
1648 for (bool inverted : {false, true}) {
1649 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1650 for (unsigned start = 0; start < 8; ++start) {
1651 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1652 for (bool dash : {false, true}) {
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001653 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
bsalomoncadb5a22016-06-10 18:28:06 -07001654 shapes[index(inverted, dir, start, style, dash)] =
1655 GrShape(rrect, dir, start, SkToBool(inverted),
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001656 GrStyle(strokeRecs[style], std::move(pe)));
bsalomon70493962016-06-10 08:05:14 -07001657 }
1658 }
1659 }
1660 }
1661 }
1662
bsalomonfd32df72016-06-14 14:37:21 -07001663 // Get the keys for some example shape instances that we'll use for comparision against the
1664 // rest.
1665 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction;
1666 static constexpr unsigned kExamplesStart = 0;
1667 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1668 false)];
bsalomon70493962016-06-10 08:05:14 -07001669 Key exampleFillCaseKey;
1670 make_key(&exampleFillCaseKey, exampleFillCase);
1671
bsalomonfd32df72016-06-14 14:37:21 -07001672 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart,
1673 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001674 Key exampleStrokeAndFillCaseKey;
1675 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1676
bsalomonfd32df72016-06-14 14:37:21 -07001677 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill,
1678 false)];
bsalomon70493962016-06-10 08:05:14 -07001679 Key exampleInvFillCaseKey;
1680 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1681
bsalomonfd32df72016-06-14 14:37:21 -07001682 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart,
1683 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001684 Key exampleInvStrokeAndFillCaseKey;
1685 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1686
bsalomonfd32df72016-06-14 14:37:21 -07001687 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke,
1688 false)];
bsalomon70493962016-06-10 08:05:14 -07001689 Key exampleStrokeCaseKey;
1690 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1691
bsalomonfd32df72016-06-14 14:37:21 -07001692 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke,
1693 false)];
1694 Key exampleInvStrokeCaseKey;
1695 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1696
1697 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1698 kHairline, false)];
bsalomon70493962016-06-10 08:05:14 -07001699 Key exampleHairlineCaseKey;
1700 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1701
bsalomonfd32df72016-06-14 14:37:21 -07001702 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1703 kHairline, false)];
1704 Key exampleInvHairlineCaseKey;
1705 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1706
bsalomon70493962016-06-10 08:05:14 -07001707 // These are dummy initializations to suppress warnings.
bsalomoncadb5a22016-06-10 18:28:06 -07001708 SkRRect queryRR = SkRRect::MakeEmpty();
1709 SkPath::Direction queryDir = SkPath::kCW_Direction;
1710 unsigned queryStart = ~0U;
1711 bool queryInverted = true;
bsalomon70493962016-06-10 08:05:14 -07001712
bsalomoncadb5a22016-06-10 18:28:06 -07001713 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1714 REPORTER_ASSERT(r, queryRR == rrect);
1715 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1716 REPORTER_ASSERT(r, 0 == queryStart);
1717 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001718
bsalomoncadb5a22016-06-10 18:28:06 -07001719 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1720 &queryInverted));
1721 REPORTER_ASSERT(r, queryRR == rrect);
1722 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1723 REPORTER_ASSERT(r, 0 == queryStart);
1724 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001725
bsalomoncadb5a22016-06-10 18:28:06 -07001726 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1727 &queryInverted));
1728 REPORTER_ASSERT(r, queryRR == rrect);
1729 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1730 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, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1734 &queryInverted));
1735 REPORTER_ASSERT(r, queryRR == rrect);
1736 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1737 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, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1741 &queryInverted));
1742 REPORTER_ASSERT(r, queryRR == rrect);
1743 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1744 REPORTER_ASSERT(r, 0 == queryStart);
1745 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001746
bsalomonfd32df72016-06-14 14:37:21 -07001747 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1748 &queryInverted));
1749 REPORTER_ASSERT(r, queryRR == rrect);
1750 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1751 REPORTER_ASSERT(r, 0 == queryStart);
1752 REPORTER_ASSERT(r, queryInverted);
1753
bsalomoncadb5a22016-06-10 18:28:06 -07001754 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1755 REPORTER_ASSERT(r, queryRR == rrect);
1756 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1757 REPORTER_ASSERT(r, 0 == queryStart);
1758 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001759
bsalomonfd32df72016-06-14 14:37:21 -07001760 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1761 &queryInverted));
1762 REPORTER_ASSERT(r, queryRR == rrect);
1763 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1764 REPORTER_ASSERT(r, 0 == queryStart);
1765 REPORTER_ASSERT(r, queryInverted);
1766
bsalomon70493962016-06-10 08:05:14 -07001767 // Remember that the key reflects the geometry before styling is applied.
1768 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1769 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1770 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1771 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001772 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001773 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001774 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001775 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001776 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1777 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001778
bsalomoncadb5a22016-06-10 18:28:06 -07001779 for (bool inverted : {false, true}) {
1780 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1781 for (unsigned start = 0; start < 8; ++start) {
1782 for (bool dash : {false, true}) {
1783 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001784 Key fillCaseKey;
1785 make_key(&fillCaseKey, fillCase);
1786
bsalomoncadb5a22016-06-10 18:28:06 -07001787 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1788 kStrokeAndFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001789 Key strokeAndFillCaseKey;
1790 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1791
1792 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1793 // ignore dashing.
1794 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1795 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1796 TestCase a(fillCase, r);
1797 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1798 TestCase c(strokeAndFillCase, r);
1799 TestCase d(inverted ? exampleInvStrokeAndFillCase
1800 : exampleStrokeAndFillCase, r);
1801 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1802 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1803
bsalomoncadb5a22016-06-10 18:28:06 -07001804 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)];
1805 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline,
1806 dash)];
bsalomon70493962016-06-10 08:05:14 -07001807
1808 TestCase e(strokeCase, r);
bsalomon70493962016-06-10 08:05:14 -07001809 TestCase g(hairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001810
bsalomonfd32df72016-06-14 14:37:21 -07001811 // Both hairline and stroke shapes must respect the dashing.
bsalomon70493962016-06-10 08:05:14 -07001812 if (dash) {
bsalomonfd32df72016-06-14 14:37:21 -07001813 // Dashing always ignores the inverseness. skbug.com/5421
1814 TestCase f(exampleStrokeCase, r);
1815 TestCase h(exampleHairlineCase, r);
bsalomoncadb5a22016-06-10 18:28:06 -07001816 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
bsalomon70493962016-06-10 08:05:14 -07001817 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1818 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1819
bsalomoncadb5a22016-06-10 18:28:06 -07001820 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1821 &queryInverted));
1822 REPORTER_ASSERT(r, queryRR == rrect);
1823 REPORTER_ASSERT(r, queryDir == dir);
1824 REPORTER_ASSERT(r, queryStart == expectedStart);
1825 REPORTER_ASSERT(r, !queryInverted);
1826 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1827 &queryInverted));
1828 REPORTER_ASSERT(r, queryRR == rrect);
1829 REPORTER_ASSERT(r, queryDir == dir);
1830 REPORTER_ASSERT(r, queryStart == expectedStart);
1831 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001832
1833 // The pre-style case for the dash will match the non-dash example iff the
1834 // dir and start match (dir=cw, start=0).
bsalomoncadb5a22016-06-10 18:28:06 -07001835 if (0 == expectedStart && SkPath::kCW_Direction == dir) {
bsalomon70493962016-06-10 08:05:14 -07001836 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1837 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1838 } else {
1839 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1840 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1841 }
1842 } else {
bsalomonfd32df72016-06-14 14:37:21 -07001843 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1844 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001845 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1846 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1847 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1848 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1849 }
1850 }
1851 }
1852 }
1853 }
1854}
1855
Mike Klein43344282017-08-16 11:56:22 -04001856DEF_TEST(GrShape_lines, r) {
bsalomon0a0f67e2016-06-28 11:56:42 -07001857 static constexpr SkPoint kA { 1, 1};
1858 static constexpr SkPoint kB { 5, -9};
1859 static constexpr SkPoint kC {-3, 17};
1860
1861 SkPath lineAB;
1862 lineAB.moveTo(kA);
1863 lineAB.lineTo(kB);
1864
1865 SkPath lineBA;
1866 lineBA.moveTo(kB);
1867 lineBA.lineTo(kA);
1868
1869 SkPath lineAC;
1870 lineAC.moveTo(kB);
1871 lineAC.lineTo(kC);
1872
1873 SkPath invLineAB = lineAB;
1874 invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType);
1875
1876 SkPaint fill;
1877 SkPaint stroke;
1878 stroke.setStyle(SkPaint::kStroke_Style);
1879 stroke.setStrokeWidth(2.f);
1880 SkPaint hairline;
1881 hairline.setStyle(SkPaint::kStroke_Style);
1882 hairline.setStrokeWidth(0.f);
1883 SkPaint dash = stroke;
1884 dash.setPathEffect(make_dash());
1885
bsalomona395f7c2016-08-24 17:47:40 -07001886 TestCase fillAB(r, lineAB, fill);
1887 TestCase fillEmpty(r, SkPath(), fill);
bsalomon0a0f67e2016-06-28 11:56:42 -07001888 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1889 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1890
Brian Salomon085c0862017-08-31 15:44:51 -04001891 SkPath path;
1892 path.toggleInverseFillType();
1893 TestCase fillEmptyInverted(r, path, fill);
1894 TestCase fillABInverted(r, invLineAB, fill);
1895 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
1896 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
1897
bsalomona395f7c2016-08-24 17:47:40 -07001898 TestCase strokeAB(r, lineAB, stroke);
1899 TestCase strokeBA(r, lineBA, stroke);
1900 TestCase strokeAC(r, lineAC, stroke);
bsalomon0a0f67e2016-06-28 11:56:42 -07001901
bsalomona395f7c2016-08-24 17:47:40 -07001902 TestCase hairlineAB(r, lineAB, hairline);
1903 TestCase hairlineBA(r, lineBA, hairline);
1904 TestCase hairlineAC(r, lineAC, hairline);
bsalomon0a0f67e2016-06-28 11:56:42 -07001905
bsalomona395f7c2016-08-24 17:47:40 -07001906 TestCase dashAB(r, lineAB, dash);
1907 TestCase dashBA(r, lineBA, dash);
1908 TestCase dashAC(r, lineAC, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001909
1910 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1911
1912 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1913 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1914
1915 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1916 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1917
1918 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1919 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1920
1921 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1922
1923 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1924 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1925 bool canonicalizeAsAB;
1926 SkPoint canonicalPts[2] {kA, kB};
1927 // Init these to suppress warnings.
1928 bool inverted = true;
1929 SkPoint pts[2] {{0, 0}, {0, 0}};
1930 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1931 if (pts[0] == kA && pts[1] == kB) {
1932 canonicalizeAsAB = true;
1933 } else if (pts[1] == kA && pts[0] == kB) {
1934 canonicalizeAsAB = false;
1935 SkTSwap(canonicalPts[0], canonicalPts[1]);
1936 } else {
1937 ERRORF(r, "Should return pts (a,b) or (b, a)");
1938 return;
1939 };
1940
1941 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1942 TestCase::kSameUpToPE_ComparisonExpecation);
1943 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1944 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1945 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1946 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1947 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1948 pts[0] == kA && pts[1] == kB);
1949 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1950 pts[0] == kB && pts[1] == kA);
1951
1952
bsalomona395f7c2016-08-24 17:47:40 -07001953 TestCase strokeInvAB(r, invLineAB, stroke);
1954 TestCase hairlineInvAB(r, invLineAB, hairline);
1955 TestCase dashInvAB(r, invLineAB, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001956 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1957 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1958 // Dashing ignores inverse.
1959 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1960
1961 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1962 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1963 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1964 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1965 // Dashing ignores inverse.
1966 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1967 pts[0] == kA && pts[1] == kB);
1968
1969}
1970
Mike Klein43344282017-08-16 11:56:22 -04001971DEF_TEST(GrShape_stroked_lines, r) {
Brian Salomon72f78c32017-12-21 11:56:42 -05001972 static constexpr SkScalar kIntervals1[] = {1.f, 0.f};
1973 auto dash1 = SkDashPathEffect::Make(kIntervals1, SK_ARRAY_COUNT(kIntervals1), 0.f);
1974 REPORTER_ASSERT(r, dash1);
1975 static constexpr SkScalar kIntervals2[] = {10.f, 0.f, 5.f, 0.f};
1976 auto dash2 = SkDashPathEffect::Make(kIntervals2, SK_ARRAY_COUNT(kIntervals2), 10.f);
1977 REPORTER_ASSERT(r, dash2);
bsalomon0ae36a22016-07-18 07:31:13 -07001978
Brian Salomon72f78c32017-12-21 11:56:42 -05001979 sk_sp<SkPathEffect> pathEffects[] = {nullptr, std::move(dash1), std::move(dash2)};
bsalomon0ae36a22016-07-18 07:31:13 -07001980
Brian Salomon72f78c32017-12-21 11:56:42 -05001981 for (const auto& pe : pathEffects) {
1982 // Paints to try
1983 SkPaint buttCap;
1984 buttCap.setStyle(SkPaint::kStroke_Style);
1985 buttCap.setStrokeWidth(4);
1986 buttCap.setStrokeCap(SkPaint::kButt_Cap);
1987 buttCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07001988
Brian Salomon72f78c32017-12-21 11:56:42 -05001989 SkPaint squareCap = buttCap;
1990 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
1991 squareCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07001992
Brian Salomon72f78c32017-12-21 11:56:42 -05001993 SkPaint roundCap = buttCap;
1994 roundCap.setStrokeCap(SkPaint::kRound_Cap);
1995 roundCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07001996
Brian Salomon72f78c32017-12-21 11:56:42 -05001997 // vertical
1998 SkPath linePath;
1999 linePath.moveTo(4, 4);
2000 linePath.lineTo(4, 5);
bsalomon0ae36a22016-07-18 07:31:13 -07002001
Brian Salomon72f78c32017-12-21 11:56:42 -05002002 SkPaint fill;
bsalomon0ae36a22016-07-18 07:31:13 -07002003
Brian Salomon72f78c32017-12-21 11:56:42 -05002004 make_TestCase(r, linePath, buttCap)->compare(
2005 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
2006 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002007
Brian Salomon72f78c32017-12-21 11:56:42 -05002008 make_TestCase(r, linePath, squareCap)->compare(
2009 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
2010 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002011
Brian Salomon72f78c32017-12-21 11:56:42 -05002012 make_TestCase(r, linePath, roundCap)->compare(r,
2013 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
2014 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002015
Brian Salomon72f78c32017-12-21 11:56:42 -05002016 // horizontal
2017 linePath.reset();
2018 linePath.moveTo(4, 4);
2019 linePath.lineTo(5, 4);
bsalomon0ae36a22016-07-18 07:31:13 -07002020
Brian Salomon72f78c32017-12-21 11:56:42 -05002021 make_TestCase(r, linePath, buttCap)->compare(
2022 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
2023 TestCase::kAllSame_ComparisonExpecation);
2024 make_TestCase(r, linePath, squareCap)->compare(
2025 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
2026 TestCase::kAllSame_ComparisonExpecation);
2027 make_TestCase(r, linePath, roundCap)->compare(
2028 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
2029 TestCase::kAllSame_ComparisonExpecation);
2030
2031 // point
2032 linePath.reset();
2033 linePath.moveTo(4, 4);
2034 linePath.lineTo(4, 4);
2035
2036 make_TestCase(r, linePath, buttCap)->compare(
2037 r, TestCase(r, SkRect::MakeEmpty(), fill),
2038 TestCase::kAllSame_ComparisonExpecation);
2039 make_TestCase(r, linePath, squareCap)->compare(
2040 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
2041 TestCase::kAllSame_ComparisonExpecation);
2042 make_TestCase(r, linePath, roundCap)->compare(
2043 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
2044 TestCase::kAllSame_ComparisonExpecation);
2045 }
bsalomon0ae36a22016-07-18 07:31:13 -07002046}
2047
Mike Klein43344282017-08-16 11:56:22 -04002048DEF_TEST(GrShape_short_path_keys, r) {
bsalomon67fa4e32016-09-21 08:26:57 -07002049 SkPaint paints[4];
2050 paints[1].setStyle(SkPaint::kStroke_Style);
2051 paints[1].setStrokeWidth(5.f);
2052 paints[2].setStyle(SkPaint::kStroke_Style);
2053 paints[2].setStrokeWidth(0.f);
2054 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
2055 paints[3].setStrokeWidth(5.f);
2056
bsalomonaa840642016-09-23 12:09:16 -07002057 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
bsalomon67fa4e32016-09-21 08:26:57 -07002058 TestCase::ComparisonExpecation expectation) {
bsalomonaa840642016-09-23 12:09:16 -07002059 SkPath volatileA = pathA;
2060 SkPath volatileB = pathB;
2061 volatileA.setIsVolatile(true);
2062 volatileB.setIsVolatile(true);
bsalomon67fa4e32016-09-21 08:26:57 -07002063 for (const SkPaint& paint : paints) {
bsalomonaa840642016-09-23 12:09:16 -07002064 REPORTER_ASSERT(r, !GrShape(volatileA, paint).hasUnstyledKey());
2065 REPORTER_ASSERT(r, !GrShape(volatileB, paint).hasUnstyledKey());
bsalomon67fa4e32016-09-21 08:26:57 -07002066 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
bsalomonaa840642016-09-23 12:09:16 -07002067 TestCase caseA(PathGeo(pathA, invert), paint, r);
2068 TestCase caseB(PathGeo(pathB, invert), paint, r);
2069 caseA.compare(r, caseB, expectation);
bsalomon67fa4e32016-09-21 08:26:57 -07002070 }
2071 }
2072 };
2073
2074 SkPath pathA;
2075 SkPath pathB;
2076
2077 // Two identical paths
2078 pathA.lineTo(10.f, 10.f);
2079 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2080
2081 pathB.lineTo(10.f, 10.f);
2082 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002083 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002084
2085 // Give path b a different point
2086 pathB.reset();
2087 pathB.lineTo(10.f, 10.f);
2088 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002089 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002090
2091 // Give path b a different conic weight
2092 pathB.reset();
2093 pathB.lineTo(10.f, 10.f);
2094 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
bsalomonaa840642016-09-23 12:09:16 -07002095 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002096
2097 // Give path b an extra lineTo verb
2098 pathB.reset();
2099 pathB.lineTo(10.f, 10.f);
2100 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2101 pathB.lineTo(50.f, 50.f);
bsalomonaa840642016-09-23 12:09:16 -07002102 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002103
2104 // Give path b a close
2105 pathB.reset();
2106 pathB.lineTo(10.f, 10.f);
2107 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2108 pathB.close();
bsalomonaa840642016-09-23 12:09:16 -07002109 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002110}
2111
bsalomon47cc7692016-04-26 12:56:00 -07002112DEF_TEST(GrShape, reporter) {
bsalomona395f7c2016-08-24 17:47:40 -07002113 SkTArray<std::unique_ptr<Geo>> geos;
2114 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
2115
bsalomonee295642016-06-06 14:01:25 -07002116 for (auto r : { SkRect::MakeWH(10, 20),
2117 SkRect::MakeWH(-10, -20),
2118 SkRect::MakeWH(-10, 20),
2119 SkRect::MakeWH(10, -20)}) {
bsalomona395f7c2016-08-24 17:47:40 -07002120 geos.emplace_back(new RectGeo(r));
2121 SkPath rectPath;
2122 rectPath.addRect(r);
2123 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2124 PathGeo::Invert::kNo));
2125 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2126 PathGeo::Invert::kYes));
2127 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2128 PathGeo::Invert::kNo));
bsalomonee295642016-06-06 14:01:25 -07002129 }
bsalomon47cc7692016-04-26 12:56:00 -07002130 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -07002131 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
2132 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomona395f7c2016-08-24 17:47:40 -07002133 geos.emplace_back(new RRectGeo(rr));
bsalomon70493962016-06-10 08:05:14 -07002134 test_rrect(reporter, rr);
bsalomona395f7c2016-08-24 17:47:40 -07002135 SkPath rectPath;
2136 rectPath.addRRect(rr);
2137 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2138 PathGeo::Invert::kNo));
2139 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2140 PathGeo::Invert::kYes));
2141 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
2142 RRectPathGeo::RRectForStroke::kYes,
2143 PathGeo::Invert::kNo));
bsalomon72dc51c2016-04-27 06:46:23 -07002144 }
2145
Brian Salomone4949402018-04-26 15:22:04 -04002146 // Arcs
2147 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, false));
2148 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, true));
2149
Mike Klein43344282017-08-16 11:56:22 -04002150 {
2151 SkPath openRectPath;
2152 openRectPath.moveTo(0, 0);
2153 openRectPath.lineTo(10, 0);
2154 openRectPath.lineTo(10, 10);
2155 openRectPath.lineTo(0, 10);
2156 geos.emplace_back(new RRectPathGeo(
2157 openRectPath, SkRect::MakeWH(10, 10),
2158 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2159 geos.emplace_back(new RRectPathGeo(
2160 openRectPath, SkRect::MakeWH(10, 10),
2161 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
2162 rrectPathGeos.emplace_back(new RRectPathGeo(
2163 openRectPath, SkRect::MakeWH(10, 10),
2164 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2165 }
bsalomon72dc51c2016-04-27 06:46:23 -07002166
Mike Klein43344282017-08-16 11:56:22 -04002167 {
2168 SkPath quadPath;
2169 quadPath.quadTo(10, 10, 5, 8);
2170 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
2171 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
2172 }
bsalomon398e3f42016-06-13 10:22:48 -07002173
Mike Klein43344282017-08-16 11:56:22 -04002174 {
2175 SkPath linePath;
2176 linePath.lineTo(10, 10);
2177 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
2178 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
2179 }
bsalomon72dc51c2016-04-27 06:46:23 -07002180
bsalomon0ae36a22016-07-18 07:31:13 -07002181 // Horizontal and vertical paths become rrects when stroked.
Mike Klein43344282017-08-16 11:56:22 -04002182 {
2183 SkPath vLinePath;
2184 vLinePath.lineTo(0, 10);
2185 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
2186 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
2187 }
bsalomon0ae36a22016-07-18 07:31:13 -07002188
Mike Klein43344282017-08-16 11:56:22 -04002189 {
2190 SkPath hLinePath;
2191 hLinePath.lineTo(10, 0);
2192 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
2193 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
2194 }
bsalomon0ae36a22016-07-18 07:31:13 -07002195
bsalomona395f7c2016-08-24 17:47:40 -07002196 for (int i = 0; i < geos.count(); ++i) {
2197 test_basic(reporter, *geos[i]);
2198 test_scale(reporter, *geos[i]);
2199 test_dash_fill(reporter, *geos[i]);
2200 test_null_dash(reporter, *geos[i]);
2201 // Test modifying various stroke params.
2202 test_stroke_param<SkScalar>(
2203 reporter, *geos[i],
bsalomon70493962016-06-10 08:05:14 -07002204 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
2205 SkIntToScalar(2), SkIntToScalar(4));
bsalomona395f7c2016-08-24 17:47:40 -07002206 test_stroke_join(reporter, *geos[i]);
2207 test_stroke_cap(reporter, *geos[i]);
2208 test_miter_limit(reporter, *geos[i]);
2209 test_path_effect_makes_rrect(reporter, *geos[i]);
2210 test_unknown_path_effect(reporter, *geos[i]);
2211 test_path_effect_makes_empty_shape(reporter, *geos[i]);
2212 test_path_effect_fails(reporter, *geos[i]);
2213 test_make_hairline_path_effect(reporter, *geos[i]);
2214 test_volatile_path(reporter, *geos[i]);
bsalomon70493962016-06-10 08:05:14 -07002215 }
bsalomonfd32df72016-06-14 14:37:21 -07002216
bsalomona395f7c2016-08-24 17:47:40 -07002217 for (int i = 0; i < rrectPathGeos.count(); ++i) {
2218 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
bsalomon72dc51c2016-04-27 06:46:23 -07002219 SkPaint fillPaint;
bsalomona395f7c2016-08-24 17:47:40 -07002220 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002221 SkRRect rrect;
bsalomona395f7c2016-08-24 17:47:40 -07002222 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
bsalomon70493962016-06-10 08:05:14 -07002223 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2224 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002225 if (rrgeo.isNonPath(fillPaint)) {
2226 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
2227 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2228 TestCase fillRRectCase(reporter, rrect, fillPaint);
bsalomon70493962016-06-10 08:05:14 -07002229 fillPathCase2.compare(reporter, fillRRectCase,
2230 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002231 }
bsalomon72dc51c2016-04-27 06:46:23 -07002232 SkPaint strokePaint;
2233 strokePaint.setStrokeWidth(3.f);
2234 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07002235 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
2236 if (rrgeo.isNonPath(strokePaint)) {
bsalomon0ae36a22016-07-18 07:31:13 -07002237 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2238 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002239 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2240 TestCase strokeRRectCase(reporter, rrect, strokePaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002241 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07002242 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002243 }
bsalomon47cc7692016-04-26 12:56:00 -07002244 }
bsalomon409ed732016-04-27 12:36:02 -07002245
bsalomon4eeccc92016-04-27 13:30:25 -07002246 // Test a volatile empty path.
bsalomona395f7c2016-08-24 17:47:40 -07002247 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
bsalomon47cc7692016-04-26 12:56:00 -07002248}
2249
Brian Salomone4949402018-04-26 15:22:04 -04002250DEF_TEST(GrShape_arcs, reporter) {
2251 SkStrokeRec roundStroke(SkStrokeRec::kFill_InitStyle);
2252 roundStroke.setStrokeStyle(2.f);
2253 roundStroke.setStrokeParams(SkPaint::kRound_Cap, SkPaint::kRound_Join, 1.f);
2254
2255 SkStrokeRec squareStroke(roundStroke);
2256 squareStroke.setStrokeParams(SkPaint::kSquare_Cap, SkPaint::kRound_Join, 1.f);
2257
2258 SkStrokeRec roundStrokeAndFill(roundStroke);
2259 roundStrokeAndFill.setStrokeStyle(2.f, true);
2260
2261 static constexpr SkScalar kIntervals[] = {1, 2};
2262 auto dash = SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), 1.5f);
2263
2264 SkTArray<GrStyle> styles;
2265 styles.push_back(GrStyle::SimpleFill());
2266 styles.push_back(GrStyle::SimpleHairline());
2267 styles.push_back(GrStyle(roundStroke, nullptr));
2268 styles.push_back(GrStyle(squareStroke, nullptr));
2269 styles.push_back(GrStyle(roundStrokeAndFill, nullptr));
2270 styles.push_back(GrStyle(roundStroke, dash));
2271
2272 for (const auto& style : styles) {
2273 // An empty rect never draws anything according to SkCanvas::drawArc() docs.
2274 TestCase emptyArc(GrShape::MakeArc(SkRect::MakeEmpty(), 0, 90.f, false, style), reporter);
2275 TestCase emptyPath(reporter, SkPath(), style);
2276 emptyArc.compare(reporter, emptyPath, TestCase::kAllSame_ComparisonExpecation);
2277
2278 static constexpr SkRect kOval1{0, 0, 50, 50};
2279 static constexpr SkRect kOval2{50, 0, 100, 50};
2280 // Test that swapping starting and ending angle doesn't change the shape unless the arc
2281 // has a path effect. Also test that different ovals produce different shapes.
2282 TestCase arc1CW(GrShape::MakeArc(kOval1, 0, 90.f, false, style), reporter);
2283 TestCase arc1CCW(GrShape::MakeArc(kOval1, 90.f, -90.f, false, style), reporter);
2284
2285 TestCase arc1CWWithCenter(GrShape::MakeArc(kOval1, 0, 90.f, true, style), reporter);
2286 TestCase arc1CCWWithCenter(GrShape::MakeArc(kOval1, 90.f, -90.f, true, style), reporter);
2287
2288 TestCase arc2CW(GrShape::MakeArc(kOval2, 0, 90.f, false, style), reporter);
2289 TestCase arc2CWWithCenter(GrShape::MakeArc(kOval2, 0, 90.f, true, style), reporter);
2290
2291 auto reversedExepectations = style.hasPathEffect()
2292 ? TestCase::kAllDifferent_ComparisonExpecation
2293 : TestCase::kAllSame_ComparisonExpecation;
2294 arc1CW.compare(reporter, arc1CCW, reversedExepectations);
2295 arc1CWWithCenter.compare(reporter, arc1CCWWithCenter, reversedExepectations);
2296 arc1CW.compare(reporter, arc2CW, TestCase::kAllDifferent_ComparisonExpecation);
2297 arc1CW.compare(reporter, arc1CWWithCenter, TestCase::kAllDifferent_ComparisonExpecation);
2298 arc1CWWithCenter.compare(reporter, arc2CWWithCenter,
2299 TestCase::kAllDifferent_ComparisonExpecation);
2300
2301 // Test that two arcs that start at the same angle but specified differently are equivalent.
2302 TestCase arc3A(GrShape::MakeArc(kOval1, 224.f, 73.f, false, style), reporter);
2303 TestCase arc3B(GrShape::MakeArc(kOval1, 224.f - 360.f, 73.f, false, style), reporter);
2304 arc3A.compare(reporter, arc3B, TestCase::kAllDifferent_ComparisonExpecation);
2305
2306 // Test that an arc that traverses the entire oval (and then some) is equivalent to the
2307 // oval itself unless there is a path effect.
2308 TestCase ovalArc(GrShape::MakeArc(kOval1, 150.f, -790.f, false, style), reporter);
2309 TestCase oval(GrShape(SkRRect::MakeOval(kOval1)), reporter);
2310 auto ovalExpectations = style.hasPathEffect() ? TestCase::kAllDifferent_ComparisonExpecation
2311 : TestCase::kAllSame_ComparisonExpecation;
2312 if (style.strokeRec().getWidth() >= 0 && style.strokeRec().getCap() != SkPaint::kButt_Cap) {
2313 ovalExpectations = TestCase::kAllDifferent_ComparisonExpecation;
2314 }
2315 ovalArc.compare(reporter, oval, ovalExpectations);
2316
2317 // If the the arc starts/ends at the center then it is then equivalent to the oval only for
2318 // simple fills.
2319 TestCase ovalArcWithCenter(GrShape::MakeArc(kOval1, 304.f, 1225.f, true, style), reporter);
2320 ovalExpectations = style.isSimpleFill() ? TestCase::kAllSame_ComparisonExpecation
2321 : TestCase::kAllDifferent_ComparisonExpecation;
2322 ovalArcWithCenter.compare(reporter, oval, ovalExpectations);
2323 }
2324}