blob: a53dad851726bac8b2d18f85216f21e5edcf9ad8 [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"
11#if SK_SUPPORT_GPU
12#include "GrShape.h"
bsalomon9fb42032016-05-13 09:23:38 -070013#include "SkCanvas.h"
bsalomon47cc7692016-04-26 12:56:00 -070014#include "SkDashPathEffect.h"
bsalomon9fb42032016-05-13 09:23:38 -070015#include "SkPath.h"
bsalomonee295642016-06-06 14:01:25 -070016#include "SkPathOps.h"
Mike Reed185ffe92018-01-08 17:09:54 -050017#include "SkRectPriv.h"
bsalomon9fb42032016-05-13 09:23:38 -070018#include "SkSurface.h"
Mike Reedebfce6d2016-12-12 10:02:12 -050019#include "SkClipOpPriv.h"
bsalomon47cc7692016-04-26 12:56:00 -070020
Brian Osmanf6f7cf62017-09-25 16:49:55 -040021uint32_t GrShape::testingOnly_getOriginalGenerationID() const {
Brian Salomonda6d0722018-01-03 13:54:35 -050022 if (const auto* lp = this->originalPathForListeners()) {
23 return lp->getGenerationID();
24 }
25 return SkPath().getGenerationID();
Brian Osmanf6f7cf62017-09-25 16:49:55 -040026}
27
Brian Osmanb379dcd2017-10-04 15:44:05 -040028bool GrShape::testingOnly_isPath() const {
29 return Type::kPath == fType;
30}
31
Brian Salomonda6d0722018-01-03 13:54:35 -050032bool GrShape::testingOnly_isNonVolatilePath() const {
33 return Type::kPath == fType && !fPathData.fPath.isVolatile();
34}
35
bsalomon72dc51c2016-04-27 06:46:23 -070036using Key = SkTArray<uint32_t>;
37
38static bool make_key(Key* key, const GrShape& shape) {
39 int size = shape.unstyledKeySize();
40 if (size <= 0) {
41 key->reset(0);
42 return false;
43 }
44 SkASSERT(size);
45 key->reset(size);
46 shape.writeUnstyledKey(key->begin());
47 return true;
48}
49
bsalomonee295642016-06-06 14:01:25 -070050static bool paths_fill_same(const SkPath& a, const SkPath& b) {
51 SkPath pathXor;
52 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
53 return pathXor.isEmpty();
54}
55
bsalomon9fb42032016-05-13 09:23:38 -070056static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
bsalomon164fd9f2016-08-26 06:45:06 -070057 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
58 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
59 // rendering within the bounds (with a tolerance). Then we render the path and check that
60 // everything got clipped out.
bsalomon9fb42032016-05-13 09:23:38 -070061 static constexpr int kRes = 2000;
62 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
Brian Salomone4949402018-04-26 15:22:04 -040063 static constexpr int kTol = 2;
bsalomon9fb42032016-05-13 09:23:38 -070064 GR_STATIC_ASSERT(kRes % 4 == 0);
65 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
66 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
67 surface->getCanvas()->clear(0x0);
68 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
69 SkMatrix matrix;
70 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit);
71 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
Mike Reedc1f77742016-12-09 09:00:50 -050072 surface->getCanvas()->clipRect(clip, kDifference_SkClipOp);
bsalomon9fb42032016-05-13 09:23:38 -070073 surface->getCanvas()->concat(matrix);
74 SkPaint whitePaint;
75 whitePaint.setColor(SK_ColorWHITE);
76 surface->getCanvas()->drawPath(path, whitePaint);
77 SkPixmap pixmap;
78 surface->getCanvas()->peekPixels(&pixmap);
79#if defined(SK_BUILD_FOR_WIN)
80 // The static constexpr version in #else causes cl.exe to crash.
81 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
82#else
83 static constexpr uint8_t kZeros[kRes] = {0};
84#endif
bsalomon164fd9f2016-08-26 06:45:06 -070085 for (int y = 0; y < kRes; ++y) {
bsalomon9fb42032016-05-13 09:23:38 -070086 const uint8_t* row = pixmap.addr8(0, y);
87 if (0 != memcmp(kZeros, row, kRes)) {
88 return false;
89 }
90 }
91#ifdef SK_BUILD_FOR_WIN
92 free(const_cast<uint8_t*>(kZeros));
93#endif
94 return true;
95}
bsalomon72dc51c2016-04-27 06:46:23 -070096
Brian Salomon4f40caf2017-09-01 09:00:45 -040097static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
98 SkPath path;
99 shape.asPath(&path);
100 if (shape.style().hasNonDashPathEffect()) {
101 return false;
102 }
103 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
104 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
105 strokeRecStyle == SkStrokeRec::kHairline_Style ||
106 (shape.style().isSimpleFill() && path.isConvex());
107}
108
109static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
110 const Key& keyA, const Key& keyB) {
111 // GrShape only respects the input winding direction and start point for rrect shapes
112 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
113 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
114 // key will differ. GrShape will have canonicalized the direction and start point for the shape
115 // without the path effect. If *both* have path effects then they should have both preserved
116 // the direction and starting point.
117
118 // The asRRect() output params are all initialized just to silence compiler warnings about
119 // uninitialized variables.
120 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
121 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
122 unsigned startA = ~0U, startB = ~0U;
123 bool invertedA = true, invertedB = true;
124
125 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
126 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
127 bool aHasPE = a.style().hasPathEffect();
128 bool bHasPE = b.style().hasPathEffect();
129 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
130 // GrShape will close paths with simple fill style.
131 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
132 SkPath pathA, pathB;
133 a.asPath(&pathA);
134 b.asPath(&pathB);
135
136 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
137 // non-inverse fill type (or vice versa).
138 bool ignoreInversenessDifference = false;
139 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
140 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
141 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
142 bool canDropInverse1 = s1->style().isDashed();
143 bool canDropInverse2 = s2->style().isDashed();
144 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
145 }
146 bool ignoreWindingVsEvenOdd = false;
147 if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
148 SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
149 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
150 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
151 if (aCanChange != bCanChange) {
152 ignoreWindingVsEvenOdd = true;
153 }
154 }
155 if (allowSameRRectButDiffStartAndDir) {
156 REPORTER_ASSERT(r, rrectA == rrectB);
157 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
158 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
159 } else {
160 SkPath pA = pathA;
161 SkPath pB = pathB;
162 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
163 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
164 if (ignoreInversenessDifference) {
165 pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
166 pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
167 }
168 if (ignoreWindingVsEvenOdd) {
169 pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
170 : SkPath::kEvenOdd_FillType);
171 pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
172 : SkPath::kEvenOdd_FillType);
173 }
174 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
175 REPORTER_ASSERT(r, keyA == keyB);
176 } else {
177 REPORTER_ASSERT(r, keyA != keyB);
178 }
179 if (allowedClosednessDiff) {
180 // GrShape will close paths with simple fill style. Make the non-filled path closed
181 // so that the comparision will succeed. Make sure both are closed before comparing.
182 pA.close();
183 pB.close();
184 }
185 REPORTER_ASSERT(r, pA == pB);
186 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
187 if (aIsRRect) {
188 REPORTER_ASSERT(r, rrectA == rrectB);
189 REPORTER_ASSERT(r, dirA == dirB);
190 REPORTER_ASSERT(r, startA == startB);
191 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
192 }
193 }
194 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
195 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
196 // closedness can affect convexity.
197 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
198 if (a.knownToBeConvex()) {
199 REPORTER_ASSERT(r, pathA.isConvex());
200 }
201 if (b.knownToBeConvex()) {
202 REPORTER_ASSERT(r, pathB.isConvex());
203 }
204 REPORTER_ASSERT(r, a.bounds() == b.bounds());
205 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
206 // Init these to suppress warnings.
207 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
208 bool invertedLine[2] {true, true};
209 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
210 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
211 // doesn't (since the PE can set any fill type on its output path).
212 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
213 // then they may disagree about inverseness.
214 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
215 a.style().isDashed() == b.style().isDashed()) {
216 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
217 b.mayBeInverseFilledAfterStyling());
218 }
219 if (a.asLine(nullptr, nullptr)) {
220 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
221 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
222 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
223 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
224 }
225 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
226}
227
Brian Osmanb379dcd2017-10-04 15:44:05 -0400228static void check_original_path_ids(skiatest::Reporter* r, const GrShape& base, const GrShape& pe,
229 const GrShape& peStroke, const GrShape& full) {
Brian Salomonda6d0722018-01-03 13:54:35 -0500230 bool baseIsNonVolatilePath = base.testingOnly_isNonVolatilePath();
Brian Osmanb379dcd2017-10-04 15:44:05 -0400231 bool peIsPath = pe.testingOnly_isPath();
232 bool peStrokeIsPath = peStroke.testingOnly_isPath();
233 bool fullIsPath = full.testingOnly_isPath();
234
235 REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath);
236
237 uint32_t baseID = base.testingOnly_getOriginalGenerationID();
238 uint32_t peID = pe.testingOnly_getOriginalGenerationID();
239 uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID();
240 uint32_t fullID = full.testingOnly_getOriginalGenerationID();
241
242 // All empty paths have the same gen ID
243 uint32_t emptyID = SkPath().getGenerationID();
244
245 // 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 -0500246 // empty). If we started with a simple shape or a volatile path, our original path should have
247 // been reset.
248 REPORTER_ASSERT(r, baseIsNonVolatilePath == (baseID != emptyID));
Brian Osmanb379dcd2017-10-04 15:44:05 -0400249
250 // For the derived shapes, if they're simple types, their original paths should have been reset
251 REPORTER_ASSERT(r, peIsPath || (peID == emptyID));
252 REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID));
253 REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID));
254
255 if (!peIsPath) {
256 // If the path effect produces a simple shape, then there are no unbroken chains to test
257 return;
258 }
259
260 // From here on, we know that the path effect produced a shape that was a "real" path
261
Brian Salomonda6d0722018-01-03 13:54:35 -0500262 if (baseIsNonVolatilePath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400263 REPORTER_ASSERT(r, baseID == peID);
264 }
265
266 if (peStrokeIsPath) {
267 REPORTER_ASSERT(r, peID == peStrokeID);
268 REPORTER_ASSERT(r, peStrokeID == fullID);
269 }
270
Brian Salomonda6d0722018-01-03 13:54:35 -0500271 if (baseIsNonVolatilePath && peStrokeIsPath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400272 REPORTER_ASSERT(r, baseID == peStrokeID);
273 REPORTER_ASSERT(r, baseID == fullID);
274 }
275}
276
Brian Salomon4f40caf2017-09-01 09:00:45 -0400277void test_inversions(skiatest::Reporter* r, const GrShape& shape, const Key& shapeKey) {
278 GrShape preserve = GrShape::MakeFilled(shape, GrShape::FillInversion::kPreserve);
279 Key preserveKey;
280 make_key(&preserveKey, preserve);
281
282 GrShape flip = GrShape::MakeFilled(shape, GrShape::FillInversion::kFlip);
283 Key flipKey;
284 make_key(&flipKey, flip);
285
286 GrShape inverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceInverted);
287 Key invertedKey;
288 make_key(&invertedKey, inverted);
289
290 GrShape noninverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceNoninverted);
291 Key noninvertedKey;
292 make_key(&noninvertedKey, noninverted);
293
294 if (invertedKey.count() || noninvertedKey.count()) {
295 REPORTER_ASSERT(r, invertedKey != noninvertedKey);
296 }
297 if (shape.style().isSimpleFill()) {
298 check_equivalence(r, shape, preserve, shapeKey, preserveKey);
299 }
300 if (shape.inverseFilled()) {
301 check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
302 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
303 } else {
304 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
305 check_equivalence(r, flip, inverted, flipKey, invertedKey);
306 }
307
308 GrShape doubleFlip = GrShape::MakeFilled(flip, GrShape::FillInversion::kFlip);
309 Key doubleFlipKey;
310 make_key(&doubleFlipKey, doubleFlip);
311 // It can be the case that the double flip has no key but preserve does. This happens when the
312 // original shape has an inherited style key. That gets dropped on the first inversion flip.
313 if (preserveKey.count() && !doubleFlipKey.count()) {
314 preserveKey.reset();
315 }
316 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
317}
318
bsalomon9fb42032016-05-13 09:23:38 -0700319namespace {
bsalomona395f7c2016-08-24 17:47:40 -0700320/**
321 * Geo is a factory for creating a GrShape from another representation. It also answers some
322 * questions about expected behavior for GrShape given the inputs.
323 */
324class Geo {
325public:
Mike Kleinfc6c37b2016-09-27 09:34:10 -0400326 virtual ~Geo() {}
bsalomona395f7c2016-08-24 17:47:40 -0700327 virtual GrShape makeShape(const SkPaint&) const = 0;
328 virtual SkPath path() const = 0;
329 // These functions allow tests to check for special cases where style gets
330 // applied by GrShape in its constructor (without calling GrShape::applyStyle).
331 // These unfortunately rely on knowing details of GrShape's implementation.
332 // These predicates are factored out here to avoid littering the rest of the
333 // test code with GrShape implementation details.
334 virtual bool fillChangesGeom() const { return false; }
335 virtual bool strokeIsConvertedToFill() const { return false; }
336 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
337 // Is this something we expect GrShape to recognize as something simpler than a path.
338 virtual bool isNonPath(const SkPaint& paint) const { return true; }
339};
340
341class RectGeo : public Geo {
342public:
343 RectGeo(const SkRect& rect) : fRect(rect) {}
344
345 SkPath path() const override {
346 SkPath path;
347 path.addRect(fRect);
348 return path;
349 }
350
351 GrShape makeShape(const SkPaint& paint) const override {
352 return GrShape(fRect, paint);
353 }
354
355 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
356 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
357 // Converted to an outset rectangle.
358 return paint.getStrokeJoin() == SkPaint::kMiter_Join &&
359 paint.getStrokeMiter() >= SK_ScalarSqrt2;
360 }
361
362private:
363 SkRect fRect;
364};
365
366class RRectGeo : public Geo {
367public:
368 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
369
370 GrShape makeShape(const SkPaint& paint) const override {
371 return GrShape(fRRect, paint);
372 }
373
374 SkPath path() const override {
375 SkPath path;
376 path.addRRect(fRRect);
377 return path;
378 }
379
380 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
381 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
382 if (fRRect.isRect()) {
383 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
384 }
385 return false;
386 }
387
388private:
389 SkRRect fRRect;
390};
391
Brian Salomone4949402018-04-26 15:22:04 -0400392class ArcGeo : public Geo {
393public:
394 ArcGeo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter)
395 : fOval(oval)
396 , fStartAngle(startAngle)
397 , fSweepAngle(sweepAngle)
398 , fUseCenter(useCenter) {}
399
400 SkPath path() const override {
401 SkPath path;
402 SkPathPriv::CreateDrawArcPath(&path, fOval, fStartAngle, fSweepAngle, fUseCenter, false);
403 return path;
404 }
405
406 GrShape makeShape(const SkPaint& paint) const override {
407 return GrShape::MakeArc(fOval, fStartAngle, fSweepAngle, fUseCenter, GrStyle(paint));
408 }
409
410 // GrShape specializes when created from arc params but it doesn't recognize arcs from SkPath.
411 bool isNonPath(const SkPaint& paint) const override { return false; }
412
413private:
414 SkRect fOval;
415 SkScalar fStartAngle;
416 SkScalar fSweepAngle;
417 bool fUseCenter;
418};
419
bsalomona395f7c2016-08-24 17:47:40 -0700420class PathGeo : public Geo {
421public:
422 enum class Invert { kNo, kYes };
423
424 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
425 SkASSERT(!path.isInverseFillType());
426 if (Invert::kYes == invert) {
427 if (fPath.getFillType() == SkPath::kEvenOdd_FillType) {
428 fPath.setFillType(SkPath::kInverseEvenOdd_FillType);
429 } else {
430 SkASSERT(fPath.getFillType() == SkPath::kWinding_FillType);
431 fPath.setFillType(SkPath::kInverseWinding_FillType);
432 }
433 }
434 }
435
436 GrShape makeShape(const SkPaint& paint) const override {
437 return GrShape(fPath, paint);
438 }
439
440 SkPath path() const override { return fPath; }
441
442 bool fillChangesGeom() const override {
443 // unclosed rects get closed. Lines get turned into empty geometry
Brian Salomon085c0862017-08-31 15:44:51 -0400444 return this->isUnclosedRect() || fPath.isLine(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -0700445 }
446
447 bool strokeIsConvertedToFill() const override {
448 return this->isAxisAlignedLine();
449 }
450
451 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
452 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
453 if (this->isAxisAlignedLine()) {
454 // The fill is ignored (zero area) and the stroke is converted to a rrect.
455 return true;
456 }
457 SkRect rect;
458 unsigned start;
459 SkPath::Direction dir;
460 if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
461 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
462 }
463 return false;
464 }
465
466 bool isNonPath(const SkPaint& paint) const override {
467 return fPath.isLine(nullptr) || fPath.isEmpty();
468 }
469
470private:
471 bool isAxisAlignedLine() const {
472 SkPoint pts[2];
473 if (!fPath.isLine(pts)) {
474 return false;
475 }
476 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
477 }
478
479 bool isUnclosedRect() const {
480 bool closed;
481 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
482 }
483
484 SkPath fPath;
485};
486
487class RRectPathGeo : public PathGeo {
488public:
489 enum class RRectForStroke { kNo, kYes };
490
491 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
492 Invert invert)
493 : PathGeo(path, invert)
494 , fRRect(equivalentRRect)
495 , fRRectForStroke(rrectForStroke) {}
496
497 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
498 Invert invert)
499 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
500
501 bool isNonPath(const SkPaint& paint) const override {
502 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
503 return true;
504 }
505 return false;
506 }
507
508 const SkRRect& rrect() const { return fRRect; }
509
510private:
511 SkRRect fRRect;
512 RRectForStroke fRRectForStroke;
513};
514
bsalomon47cc7692016-04-26 12:56:00 -0700515class TestCase {
516public:
bsalomona395f7c2016-08-24 17:47:40 -0700517 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
Brian Salomonc8cdad72018-04-16 09:46:09 -0400518 SkScalar scale = SK_Scalar1)
519 : fBase(new GrShape(geo.makeShape(paint))) {
bsalomon97fd2d42016-05-09 13:02:01 -0700520 this->init(r, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700521 }
522
Brian Salomonc8cdad72018-04-16 09:46:09 -0400523 template <typename... ShapeArgs>
524 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs) : fBase(new GrShape(shapeArgs...)) {
bsalomona395f7c2016-08-24 17:47:40 -0700525 this->init(r, SK_Scalar1);
526 }
527
bsalomon70493962016-06-10 08:05:14 -0700528 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
Brian Salomonc8cdad72018-04-16 09:46:09 -0400529 : fBase(new GrShape(shape)) {
bsalomon70493962016-06-10 08:05:14 -0700530 this->init(r, scale);
531 }
532
bsalomon47cc7692016-04-26 12:56:00 -0700533 struct SelfExpectations {
534 bool fPEHasEffect;
535 bool fPEHasValidKey;
536 bool fStrokeApplies;
537 };
538
539 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
540
541 enum ComparisonExpecation {
542 kAllDifferent_ComparisonExpecation,
543 kSameUpToPE_ComparisonExpecation,
544 kSameUpToStroke_ComparisonExpecation,
545 kAllSame_ComparisonExpecation,
546 };
547
548 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
549
Brian Salomonc8cdad72018-04-16 09:46:09 -0400550 const GrShape& baseShape() const { return *fBase; }
551 const GrShape& appliedPathEffectShape() const { return *fAppliedPE; }
552 const GrShape& appliedFullStyleShape() const { return *fAppliedFull; }
bsalomon72dc51c2016-04-27 06:46:23 -0700553
554 // The returned array's count will be 0 if the key shape has no key.
555 const Key& baseKey() const { return fBaseKey; }
556 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
557 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700558 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700559
bsalomon47cc7692016-04-26 12:56:00 -0700560private:
bsalomon9fb42032016-05-13 09:23:38 -0700561 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
562 SkPath path;
563 shape.asPath(&path);
564 // If the bounds are empty, the path ought to be as well.
bsalomon0ae36a22016-07-18 07:31:13 -0700565 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
bsalomon9fb42032016-05-13 09:23:38 -0700566 REPORTER_ASSERT(r, path.isEmpty());
567 return;
568 }
569 if (path.isEmpty()) {
570 return;
571 }
bsalomon70493962016-06-10 08:05:14 -0700572 // The bounds API explicitly calls out that it does not consider inverseness.
573 SkPath p = path;
574 p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType()));
575 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
bsalomon9fb42032016-05-13 09:23:38 -0700576 }
577
bsalomon97fd2d42016-05-09 13:02:01 -0700578 void init(skiatest::Reporter* r, SkScalar scale) {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400579 fAppliedPE.reset(new GrShape);
580 fAppliedPEThenStroke.reset(new GrShape);
581 fAppliedFull.reset(new GrShape);
bsalomon47cc7692016-04-26 12:56:00 -0700582
Brian Salomonc8cdad72018-04-16 09:46:09 -0400583 *fAppliedPE = fBase->applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
584 *fAppliedPEThenStroke =
585 fAppliedPE->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
586 *fAppliedFull = fBase->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
587
588 make_key(&fBaseKey, *fBase);
589 make_key(&fAppliedPEKey, *fAppliedPE);
590 make_key(&fAppliedPEThenStrokeKey, *fAppliedPEThenStroke);
591 make_key(&fAppliedFullKey, *fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700592
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400593 // All shapes should report the same "original" path, so that path renderers can get to it
594 // if necessary.
Brian Salomonc8cdad72018-04-16 09:46:09 -0400595 check_original_path_ids(r, *fBase, *fAppliedPE, *fAppliedPEThenStroke, *fAppliedFull);
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400596
bsalomonfb083272016-05-04 08:27:41 -0700597 // Applying the path effect and then the stroke should always be the same as applying
598 // both in one go.
599 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
600 SkPath a, b;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400601 fAppliedPEThenStroke->asPath(&a);
602 fAppliedFull->asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700603 // If the output of the path effect is a rrect then it is possible for a and b to be
604 // different paths that fill identically. The reason is that fAppliedFull will do this:
605 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
606 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
607 // now that there is no longer a path effect, the direction and starting index get
608 // canonicalized before the stroke.
Brian Salomonc8cdad72018-04-16 09:46:09 -0400609 if (fAppliedPE->asRRect(nullptr, nullptr, nullptr, nullptr)) {
bsalomonee295642016-06-06 14:01:25 -0700610 REPORTER_ASSERT(r, paths_fill_same(a, b));
611 } else {
612 REPORTER_ASSERT(r, a == b);
613 }
Brian Salomonc8cdad72018-04-16 09:46:09 -0400614 REPORTER_ASSERT(r, fAppliedFull->isEmpty() == fAppliedPEThenStroke->isEmpty());
bsalomon7c73a532016-05-11 15:15:56 -0700615
616 SkPath path;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400617 fBase->asPath(&path);
618 REPORTER_ASSERT(r, path.isEmpty() == fBase->isEmpty());
619 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase->segmentMask());
620 fAppliedPE->asPath(&path);
621 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE->isEmpty());
622 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE->segmentMask());
623 fAppliedFull->asPath(&path);
624 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull->isEmpty());
625 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull->segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700626
Brian Salomonc8cdad72018-04-16 09:46:09 -0400627 CheckBounds(r, *fBase, fBase->bounds());
628 CheckBounds(r, *fAppliedPE, fAppliedPE->bounds());
629 CheckBounds(r, *fAppliedPEThenStroke, fAppliedPEThenStroke->bounds());
630 CheckBounds(r, *fAppliedFull, fAppliedFull->bounds());
631 SkRect styledBounds = fBase->styledBounds();
632 CheckBounds(r, *fAppliedFull, styledBounds);
633 styledBounds = fAppliedPE->styledBounds();
634 CheckBounds(r, *fAppliedFull, styledBounds);
bsalomon9fb42032016-05-13 09:23:38 -0700635
bsalomonfb083272016-05-04 08:27:41 -0700636 // Check that the same path is produced when style is applied by GrShape and GrStyle.
637 SkPath preStyle;
638 SkPath postPathEffect;
639 SkPath postAllStyle;
640
Brian Salomonc8cdad72018-04-16 09:46:09 -0400641 fBase->asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700642 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
Brian Salomonc8cdad72018-04-16 09:46:09 -0400643 if (fBase->style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
644 scale)) {
bsalomon1a0b9ed2016-05-06 11:07:03 -0700645 // run postPathEffect through GrShape to get any geometry reductions that would have
646 // occurred to fAppliedPE.
647 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
648
bsalomonfb083272016-05-04 08:27:41 -0700649 SkPath testPath;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400650 fAppliedPE->asPath(&testPath);
bsalomonfb083272016-05-04 08:27:41 -0700651 REPORTER_ASSERT(r, testPath == postPathEffect);
Brian Salomonc8cdad72018-04-16 09:46:09 -0400652 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE->style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700653 }
654 SkStrokeRec::InitStyle fillOrHairline;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400655 if (fBase->style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomonfb083272016-05-04 08:27:41 -0700656 SkPath testPath;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400657 fAppliedFull->asPath(&testPath);
658 if (fBase->style().hasPathEffect()) {
bsalomon1b28c1a2016-06-20 12:28:17 -0700659 // Because GrShape always does two-stage application when there is a path effect
660 // there may be a reduction/canonicalization step between the path effect and
661 // strokerec not reflected in postAllStyle since it applied both the path effect
662 // and strokerec without analyzing the intermediate path.
663 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
664 } else {
665 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape
666 // would apply.
667 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
668 REPORTER_ASSERT(r, testPath == postAllStyle);
669 }
670
bsalomonfb083272016-05-04 08:27:41 -0700671 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400672 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleFill());
bsalomonfb083272016-05-04 08:27:41 -0700673 } else {
Brian Salomonc8cdad72018-04-16 09:46:09 -0400674 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleHairline());
bsalomonfb083272016-05-04 08:27:41 -0700675 }
676 }
Brian Salomonc8cdad72018-04-16 09:46:09 -0400677 test_inversions(r, *fBase, fBaseKey);
678 test_inversions(r, *fAppliedPE, fAppliedPEKey);
679 test_inversions(r, *fAppliedFull, fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700680 }
681
Brian Salomonc8cdad72018-04-16 09:46:09 -0400682 std::unique_ptr<GrShape> fBase;
683 std::unique_ptr<GrShape> fAppliedPE;
684 std::unique_ptr<GrShape> fAppliedPEThenStroke;
685 std::unique_ptr<GrShape> fAppliedFull;
bsalomon47cc7692016-04-26 12:56:00 -0700686
687 Key fBaseKey;
688 Key fAppliedPEKey;
689 Key fAppliedPEThenStrokeKey;
690 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700691};
692
693void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700694 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700695 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700696 if (expectations.fPEHasEffect) {
697 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700698 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700699 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700700 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700701 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
702 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700703 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700704 }
705 } else {
706 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700707 SkPath a, b;
Brian Salomonc8cdad72018-04-16 09:46:09 -0400708 fBase->asPath(&a);
709 fAppliedPE->asPath(&b);
bsalomon72dc51c2016-04-27 06:46:23 -0700710 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700711 if (expectations.fStrokeApplies) {
712 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
713 } else {
714 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
715 }
716 }
717}
718
bsalomonee295642016-06-06 14:01:25 -0700719void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700720 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700721 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700722 switch (expectation) {
723 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700724 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
725 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
726 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700727 break;
728 case kSameUpToPE_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400729 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
bsalomonee295642016-06-06 14:01:25 -0700730 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
731 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700732 break;
733 case kSameUpToStroke_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400734 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
735 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
bsalomonee295642016-06-06 14:01:25 -0700736 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700737 break;
738 case kAllSame_ComparisonExpecation:
Brian Salomonc8cdad72018-04-16 09:46:09 -0400739 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
740 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
741 check_equivalence(r, *fAppliedFull, *that.fAppliedFull, fAppliedFullKey,
bsalomonee295642016-06-06 14:01:25 -0700742 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700743 break;
744 }
745}
746} // namespace
747
748static sk_sp<SkPathEffect> make_dash() {
749 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
750 static const SkScalar kPhase = 0.75;
751 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
752}
753
754static sk_sp<SkPathEffect> make_null_dash() {
755 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
756 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
757}
758
Mike Klein43344282017-08-16 11:56:22 -0400759// We make enough TestCases, and they're large enough, that on Google3 builds we exceed
760// the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
761template <typename... Args>
762static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
763 return std::unique_ptr<TestCase>{ new TestCase(std::forward<Args>(args)...) };
764}
765
bsalomona395f7c2016-08-24 17:47:40 -0700766static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700767 sk_sp<SkPathEffect> dashPE = make_dash();
768
769 TestCase::SelfExpectations expectations;
770 SkPaint fill;
771
bsalomonfb083272016-05-04 08:27:41 -0700772 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700773 expectations.fPEHasEffect = false;
774 expectations.fPEHasValidKey = false;
775 expectations.fStrokeApplies = false;
776 fillCase.testExpectations(reporter, expectations);
777 // Test that another GrShape instance built from the same primitive is the same.
Mike Klein43344282017-08-16 11:56:22 -0400778 make_TestCase(geo, fill, reporter)
779 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700780
781 SkPaint stroke2RoundBevel;
782 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
783 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
784 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
785 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700786 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700787 expectations.fPEHasValidKey = true;
788 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700789 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomon47cc7692016-04-26 12:56:00 -0700790 stroke2RoundBevelCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400791 make_TestCase(geo, stroke2RoundBevel, reporter)
792 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700793
794 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
795 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700796 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700797 expectations.fPEHasValidKey = true;
798 expectations.fPEHasEffect = true;
799 expectations.fStrokeApplies = true;
800 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400801 make_TestCase(geo, stroke2RoundBevelDash, reporter)
802 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700803
bsalomona395f7c2016-08-24 17:47:40 -0700804 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700805 fillCase.compare(reporter, stroke2RoundBevelCase,
806 TestCase::kAllDifferent_ComparisonExpecation);
807 fillCase.compare(reporter, stroke2RoundBevelDashCase,
808 TestCase::kAllDifferent_ComparisonExpecation);
809 } else {
810 fillCase.compare(reporter, stroke2RoundBevelCase,
811 TestCase::kSameUpToStroke_ComparisonExpecation);
812 fillCase.compare(reporter, stroke2RoundBevelDashCase,
813 TestCase::kSameUpToPE_ComparisonExpecation);
814 }
bsalomona395f7c2016-08-24 17:47:40 -0700815 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700816 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
817 TestCase::kAllDifferent_ComparisonExpecation);
818 } else {
819 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
820 TestCase::kSameUpToPE_ComparisonExpecation);
821 }
bsalomon72dc51c2016-04-27 06:46:23 -0700822
bsalomonf0cf3552016-05-05 08:28:30 -0700823 // Stroke and fill cases
824 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
825 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
826 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
827 expectations.fPEHasValidKey = true;
828 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700829 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700830 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400831 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
832 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700833
834 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
835 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
836 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
837 expectations.fPEHasValidKey = true;
bsalomona0587862016-06-09 06:03:38 -0700838 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700839 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700840 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400841 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
bsalomonf0cf3552016-05-05 08:28:30 -0700842 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomona0587862016-06-09 06:03:38 -0700843 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
844 TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700845
bsalomon72dc51c2016-04-27 06:46:23 -0700846 SkPaint hairline;
847 hairline.setStyle(SkPaint::kStroke_Style);
848 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700849 TestCase hairlineCase(geo, hairline, reporter);
bsalomon487f8d32016-07-20 07:15:44 -0700850 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
851 // in the line and unclosed rect cases).
bsalomona395f7c2016-08-24 17:47:40 -0700852 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -0700853 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
854 } else {
855 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
856 }
bsalomon9ad5d7c2016-05-04 08:44:15 -0700857 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
858 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
859 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700860
bsalomon0ae36a22016-07-18 07:31:13 -0700861}
862
bsalomona395f7c2016-08-24 17:47:40 -0700863static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon97fd2d42016-05-09 13:02:01 -0700864 sk_sp<SkPathEffect> dashPE = make_dash();
865
866 static const SkScalar kS1 = 1.f;
867 static const SkScalar kS2 = 2.f;
868
869 SkPaint fill;
870 TestCase fillCase1(geo, fill, reporter, kS1);
871 TestCase fillCase2(geo, fill, reporter, kS2);
872 // Scale doesn't affect fills.
873 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
874
875 SkPaint hairline;
876 hairline.setStyle(SkPaint::kStroke_Style);
877 hairline.setStrokeWidth(0.f);
878 TestCase hairlineCase1(geo, hairline, reporter, kS1);
879 TestCase hairlineCase2(geo, hairline, reporter, kS2);
880 // Scale doesn't affect hairlines.
881 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
882
883 SkPaint stroke;
884 stroke.setStyle(SkPaint::kStroke_Style);
885 stroke.setStrokeWidth(2.f);
886 TestCase strokeCase1(geo, stroke, reporter, kS1);
887 TestCase strokeCase2(geo, stroke, reporter, kS2);
bsalomon0ae36a22016-07-18 07:31:13 -0700888 // Scale affects the stroke
bsalomona395f7c2016-08-24 17:47:40 -0700889 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700890 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
bsalomon0ae36a22016-07-18 07:31:13 -0700891 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
892 } else {
893 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
894 }
bsalomon97fd2d42016-05-09 13:02:01 -0700895
896 SkPaint strokeDash = stroke;
897 strokeDash.setPathEffect(make_dash());
898 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
899 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
900 // Scale affects the dash and the stroke.
bsalomon487f8d32016-07-20 07:15:44 -0700901 strokeDashCase1.compare(reporter, strokeDashCase2,
902 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700903
904 // Stroke and fill cases
905 SkPaint strokeAndFill = stroke;
906 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
907 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
908 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
bsalomona0587862016-06-09 06:03:38 -0700909 SkPaint strokeAndFillDash = strokeDash;
910 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
911 // Dash is ignored for stroke and fill
912 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
913 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
bsalomon487f8d32016-07-20 07:15:44 -0700914 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
915 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
916 // geometries should agree.
bsalomona395f7c2016-08-24 17:47:40 -0700917 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
bsalomon487f8d32016-07-20 07:15:44 -0700918 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
bsalomon97fd2d42016-05-09 13:02:01 -0700919 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
920 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700921 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
922 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700923 } else {
924 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
925 TestCase::kSameUpToStroke_ComparisonExpecation);
926 }
bsalomona0587862016-06-09 06:03:38 -0700927 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
928 TestCase::kAllSame_ComparisonExpecation);
929 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
930 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700931}
932
bsalomona395f7c2016-08-24 17:47:40 -0700933template <typename T>
934static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700935 std::function<void(SkPaint*, T)> setter, T a, T b,
936 bool paramAffectsStroke,
937 bool paramAffectsDashAndStroke) {
938 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
939 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700940 SkPaint strokeA;
941 strokeA.setStyle(SkPaint::kStroke_Style);
942 strokeA.setStrokeWidth(2.f);
943 setter(&strokeA, a);
944 SkPaint strokeB;
945 strokeB.setStyle(SkPaint::kStroke_Style);
946 strokeB.setStrokeWidth(2.f);
947 setter(&strokeB, b);
948
bsalomonfb083272016-05-04 08:27:41 -0700949 TestCase strokeACase(geo, strokeA, reporter);
950 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700951 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700952 // If stroking is immediately incorporated into a geometric transformation then the base
953 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700954 if (geo.strokeIsConvertedToFill()) {
bsalomon0ae36a22016-07-18 07:31:13 -0700955 strokeACase.compare(reporter, strokeBCase,
956 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700957 } else {
958 strokeACase.compare(reporter, strokeBCase,
959 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700960 }
bsalomon06077562016-05-04 13:50:29 -0700961 } else {
962 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
963 }
bsalomon47cc7692016-04-26 12:56:00 -0700964
bsalomonf0cf3552016-05-05 08:28:30 -0700965 SkPaint strokeAndFillA = strokeA;
966 SkPaint strokeAndFillB = strokeB;
967 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
968 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
969 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
970 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
971 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700972 // If stroking is immediately incorporated into a geometric transformation then the base
973 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700974 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
975 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700976 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700977 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700978 } else {
979 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700980 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700981 }
bsalomonf0cf3552016-05-05 08:28:30 -0700982 } else {
983 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
984 TestCase::kAllSame_ComparisonExpecation);
985 }
986
bsalomon47cc7692016-04-26 12:56:00 -0700987 // Make sure stroking params don't affect fill style.
988 SkPaint fillA = strokeA, fillB = strokeB;
989 fillA.setStyle(SkPaint::kFill_Style);
990 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -0700991 TestCase fillACase(geo, fillA, reporter);
992 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700993 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
994
995 // Make sure just applying the dash but not stroke gives the same key for both stroking
996 // variations.
997 SkPaint dashA = strokeA, dashB = strokeB;
998 dashA.setPathEffect(make_dash());
999 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -07001000 TestCase dashACase(geo, dashA, reporter);
1001 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -07001002 if (paramAffectsDashAndStroke) {
bsalomon487f8d32016-07-20 07:15:44 -07001003 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon06077562016-05-04 13:50:29 -07001004 } else {
1005 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
1006 }
bsalomon47cc7692016-04-26 12:56:00 -07001007}
1008
bsalomona395f7c2016-08-24 17:47:40 -07001009template <typename T>
1010static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -07001011 std::function<void(SkPaint*, T)> setter, T a, T b) {
1012 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
1013};
1014
bsalomona395f7c2016-08-24 17:47:40 -07001015static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
1016 SkPaint hairline;
1017 hairline.setStrokeWidth(0);
1018 hairline.setStyle(SkPaint::kStroke_Style);
1019 GrShape shape = geo.makeShape(hairline);
bsalomon06077562016-05-04 13:50:29 -07001020 // The cap should only affect shapes that may be open.
1021 bool affectsStroke = !shape.knownToBeClosed();
1022 // Dashing adds ends that need caps.
1023 bool affectsDashAndStroke = true;
bsalomona395f7c2016-08-24 17:47:40 -07001024 test_stroke_param_impl<SkPaint::Cap>(
bsalomon06077562016-05-04 13:50:29 -07001025 reporter,
1026 geo,
1027 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
1028 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
1029 affectsStroke,
1030 affectsDashAndStroke);
1031};
1032
bsalomon0ae36a22016-07-18 07:31:13 -07001033static bool shape_known_not_to_have_joins(const GrShape& shape) {
1034 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
1035}
1036
bsalomona395f7c2016-08-24 17:47:40 -07001037static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
1038 SkPaint hairline;
1039 hairline.setStrokeWidth(0);
1040 hairline.setStyle(SkPaint::kStroke_Style);
1041 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001042 // GrShape recognizes certain types don't have joins and will prevent the join type from
1043 // affecting the style key.
1044 // Dashing doesn't add additional joins. However, GrShape currently loses track of this
1045 // after applying the dash.
1046 bool affectsStroke = !shape_known_not_to_have_joins(shape);
bsalomona395f7c2016-08-24 17:47:40 -07001047 test_stroke_param_impl<SkPaint::Join>(
bsalomon0ae36a22016-07-18 07:31:13 -07001048 reporter,
1049 geo,
1050 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
1051 SkPaint::kRound_Join, SkPaint::kBevel_Join,
1052 affectsStroke, true);
1053};
1054
bsalomona395f7c2016-08-24 17:47:40 -07001055static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon06077562016-05-04 13:50:29 -07001056 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1057 p->setStrokeJoin(SkPaint::kMiter_Join);
1058 p->setStrokeMiter(miter);
1059 };
bsalomon47cc7692016-04-26 12:56:00 -07001060
bsalomon06077562016-05-04 13:50:29 -07001061 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1062 p->setStrokeJoin(SkPaint::kRound_Join);
1063 p->setStrokeMiter(miter);
1064 };
bsalomon47cc7692016-04-26 12:56:00 -07001065
bsalomona395f7c2016-08-24 17:47:40 -07001066 SkPaint hairline;
1067 hairline.setStrokeWidth(0);
1068 hairline.setStyle(SkPaint::kStroke_Style);
1069 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001070 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
1071
bsalomon06077562016-05-04 13:50:29 -07001072 // The miter limit should affect stroked and dashed-stroked cases when the join type is
1073 // miter.
bsalomona395f7c2016-08-24 17:47:40 -07001074 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001075 reporter,
1076 geo,
1077 setMiterJoinAndLimit,
1078 0.5f, 0.75f,
bsalomon0ae36a22016-07-18 07:31:13 -07001079 mayHaveJoins,
bsalomon06077562016-05-04 13:50:29 -07001080 true);
bsalomon47cc7692016-04-26 12:56:00 -07001081
bsalomon06077562016-05-04 13:50:29 -07001082 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
1083 // not miter.
bsalomona395f7c2016-08-24 17:47:40 -07001084 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001085 reporter,
1086 geo,
1087 setOtherJoinAndLimit,
1088 0.5f, 0.75f,
1089 false,
1090 false);
bsalomon47cc7692016-04-26 12:56:00 -07001091}
1092
bsalomona395f7c2016-08-24 17:47:40 -07001093static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001094 // A dash with no stroke should have no effect
1095 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
1096 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
1097 SkPaint dashFill;
1098 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -07001099 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001100
bsalomonfb083272016-05-04 08:27:41 -07001101 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001102 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
1103 }
1104}
1105
bsalomona395f7c2016-08-24 17:47:40 -07001106void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001107 SkPaint fill;
1108 SkPaint stroke;
1109 stroke.setStyle(SkPaint::kStroke_Style);
1110 stroke.setStrokeWidth(1.f);
1111 SkPaint dash;
1112 dash.setStyle(SkPaint::kStroke_Style);
1113 dash.setStrokeWidth(1.f);
1114 dash.setPathEffect(make_dash());
1115 SkPaint nullDash;
1116 nullDash.setStyle(SkPaint::kStroke_Style);
1117 nullDash.setStrokeWidth(1.f);
1118 nullDash.setPathEffect(make_null_dash());
1119
bsalomonfb083272016-05-04 08:27:41 -07001120 TestCase fillCase(geo, fill, reporter);
1121 TestCase strokeCase(geo, stroke, reporter);
1122 TestCase dashCase(geo, dash, reporter);
1123 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001124
bsalomon487f8d32016-07-20 07:15:44 -07001125 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
bsalomon47cc7692016-04-26 12:56:00 -07001126 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -07001127 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
1128 // on construction in order to determine how to compare the fill and stroke.
bsalomona395f7c2016-08-24 17:47:40 -07001129 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001130 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
1131 } else {
1132 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1133 }
1134 // 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 -07001135 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001136 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
1137 } else {
1138 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
1139 }
bsalomon47cc7692016-04-26 12:56:00 -07001140}
1141
bsalomona395f7c2016-08-24 17:47:40 -07001142void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001143 /**
1144 * This path effect takes any input path and turns it into a rrect. It passes through stroke
1145 * info.
1146 */
1147 class RRectPathEffect : SkPathEffect {
1148 public:
1149 static const SkRRect& RRect() {
1150 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1151 return kRRect;
1152 }
1153
1154 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1155 const SkRect* cullR) const override {
1156 dst->reset();
1157 dst->addRRect(RRect());
1158 return true;
1159 }
1160 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1161 *dst = RRect().getBounds();
1162 }
1163 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1164 Factory getFactory() const override { return nullptr; }
1165 void toString(SkString*) const override {}
1166 private:
1167 RRectPathEffect() {}
1168 };
1169
1170 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -07001171 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001172
1173 SkPaint pe;
1174 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001175 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001176
1177 SkPaint peStroke;
1178 peStroke.setPathEffect(RRectPathEffect::Make());
1179 peStroke.setStrokeWidth(2.f);
1180 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001181 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001182
bsalomon487f8d32016-07-20 07:15:44 -07001183 // Check whether constructing the filled case would cause the base shape to have a different
1184 // geometry (because of a geometric transformation upon initial GrShape construction).
bsalomona395f7c2016-08-24 17:47:40 -07001185 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -07001186 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1187 fillGeoCase.compare(reporter, geoPEStrokeCase,
1188 TestCase::kAllDifferent_ComparisonExpecation);
1189 } else {
1190 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1191 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1192 }
bsalomon72dc51c2016-04-27 06:46:23 -07001193 geoPECase.compare(reporter, geoPEStrokeCase,
1194 TestCase::kSameUpToStroke_ComparisonExpecation);
1195
bsalomona395f7c2016-08-24 17:47:40 -07001196 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
bsalomon72dc51c2016-04-27 06:46:23 -07001197 SkPaint stroke = peStroke;
1198 stroke.setPathEffect(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -07001199 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001200
1201 SkRRect rrect;
1202 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1203 // geoPECase, so the full style should be the same as just the PE.
bsalomon70493962016-06-10 08:05:14 -07001204 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1205 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001206 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1207 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1208
bsalomon70493962016-06-10 08:05:14 -07001209 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1210 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001211 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1212 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1213
1214 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomon70493962016-06-10 08:05:14 -07001215 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1216 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001217 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1218 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1219
bsalomon70493962016-06-10 08:05:14 -07001220 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1221 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001222 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1223 rrectStrokeCase.appliedFullStyleKey());
1224}
1225
bsalomona395f7c2016-08-24 17:47:40 -07001226void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001227 /**
1228 * This path effect just adds two lineTos to the input path.
1229 */
1230 class AddLineTosPathEffect : SkPathEffect {
1231 public:
1232 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1233 const SkRect* cullR) const override {
1234 *dst = src;
bsalomon67fa4e32016-09-21 08:26:57 -07001235 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1236 for (int i = 0; i < 100; ++i) {
1237 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1238 }
bsalomon72dc51c2016-04-27 06:46:23 -07001239 return true;
1240 }
1241 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1242 *dst = src;
Mike Reed185ffe92018-01-08 17:09:54 -05001243 SkRectPriv::GrowToInclude(dst, {0, 0});
1244 SkRectPriv::GrowToInclude(dst, {100, 100});
bsalomon72dc51c2016-04-27 06:46:23 -07001245 }
1246 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1247 Factory getFactory() const override { return nullptr; }
1248 void toString(SkString*) const override {}
1249 private:
1250 AddLineTosPathEffect() {}
1251 };
1252
bsalomon9ad5d7c2016-05-04 08:44:15 -07001253 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -07001254 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1255 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1256 SkPaint peStroke;
1257 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1258 peStroke.setStrokeWidth(2.f);
1259 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001260 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001261 TestCase::SelfExpectations expectations;
1262 expectations.fPEHasEffect = true;
1263 expectations.fPEHasValidKey = false;
1264 expectations.fStrokeApplies = true;
1265 geoPEStrokeCase.testExpectations(reporter, expectations);
1266}
1267
bsalomona395f7c2016-08-24 17:47:40 -07001268void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon9ad5d7c2016-05-04 08:44:15 -07001269 /**
1270 * This path effect just changes the stroke rec to hairline.
1271 */
1272 class MakeHairlinePathEffect : SkPathEffect {
1273 public:
1274 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1275 const SkRect* cullR) const override {
1276 *dst = src;
1277 strokeRec->setHairlineStyle();
1278 return true;
1279 }
bsalomon70493962016-06-10 08:05:14 -07001280 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001281 static sk_sp<SkPathEffect> Make() {
1282 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1283 }
1284 Factory getFactory() const override { return nullptr; }
1285 void toString(SkString*) const override {}
1286 private:
1287 MakeHairlinePathEffect() {}
1288 };
1289
1290 SkPaint fill;
1291 SkPaint pe;
1292 pe.setPathEffect(MakeHairlinePathEffect::Make());
1293
1294 TestCase peCase(geo, pe, reporter);
1295
bsalomonee295642016-06-06 14:01:25 -07001296 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -07001297 peCase.baseShape().asPath(&a);
1298 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -07001299 peCase.appliedFullStyleShape().asPath(&c);
bsalomona395f7c2016-08-24 17:47:40 -07001300 if (geo.isNonPath(pe)) {
bsalomonee295642016-06-06 14:01:25 -07001301 // RRect types can have a change in start index or direction after the PE is applied. This
1302 // is because once the PE is applied, GrShape may canonicalize the dir and index since it
1303 // is not germane to the styling any longer.
1304 // Instead we just check that the paths would fill the same both before and after styling.
1305 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1306 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -07001307 } else {
bsalomona4817af2016-06-23 11:48:26 -07001308 // The base shape cannot perform canonicalization on the path's fill type because of an
1309 // unknown path effect. However, after the path effect is applied the resulting hairline
1310 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1311 // don't distinguish between even/odd and non-zero winding.
1312 a.setFillType(b.getFillType());
bsalomonee295642016-06-06 14:01:25 -07001313 REPORTER_ASSERT(reporter, a == b);
1314 REPORTER_ASSERT(reporter, a == c);
bsalomon67fa4e32016-09-21 08:26:57 -07001315 // If the resulting path is small enough then it will have a key.
1316 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1317 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomonaa840642016-09-23 12:09:16 -07001318 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1319 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001320 }
bsalomonee295642016-06-06 14:01:25 -07001321 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1322 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001323}
1324
bsalomona395f7c2016-08-24 17:47:40 -07001325void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1326 SkPath vPath = geo.path();
bsalomon4eeccc92016-04-27 13:30:25 -07001327 vPath.setIsVolatile(true);
1328
1329 SkPaint dashAndStroke;
1330 dashAndStroke.setPathEffect(make_dash());
1331 dashAndStroke.setStrokeWidth(2.f);
1332 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001333 TestCase volatileCase(reporter, vPath, dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001334 // We expect a shape made from a volatile path to have a key iff the shape is recognized
bsalomonaa840642016-09-23 12:09:16 -07001335 // as a specialized geometry.
1336 if (geo.isNonPath(dashAndStroke)) {
bsalomon4eeccc92016-04-27 13:30:25 -07001337 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1338 // In this case all the keys should be identical to the non-volatile case.
bsalomona395f7c2016-08-24 17:47:40 -07001339 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001340 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1341 } else {
1342 // None of the keys should be valid.
1343 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1344 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1345 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1346 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1347 }
1348}
1349
bsalomona395f7c2016-08-24 17:47:40 -07001350void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon409ed732016-04-27 12:36:02 -07001351 /**
Brian Salomon085c0862017-08-31 15:44:51 -04001352 * This path effect returns an empty path (possibly inverted)
bsalomon409ed732016-04-27 12:36:02 -07001353 */
1354 class EmptyPathEffect : SkPathEffect {
1355 public:
1356 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1357 const SkRect* cullR) const override {
1358 dst->reset();
Brian Salomon085c0862017-08-31 15:44:51 -04001359 if (fInvert) {
1360 dst->toggleInverseFillType();
1361 }
bsalomon409ed732016-04-27 12:36:02 -07001362 return true;
1363 }
1364 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1365 dst->setEmpty();
1366 }
Brian Salomon085c0862017-08-31 15:44:51 -04001367 static sk_sp<SkPathEffect> Make(bool invert) {
1368 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
1369 }
bsalomon409ed732016-04-27 12:36:02 -07001370 Factory getFactory() const override { return nullptr; }
1371 void toString(SkString*) const override {}
1372 private:
Brian Salomon085c0862017-08-31 15:44:51 -04001373 bool fInvert;
1374 EmptyPathEffect(bool invert) : fInvert(invert) {}
bsalomon409ed732016-04-27 12:36:02 -07001375 };
1376
1377 SkPath emptyPath;
1378 GrShape emptyShape(emptyPath);
1379 Key emptyKey;
1380 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -07001381 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001382
Brian Salomon085c0862017-08-31 15:44:51 -04001383 emptyPath.toggleInverseFillType();
1384 GrShape invertedEmptyShape(emptyPath);
1385 Key invertedEmptyKey;
1386 make_key(&invertedEmptyKey, invertedEmptyShape);
1387 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
1388
1389 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
1390
bsalomon409ed732016-04-27 12:36:02 -07001391 SkPaint pe;
Brian Salomon085c0862017-08-31 15:44:51 -04001392 pe.setPathEffect(EmptyPathEffect::Make(false));
1393 TestCase geoPECase(geo, pe, reporter);
1394 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
1395 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
1396 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
1397 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
1398 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
1399 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
1400 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001401
1402 SkPaint peStroke;
Brian Salomon085c0862017-08-31 15:44:51 -04001403 peStroke.setPathEffect(EmptyPathEffect::Make(false));
bsalomon409ed732016-04-27 12:36:02 -07001404 peStroke.setStrokeWidth(2.f);
1405 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001406 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001407 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1408 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1409 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001410 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1411 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001412 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
1413 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
1414 pe.setPathEffect(EmptyPathEffect::Make(true));
1415
1416 TestCase geoPEInvertCase(geo, pe, reporter);
1417 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
1418 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
1419 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1420 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
1421 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
1422 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
1423 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
1424
1425 peStroke.setPathEffect(EmptyPathEffect::Make(true));
1426 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
1427 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
1428 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
1429 REPORTER_ASSERT(reporter,
1430 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1431 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
1432 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
1433 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
1434 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001435}
1436
bsalomona395f7c2016-08-24 17:47:40 -07001437void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
bsalomond6723842016-06-07 12:20:15 -07001438 /**
bsalomon0ae36a22016-07-18 07:31:13 -07001439 * This path effect always fails to apply.
bsalomond6723842016-06-07 12:20:15 -07001440 */
1441 class FailurePathEffect : SkPathEffect {
1442 public:
1443 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1444 const SkRect* cullR) const override {
1445 return false;
1446 }
1447 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1448 *dst = src;
1449 }
1450 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1451 Factory getFactory() const override { return nullptr; }
1452 void toString(SkString*) const override {}
1453 private:
1454 FailurePathEffect() {}
1455 };
1456
1457 SkPaint fill;
1458 TestCase fillCase(geo, fill, reporter);
1459
1460 SkPaint pe;
1461 pe.setPathEffect(FailurePathEffect::Make());
1462 TestCase peCase(geo, pe, reporter);
1463
1464 SkPaint stroke;
1465 stroke.setStrokeWidth(2.f);
1466 stroke.setStyle(SkPaint::kStroke_Style);
1467 TestCase strokeCase(geo, stroke, reporter);
1468
1469 SkPaint peStroke = stroke;
1470 peStroke.setPathEffect(FailurePathEffect::Make());
1471 TestCase peStrokeCase(geo, peStroke, reporter);
1472
1473 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1474 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1475 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its
1476 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from
1477 // closing it but after the effect fails we can (for the fill+pe case). This causes different
1478 // routes through GrShape to have equivalent but different representations of the path (closed
1479 // or not) but that fill the same.
1480 SkPath a;
1481 SkPath b;
1482 fillCase.appliedPathEffectShape().asPath(&a);
1483 peCase.appliedPathEffectShape().asPath(&b);
1484 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1485
1486 fillCase.appliedFullStyleShape().asPath(&a);
1487 peCase.appliedFullStyleShape().asPath(&b);
1488 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1489
1490 strokeCase.appliedPathEffectShape().asPath(&a);
1491 peStrokeCase.appliedPathEffectShape().asPath(&b);
1492 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1493
1494 strokeCase.appliedFullStyleShape().asPath(&a);
1495 peStrokeCase.appliedFullStyleShape().asPath(&b);
1496 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1497}
1498
Mike Klein43344282017-08-16 11:56:22 -04001499DEF_TEST(GrShape_empty_shape, reporter) {
bsalomon409ed732016-04-27 12:36:02 -07001500 SkPath emptyPath;
Brian Salomon085c0862017-08-31 15:44:51 -04001501 SkPath invertedEmptyPath;
1502 invertedEmptyPath.toggleInverseFillType();
bsalomon409ed732016-04-27 12:36:02 -07001503 SkPaint fill;
bsalomona395f7c2016-08-24 17:47:40 -07001504 TestCase fillEmptyCase(reporter, emptyPath, fill);
bsalomon7c73a532016-05-11 15:15:56 -07001505 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1506 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1507 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001508 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
1509 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
1510 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
1511 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
1512 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
1513 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
1514 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
1515 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
1516 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
1517 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001518
1519 Key emptyKey(fillEmptyCase.baseKey());
1520 REPORTER_ASSERT(reporter, emptyKey.count());
Brian Salomon085c0862017-08-31 15:44:51 -04001521 Key inverseEmptyKey(fillInvertedEmptyCase.baseKey());
1522 REPORTER_ASSERT(reporter, inverseEmptyKey.count());
bsalomon409ed732016-04-27 12:36:02 -07001523 TestCase::SelfExpectations expectations;
1524 expectations.fStrokeApplies = false;
1525 expectations.fPEHasEffect = false;
1526 // This will test whether applying style preserves emptiness
1527 fillEmptyCase.testExpectations(reporter, expectations);
Brian Salomon085c0862017-08-31 15:44:51 -04001528 fillInvertedEmptyCase.testExpectations(reporter, expectations);
bsalomon409ed732016-04-27 12:36:02 -07001529
1530 // Stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001531 SkPaint stroke;
1532 stroke.setStrokeWidth(2.f);
1533 stroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001534 stroke.setStrokeJoin(SkPaint::kRound_Join);
1535 stroke.setStrokeCap(SkPaint::kRound_Cap);
Brian Salomon085c0862017-08-31 15:44:51 -04001536 TestCase strokeEmptyCase(reporter, emptyPath, stroke);
bsalomon409ed732016-04-27 12:36:02 -07001537 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001538 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
1539 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
1540 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001541
1542 // Dashing and stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001543 SkPaint dashAndStroke;
1544 dashAndStroke.setPathEffect(make_dash());
1545 dashAndStroke.setStrokeWidth(2.f);
1546 dashAndStroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon085c0862017-08-31 15:44:51 -04001547 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
bsalomon409ed732016-04-27 12:36:02 -07001548 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1549 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001550 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
1551 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1552 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
1553 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001554
Brian Salomon2fad74a2017-12-20 13:28:55 -05001555 // A shape made from an empty rrect should behave the same as an empty path when filled but not
1556 // when stroked. However, dashing an empty rrect produces an empty path leaving nothing to
1557 // stroke - so equivalent to filling an empty path.
1558 SkRRect emptyRRect = SkRRect::MakeEmpty();
bsalomon5e410b42016-04-28 09:30:46 -07001559 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001560
1561 TestCase fillEmptyRRectCase(reporter, emptyRRect, fill);
1562 fillEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1563
1564 TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke);
1565 strokeEmptyRRectCase.compare(reporter, strokeEmptyCase,
1566 TestCase::kAllDifferent_ComparisonExpecation);
1567
bsalomona395f7c2016-08-24 17:47:40 -07001568 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001569 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1570 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001571
Brian Salomon085c0862017-08-31 15:44:51 -04001572 static constexpr SkPath::Direction kDir = SkPath::kCCW_Direction;
1573 static constexpr int kStart = 0;
Brian Salomon2fad74a2017-12-20 13:28:55 -05001574
1575 TestCase fillInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, GrStyle(fill));
1576 fillInvertedEmptyRRectCase.compare(reporter, fillInvertedEmptyCase,
1577 TestCase::kAllSame_ComparisonExpecation);
1578
1579 TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true,
1580 GrStyle(stroke));
1581 strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase,
1582 TestCase::kAllDifferent_ComparisonExpecation);
1583
Brian Salomon085c0862017-08-31 15:44:51 -04001584 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
1585 GrStyle(dashAndStroke));
Brian Salomon085c0862017-08-31 15:44:51 -04001586 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
1587 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001588
1589 // Same for a rect.
1590 SkRect emptyRect = SkRect::MakeEmpty();
Brian Salomon2fad74a2017-12-20 13:28:55 -05001591 TestCase fillEmptyRectCase(reporter, emptyRect, fill);
1592 fillEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1593
bsalomona395f7c2016-08-24 17:47:40 -07001594 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001595 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1596 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001597
Brian Salomon085c0862017-08-31 15:44:51 -04001598 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
1599 kStart, true, GrStyle(dashAndStroke));
1600 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1601 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
1602 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001603}
1604
bsalomon70493962016-06-10 08:05:14 -07001605// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1606// canonical point in these cases.
1607unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1608 switch (rrect.getType()) {
1609 case SkRRect::kRect_Type:
1610 return (s + 1) & 0b110;
1611 case SkRRect::kOval_Type:
1612 return s & 0b110;
1613 default:
1614 return s;
1615 }
1616}
1617
1618void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
bsalomoncadb5a22016-06-10 18:28:06 -07001619 enum Style {
bsalomon70493962016-06-10 08:05:14 -07001620 kFill,
1621 kStroke,
1622 kHairline,
1623 kStrokeAndFill
1624 };
1625
1626 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1627 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1628 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1629 strokeRecs[kFill].setFillStyle();
1630 strokeRecs[kStroke].setStrokeStyle(2.f);
1631 strokeRecs[kHairline].setHairlineStyle();
1632 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
bsalomon487f8d32016-07-20 07:15:44 -07001633 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1634 // applyStyle() is called.
1635 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
bsalomon70493962016-06-10 08:05:14 -07001636 sk_sp<SkPathEffect> dashEffect = make_dash();
1637
bsalomoncadb5a22016-06-10 18:28:06 -07001638 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1639
1640 auto index = [](bool inverted,
1641 SkPath::Direction dir,
1642 unsigned start,
1643 Style style,
1644 bool dash) -> int {
1645 return inverted * (2 * 8 * kStyleCnt * 2) +
1646 dir * ( 8 * kStyleCnt * 2) +
1647 start * ( kStyleCnt * 2) +
1648 style * ( 2) +
1649 dash;
1650 };
1651 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1);
1652 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1653 SkAutoTArray<GrShape> shapes(cnt);
1654 for (bool inverted : {false, true}) {
1655 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1656 for (unsigned start = 0; start < 8; ++start) {
1657 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1658 for (bool dash : {false, true}) {
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001659 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
bsalomoncadb5a22016-06-10 18:28:06 -07001660 shapes[index(inverted, dir, start, style, dash)] =
1661 GrShape(rrect, dir, start, SkToBool(inverted),
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001662 GrStyle(strokeRecs[style], std::move(pe)));
bsalomon70493962016-06-10 08:05:14 -07001663 }
1664 }
1665 }
1666 }
1667 }
1668
bsalomonfd32df72016-06-14 14:37:21 -07001669 // Get the keys for some example shape instances that we'll use for comparision against the
1670 // rest.
1671 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction;
1672 static constexpr unsigned kExamplesStart = 0;
1673 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1674 false)];
bsalomon70493962016-06-10 08:05:14 -07001675 Key exampleFillCaseKey;
1676 make_key(&exampleFillCaseKey, exampleFillCase);
1677
bsalomonfd32df72016-06-14 14:37:21 -07001678 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart,
1679 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001680 Key exampleStrokeAndFillCaseKey;
1681 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1682
bsalomonfd32df72016-06-14 14:37:21 -07001683 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill,
1684 false)];
bsalomon70493962016-06-10 08:05:14 -07001685 Key exampleInvFillCaseKey;
1686 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1687
bsalomonfd32df72016-06-14 14:37:21 -07001688 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart,
1689 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001690 Key exampleInvStrokeAndFillCaseKey;
1691 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1692
bsalomonfd32df72016-06-14 14:37:21 -07001693 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke,
1694 false)];
bsalomon70493962016-06-10 08:05:14 -07001695 Key exampleStrokeCaseKey;
1696 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1697
bsalomonfd32df72016-06-14 14:37:21 -07001698 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke,
1699 false)];
1700 Key exampleInvStrokeCaseKey;
1701 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1702
1703 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1704 kHairline, false)];
bsalomon70493962016-06-10 08:05:14 -07001705 Key exampleHairlineCaseKey;
1706 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1707
bsalomonfd32df72016-06-14 14:37:21 -07001708 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1709 kHairline, false)];
1710 Key exampleInvHairlineCaseKey;
1711 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1712
bsalomon70493962016-06-10 08:05:14 -07001713 // These are dummy initializations to suppress warnings.
bsalomoncadb5a22016-06-10 18:28:06 -07001714 SkRRect queryRR = SkRRect::MakeEmpty();
1715 SkPath::Direction queryDir = SkPath::kCW_Direction;
1716 unsigned queryStart = ~0U;
1717 bool queryInverted = true;
bsalomon70493962016-06-10 08:05:14 -07001718
bsalomoncadb5a22016-06-10 18:28:06 -07001719 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1720 REPORTER_ASSERT(r, queryRR == rrect);
1721 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1722 REPORTER_ASSERT(r, 0 == queryStart);
1723 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001724
bsalomoncadb5a22016-06-10 18:28:06 -07001725 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1726 &queryInverted));
1727 REPORTER_ASSERT(r, queryRR == rrect);
1728 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1729 REPORTER_ASSERT(r, 0 == queryStart);
1730 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001731
bsalomoncadb5a22016-06-10 18:28:06 -07001732 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1733 &queryInverted));
1734 REPORTER_ASSERT(r, queryRR == rrect);
1735 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1736 REPORTER_ASSERT(r, 0 == queryStart);
1737 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001738
bsalomoncadb5a22016-06-10 18:28:06 -07001739 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1740 &queryInverted));
1741 REPORTER_ASSERT(r, queryRR == rrect);
1742 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1743 REPORTER_ASSERT(r, 0 == queryStart);
1744 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001745
bsalomoncadb5a22016-06-10 18:28:06 -07001746 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1747 &queryInverted));
1748 REPORTER_ASSERT(r, queryRR == rrect);
1749 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1750 REPORTER_ASSERT(r, 0 == queryStart);
1751 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001752
bsalomonfd32df72016-06-14 14:37:21 -07001753 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1754 &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);
1759
bsalomoncadb5a22016-06-10 18:28:06 -07001760 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1761 REPORTER_ASSERT(r, queryRR == rrect);
1762 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1763 REPORTER_ASSERT(r, 0 == queryStart);
1764 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001765
bsalomonfd32df72016-06-14 14:37:21 -07001766 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1767 &queryInverted));
1768 REPORTER_ASSERT(r, queryRR == rrect);
1769 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1770 REPORTER_ASSERT(r, 0 == queryStart);
1771 REPORTER_ASSERT(r, queryInverted);
1772
bsalomon70493962016-06-10 08:05:14 -07001773 // Remember that the key reflects the geometry before styling is applied.
1774 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1775 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1776 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1777 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001778 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001779 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001780 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001781 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001782 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1783 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001784
bsalomoncadb5a22016-06-10 18:28:06 -07001785 for (bool inverted : {false, true}) {
1786 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1787 for (unsigned start = 0; start < 8; ++start) {
1788 for (bool dash : {false, true}) {
1789 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001790 Key fillCaseKey;
1791 make_key(&fillCaseKey, fillCase);
1792
bsalomoncadb5a22016-06-10 18:28:06 -07001793 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1794 kStrokeAndFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001795 Key strokeAndFillCaseKey;
1796 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1797
1798 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1799 // ignore dashing.
1800 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1801 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1802 TestCase a(fillCase, r);
1803 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1804 TestCase c(strokeAndFillCase, r);
1805 TestCase d(inverted ? exampleInvStrokeAndFillCase
1806 : exampleStrokeAndFillCase, r);
1807 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1808 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1809
bsalomoncadb5a22016-06-10 18:28:06 -07001810 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)];
1811 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline,
1812 dash)];
bsalomon70493962016-06-10 08:05:14 -07001813
1814 TestCase e(strokeCase, r);
bsalomon70493962016-06-10 08:05:14 -07001815 TestCase g(hairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001816
bsalomonfd32df72016-06-14 14:37:21 -07001817 // Both hairline and stroke shapes must respect the dashing.
bsalomon70493962016-06-10 08:05:14 -07001818 if (dash) {
bsalomonfd32df72016-06-14 14:37:21 -07001819 // Dashing always ignores the inverseness. skbug.com/5421
1820 TestCase f(exampleStrokeCase, r);
1821 TestCase h(exampleHairlineCase, r);
bsalomoncadb5a22016-06-10 18:28:06 -07001822 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
bsalomon70493962016-06-10 08:05:14 -07001823 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1824 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1825
bsalomoncadb5a22016-06-10 18:28:06 -07001826 REPORTER_ASSERT(r, strokeCase.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);
1832 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1833 &queryInverted));
1834 REPORTER_ASSERT(r, queryRR == rrect);
1835 REPORTER_ASSERT(r, queryDir == dir);
1836 REPORTER_ASSERT(r, queryStart == expectedStart);
1837 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001838
1839 // The pre-style case for the dash will match the non-dash example iff the
1840 // dir and start match (dir=cw, start=0).
bsalomoncadb5a22016-06-10 18:28:06 -07001841 if (0 == expectedStart && SkPath::kCW_Direction == dir) {
bsalomon70493962016-06-10 08:05:14 -07001842 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1843 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1844 } else {
1845 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1846 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1847 }
1848 } else {
bsalomonfd32df72016-06-14 14:37:21 -07001849 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1850 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001851 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1852 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1853 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1854 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1855 }
1856 }
1857 }
1858 }
1859 }
1860}
1861
Mike Klein43344282017-08-16 11:56:22 -04001862DEF_TEST(GrShape_lines, r) {
bsalomon0a0f67e2016-06-28 11:56:42 -07001863 static constexpr SkPoint kA { 1, 1};
1864 static constexpr SkPoint kB { 5, -9};
1865 static constexpr SkPoint kC {-3, 17};
1866
1867 SkPath lineAB;
1868 lineAB.moveTo(kA);
1869 lineAB.lineTo(kB);
1870
1871 SkPath lineBA;
1872 lineBA.moveTo(kB);
1873 lineBA.lineTo(kA);
1874
1875 SkPath lineAC;
1876 lineAC.moveTo(kB);
1877 lineAC.lineTo(kC);
1878
1879 SkPath invLineAB = lineAB;
1880 invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType);
1881
1882 SkPaint fill;
1883 SkPaint stroke;
1884 stroke.setStyle(SkPaint::kStroke_Style);
1885 stroke.setStrokeWidth(2.f);
1886 SkPaint hairline;
1887 hairline.setStyle(SkPaint::kStroke_Style);
1888 hairline.setStrokeWidth(0.f);
1889 SkPaint dash = stroke;
1890 dash.setPathEffect(make_dash());
1891
bsalomona395f7c2016-08-24 17:47:40 -07001892 TestCase fillAB(r, lineAB, fill);
1893 TestCase fillEmpty(r, SkPath(), fill);
bsalomon0a0f67e2016-06-28 11:56:42 -07001894 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1895 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1896
Brian Salomon085c0862017-08-31 15:44:51 -04001897 SkPath path;
1898 path.toggleInverseFillType();
1899 TestCase fillEmptyInverted(r, path, fill);
1900 TestCase fillABInverted(r, invLineAB, fill);
1901 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
1902 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
1903
bsalomona395f7c2016-08-24 17:47:40 -07001904 TestCase strokeAB(r, lineAB, stroke);
1905 TestCase strokeBA(r, lineBA, stroke);
1906 TestCase strokeAC(r, lineAC, stroke);
bsalomon0a0f67e2016-06-28 11:56:42 -07001907
bsalomona395f7c2016-08-24 17:47:40 -07001908 TestCase hairlineAB(r, lineAB, hairline);
1909 TestCase hairlineBA(r, lineBA, hairline);
1910 TestCase hairlineAC(r, lineAC, hairline);
bsalomon0a0f67e2016-06-28 11:56:42 -07001911
bsalomona395f7c2016-08-24 17:47:40 -07001912 TestCase dashAB(r, lineAB, dash);
1913 TestCase dashBA(r, lineBA, dash);
1914 TestCase dashAC(r, lineAC, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001915
1916 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1917
1918 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1919 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1920
1921 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1922 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1923
1924 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1925 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1926
1927 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1928
1929 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1930 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1931 bool canonicalizeAsAB;
1932 SkPoint canonicalPts[2] {kA, kB};
1933 // Init these to suppress warnings.
1934 bool inverted = true;
1935 SkPoint pts[2] {{0, 0}, {0, 0}};
1936 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1937 if (pts[0] == kA && pts[1] == kB) {
1938 canonicalizeAsAB = true;
1939 } else if (pts[1] == kA && pts[0] == kB) {
1940 canonicalizeAsAB = false;
1941 SkTSwap(canonicalPts[0], canonicalPts[1]);
1942 } else {
1943 ERRORF(r, "Should return pts (a,b) or (b, a)");
1944 return;
1945 };
1946
1947 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1948 TestCase::kSameUpToPE_ComparisonExpecation);
1949 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1950 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1951 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1952 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1953 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1954 pts[0] == kA && pts[1] == kB);
1955 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1956 pts[0] == kB && pts[1] == kA);
1957
1958
bsalomona395f7c2016-08-24 17:47:40 -07001959 TestCase strokeInvAB(r, invLineAB, stroke);
1960 TestCase hairlineInvAB(r, invLineAB, hairline);
1961 TestCase dashInvAB(r, invLineAB, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001962 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1963 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1964 // Dashing ignores inverse.
1965 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1966
1967 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1968 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1969 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1970 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1971 // Dashing ignores inverse.
1972 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1973 pts[0] == kA && pts[1] == kB);
1974
1975}
1976
Mike Klein43344282017-08-16 11:56:22 -04001977DEF_TEST(GrShape_stroked_lines, r) {
Brian Salomon72f78c32017-12-21 11:56:42 -05001978 static constexpr SkScalar kIntervals1[] = {1.f, 0.f};
1979 auto dash1 = SkDashPathEffect::Make(kIntervals1, SK_ARRAY_COUNT(kIntervals1), 0.f);
1980 REPORTER_ASSERT(r, dash1);
1981 static constexpr SkScalar kIntervals2[] = {10.f, 0.f, 5.f, 0.f};
1982 auto dash2 = SkDashPathEffect::Make(kIntervals2, SK_ARRAY_COUNT(kIntervals2), 10.f);
1983 REPORTER_ASSERT(r, dash2);
bsalomon0ae36a22016-07-18 07:31:13 -07001984
Brian Salomon72f78c32017-12-21 11:56:42 -05001985 sk_sp<SkPathEffect> pathEffects[] = {nullptr, std::move(dash1), std::move(dash2)};
bsalomon0ae36a22016-07-18 07:31:13 -07001986
Brian Salomon72f78c32017-12-21 11:56:42 -05001987 for (const auto& pe : pathEffects) {
1988 // Paints to try
1989 SkPaint buttCap;
1990 buttCap.setStyle(SkPaint::kStroke_Style);
1991 buttCap.setStrokeWidth(4);
1992 buttCap.setStrokeCap(SkPaint::kButt_Cap);
1993 buttCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07001994
Brian Salomon72f78c32017-12-21 11:56:42 -05001995 SkPaint squareCap = buttCap;
1996 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
1997 squareCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07001998
Brian Salomon72f78c32017-12-21 11:56:42 -05001999 SkPaint roundCap = buttCap;
2000 roundCap.setStrokeCap(SkPaint::kRound_Cap);
2001 roundCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07002002
Brian Salomon72f78c32017-12-21 11:56:42 -05002003 // vertical
2004 SkPath linePath;
2005 linePath.moveTo(4, 4);
2006 linePath.lineTo(4, 5);
bsalomon0ae36a22016-07-18 07:31:13 -07002007
Brian Salomon72f78c32017-12-21 11:56:42 -05002008 SkPaint fill;
bsalomon0ae36a22016-07-18 07:31:13 -07002009
Brian Salomon72f78c32017-12-21 11:56:42 -05002010 make_TestCase(r, linePath, buttCap)->compare(
2011 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
2012 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002013
Brian Salomon72f78c32017-12-21 11:56:42 -05002014 make_TestCase(r, linePath, squareCap)->compare(
2015 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
2016 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002017
Brian Salomon72f78c32017-12-21 11:56:42 -05002018 make_TestCase(r, linePath, roundCap)->compare(r,
2019 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
2020 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07002021
Brian Salomon72f78c32017-12-21 11:56:42 -05002022 // horizontal
2023 linePath.reset();
2024 linePath.moveTo(4, 4);
2025 linePath.lineTo(5, 4);
bsalomon0ae36a22016-07-18 07:31:13 -07002026
Brian Salomon72f78c32017-12-21 11:56:42 -05002027 make_TestCase(r, linePath, buttCap)->compare(
2028 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
2029 TestCase::kAllSame_ComparisonExpecation);
2030 make_TestCase(r, linePath, squareCap)->compare(
2031 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
2032 TestCase::kAllSame_ComparisonExpecation);
2033 make_TestCase(r, linePath, roundCap)->compare(
2034 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
2035 TestCase::kAllSame_ComparisonExpecation);
2036
2037 // point
2038 linePath.reset();
2039 linePath.moveTo(4, 4);
2040 linePath.lineTo(4, 4);
2041
2042 make_TestCase(r, linePath, buttCap)->compare(
2043 r, TestCase(r, SkRect::MakeEmpty(), fill),
2044 TestCase::kAllSame_ComparisonExpecation);
2045 make_TestCase(r, linePath, squareCap)->compare(
2046 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
2047 TestCase::kAllSame_ComparisonExpecation);
2048 make_TestCase(r, linePath, roundCap)->compare(
2049 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
2050 TestCase::kAllSame_ComparisonExpecation);
2051 }
bsalomon0ae36a22016-07-18 07:31:13 -07002052}
2053
Mike Klein43344282017-08-16 11:56:22 -04002054DEF_TEST(GrShape_short_path_keys, r) {
bsalomon67fa4e32016-09-21 08:26:57 -07002055 SkPaint paints[4];
2056 paints[1].setStyle(SkPaint::kStroke_Style);
2057 paints[1].setStrokeWidth(5.f);
2058 paints[2].setStyle(SkPaint::kStroke_Style);
2059 paints[2].setStrokeWidth(0.f);
2060 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
2061 paints[3].setStrokeWidth(5.f);
2062
bsalomonaa840642016-09-23 12:09:16 -07002063 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
bsalomon67fa4e32016-09-21 08:26:57 -07002064 TestCase::ComparisonExpecation expectation) {
bsalomonaa840642016-09-23 12:09:16 -07002065 SkPath volatileA = pathA;
2066 SkPath volatileB = pathB;
2067 volatileA.setIsVolatile(true);
2068 volatileB.setIsVolatile(true);
bsalomon67fa4e32016-09-21 08:26:57 -07002069 for (const SkPaint& paint : paints) {
bsalomonaa840642016-09-23 12:09:16 -07002070 REPORTER_ASSERT(r, !GrShape(volatileA, paint).hasUnstyledKey());
2071 REPORTER_ASSERT(r, !GrShape(volatileB, paint).hasUnstyledKey());
bsalomon67fa4e32016-09-21 08:26:57 -07002072 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
bsalomonaa840642016-09-23 12:09:16 -07002073 TestCase caseA(PathGeo(pathA, invert), paint, r);
2074 TestCase caseB(PathGeo(pathB, invert), paint, r);
2075 caseA.compare(r, caseB, expectation);
bsalomon67fa4e32016-09-21 08:26:57 -07002076 }
2077 }
2078 };
2079
2080 SkPath pathA;
2081 SkPath pathB;
2082
2083 // Two identical paths
2084 pathA.lineTo(10.f, 10.f);
2085 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2086
2087 pathB.lineTo(10.f, 10.f);
2088 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002089 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002090
2091 // Give path b a different point
2092 pathB.reset();
2093 pathB.lineTo(10.f, 10.f);
2094 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002095 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002096
2097 // Give path b a different conic weight
2098 pathB.reset();
2099 pathB.lineTo(10.f, 10.f);
2100 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
bsalomonaa840642016-09-23 12:09:16 -07002101 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002102
2103 // Give path b an extra lineTo verb
2104 pathB.reset();
2105 pathB.lineTo(10.f, 10.f);
2106 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2107 pathB.lineTo(50.f, 50.f);
bsalomonaa840642016-09-23 12:09:16 -07002108 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002109
2110 // Give path b a close
2111 pathB.reset();
2112 pathB.lineTo(10.f, 10.f);
2113 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2114 pathB.close();
bsalomonaa840642016-09-23 12:09:16 -07002115 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002116}
2117
bsalomon47cc7692016-04-26 12:56:00 -07002118DEF_TEST(GrShape, reporter) {
bsalomona395f7c2016-08-24 17:47:40 -07002119 SkTArray<std::unique_ptr<Geo>> geos;
2120 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
2121
bsalomonee295642016-06-06 14:01:25 -07002122 for (auto r : { SkRect::MakeWH(10, 20),
2123 SkRect::MakeWH(-10, -20),
2124 SkRect::MakeWH(-10, 20),
2125 SkRect::MakeWH(10, -20)}) {
bsalomona395f7c2016-08-24 17:47:40 -07002126 geos.emplace_back(new RectGeo(r));
2127 SkPath rectPath;
2128 rectPath.addRect(r);
2129 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2130 PathGeo::Invert::kNo));
2131 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2132 PathGeo::Invert::kYes));
2133 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2134 PathGeo::Invert::kNo));
bsalomonee295642016-06-06 14:01:25 -07002135 }
bsalomon47cc7692016-04-26 12:56:00 -07002136 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -07002137 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
2138 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomona395f7c2016-08-24 17:47:40 -07002139 geos.emplace_back(new RRectGeo(rr));
bsalomon70493962016-06-10 08:05:14 -07002140 test_rrect(reporter, rr);
bsalomona395f7c2016-08-24 17:47:40 -07002141 SkPath rectPath;
2142 rectPath.addRRect(rr);
2143 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2144 PathGeo::Invert::kNo));
2145 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2146 PathGeo::Invert::kYes));
2147 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
2148 RRectPathGeo::RRectForStroke::kYes,
2149 PathGeo::Invert::kNo));
bsalomon72dc51c2016-04-27 06:46:23 -07002150 }
2151
Brian Salomone4949402018-04-26 15:22:04 -04002152 // Arcs
2153 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, false));
2154 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, true));
2155
Mike Klein43344282017-08-16 11:56:22 -04002156 {
2157 SkPath openRectPath;
2158 openRectPath.moveTo(0, 0);
2159 openRectPath.lineTo(10, 0);
2160 openRectPath.lineTo(10, 10);
2161 openRectPath.lineTo(0, 10);
2162 geos.emplace_back(new RRectPathGeo(
2163 openRectPath, SkRect::MakeWH(10, 10),
2164 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2165 geos.emplace_back(new RRectPathGeo(
2166 openRectPath, SkRect::MakeWH(10, 10),
2167 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
2168 rrectPathGeos.emplace_back(new RRectPathGeo(
2169 openRectPath, SkRect::MakeWH(10, 10),
2170 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2171 }
bsalomon72dc51c2016-04-27 06:46:23 -07002172
Mike Klein43344282017-08-16 11:56:22 -04002173 {
2174 SkPath quadPath;
2175 quadPath.quadTo(10, 10, 5, 8);
2176 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
2177 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
2178 }
bsalomon398e3f42016-06-13 10:22:48 -07002179
Mike Klein43344282017-08-16 11:56:22 -04002180 {
2181 SkPath linePath;
2182 linePath.lineTo(10, 10);
2183 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
2184 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
2185 }
bsalomon72dc51c2016-04-27 06:46:23 -07002186
bsalomon0ae36a22016-07-18 07:31:13 -07002187 // Horizontal and vertical paths become rrects when stroked.
Mike Klein43344282017-08-16 11:56:22 -04002188 {
2189 SkPath vLinePath;
2190 vLinePath.lineTo(0, 10);
2191 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
2192 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
2193 }
bsalomon0ae36a22016-07-18 07:31:13 -07002194
Mike Klein43344282017-08-16 11:56:22 -04002195 {
2196 SkPath hLinePath;
2197 hLinePath.lineTo(10, 0);
2198 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
2199 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
2200 }
bsalomon0ae36a22016-07-18 07:31:13 -07002201
bsalomona395f7c2016-08-24 17:47:40 -07002202 for (int i = 0; i < geos.count(); ++i) {
2203 test_basic(reporter, *geos[i]);
2204 test_scale(reporter, *geos[i]);
2205 test_dash_fill(reporter, *geos[i]);
2206 test_null_dash(reporter, *geos[i]);
2207 // Test modifying various stroke params.
2208 test_stroke_param<SkScalar>(
2209 reporter, *geos[i],
bsalomon70493962016-06-10 08:05:14 -07002210 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
2211 SkIntToScalar(2), SkIntToScalar(4));
bsalomona395f7c2016-08-24 17:47:40 -07002212 test_stroke_join(reporter, *geos[i]);
2213 test_stroke_cap(reporter, *geos[i]);
2214 test_miter_limit(reporter, *geos[i]);
2215 test_path_effect_makes_rrect(reporter, *geos[i]);
2216 test_unknown_path_effect(reporter, *geos[i]);
2217 test_path_effect_makes_empty_shape(reporter, *geos[i]);
2218 test_path_effect_fails(reporter, *geos[i]);
2219 test_make_hairline_path_effect(reporter, *geos[i]);
2220 test_volatile_path(reporter, *geos[i]);
bsalomon70493962016-06-10 08:05:14 -07002221 }
bsalomonfd32df72016-06-14 14:37:21 -07002222
bsalomona395f7c2016-08-24 17:47:40 -07002223 for (int i = 0; i < rrectPathGeos.count(); ++i) {
2224 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
bsalomon72dc51c2016-04-27 06:46:23 -07002225 SkPaint fillPaint;
bsalomona395f7c2016-08-24 17:47:40 -07002226 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002227 SkRRect rrect;
bsalomona395f7c2016-08-24 17:47:40 -07002228 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
bsalomon70493962016-06-10 08:05:14 -07002229 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2230 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002231 if (rrgeo.isNonPath(fillPaint)) {
2232 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
2233 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2234 TestCase fillRRectCase(reporter, rrect, fillPaint);
bsalomon70493962016-06-10 08:05:14 -07002235 fillPathCase2.compare(reporter, fillRRectCase,
2236 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002237 }
bsalomon72dc51c2016-04-27 06:46:23 -07002238 SkPaint strokePaint;
2239 strokePaint.setStrokeWidth(3.f);
2240 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07002241 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
2242 if (rrgeo.isNonPath(strokePaint)) {
bsalomon0ae36a22016-07-18 07:31:13 -07002243 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2244 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002245 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2246 TestCase strokeRRectCase(reporter, rrect, strokePaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002247 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07002248 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002249 }
bsalomon47cc7692016-04-26 12:56:00 -07002250 }
bsalomon409ed732016-04-27 12:36:02 -07002251
bsalomon4eeccc92016-04-27 13:30:25 -07002252 // Test a volatile empty path.
bsalomona395f7c2016-08-24 17:47:40 -07002253 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
bsalomon47cc7692016-04-26 12:56:00 -07002254}
2255
Brian Salomone4949402018-04-26 15:22:04 -04002256DEF_TEST(GrShape_arcs, reporter) {
2257 SkStrokeRec roundStroke(SkStrokeRec::kFill_InitStyle);
2258 roundStroke.setStrokeStyle(2.f);
2259 roundStroke.setStrokeParams(SkPaint::kRound_Cap, SkPaint::kRound_Join, 1.f);
2260
2261 SkStrokeRec squareStroke(roundStroke);
2262 squareStroke.setStrokeParams(SkPaint::kSquare_Cap, SkPaint::kRound_Join, 1.f);
2263
2264 SkStrokeRec roundStrokeAndFill(roundStroke);
2265 roundStrokeAndFill.setStrokeStyle(2.f, true);
2266
2267 static constexpr SkScalar kIntervals[] = {1, 2};
2268 auto dash = SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), 1.5f);
2269
2270 SkTArray<GrStyle> styles;
2271 styles.push_back(GrStyle::SimpleFill());
2272 styles.push_back(GrStyle::SimpleHairline());
2273 styles.push_back(GrStyle(roundStroke, nullptr));
2274 styles.push_back(GrStyle(squareStroke, nullptr));
2275 styles.push_back(GrStyle(roundStrokeAndFill, nullptr));
2276 styles.push_back(GrStyle(roundStroke, dash));
2277
2278 for (const auto& style : styles) {
2279 // An empty rect never draws anything according to SkCanvas::drawArc() docs.
2280 TestCase emptyArc(GrShape::MakeArc(SkRect::MakeEmpty(), 0, 90.f, false, style), reporter);
2281 TestCase emptyPath(reporter, SkPath(), style);
2282 emptyArc.compare(reporter, emptyPath, TestCase::kAllSame_ComparisonExpecation);
2283
2284 static constexpr SkRect kOval1{0, 0, 50, 50};
2285 static constexpr SkRect kOval2{50, 0, 100, 50};
2286 // Test that swapping starting and ending angle doesn't change the shape unless the arc
2287 // has a path effect. Also test that different ovals produce different shapes.
2288 TestCase arc1CW(GrShape::MakeArc(kOval1, 0, 90.f, false, style), reporter);
2289 TestCase arc1CCW(GrShape::MakeArc(kOval1, 90.f, -90.f, false, style), reporter);
2290
2291 TestCase arc1CWWithCenter(GrShape::MakeArc(kOval1, 0, 90.f, true, style), reporter);
2292 TestCase arc1CCWWithCenter(GrShape::MakeArc(kOval1, 90.f, -90.f, true, style), reporter);
2293
2294 TestCase arc2CW(GrShape::MakeArc(kOval2, 0, 90.f, false, style), reporter);
2295 TestCase arc2CWWithCenter(GrShape::MakeArc(kOval2, 0, 90.f, true, style), reporter);
2296
2297 auto reversedExepectations = style.hasPathEffect()
2298 ? TestCase::kAllDifferent_ComparisonExpecation
2299 : TestCase::kAllSame_ComparisonExpecation;
2300 arc1CW.compare(reporter, arc1CCW, reversedExepectations);
2301 arc1CWWithCenter.compare(reporter, arc1CCWWithCenter, reversedExepectations);
2302 arc1CW.compare(reporter, arc2CW, TestCase::kAllDifferent_ComparisonExpecation);
2303 arc1CW.compare(reporter, arc1CWWithCenter, TestCase::kAllDifferent_ComparisonExpecation);
2304 arc1CWWithCenter.compare(reporter, arc2CWWithCenter,
2305 TestCase::kAllDifferent_ComparisonExpecation);
2306
2307 // Test that two arcs that start at the same angle but specified differently are equivalent.
2308 TestCase arc3A(GrShape::MakeArc(kOval1, 224.f, 73.f, false, style), reporter);
2309 TestCase arc3B(GrShape::MakeArc(kOval1, 224.f - 360.f, 73.f, false, style), reporter);
2310 arc3A.compare(reporter, arc3B, TestCase::kAllDifferent_ComparisonExpecation);
2311
2312 // Test that an arc that traverses the entire oval (and then some) is equivalent to the
2313 // oval itself unless there is a path effect.
2314 TestCase ovalArc(GrShape::MakeArc(kOval1, 150.f, -790.f, false, style), reporter);
2315 TestCase oval(GrShape(SkRRect::MakeOval(kOval1)), reporter);
2316 auto ovalExpectations = style.hasPathEffect() ? TestCase::kAllDifferent_ComparisonExpecation
2317 : TestCase::kAllSame_ComparisonExpecation;
2318 if (style.strokeRec().getWidth() >= 0 && style.strokeRec().getCap() != SkPaint::kButt_Cap) {
2319 ovalExpectations = TestCase::kAllDifferent_ComparisonExpecation;
2320 }
2321 ovalArc.compare(reporter, oval, ovalExpectations);
2322
2323 // If the the arc starts/ends at the center then it is then equivalent to the oval only for
2324 // simple fills.
2325 TestCase ovalArcWithCenter(GrShape::MakeArc(kOval1, 304.f, 1225.f, true, style), reporter);
2326 ovalExpectations = style.isSimpleFill() ? TestCase::kAllSame_ComparisonExpecation
2327 : TestCase::kAllDifferent_ComparisonExpecation;
2328 ovalArcWithCenter.compare(reporter, oval, ovalExpectations);
2329 }
2330}
2331
bsalomon47cc7692016-04-26 12:56:00 -07002332#endif