blob: 087ac525c83ad36f2fccbe14cab4791c86e5ba64 [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"
bsalomon9fb42032016-05-13 09:23:38 -070017#include "SkSurface.h"
Mike Reedebfce6d2016-12-12 10:02:12 -050018#include "SkClipOpPriv.h"
bsalomon47cc7692016-04-26 12:56:00 -070019
bsalomon72dc51c2016-04-27 06:46:23 -070020using Key = SkTArray<uint32_t>;
21
22static bool make_key(Key* key, const GrShape& shape) {
23 int size = shape.unstyledKeySize();
24 if (size <= 0) {
25 key->reset(0);
26 return false;
27 }
28 SkASSERT(size);
29 key->reset(size);
30 shape.writeUnstyledKey(key->begin());
31 return true;
32}
33
bsalomonee295642016-06-06 14:01:25 -070034static bool paths_fill_same(const SkPath& a, const SkPath& b) {
35 SkPath pathXor;
36 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
37 return pathXor.isEmpty();
38}
39
bsalomon9fb42032016-05-13 09:23:38 -070040static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
bsalomon164fd9f2016-08-26 06:45:06 -070041 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
42 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
43 // rendering within the bounds (with a tolerance). Then we render the path and check that
44 // everything got clipped out.
bsalomon9fb42032016-05-13 09:23:38 -070045 static constexpr int kRes = 2000;
46 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
47 static constexpr int kTol = 0;
48 GR_STATIC_ASSERT(kRes % 4 == 0);
49 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
50 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
51 surface->getCanvas()->clear(0x0);
52 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
53 SkMatrix matrix;
54 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit);
55 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
Mike Reedc1f77742016-12-09 09:00:50 -050056 surface->getCanvas()->clipRect(clip, kDifference_SkClipOp);
bsalomon9fb42032016-05-13 09:23:38 -070057 surface->getCanvas()->concat(matrix);
58 SkPaint whitePaint;
59 whitePaint.setColor(SK_ColorWHITE);
60 surface->getCanvas()->drawPath(path, whitePaint);
61 SkPixmap pixmap;
62 surface->getCanvas()->peekPixels(&pixmap);
63#if defined(SK_BUILD_FOR_WIN)
64 // The static constexpr version in #else causes cl.exe to crash.
65 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
66#else
67 static constexpr uint8_t kZeros[kRes] = {0};
68#endif
bsalomon164fd9f2016-08-26 06:45:06 -070069 for (int y = 0; y < kRes; ++y) {
bsalomon9fb42032016-05-13 09:23:38 -070070 const uint8_t* row = pixmap.addr8(0, y);
71 if (0 != memcmp(kZeros, row, kRes)) {
72 return false;
73 }
74 }
75#ifdef SK_BUILD_FOR_WIN
76 free(const_cast<uint8_t*>(kZeros));
77#endif
78 return true;
79}
bsalomon72dc51c2016-04-27 06:46:23 -070080
Brian Salomon4f40caf2017-09-01 09:00:45 -040081static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
82 SkPath path;
83 shape.asPath(&path);
84 if (shape.style().hasNonDashPathEffect()) {
85 return false;
86 }
87 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
88 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
89 strokeRecStyle == SkStrokeRec::kHairline_Style ||
90 (shape.style().isSimpleFill() && path.isConvex());
91}
92
93static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
94 const Key& keyA, const Key& keyB) {
95 // GrShape only respects the input winding direction and start point for rrect shapes
96 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
97 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
98 // key will differ. GrShape will have canonicalized the direction and start point for the shape
99 // without the path effect. If *both* have path effects then they should have both preserved
100 // the direction and starting point.
101
102 // The asRRect() output params are all initialized just to silence compiler warnings about
103 // uninitialized variables.
104 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
105 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
106 unsigned startA = ~0U, startB = ~0U;
107 bool invertedA = true, invertedB = true;
108
109 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
110 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
111 bool aHasPE = a.style().hasPathEffect();
112 bool bHasPE = b.style().hasPathEffect();
113 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
114 // GrShape will close paths with simple fill style.
115 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
116 SkPath pathA, pathB;
117 a.asPath(&pathA);
118 b.asPath(&pathB);
119
120 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
121 // non-inverse fill type (or vice versa).
122 bool ignoreInversenessDifference = false;
123 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
124 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
125 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
126 bool canDropInverse1 = s1->style().isDashed();
127 bool canDropInverse2 = s2->style().isDashed();
128 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
129 }
130 bool ignoreWindingVsEvenOdd = false;
131 if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
132 SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
133 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
134 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
135 if (aCanChange != bCanChange) {
136 ignoreWindingVsEvenOdd = true;
137 }
138 }
139 if (allowSameRRectButDiffStartAndDir) {
140 REPORTER_ASSERT(r, rrectA == rrectB);
141 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
142 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
143 } else {
144 SkPath pA = pathA;
145 SkPath pB = pathB;
146 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
147 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
148 if (ignoreInversenessDifference) {
149 pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
150 pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
151 }
152 if (ignoreWindingVsEvenOdd) {
153 pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
154 : SkPath::kEvenOdd_FillType);
155 pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
156 : SkPath::kEvenOdd_FillType);
157 }
158 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
159 REPORTER_ASSERT(r, keyA == keyB);
160 } else {
161 REPORTER_ASSERT(r, keyA != keyB);
162 }
163 if (allowedClosednessDiff) {
164 // GrShape will close paths with simple fill style. Make the non-filled path closed
165 // so that the comparision will succeed. Make sure both are closed before comparing.
166 pA.close();
167 pB.close();
168 }
169 REPORTER_ASSERT(r, pA == pB);
170 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
171 if (aIsRRect) {
172 REPORTER_ASSERT(r, rrectA == rrectB);
173 REPORTER_ASSERT(r, dirA == dirB);
174 REPORTER_ASSERT(r, startA == startB);
175 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
176 }
177 }
178 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
179 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
180 // closedness can affect convexity.
181 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
182 if (a.knownToBeConvex()) {
183 REPORTER_ASSERT(r, pathA.isConvex());
184 }
185 if (b.knownToBeConvex()) {
186 REPORTER_ASSERT(r, pathB.isConvex());
187 }
188 REPORTER_ASSERT(r, a.bounds() == b.bounds());
189 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
190 // Init these to suppress warnings.
191 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
192 bool invertedLine[2] {true, true};
193 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
194 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
195 // doesn't (since the PE can set any fill type on its output path).
196 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
197 // then they may disagree about inverseness.
198 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
199 a.style().isDashed() == b.style().isDashed()) {
200 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
201 b.mayBeInverseFilledAfterStyling());
202 }
203 if (a.asLine(nullptr, nullptr)) {
204 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
205 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
206 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
207 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
208 }
209 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
210}
211
212void test_inversions(skiatest::Reporter* r, const GrShape& shape, const Key& shapeKey) {
213 GrShape preserve = GrShape::MakeFilled(shape, GrShape::FillInversion::kPreserve);
214 Key preserveKey;
215 make_key(&preserveKey, preserve);
216
217 GrShape flip = GrShape::MakeFilled(shape, GrShape::FillInversion::kFlip);
218 Key flipKey;
219 make_key(&flipKey, flip);
220
221 GrShape inverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceInverted);
222 Key invertedKey;
223 make_key(&invertedKey, inverted);
224
225 GrShape noninverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceNoninverted);
226 Key noninvertedKey;
227 make_key(&noninvertedKey, noninverted);
228
229 if (invertedKey.count() || noninvertedKey.count()) {
230 REPORTER_ASSERT(r, invertedKey != noninvertedKey);
231 }
232 if (shape.style().isSimpleFill()) {
233 check_equivalence(r, shape, preserve, shapeKey, preserveKey);
234 }
235 if (shape.inverseFilled()) {
236 check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
237 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
238 } else {
239 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
240 check_equivalence(r, flip, inverted, flipKey, invertedKey);
241 }
242
243 GrShape doubleFlip = GrShape::MakeFilled(flip, GrShape::FillInversion::kFlip);
244 Key doubleFlipKey;
245 make_key(&doubleFlipKey, doubleFlip);
246 // It can be the case that the double flip has no key but preserve does. This happens when the
247 // original shape has an inherited style key. That gets dropped on the first inversion flip.
248 if (preserveKey.count() && !doubleFlipKey.count()) {
249 preserveKey.reset();
250 }
251 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
252}
253
bsalomon9fb42032016-05-13 09:23:38 -0700254namespace {
bsalomona395f7c2016-08-24 17:47:40 -0700255/**
256 * Geo is a factory for creating a GrShape from another representation. It also answers some
257 * questions about expected behavior for GrShape given the inputs.
258 */
259class Geo {
260public:
Mike Kleinfc6c37b2016-09-27 09:34:10 -0400261 virtual ~Geo() {}
bsalomona395f7c2016-08-24 17:47:40 -0700262 virtual GrShape makeShape(const SkPaint&) const = 0;
263 virtual SkPath path() const = 0;
264 // These functions allow tests to check for special cases where style gets
265 // applied by GrShape in its constructor (without calling GrShape::applyStyle).
266 // These unfortunately rely on knowing details of GrShape's implementation.
267 // These predicates are factored out here to avoid littering the rest of the
268 // test code with GrShape implementation details.
269 virtual bool fillChangesGeom() const { return false; }
270 virtual bool strokeIsConvertedToFill() const { return false; }
271 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
272 // Is this something we expect GrShape to recognize as something simpler than a path.
273 virtual bool isNonPath(const SkPaint& paint) const { return true; }
274};
275
276class RectGeo : public Geo {
277public:
278 RectGeo(const SkRect& rect) : fRect(rect) {}
279
280 SkPath path() const override {
281 SkPath path;
282 path.addRect(fRect);
283 return path;
284 }
285
286 GrShape makeShape(const SkPaint& paint) const override {
287 return GrShape(fRect, paint);
288 }
289
290 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
291 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
292 // Converted to an outset rectangle.
293 return paint.getStrokeJoin() == SkPaint::kMiter_Join &&
294 paint.getStrokeMiter() >= SK_ScalarSqrt2;
295 }
296
297private:
298 SkRect fRect;
299};
300
301class RRectGeo : public Geo {
302public:
303 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
304
305 GrShape makeShape(const SkPaint& paint) const override {
306 return GrShape(fRRect, paint);
307 }
308
309 SkPath path() const override {
310 SkPath path;
311 path.addRRect(fRRect);
312 return path;
313 }
314
315 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
316 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
317 if (fRRect.isRect()) {
318 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
319 }
320 return false;
321 }
322
323private:
324 SkRRect fRRect;
325};
326
327class PathGeo : public Geo {
328public:
329 enum class Invert { kNo, kYes };
330
331 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
332 SkASSERT(!path.isInverseFillType());
333 if (Invert::kYes == invert) {
334 if (fPath.getFillType() == SkPath::kEvenOdd_FillType) {
335 fPath.setFillType(SkPath::kInverseEvenOdd_FillType);
336 } else {
337 SkASSERT(fPath.getFillType() == SkPath::kWinding_FillType);
338 fPath.setFillType(SkPath::kInverseWinding_FillType);
339 }
340 }
341 }
342
343 GrShape makeShape(const SkPaint& paint) const override {
344 return GrShape(fPath, paint);
345 }
346
347 SkPath path() const override { return fPath; }
348
349 bool fillChangesGeom() const override {
350 // unclosed rects get closed. Lines get turned into empty geometry
Brian Salomon085c0862017-08-31 15:44:51 -0400351 return this->isUnclosedRect() || fPath.isLine(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -0700352 }
353
354 bool strokeIsConvertedToFill() const override {
355 return this->isAxisAlignedLine();
356 }
357
358 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
359 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
360 if (this->isAxisAlignedLine()) {
361 // The fill is ignored (zero area) and the stroke is converted to a rrect.
362 return true;
363 }
364 SkRect rect;
365 unsigned start;
366 SkPath::Direction dir;
367 if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
368 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
369 }
370 return false;
371 }
372
373 bool isNonPath(const SkPaint& paint) const override {
374 return fPath.isLine(nullptr) || fPath.isEmpty();
375 }
376
377private:
378 bool isAxisAlignedLine() const {
379 SkPoint pts[2];
380 if (!fPath.isLine(pts)) {
381 return false;
382 }
383 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
384 }
385
386 bool isUnclosedRect() const {
387 bool closed;
388 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
389 }
390
391 SkPath fPath;
392};
393
394class RRectPathGeo : public PathGeo {
395public:
396 enum class RRectForStroke { kNo, kYes };
397
398 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
399 Invert invert)
400 : PathGeo(path, invert)
401 , fRRect(equivalentRRect)
402 , fRRectForStroke(rrectForStroke) {}
403
404 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
405 Invert invert)
406 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
407
408 bool isNonPath(const SkPaint& paint) const override {
409 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
410 return true;
411 }
412 return false;
413 }
414
415 const SkRRect& rrect() const { return fRRect; }
416
417private:
418 SkRRect fRRect;
419 RRectForStroke fRRectForStroke;
420};
421
bsalomon47cc7692016-04-26 12:56:00 -0700422class TestCase {
423public:
bsalomona395f7c2016-08-24 17:47:40 -0700424 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
425 SkScalar scale = SK_Scalar1) : fBase(geo.makeShape(paint)) {
bsalomon97fd2d42016-05-09 13:02:01 -0700426 this->init(r, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700427 }
428
bsalomona395f7c2016-08-24 17:47:40 -0700429 template<typename... ShapeArgs>
430 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs)
431 : fBase(shapeArgs...) {
432 this->init(r, SK_Scalar1);
433 }
434
bsalomon70493962016-06-10 08:05:14 -0700435 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
436 : fBase(shape) {
437 this->init(r, scale);
438 }
439
bsalomon47cc7692016-04-26 12:56:00 -0700440 struct SelfExpectations {
441 bool fPEHasEffect;
442 bool fPEHasValidKey;
443 bool fStrokeApplies;
444 };
445
446 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
447
448 enum ComparisonExpecation {
449 kAllDifferent_ComparisonExpecation,
450 kSameUpToPE_ComparisonExpecation,
451 kSameUpToStroke_ComparisonExpecation,
452 kAllSame_ComparisonExpecation,
453 };
454
455 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
456
bsalomon72dc51c2016-04-27 06:46:23 -0700457 const GrShape& baseShape() const { return fBase; }
458 const GrShape& appliedPathEffectShape() const { return fAppliedPE; }
459 const GrShape& appliedFullStyleShape() const { return fAppliedFull; }
460
461 // The returned array's count will be 0 if the key shape has no key.
462 const Key& baseKey() const { return fBaseKey; }
463 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
464 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700465 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700466
bsalomon47cc7692016-04-26 12:56:00 -0700467private:
bsalomon9fb42032016-05-13 09:23:38 -0700468 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
469 SkPath path;
470 shape.asPath(&path);
471 // If the bounds are empty, the path ought to be as well.
bsalomon0ae36a22016-07-18 07:31:13 -0700472 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
bsalomon9fb42032016-05-13 09:23:38 -0700473 REPORTER_ASSERT(r, path.isEmpty());
474 return;
475 }
476 if (path.isEmpty()) {
477 return;
478 }
bsalomon70493962016-06-10 08:05:14 -0700479 // The bounds API explicitly calls out that it does not consider inverseness.
480 SkPath p = path;
481 p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType()));
482 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
bsalomon9fb42032016-05-13 09:23:38 -0700483 }
484
bsalomon97fd2d42016-05-09 13:02:01 -0700485 void init(skiatest::Reporter* r, SkScalar scale) {
486 fAppliedPE = fBase.applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
487 fAppliedPEThenStroke = fAppliedPE.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec,
488 scale);
489 fAppliedFull = fBase.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700490
bsalomon72dc51c2016-04-27 06:46:23 -0700491 make_key(&fBaseKey, fBase);
492 make_key(&fAppliedPEKey, fAppliedPE);
493 make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke);
494 make_key(&fAppliedFullKey, fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700495
496 // Applying the path effect and then the stroke should always be the same as applying
497 // both in one go.
498 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
499 SkPath a, b;
500 fAppliedPEThenStroke.asPath(&a);
501 fAppliedFull.asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700502 // If the output of the path effect is a rrect then it is possible for a and b to be
503 // different paths that fill identically. The reason is that fAppliedFull will do this:
504 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
505 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
506 // now that there is no longer a path effect, the direction and starting index get
507 // canonicalized before the stroke.
bsalomon70493962016-06-10 08:05:14 -0700508 if (fAppliedPE.asRRect(nullptr, nullptr, nullptr, nullptr)) {
bsalomonee295642016-06-06 14:01:25 -0700509 REPORTER_ASSERT(r, paths_fill_same(a, b));
510 } else {
511 REPORTER_ASSERT(r, a == b);
512 }
bsalomon7c73a532016-05-11 15:15:56 -0700513 REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty());
514
515 SkPath path;
516 fBase.asPath(&path);
517 REPORTER_ASSERT(r, path.isEmpty() == fBase.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700518 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700519 fAppliedPE.asPath(&path);
520 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700521 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700522 fAppliedFull.asPath(&path);
523 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700524 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull.segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700525
bsalomon9fb42032016-05-13 09:23:38 -0700526 CheckBounds(r, fBase, fBase.bounds());
527 CheckBounds(r, fAppliedPE, fAppliedPE.bounds());
528 CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds());
529 CheckBounds(r, fAppliedFull, fAppliedFull.bounds());
bsalomon0a0f67e2016-06-28 11:56:42 -0700530 SkRect styledBounds = fBase.styledBounds();
bsalomon9fb42032016-05-13 09:23:38 -0700531 CheckBounds(r, fAppliedFull, styledBounds);
bsalomon0a0f67e2016-06-28 11:56:42 -0700532 styledBounds = fAppliedPE.styledBounds();
bsalomon9fb42032016-05-13 09:23:38 -0700533 CheckBounds(r, fAppliedFull, styledBounds);
534
bsalomonfb083272016-05-04 08:27:41 -0700535 // Check that the same path is produced when style is applied by GrShape and GrStyle.
536 SkPath preStyle;
537 SkPath postPathEffect;
538 SkPath postAllStyle;
539
540 fBase.asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700541 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
bsalomon97fd2d42016-05-09 13:02:01 -0700542 if (fBase.style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
543 scale)) {
bsalomon1a0b9ed2016-05-06 11:07:03 -0700544 // run postPathEffect through GrShape to get any geometry reductions that would have
545 // occurred to fAppliedPE.
546 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
547
bsalomonfb083272016-05-04 08:27:41 -0700548 SkPath testPath;
549 fAppliedPE.asPath(&testPath);
550 REPORTER_ASSERT(r, testPath == postPathEffect);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700551 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE.style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700552 }
553 SkStrokeRec::InitStyle fillOrHairline;
bsalomon97fd2d42016-05-09 13:02:01 -0700554 if (fBase.style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomonfb083272016-05-04 08:27:41 -0700555 SkPath testPath;
556 fAppliedFull.asPath(&testPath);
bsalomon1b28c1a2016-06-20 12:28:17 -0700557 if (fBase.style().hasPathEffect()) {
558 // Because GrShape always does two-stage application when there is a path effect
559 // there may be a reduction/canonicalization step between the path effect and
560 // strokerec not reflected in postAllStyle since it applied both the path effect
561 // and strokerec without analyzing the intermediate path.
562 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
563 } else {
564 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape
565 // would apply.
566 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
567 REPORTER_ASSERT(r, testPath == postAllStyle);
568 }
569
bsalomonfb083272016-05-04 08:27:41 -0700570 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
571 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleFill());
572 } else {
573 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline());
574 }
575 }
Brian Salomon4f40caf2017-09-01 09:00:45 -0400576 test_inversions(r, fBase, fBaseKey);
577 test_inversions(r, fAppliedPE, fAppliedPEKey);
578 test_inversions(r, fAppliedFull, fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700579 }
580
581 GrShape fBase;
582 GrShape fAppliedPE;
583 GrShape fAppliedPEThenStroke;
584 GrShape fAppliedFull;
585
586 Key fBaseKey;
587 Key fAppliedPEKey;
588 Key fAppliedPEThenStrokeKey;
589 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700590};
591
592void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700593 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700594 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700595 if (expectations.fPEHasEffect) {
596 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700597 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700598 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700599 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700600 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
601 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700602 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700603 }
604 } else {
605 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700606 SkPath a, b;
bsalomon72dc51c2016-04-27 06:46:23 -0700607 fBase.asPath(&a);
608 fAppliedPE.asPath(&b);
609 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700610 if (expectations.fStrokeApplies) {
611 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
612 } else {
613 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
614 }
615 }
616}
617
bsalomonee295642016-06-06 14:01:25 -0700618void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700619 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700620 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700621 switch (expectation) {
622 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700623 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
624 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
625 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700626 break;
627 case kSameUpToPE_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700628 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
629 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
630 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700631 break;
632 case kSameUpToStroke_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700633 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
634 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
635 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700636 break;
637 case kAllSame_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700638 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
639 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
640 check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey,
641 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700642 break;
643 }
644}
645} // namespace
646
647static sk_sp<SkPathEffect> make_dash() {
648 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
649 static const SkScalar kPhase = 0.75;
650 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
651}
652
653static sk_sp<SkPathEffect> make_null_dash() {
654 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
655 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
656}
657
Mike Klein43344282017-08-16 11:56:22 -0400658// We make enough TestCases, and they're large enough, that on Google3 builds we exceed
659// the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
660template <typename... Args>
661static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
662 return std::unique_ptr<TestCase>{ new TestCase(std::forward<Args>(args)...) };
663}
664
bsalomona395f7c2016-08-24 17:47:40 -0700665static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700666 sk_sp<SkPathEffect> dashPE = make_dash();
667
668 TestCase::SelfExpectations expectations;
669 SkPaint fill;
670
bsalomonfb083272016-05-04 08:27:41 -0700671 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700672 expectations.fPEHasEffect = false;
673 expectations.fPEHasValidKey = false;
674 expectations.fStrokeApplies = false;
675 fillCase.testExpectations(reporter, expectations);
676 // Test that another GrShape instance built from the same primitive is the same.
Mike Klein43344282017-08-16 11:56:22 -0400677 make_TestCase(geo, fill, reporter)
678 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700679
680 SkPaint stroke2RoundBevel;
681 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
682 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
683 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
684 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700685 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700686 expectations.fPEHasValidKey = true;
687 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700688 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomon47cc7692016-04-26 12:56:00 -0700689 stroke2RoundBevelCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400690 make_TestCase(geo, stroke2RoundBevel, reporter)
691 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700692
693 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
694 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700695 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700696 expectations.fPEHasValidKey = true;
697 expectations.fPEHasEffect = true;
698 expectations.fStrokeApplies = true;
699 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400700 make_TestCase(geo, stroke2RoundBevelDash, reporter)
701 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700702
bsalomona395f7c2016-08-24 17:47:40 -0700703 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700704 fillCase.compare(reporter, stroke2RoundBevelCase,
705 TestCase::kAllDifferent_ComparisonExpecation);
706 fillCase.compare(reporter, stroke2RoundBevelDashCase,
707 TestCase::kAllDifferent_ComparisonExpecation);
708 } else {
709 fillCase.compare(reporter, stroke2RoundBevelCase,
710 TestCase::kSameUpToStroke_ComparisonExpecation);
711 fillCase.compare(reporter, stroke2RoundBevelDashCase,
712 TestCase::kSameUpToPE_ComparisonExpecation);
713 }
bsalomona395f7c2016-08-24 17:47:40 -0700714 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700715 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
716 TestCase::kAllDifferent_ComparisonExpecation);
717 } else {
718 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
719 TestCase::kSameUpToPE_ComparisonExpecation);
720 }
bsalomon72dc51c2016-04-27 06:46:23 -0700721
bsalomonf0cf3552016-05-05 08:28:30 -0700722 // Stroke and fill cases
723 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
724 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
725 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
726 expectations.fPEHasValidKey = true;
727 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700728 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700729 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400730 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
731 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700732
733 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
734 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
735 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
736 expectations.fPEHasValidKey = true;
bsalomona0587862016-06-09 06:03:38 -0700737 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700738 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700739 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400740 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
bsalomonf0cf3552016-05-05 08:28:30 -0700741 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomona0587862016-06-09 06:03:38 -0700742 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
743 TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700744
bsalomon72dc51c2016-04-27 06:46:23 -0700745 SkPaint hairline;
746 hairline.setStyle(SkPaint::kStroke_Style);
747 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700748 TestCase hairlineCase(geo, hairline, reporter);
bsalomon487f8d32016-07-20 07:15:44 -0700749 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
750 // in the line and unclosed rect cases).
bsalomona395f7c2016-08-24 17:47:40 -0700751 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -0700752 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
753 } else {
754 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
755 }
bsalomon9ad5d7c2016-05-04 08:44:15 -0700756 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
757 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
758 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700759
bsalomon0ae36a22016-07-18 07:31:13 -0700760}
761
bsalomona395f7c2016-08-24 17:47:40 -0700762static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon97fd2d42016-05-09 13:02:01 -0700763 sk_sp<SkPathEffect> dashPE = make_dash();
764
765 static const SkScalar kS1 = 1.f;
766 static const SkScalar kS2 = 2.f;
767
768 SkPaint fill;
769 TestCase fillCase1(geo, fill, reporter, kS1);
770 TestCase fillCase2(geo, fill, reporter, kS2);
771 // Scale doesn't affect fills.
772 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
773
774 SkPaint hairline;
775 hairline.setStyle(SkPaint::kStroke_Style);
776 hairline.setStrokeWidth(0.f);
777 TestCase hairlineCase1(geo, hairline, reporter, kS1);
778 TestCase hairlineCase2(geo, hairline, reporter, kS2);
779 // Scale doesn't affect hairlines.
780 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
781
782 SkPaint stroke;
783 stroke.setStyle(SkPaint::kStroke_Style);
784 stroke.setStrokeWidth(2.f);
785 TestCase strokeCase1(geo, stroke, reporter, kS1);
786 TestCase strokeCase2(geo, stroke, reporter, kS2);
bsalomon0ae36a22016-07-18 07:31:13 -0700787 // Scale affects the stroke
bsalomona395f7c2016-08-24 17:47:40 -0700788 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700789 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
bsalomon0ae36a22016-07-18 07:31:13 -0700790 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
791 } else {
792 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
793 }
bsalomon97fd2d42016-05-09 13:02:01 -0700794
795 SkPaint strokeDash = stroke;
796 strokeDash.setPathEffect(make_dash());
797 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
798 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
799 // Scale affects the dash and the stroke.
bsalomon487f8d32016-07-20 07:15:44 -0700800 strokeDashCase1.compare(reporter, strokeDashCase2,
801 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700802
803 // Stroke and fill cases
804 SkPaint strokeAndFill = stroke;
805 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
806 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
807 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
bsalomona0587862016-06-09 06:03:38 -0700808 SkPaint strokeAndFillDash = strokeDash;
809 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
810 // Dash is ignored for stroke and fill
811 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
812 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
bsalomon487f8d32016-07-20 07:15:44 -0700813 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
814 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
815 // geometries should agree.
bsalomona395f7c2016-08-24 17:47:40 -0700816 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
bsalomon487f8d32016-07-20 07:15:44 -0700817 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
bsalomon97fd2d42016-05-09 13:02:01 -0700818 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
819 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700820 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
821 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700822 } else {
823 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
824 TestCase::kSameUpToStroke_ComparisonExpecation);
825 }
bsalomona0587862016-06-09 06:03:38 -0700826 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
827 TestCase::kAllSame_ComparisonExpecation);
828 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
829 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700830}
831
bsalomona395f7c2016-08-24 17:47:40 -0700832template <typename T>
833static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700834 std::function<void(SkPaint*, T)> setter, T a, T b,
835 bool paramAffectsStroke,
836 bool paramAffectsDashAndStroke) {
837 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
838 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700839 SkPaint strokeA;
840 strokeA.setStyle(SkPaint::kStroke_Style);
841 strokeA.setStrokeWidth(2.f);
842 setter(&strokeA, a);
843 SkPaint strokeB;
844 strokeB.setStyle(SkPaint::kStroke_Style);
845 strokeB.setStrokeWidth(2.f);
846 setter(&strokeB, b);
847
bsalomonfb083272016-05-04 08:27:41 -0700848 TestCase strokeACase(geo, strokeA, reporter);
849 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700850 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700851 // If stroking is immediately incorporated into a geometric transformation then the base
852 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700853 if (geo.strokeIsConvertedToFill()) {
bsalomon0ae36a22016-07-18 07:31:13 -0700854 strokeACase.compare(reporter, strokeBCase,
855 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700856 } else {
857 strokeACase.compare(reporter, strokeBCase,
858 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700859 }
bsalomon06077562016-05-04 13:50:29 -0700860 } else {
861 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
862 }
bsalomon47cc7692016-04-26 12:56:00 -0700863
bsalomonf0cf3552016-05-05 08:28:30 -0700864 SkPaint strokeAndFillA = strokeA;
865 SkPaint strokeAndFillB = strokeB;
866 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
867 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
868 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
869 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
870 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700871 // If stroking is immediately incorporated into a geometric transformation then the base
872 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700873 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
874 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700875 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700876 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700877 } else {
878 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700879 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700880 }
bsalomonf0cf3552016-05-05 08:28:30 -0700881 } else {
882 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
883 TestCase::kAllSame_ComparisonExpecation);
884 }
885
bsalomon47cc7692016-04-26 12:56:00 -0700886 // Make sure stroking params don't affect fill style.
887 SkPaint fillA = strokeA, fillB = strokeB;
888 fillA.setStyle(SkPaint::kFill_Style);
889 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -0700890 TestCase fillACase(geo, fillA, reporter);
891 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700892 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
893
894 // Make sure just applying the dash but not stroke gives the same key for both stroking
895 // variations.
896 SkPaint dashA = strokeA, dashB = strokeB;
897 dashA.setPathEffect(make_dash());
898 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700899 TestCase dashACase(geo, dashA, reporter);
900 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700901 if (paramAffectsDashAndStroke) {
bsalomon487f8d32016-07-20 07:15:44 -0700902 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon06077562016-05-04 13:50:29 -0700903 } else {
904 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
905 }
bsalomon47cc7692016-04-26 12:56:00 -0700906}
907
bsalomona395f7c2016-08-24 17:47:40 -0700908template <typename T>
909static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700910 std::function<void(SkPaint*, T)> setter, T a, T b) {
911 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
912};
913
bsalomona395f7c2016-08-24 17:47:40 -0700914static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
915 SkPaint hairline;
916 hairline.setStrokeWidth(0);
917 hairline.setStyle(SkPaint::kStroke_Style);
918 GrShape shape = geo.makeShape(hairline);
bsalomon06077562016-05-04 13:50:29 -0700919 // The cap should only affect shapes that may be open.
920 bool affectsStroke = !shape.knownToBeClosed();
921 // Dashing adds ends that need caps.
922 bool affectsDashAndStroke = true;
bsalomona395f7c2016-08-24 17:47:40 -0700923 test_stroke_param_impl<SkPaint::Cap>(
bsalomon06077562016-05-04 13:50:29 -0700924 reporter,
925 geo,
926 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
927 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
928 affectsStroke,
929 affectsDashAndStroke);
930};
931
bsalomon0ae36a22016-07-18 07:31:13 -0700932static bool shape_known_not_to_have_joins(const GrShape& shape) {
933 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
934}
935
bsalomona395f7c2016-08-24 17:47:40 -0700936static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
937 SkPaint hairline;
938 hairline.setStrokeWidth(0);
939 hairline.setStyle(SkPaint::kStroke_Style);
940 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -0700941 // GrShape recognizes certain types don't have joins and will prevent the join type from
942 // affecting the style key.
943 // Dashing doesn't add additional joins. However, GrShape currently loses track of this
944 // after applying the dash.
945 bool affectsStroke = !shape_known_not_to_have_joins(shape);
bsalomona395f7c2016-08-24 17:47:40 -0700946 test_stroke_param_impl<SkPaint::Join>(
bsalomon0ae36a22016-07-18 07:31:13 -0700947 reporter,
948 geo,
949 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
950 SkPaint::kRound_Join, SkPaint::kBevel_Join,
951 affectsStroke, true);
952};
953
bsalomona395f7c2016-08-24 17:47:40 -0700954static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon06077562016-05-04 13:50:29 -0700955 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
956 p->setStrokeJoin(SkPaint::kMiter_Join);
957 p->setStrokeMiter(miter);
958 };
bsalomon47cc7692016-04-26 12:56:00 -0700959
bsalomon06077562016-05-04 13:50:29 -0700960 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
961 p->setStrokeJoin(SkPaint::kRound_Join);
962 p->setStrokeMiter(miter);
963 };
bsalomon47cc7692016-04-26 12:56:00 -0700964
bsalomona395f7c2016-08-24 17:47:40 -0700965 SkPaint hairline;
966 hairline.setStrokeWidth(0);
967 hairline.setStyle(SkPaint::kStroke_Style);
968 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -0700969 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
970
bsalomon06077562016-05-04 13:50:29 -0700971 // The miter limit should affect stroked and dashed-stroked cases when the join type is
972 // miter.
bsalomona395f7c2016-08-24 17:47:40 -0700973 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -0700974 reporter,
975 geo,
976 setMiterJoinAndLimit,
977 0.5f, 0.75f,
bsalomon0ae36a22016-07-18 07:31:13 -0700978 mayHaveJoins,
bsalomon06077562016-05-04 13:50:29 -0700979 true);
bsalomon47cc7692016-04-26 12:56:00 -0700980
bsalomon06077562016-05-04 13:50:29 -0700981 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
982 // not miter.
bsalomona395f7c2016-08-24 17:47:40 -0700983 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -0700984 reporter,
985 geo,
986 setOtherJoinAndLimit,
987 0.5f, 0.75f,
988 false,
989 false);
bsalomon47cc7692016-04-26 12:56:00 -0700990}
991
bsalomona395f7c2016-08-24 17:47:40 -0700992static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700993 // A dash with no stroke should have no effect
994 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
995 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
996 SkPaint dashFill;
997 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -0700998 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700999
bsalomonfb083272016-05-04 08:27:41 -07001000 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001001 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
1002 }
1003}
1004
bsalomona395f7c2016-08-24 17:47:40 -07001005void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001006 SkPaint fill;
1007 SkPaint stroke;
1008 stroke.setStyle(SkPaint::kStroke_Style);
1009 stroke.setStrokeWidth(1.f);
1010 SkPaint dash;
1011 dash.setStyle(SkPaint::kStroke_Style);
1012 dash.setStrokeWidth(1.f);
1013 dash.setPathEffect(make_dash());
1014 SkPaint nullDash;
1015 nullDash.setStyle(SkPaint::kStroke_Style);
1016 nullDash.setStrokeWidth(1.f);
1017 nullDash.setPathEffect(make_null_dash());
1018
bsalomonfb083272016-05-04 08:27:41 -07001019 TestCase fillCase(geo, fill, reporter);
1020 TestCase strokeCase(geo, stroke, reporter);
1021 TestCase dashCase(geo, dash, reporter);
1022 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001023
bsalomon487f8d32016-07-20 07:15:44 -07001024 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
bsalomon47cc7692016-04-26 12:56:00 -07001025 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -07001026 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
1027 // on construction in order to determine how to compare the fill and stroke.
bsalomona395f7c2016-08-24 17:47:40 -07001028 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001029 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
1030 } else {
1031 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1032 }
1033 // 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 -07001034 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001035 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
1036 } else {
1037 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
1038 }
bsalomon47cc7692016-04-26 12:56:00 -07001039}
1040
bsalomona395f7c2016-08-24 17:47:40 -07001041void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001042 /**
1043 * This path effect takes any input path and turns it into a rrect. It passes through stroke
1044 * info.
1045 */
1046 class RRectPathEffect : SkPathEffect {
1047 public:
1048 static const SkRRect& RRect() {
1049 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1050 return kRRect;
1051 }
1052
1053 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1054 const SkRect* cullR) const override {
1055 dst->reset();
1056 dst->addRRect(RRect());
1057 return true;
1058 }
1059 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1060 *dst = RRect().getBounds();
1061 }
1062 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1063 Factory getFactory() const override { return nullptr; }
1064 void toString(SkString*) const override {}
1065 private:
1066 RRectPathEffect() {}
1067 };
1068
1069 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -07001070 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001071
1072 SkPaint pe;
1073 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001074 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001075
1076 SkPaint peStroke;
1077 peStroke.setPathEffect(RRectPathEffect::Make());
1078 peStroke.setStrokeWidth(2.f);
1079 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001080 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001081
bsalomon487f8d32016-07-20 07:15:44 -07001082 // Check whether constructing the filled case would cause the base shape to have a different
1083 // geometry (because of a geometric transformation upon initial GrShape construction).
bsalomona395f7c2016-08-24 17:47:40 -07001084 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -07001085 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1086 fillGeoCase.compare(reporter, geoPEStrokeCase,
1087 TestCase::kAllDifferent_ComparisonExpecation);
1088 } else {
1089 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1090 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1091 }
bsalomon72dc51c2016-04-27 06:46:23 -07001092 geoPECase.compare(reporter, geoPEStrokeCase,
1093 TestCase::kSameUpToStroke_ComparisonExpecation);
1094
bsalomona395f7c2016-08-24 17:47:40 -07001095 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
bsalomon72dc51c2016-04-27 06:46:23 -07001096 SkPaint stroke = peStroke;
1097 stroke.setPathEffect(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -07001098 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001099
1100 SkRRect rrect;
1101 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1102 // geoPECase, so the full style should be the same as just the PE.
bsalomon70493962016-06-10 08:05:14 -07001103 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1104 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001105 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1106 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1107
bsalomon70493962016-06-10 08:05:14 -07001108 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1109 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001110 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1111 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1112
1113 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomon70493962016-06-10 08:05:14 -07001114 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1115 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001116 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1117 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1118
bsalomon70493962016-06-10 08:05:14 -07001119 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1120 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001121 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1122 rrectStrokeCase.appliedFullStyleKey());
1123}
1124
bsalomona395f7c2016-08-24 17:47:40 -07001125void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001126 /**
1127 * This path effect just adds two lineTos to the input path.
1128 */
1129 class AddLineTosPathEffect : SkPathEffect {
1130 public:
1131 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1132 const SkRect* cullR) const override {
1133 *dst = src;
bsalomon67fa4e32016-09-21 08:26:57 -07001134 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1135 for (int i = 0; i < 100; ++i) {
1136 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1137 }
bsalomon72dc51c2016-04-27 06:46:23 -07001138 return true;
1139 }
1140 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1141 *dst = src;
Mike Reed3c2d09f2017-08-28 13:32:37 -04001142 dst->growToInclude({0, 0});
1143 dst->growToInclude({100, 100});
bsalomon72dc51c2016-04-27 06:46:23 -07001144 }
1145 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1146 Factory getFactory() const override { return nullptr; }
1147 void toString(SkString*) const override {}
1148 private:
1149 AddLineTosPathEffect() {}
1150 };
1151
bsalomon9ad5d7c2016-05-04 08:44:15 -07001152 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -07001153 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1154 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1155 SkPaint peStroke;
1156 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1157 peStroke.setStrokeWidth(2.f);
1158 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001159 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001160 TestCase::SelfExpectations expectations;
1161 expectations.fPEHasEffect = true;
1162 expectations.fPEHasValidKey = false;
1163 expectations.fStrokeApplies = true;
1164 geoPEStrokeCase.testExpectations(reporter, expectations);
1165}
1166
bsalomona395f7c2016-08-24 17:47:40 -07001167void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon9ad5d7c2016-05-04 08:44:15 -07001168 /**
1169 * This path effect just changes the stroke rec to hairline.
1170 */
1171 class MakeHairlinePathEffect : SkPathEffect {
1172 public:
1173 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1174 const SkRect* cullR) const override {
1175 *dst = src;
1176 strokeRec->setHairlineStyle();
1177 return true;
1178 }
bsalomon70493962016-06-10 08:05:14 -07001179 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001180 static sk_sp<SkPathEffect> Make() {
1181 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1182 }
1183 Factory getFactory() const override { return nullptr; }
1184 void toString(SkString*) const override {}
1185 private:
1186 MakeHairlinePathEffect() {}
1187 };
1188
1189 SkPaint fill;
1190 SkPaint pe;
1191 pe.setPathEffect(MakeHairlinePathEffect::Make());
1192
1193 TestCase peCase(geo, pe, reporter);
1194
bsalomonee295642016-06-06 14:01:25 -07001195 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -07001196 peCase.baseShape().asPath(&a);
1197 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -07001198 peCase.appliedFullStyleShape().asPath(&c);
bsalomona395f7c2016-08-24 17:47:40 -07001199 if (geo.isNonPath(pe)) {
bsalomonee295642016-06-06 14:01:25 -07001200 // RRect types can have a change in start index or direction after the PE is applied. This
1201 // is because once the PE is applied, GrShape may canonicalize the dir and index since it
1202 // is not germane to the styling any longer.
1203 // Instead we just check that the paths would fill the same both before and after styling.
1204 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1205 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -07001206 } else {
bsalomona4817af2016-06-23 11:48:26 -07001207 // The base shape cannot perform canonicalization on the path's fill type because of an
1208 // unknown path effect. However, after the path effect is applied the resulting hairline
1209 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1210 // don't distinguish between even/odd and non-zero winding.
1211 a.setFillType(b.getFillType());
bsalomonee295642016-06-06 14:01:25 -07001212 REPORTER_ASSERT(reporter, a == b);
1213 REPORTER_ASSERT(reporter, a == c);
bsalomon67fa4e32016-09-21 08:26:57 -07001214 // If the resulting path is small enough then it will have a key.
1215 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1216 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomonaa840642016-09-23 12:09:16 -07001217 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1218 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001219 }
bsalomonee295642016-06-06 14:01:25 -07001220 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1221 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001222}
1223
bsalomona395f7c2016-08-24 17:47:40 -07001224void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1225 SkPath vPath = geo.path();
bsalomon4eeccc92016-04-27 13:30:25 -07001226 vPath.setIsVolatile(true);
1227
1228 SkPaint dashAndStroke;
1229 dashAndStroke.setPathEffect(make_dash());
1230 dashAndStroke.setStrokeWidth(2.f);
1231 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001232 TestCase volatileCase(reporter, vPath, dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001233 // We expect a shape made from a volatile path to have a key iff the shape is recognized
bsalomonaa840642016-09-23 12:09:16 -07001234 // as a specialized geometry.
1235 if (geo.isNonPath(dashAndStroke)) {
bsalomon4eeccc92016-04-27 13:30:25 -07001236 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1237 // In this case all the keys should be identical to the non-volatile case.
bsalomona395f7c2016-08-24 17:47:40 -07001238 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001239 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1240 } else {
1241 // None of the keys should be valid.
1242 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1243 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1244 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1245 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1246 }
1247}
1248
bsalomona395f7c2016-08-24 17:47:40 -07001249void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon409ed732016-04-27 12:36:02 -07001250 /**
Brian Salomon085c0862017-08-31 15:44:51 -04001251 * This path effect returns an empty path (possibly inverted)
bsalomon409ed732016-04-27 12:36:02 -07001252 */
1253 class EmptyPathEffect : SkPathEffect {
1254 public:
1255 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1256 const SkRect* cullR) const override {
1257 dst->reset();
Brian Salomon085c0862017-08-31 15:44:51 -04001258 if (fInvert) {
1259 dst->toggleInverseFillType();
1260 }
bsalomon409ed732016-04-27 12:36:02 -07001261 return true;
1262 }
1263 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1264 dst->setEmpty();
1265 }
Brian Salomon085c0862017-08-31 15:44:51 -04001266 static sk_sp<SkPathEffect> Make(bool invert) {
1267 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
1268 }
bsalomon409ed732016-04-27 12:36:02 -07001269 Factory getFactory() const override { return nullptr; }
1270 void toString(SkString*) const override {}
1271 private:
Brian Salomon085c0862017-08-31 15:44:51 -04001272 bool fInvert;
1273 EmptyPathEffect(bool invert) : fInvert(invert) {}
bsalomon409ed732016-04-27 12:36:02 -07001274 };
1275
1276 SkPath emptyPath;
1277 GrShape emptyShape(emptyPath);
1278 Key emptyKey;
1279 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -07001280 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001281
Brian Salomon085c0862017-08-31 15:44:51 -04001282 emptyPath.toggleInverseFillType();
1283 GrShape invertedEmptyShape(emptyPath);
1284 Key invertedEmptyKey;
1285 make_key(&invertedEmptyKey, invertedEmptyShape);
1286 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
1287
1288 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
1289
bsalomon409ed732016-04-27 12:36:02 -07001290 SkPaint pe;
Brian Salomon085c0862017-08-31 15:44:51 -04001291 pe.setPathEffect(EmptyPathEffect::Make(false));
1292 TestCase geoPECase(geo, pe, reporter);
1293 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
1294 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
1295 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
1296 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
1297 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
1298 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
1299 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001300
1301 SkPaint peStroke;
Brian Salomon085c0862017-08-31 15:44:51 -04001302 peStroke.setPathEffect(EmptyPathEffect::Make(false));
bsalomon409ed732016-04-27 12:36:02 -07001303 peStroke.setStrokeWidth(2.f);
1304 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001305 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001306 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1307 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1308 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001309 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1310 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001311 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
1312 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
1313 pe.setPathEffect(EmptyPathEffect::Make(true));
1314
1315 TestCase geoPEInvertCase(geo, pe, reporter);
1316 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
1317 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
1318 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1319 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
1320 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
1321 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
1322 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
1323
1324 peStroke.setPathEffect(EmptyPathEffect::Make(true));
1325 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
1326 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
1327 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
1328 REPORTER_ASSERT(reporter,
1329 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1330 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
1331 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
1332 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
1333 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001334}
1335
bsalomona395f7c2016-08-24 17:47:40 -07001336void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
bsalomond6723842016-06-07 12:20:15 -07001337 /**
bsalomon0ae36a22016-07-18 07:31:13 -07001338 * This path effect always fails to apply.
bsalomond6723842016-06-07 12:20:15 -07001339 */
1340 class FailurePathEffect : SkPathEffect {
1341 public:
1342 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1343 const SkRect* cullR) const override {
1344 return false;
1345 }
1346 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1347 *dst = src;
1348 }
1349 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1350 Factory getFactory() const override { return nullptr; }
1351 void toString(SkString*) const override {}
1352 private:
1353 FailurePathEffect() {}
1354 };
1355
1356 SkPaint fill;
1357 TestCase fillCase(geo, fill, reporter);
1358
1359 SkPaint pe;
1360 pe.setPathEffect(FailurePathEffect::Make());
1361 TestCase peCase(geo, pe, reporter);
1362
1363 SkPaint stroke;
1364 stroke.setStrokeWidth(2.f);
1365 stroke.setStyle(SkPaint::kStroke_Style);
1366 TestCase strokeCase(geo, stroke, reporter);
1367
1368 SkPaint peStroke = stroke;
1369 peStroke.setPathEffect(FailurePathEffect::Make());
1370 TestCase peStrokeCase(geo, peStroke, reporter);
1371
1372 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1373 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1374 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its
1375 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from
1376 // closing it but after the effect fails we can (for the fill+pe case). This causes different
1377 // routes through GrShape to have equivalent but different representations of the path (closed
1378 // or not) but that fill the same.
1379 SkPath a;
1380 SkPath b;
1381 fillCase.appliedPathEffectShape().asPath(&a);
1382 peCase.appliedPathEffectShape().asPath(&b);
1383 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1384
1385 fillCase.appliedFullStyleShape().asPath(&a);
1386 peCase.appliedFullStyleShape().asPath(&b);
1387 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1388
1389 strokeCase.appliedPathEffectShape().asPath(&a);
1390 peStrokeCase.appliedPathEffectShape().asPath(&b);
1391 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1392
1393 strokeCase.appliedFullStyleShape().asPath(&a);
1394 peStrokeCase.appliedFullStyleShape().asPath(&b);
1395 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1396}
1397
Mike Klein43344282017-08-16 11:56:22 -04001398DEF_TEST(GrShape_empty_shape, reporter) {
bsalomon409ed732016-04-27 12:36:02 -07001399 SkPath emptyPath;
Brian Salomon085c0862017-08-31 15:44:51 -04001400 SkPath invertedEmptyPath;
1401 invertedEmptyPath.toggleInverseFillType();
bsalomon409ed732016-04-27 12:36:02 -07001402 SkPaint fill;
bsalomona395f7c2016-08-24 17:47:40 -07001403 TestCase fillEmptyCase(reporter, emptyPath, fill);
bsalomon7c73a532016-05-11 15:15:56 -07001404 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1405 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1406 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001407 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
1408 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
1409 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
1410 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
1411 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
1412 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
1413 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
1414 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
1415 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
1416 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001417
1418 Key emptyKey(fillEmptyCase.baseKey());
1419 REPORTER_ASSERT(reporter, emptyKey.count());
Brian Salomon085c0862017-08-31 15:44:51 -04001420 Key inverseEmptyKey(fillInvertedEmptyCase.baseKey());
1421 REPORTER_ASSERT(reporter, inverseEmptyKey.count());
bsalomon409ed732016-04-27 12:36:02 -07001422 TestCase::SelfExpectations expectations;
1423 expectations.fStrokeApplies = false;
1424 expectations.fPEHasEffect = false;
1425 // This will test whether applying style preserves emptiness
1426 fillEmptyCase.testExpectations(reporter, expectations);
Brian Salomon085c0862017-08-31 15:44:51 -04001427 fillInvertedEmptyCase.testExpectations(reporter, expectations);
bsalomon409ed732016-04-27 12:36:02 -07001428
1429 // Stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001430 SkPaint stroke;
1431 stroke.setStrokeWidth(2.f);
1432 stroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon085c0862017-08-31 15:44:51 -04001433 TestCase strokeEmptyCase(reporter, emptyPath, stroke);
bsalomon409ed732016-04-27 12:36:02 -07001434 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001435 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
1436 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
1437 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001438
1439 // Dashing and stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001440 SkPaint dashAndStroke;
1441 dashAndStroke.setPathEffect(make_dash());
1442 dashAndStroke.setStrokeWidth(2.f);
1443 dashAndStroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon085c0862017-08-31 15:44:51 -04001444 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
bsalomon409ed732016-04-27 12:36:02 -07001445 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1446 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001447 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
1448 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1449 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
1450 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001451
1452 // A shape made from an empty rrect should behave the same as an empty path.
1453 SkRRect emptyRRect = SkRRect::MakeRect(SkRect::MakeEmpty());
1454 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
bsalomona395f7c2016-08-24 17:47:40 -07001455 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001456 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1457 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001458 static constexpr SkPath::Direction kDir = SkPath::kCCW_Direction;
1459 static constexpr int kStart = 0;
1460 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
1461 GrStyle(dashAndStroke));
1462 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1463 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
1464 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001465
1466 // Same for a rect.
1467 SkRect emptyRect = SkRect::MakeEmpty();
bsalomona395f7c2016-08-24 17:47:40 -07001468 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001469 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1470 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001471 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
1472 kStart, true, GrStyle(dashAndStroke));
1473 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1474 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
1475 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001476}
1477
bsalomon70493962016-06-10 08:05:14 -07001478// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1479// canonical point in these cases.
1480unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1481 switch (rrect.getType()) {
1482 case SkRRect::kRect_Type:
1483 return (s + 1) & 0b110;
1484 case SkRRect::kOval_Type:
1485 return s & 0b110;
1486 default:
1487 return s;
1488 }
1489}
1490
1491void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
bsalomoncadb5a22016-06-10 18:28:06 -07001492 enum Style {
bsalomon70493962016-06-10 08:05:14 -07001493 kFill,
1494 kStroke,
1495 kHairline,
1496 kStrokeAndFill
1497 };
1498
1499 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1500 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1501 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1502 strokeRecs[kFill].setFillStyle();
1503 strokeRecs[kStroke].setStrokeStyle(2.f);
1504 strokeRecs[kHairline].setHairlineStyle();
1505 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
bsalomon487f8d32016-07-20 07:15:44 -07001506 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1507 // applyStyle() is called.
1508 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
bsalomon70493962016-06-10 08:05:14 -07001509 sk_sp<SkPathEffect> dashEffect = make_dash();
1510
bsalomoncadb5a22016-06-10 18:28:06 -07001511 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1512
1513 auto index = [](bool inverted,
1514 SkPath::Direction dir,
1515 unsigned start,
1516 Style style,
1517 bool dash) -> int {
1518 return inverted * (2 * 8 * kStyleCnt * 2) +
1519 dir * ( 8 * kStyleCnt * 2) +
1520 start * ( kStyleCnt * 2) +
1521 style * ( 2) +
1522 dash;
1523 };
1524 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1);
1525 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1526 SkAutoTArray<GrShape> shapes(cnt);
1527 for (bool inverted : {false, true}) {
1528 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1529 for (unsigned start = 0; start < 8; ++start) {
1530 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1531 for (bool dash : {false, true}) {
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001532 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
bsalomoncadb5a22016-06-10 18:28:06 -07001533 shapes[index(inverted, dir, start, style, dash)] =
1534 GrShape(rrect, dir, start, SkToBool(inverted),
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001535 GrStyle(strokeRecs[style], std::move(pe)));
bsalomon70493962016-06-10 08:05:14 -07001536 }
1537 }
1538 }
1539 }
1540 }
1541
bsalomonfd32df72016-06-14 14:37:21 -07001542 // Get the keys for some example shape instances that we'll use for comparision against the
1543 // rest.
1544 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction;
1545 static constexpr unsigned kExamplesStart = 0;
1546 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1547 false)];
bsalomon70493962016-06-10 08:05:14 -07001548 Key exampleFillCaseKey;
1549 make_key(&exampleFillCaseKey, exampleFillCase);
1550
bsalomonfd32df72016-06-14 14:37:21 -07001551 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart,
1552 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001553 Key exampleStrokeAndFillCaseKey;
1554 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1555
bsalomonfd32df72016-06-14 14:37:21 -07001556 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill,
1557 false)];
bsalomon70493962016-06-10 08:05:14 -07001558 Key exampleInvFillCaseKey;
1559 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1560
bsalomonfd32df72016-06-14 14:37:21 -07001561 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart,
1562 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001563 Key exampleInvStrokeAndFillCaseKey;
1564 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1565
bsalomonfd32df72016-06-14 14:37:21 -07001566 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke,
1567 false)];
bsalomon70493962016-06-10 08:05:14 -07001568 Key exampleStrokeCaseKey;
1569 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1570
bsalomonfd32df72016-06-14 14:37:21 -07001571 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke,
1572 false)];
1573 Key exampleInvStrokeCaseKey;
1574 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1575
1576 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1577 kHairline, false)];
bsalomon70493962016-06-10 08:05:14 -07001578 Key exampleHairlineCaseKey;
1579 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1580
bsalomonfd32df72016-06-14 14:37:21 -07001581 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1582 kHairline, false)];
1583 Key exampleInvHairlineCaseKey;
1584 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1585
bsalomon70493962016-06-10 08:05:14 -07001586 // These are dummy initializations to suppress warnings.
bsalomoncadb5a22016-06-10 18:28:06 -07001587 SkRRect queryRR = SkRRect::MakeEmpty();
1588 SkPath::Direction queryDir = SkPath::kCW_Direction;
1589 unsigned queryStart = ~0U;
1590 bool queryInverted = true;
bsalomon70493962016-06-10 08:05:14 -07001591
bsalomoncadb5a22016-06-10 18:28:06 -07001592 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1593 REPORTER_ASSERT(r, queryRR == rrect);
1594 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1595 REPORTER_ASSERT(r, 0 == queryStart);
1596 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001597
bsalomoncadb5a22016-06-10 18:28:06 -07001598 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1599 &queryInverted));
1600 REPORTER_ASSERT(r, queryRR == rrect);
1601 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1602 REPORTER_ASSERT(r, 0 == queryStart);
1603 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001604
bsalomoncadb5a22016-06-10 18:28:06 -07001605 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1606 &queryInverted));
1607 REPORTER_ASSERT(r, queryRR == rrect);
1608 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1609 REPORTER_ASSERT(r, 0 == queryStart);
1610 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001611
bsalomoncadb5a22016-06-10 18:28:06 -07001612 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1613 &queryInverted));
1614 REPORTER_ASSERT(r, queryRR == rrect);
1615 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1616 REPORTER_ASSERT(r, 0 == queryStart);
1617 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001618
bsalomoncadb5a22016-06-10 18:28:06 -07001619 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1620 &queryInverted));
1621 REPORTER_ASSERT(r, queryRR == rrect);
1622 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1623 REPORTER_ASSERT(r, 0 == queryStart);
1624 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001625
bsalomonfd32df72016-06-14 14:37:21 -07001626 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1627 &queryInverted));
1628 REPORTER_ASSERT(r, queryRR == rrect);
1629 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1630 REPORTER_ASSERT(r, 0 == queryStart);
1631 REPORTER_ASSERT(r, queryInverted);
1632
bsalomoncadb5a22016-06-10 18:28:06 -07001633 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1634 REPORTER_ASSERT(r, queryRR == rrect);
1635 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1636 REPORTER_ASSERT(r, 0 == queryStart);
1637 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001638
bsalomonfd32df72016-06-14 14:37:21 -07001639 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1640 &queryInverted));
1641 REPORTER_ASSERT(r, queryRR == rrect);
1642 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1643 REPORTER_ASSERT(r, 0 == queryStart);
1644 REPORTER_ASSERT(r, queryInverted);
1645
bsalomon70493962016-06-10 08:05:14 -07001646 // Remember that the key reflects the geometry before styling is applied.
1647 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1648 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1649 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1650 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001651 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001652 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001653 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001654 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001655 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1656 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001657
bsalomoncadb5a22016-06-10 18:28:06 -07001658 for (bool inverted : {false, true}) {
1659 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1660 for (unsigned start = 0; start < 8; ++start) {
1661 for (bool dash : {false, true}) {
1662 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001663 Key fillCaseKey;
1664 make_key(&fillCaseKey, fillCase);
1665
bsalomoncadb5a22016-06-10 18:28:06 -07001666 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1667 kStrokeAndFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001668 Key strokeAndFillCaseKey;
1669 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1670
1671 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1672 // ignore dashing.
1673 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1674 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1675 TestCase a(fillCase, r);
1676 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1677 TestCase c(strokeAndFillCase, r);
1678 TestCase d(inverted ? exampleInvStrokeAndFillCase
1679 : exampleStrokeAndFillCase, r);
1680 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1681 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1682
bsalomoncadb5a22016-06-10 18:28:06 -07001683 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)];
1684 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline,
1685 dash)];
bsalomon70493962016-06-10 08:05:14 -07001686
1687 TestCase e(strokeCase, r);
bsalomon70493962016-06-10 08:05:14 -07001688 TestCase g(hairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001689
bsalomonfd32df72016-06-14 14:37:21 -07001690 // Both hairline and stroke shapes must respect the dashing.
bsalomon70493962016-06-10 08:05:14 -07001691 if (dash) {
bsalomonfd32df72016-06-14 14:37:21 -07001692 // Dashing always ignores the inverseness. skbug.com/5421
1693 TestCase f(exampleStrokeCase, r);
1694 TestCase h(exampleHairlineCase, r);
bsalomoncadb5a22016-06-10 18:28:06 -07001695 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
bsalomon70493962016-06-10 08:05:14 -07001696 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1697 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1698
bsalomoncadb5a22016-06-10 18:28:06 -07001699 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1700 &queryInverted));
1701 REPORTER_ASSERT(r, queryRR == rrect);
1702 REPORTER_ASSERT(r, queryDir == dir);
1703 REPORTER_ASSERT(r, queryStart == expectedStart);
1704 REPORTER_ASSERT(r, !queryInverted);
1705 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1706 &queryInverted));
1707 REPORTER_ASSERT(r, queryRR == rrect);
1708 REPORTER_ASSERT(r, queryDir == dir);
1709 REPORTER_ASSERT(r, queryStart == expectedStart);
1710 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001711
1712 // The pre-style case for the dash will match the non-dash example iff the
1713 // dir and start match (dir=cw, start=0).
bsalomoncadb5a22016-06-10 18:28:06 -07001714 if (0 == expectedStart && SkPath::kCW_Direction == dir) {
bsalomon70493962016-06-10 08:05:14 -07001715 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1716 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1717 } else {
1718 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1719 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1720 }
1721 } else {
bsalomonfd32df72016-06-14 14:37:21 -07001722 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1723 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001724 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1725 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1726 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1727 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1728 }
1729 }
1730 }
1731 }
1732 }
1733}
1734
Mike Klein43344282017-08-16 11:56:22 -04001735DEF_TEST(GrShape_lines, r) {
bsalomon0a0f67e2016-06-28 11:56:42 -07001736 static constexpr SkPoint kA { 1, 1};
1737 static constexpr SkPoint kB { 5, -9};
1738 static constexpr SkPoint kC {-3, 17};
1739
1740 SkPath lineAB;
1741 lineAB.moveTo(kA);
1742 lineAB.lineTo(kB);
1743
1744 SkPath lineBA;
1745 lineBA.moveTo(kB);
1746 lineBA.lineTo(kA);
1747
1748 SkPath lineAC;
1749 lineAC.moveTo(kB);
1750 lineAC.lineTo(kC);
1751
1752 SkPath invLineAB = lineAB;
1753 invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType);
1754
1755 SkPaint fill;
1756 SkPaint stroke;
1757 stroke.setStyle(SkPaint::kStroke_Style);
1758 stroke.setStrokeWidth(2.f);
1759 SkPaint hairline;
1760 hairline.setStyle(SkPaint::kStroke_Style);
1761 hairline.setStrokeWidth(0.f);
1762 SkPaint dash = stroke;
1763 dash.setPathEffect(make_dash());
1764
bsalomona395f7c2016-08-24 17:47:40 -07001765 TestCase fillAB(r, lineAB, fill);
1766 TestCase fillEmpty(r, SkPath(), fill);
bsalomon0a0f67e2016-06-28 11:56:42 -07001767 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1768 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1769
Brian Salomon085c0862017-08-31 15:44:51 -04001770 SkPath path;
1771 path.toggleInverseFillType();
1772 TestCase fillEmptyInverted(r, path, fill);
1773 TestCase fillABInverted(r, invLineAB, fill);
1774 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
1775 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
1776
bsalomona395f7c2016-08-24 17:47:40 -07001777 TestCase strokeAB(r, lineAB, stroke);
1778 TestCase strokeBA(r, lineBA, stroke);
1779 TestCase strokeAC(r, lineAC, stroke);
bsalomon0a0f67e2016-06-28 11:56:42 -07001780
bsalomona395f7c2016-08-24 17:47:40 -07001781 TestCase hairlineAB(r, lineAB, hairline);
1782 TestCase hairlineBA(r, lineBA, hairline);
1783 TestCase hairlineAC(r, lineAC, hairline);
bsalomon0a0f67e2016-06-28 11:56:42 -07001784
bsalomona395f7c2016-08-24 17:47:40 -07001785 TestCase dashAB(r, lineAB, dash);
1786 TestCase dashBA(r, lineBA, dash);
1787 TestCase dashAC(r, lineAC, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001788
1789 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1790
1791 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1792 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1793
1794 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1795 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1796
1797 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1798 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1799
1800 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1801
1802 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1803 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1804 bool canonicalizeAsAB;
1805 SkPoint canonicalPts[2] {kA, kB};
1806 // Init these to suppress warnings.
1807 bool inverted = true;
1808 SkPoint pts[2] {{0, 0}, {0, 0}};
1809 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1810 if (pts[0] == kA && pts[1] == kB) {
1811 canonicalizeAsAB = true;
1812 } else if (pts[1] == kA && pts[0] == kB) {
1813 canonicalizeAsAB = false;
1814 SkTSwap(canonicalPts[0], canonicalPts[1]);
1815 } else {
1816 ERRORF(r, "Should return pts (a,b) or (b, a)");
1817 return;
1818 };
1819
1820 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1821 TestCase::kSameUpToPE_ComparisonExpecation);
1822 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1823 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1824 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1825 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1826 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1827 pts[0] == kA && pts[1] == kB);
1828 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1829 pts[0] == kB && pts[1] == kA);
1830
1831
bsalomona395f7c2016-08-24 17:47:40 -07001832 TestCase strokeInvAB(r, invLineAB, stroke);
1833 TestCase hairlineInvAB(r, invLineAB, hairline);
1834 TestCase dashInvAB(r, invLineAB, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001835 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1836 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1837 // Dashing ignores inverse.
1838 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1839
1840 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1841 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1842 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1843 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1844 // Dashing ignores inverse.
1845 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1846 pts[0] == kA && pts[1] == kB);
1847
1848}
1849
Mike Klein43344282017-08-16 11:56:22 -04001850DEF_TEST(GrShape_stroked_lines, r) {
bsalomon0ae36a22016-07-18 07:31:13 -07001851 // Paints to try
1852 SkPaint buttCap;
1853 buttCap.setStyle(SkPaint::kStroke_Style);
1854 buttCap.setStrokeWidth(4);
1855 buttCap.setStrokeCap(SkPaint::kButt_Cap);
1856
1857 SkPaint squareCap = buttCap;
1858 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
1859
1860 SkPaint roundCap = buttCap;
1861 roundCap.setStrokeCap(SkPaint::kRound_Cap);
1862
1863 // vertical
1864 SkPath linePath;
1865 linePath.moveTo(4, 4);
1866 linePath.lineTo(4, 5);
1867
1868 SkPaint fill;
1869
Mike Klein43344282017-08-16 11:56:22 -04001870 make_TestCase(r, linePath, buttCap)->compare(
1871 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
1872 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001873
Mike Klein43344282017-08-16 11:56:22 -04001874 make_TestCase(r, linePath, squareCap)->compare(
1875 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
1876 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001877
Mike Klein43344282017-08-16 11:56:22 -04001878 make_TestCase(r, linePath, roundCap)->compare(r,
bsalomona395f7c2016-08-24 17:47:40 -07001879 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
bsalomon0ae36a22016-07-18 07:31:13 -07001880 TestCase::kAllSame_ComparisonExpecation);
1881
1882 // horizontal
1883 linePath.reset();
1884 linePath.moveTo(4, 4);
1885 linePath.lineTo(5, 4);
1886
Mike Klein43344282017-08-16 11:56:22 -04001887 make_TestCase(r, linePath, buttCap)->compare(
1888 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
1889 TestCase::kAllSame_ComparisonExpecation);
1890 make_TestCase(r, linePath, squareCap)->compare(
1891 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
1892 TestCase::kAllSame_ComparisonExpecation);
1893 make_TestCase(r, linePath, roundCap)->compare(
1894 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
1895 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001896
1897 // point
1898 linePath.reset();
1899 linePath.moveTo(4, 4);
1900 linePath.lineTo(4, 4);
1901
Mike Klein43344282017-08-16 11:56:22 -04001902 make_TestCase(r, linePath, buttCap)->compare(
1903 r, TestCase(r, SkRect::MakeEmpty(), fill),
1904 TestCase::kAllSame_ComparisonExpecation);
1905 make_TestCase(r, linePath, squareCap)->compare(
1906 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
1907 TestCase::kAllSame_ComparisonExpecation);
1908 make_TestCase(r, linePath, roundCap)->compare(
1909 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
1910 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001911}
1912
Mike Klein43344282017-08-16 11:56:22 -04001913DEF_TEST(GrShape_short_path_keys, r) {
bsalomon67fa4e32016-09-21 08:26:57 -07001914 SkPaint paints[4];
1915 paints[1].setStyle(SkPaint::kStroke_Style);
1916 paints[1].setStrokeWidth(5.f);
1917 paints[2].setStyle(SkPaint::kStroke_Style);
1918 paints[2].setStrokeWidth(0.f);
1919 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
1920 paints[3].setStrokeWidth(5.f);
1921
bsalomonaa840642016-09-23 12:09:16 -07001922 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
bsalomon67fa4e32016-09-21 08:26:57 -07001923 TestCase::ComparisonExpecation expectation) {
bsalomonaa840642016-09-23 12:09:16 -07001924 SkPath volatileA = pathA;
1925 SkPath volatileB = pathB;
1926 volatileA.setIsVolatile(true);
1927 volatileB.setIsVolatile(true);
bsalomon67fa4e32016-09-21 08:26:57 -07001928 for (const SkPaint& paint : paints) {
bsalomonaa840642016-09-23 12:09:16 -07001929 REPORTER_ASSERT(r, !GrShape(volatileA, paint).hasUnstyledKey());
1930 REPORTER_ASSERT(r, !GrShape(volatileB, paint).hasUnstyledKey());
bsalomon67fa4e32016-09-21 08:26:57 -07001931 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
bsalomonaa840642016-09-23 12:09:16 -07001932 TestCase caseA(PathGeo(pathA, invert), paint, r);
1933 TestCase caseB(PathGeo(pathB, invert), paint, r);
1934 caseA.compare(r, caseB, expectation);
bsalomon67fa4e32016-09-21 08:26:57 -07001935 }
1936 }
1937 };
1938
1939 SkPath pathA;
1940 SkPath pathB;
1941
1942 // Two identical paths
1943 pathA.lineTo(10.f, 10.f);
1944 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
1945
1946 pathB.lineTo(10.f, 10.f);
1947 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07001948 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07001949
1950 // Give path b a different point
1951 pathB.reset();
1952 pathB.lineTo(10.f, 10.f);
1953 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07001954 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07001955
1956 // Give path b a different conic weight
1957 pathB.reset();
1958 pathB.lineTo(10.f, 10.f);
1959 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
bsalomonaa840642016-09-23 12:09:16 -07001960 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07001961
1962 // Give path b an extra lineTo verb
1963 pathB.reset();
1964 pathB.lineTo(10.f, 10.f);
1965 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
1966 pathB.lineTo(50.f, 50.f);
bsalomonaa840642016-09-23 12:09:16 -07001967 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07001968
1969 // Give path b a close
1970 pathB.reset();
1971 pathB.lineTo(10.f, 10.f);
1972 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
1973 pathB.close();
bsalomonaa840642016-09-23 12:09:16 -07001974 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07001975}
1976
bsalomon47cc7692016-04-26 12:56:00 -07001977DEF_TEST(GrShape, reporter) {
bsalomona395f7c2016-08-24 17:47:40 -07001978 SkTArray<std::unique_ptr<Geo>> geos;
1979 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
1980
bsalomonee295642016-06-06 14:01:25 -07001981 for (auto r : { SkRect::MakeWH(10, 20),
1982 SkRect::MakeWH(-10, -20),
1983 SkRect::MakeWH(-10, 20),
1984 SkRect::MakeWH(10, -20)}) {
bsalomona395f7c2016-08-24 17:47:40 -07001985 geos.emplace_back(new RectGeo(r));
1986 SkPath rectPath;
1987 rectPath.addRect(r);
1988 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
1989 PathGeo::Invert::kNo));
1990 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
1991 PathGeo::Invert::kYes));
1992 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
1993 PathGeo::Invert::kNo));
bsalomonee295642016-06-06 14:01:25 -07001994 }
bsalomon47cc7692016-04-26 12:56:00 -07001995 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -07001996 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
1997 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomona395f7c2016-08-24 17:47:40 -07001998 geos.emplace_back(new RRectGeo(rr));
bsalomon70493962016-06-10 08:05:14 -07001999 test_rrect(reporter, rr);
bsalomona395f7c2016-08-24 17:47:40 -07002000 SkPath rectPath;
2001 rectPath.addRRect(rr);
2002 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2003 PathGeo::Invert::kNo));
2004 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2005 PathGeo::Invert::kYes));
2006 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
2007 RRectPathGeo::RRectForStroke::kYes,
2008 PathGeo::Invert::kNo));
bsalomon72dc51c2016-04-27 06:46:23 -07002009 }
2010
Mike Klein43344282017-08-16 11:56:22 -04002011 {
2012 SkPath openRectPath;
2013 openRectPath.moveTo(0, 0);
2014 openRectPath.lineTo(10, 0);
2015 openRectPath.lineTo(10, 10);
2016 openRectPath.lineTo(0, 10);
2017 geos.emplace_back(new RRectPathGeo(
2018 openRectPath, SkRect::MakeWH(10, 10),
2019 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2020 geos.emplace_back(new RRectPathGeo(
2021 openRectPath, SkRect::MakeWH(10, 10),
2022 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
2023 rrectPathGeos.emplace_back(new RRectPathGeo(
2024 openRectPath, SkRect::MakeWH(10, 10),
2025 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2026 }
bsalomon72dc51c2016-04-27 06:46:23 -07002027
Mike Klein43344282017-08-16 11:56:22 -04002028 {
2029 SkPath quadPath;
2030 quadPath.quadTo(10, 10, 5, 8);
2031 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
2032 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
2033 }
bsalomon398e3f42016-06-13 10:22:48 -07002034
Mike Klein43344282017-08-16 11:56:22 -04002035 {
2036 SkPath linePath;
2037 linePath.lineTo(10, 10);
2038 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
2039 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
2040 }
bsalomon72dc51c2016-04-27 06:46:23 -07002041
bsalomon0ae36a22016-07-18 07:31:13 -07002042 // Horizontal and vertical paths become rrects when stroked.
Mike Klein43344282017-08-16 11:56:22 -04002043 {
2044 SkPath vLinePath;
2045 vLinePath.lineTo(0, 10);
2046 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
2047 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
2048 }
bsalomon0ae36a22016-07-18 07:31:13 -07002049
Mike Klein43344282017-08-16 11:56:22 -04002050 {
2051 SkPath hLinePath;
2052 hLinePath.lineTo(10, 0);
2053 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
2054 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
2055 }
bsalomon0ae36a22016-07-18 07:31:13 -07002056
bsalomona395f7c2016-08-24 17:47:40 -07002057 for (int i = 0; i < geos.count(); ++i) {
2058 test_basic(reporter, *geos[i]);
2059 test_scale(reporter, *geos[i]);
2060 test_dash_fill(reporter, *geos[i]);
2061 test_null_dash(reporter, *geos[i]);
2062 // Test modifying various stroke params.
2063 test_stroke_param<SkScalar>(
2064 reporter, *geos[i],
bsalomon70493962016-06-10 08:05:14 -07002065 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
2066 SkIntToScalar(2), SkIntToScalar(4));
bsalomona395f7c2016-08-24 17:47:40 -07002067 test_stroke_join(reporter, *geos[i]);
2068 test_stroke_cap(reporter, *geos[i]);
2069 test_miter_limit(reporter, *geos[i]);
2070 test_path_effect_makes_rrect(reporter, *geos[i]);
2071 test_unknown_path_effect(reporter, *geos[i]);
2072 test_path_effect_makes_empty_shape(reporter, *geos[i]);
2073 test_path_effect_fails(reporter, *geos[i]);
2074 test_make_hairline_path_effect(reporter, *geos[i]);
2075 test_volatile_path(reporter, *geos[i]);
bsalomon70493962016-06-10 08:05:14 -07002076 }
bsalomonfd32df72016-06-14 14:37:21 -07002077
bsalomona395f7c2016-08-24 17:47:40 -07002078 for (int i = 0; i < rrectPathGeos.count(); ++i) {
2079 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
bsalomon72dc51c2016-04-27 06:46:23 -07002080 SkPaint fillPaint;
bsalomona395f7c2016-08-24 17:47:40 -07002081 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002082 SkRRect rrect;
bsalomona395f7c2016-08-24 17:47:40 -07002083 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
bsalomon70493962016-06-10 08:05:14 -07002084 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2085 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002086 if (rrgeo.isNonPath(fillPaint)) {
2087 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
2088 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2089 TestCase fillRRectCase(reporter, rrect, fillPaint);
bsalomon70493962016-06-10 08:05:14 -07002090 fillPathCase2.compare(reporter, fillRRectCase,
2091 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002092 }
bsalomon72dc51c2016-04-27 06:46:23 -07002093 SkPaint strokePaint;
2094 strokePaint.setStrokeWidth(3.f);
2095 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07002096 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
2097 if (rrgeo.isNonPath(strokePaint)) {
bsalomon0ae36a22016-07-18 07:31:13 -07002098 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2099 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002100 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2101 TestCase strokeRRectCase(reporter, rrect, strokePaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002102 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07002103 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002104 }
bsalomon47cc7692016-04-26 12:56:00 -07002105 }
bsalomon409ed732016-04-27 12:36:02 -07002106
bsalomon4eeccc92016-04-27 13:30:25 -07002107 // Test a volatile empty path.
bsalomona395f7c2016-08-24 17:47:40 -07002108 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
bsalomon47cc7692016-04-26 12:56:00 -07002109}
2110
2111#endif