blob: 259026c8d0c0ec899d01e009dee52c19759c347b [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
Brian Osmanf6f7cf62017-09-25 16:49:55 -040020uint32_t GrShape::testingOnly_getOriginalGenerationID() const {
21 return fOriginalPath.getGenerationID();
22}
23
Brian Osmanb379dcd2017-10-04 15:44:05 -040024bool GrShape::testingOnly_isPath() const {
25 return Type::kPath == fType;
26}
27
bsalomon72dc51c2016-04-27 06:46:23 -070028using Key = SkTArray<uint32_t>;
29
30static bool make_key(Key* key, const GrShape& shape) {
31 int size = shape.unstyledKeySize();
32 if (size <= 0) {
33 key->reset(0);
34 return false;
35 }
36 SkASSERT(size);
37 key->reset(size);
38 shape.writeUnstyledKey(key->begin());
39 return true;
40}
41
bsalomonee295642016-06-06 14:01:25 -070042static bool paths_fill_same(const SkPath& a, const SkPath& b) {
43 SkPath pathXor;
44 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
45 return pathXor.isEmpty();
46}
47
bsalomon9fb42032016-05-13 09:23:38 -070048static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
bsalomon164fd9f2016-08-26 06:45:06 -070049 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
50 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
51 // rendering within the bounds (with a tolerance). Then we render the path and check that
52 // everything got clipped out.
bsalomon9fb42032016-05-13 09:23:38 -070053 static constexpr int kRes = 2000;
54 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
55 static constexpr int kTol = 0;
56 GR_STATIC_ASSERT(kRes % 4 == 0);
57 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
58 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
59 surface->getCanvas()->clear(0x0);
60 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
61 SkMatrix matrix;
62 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit);
63 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
Mike Reedc1f77742016-12-09 09:00:50 -050064 surface->getCanvas()->clipRect(clip, kDifference_SkClipOp);
bsalomon9fb42032016-05-13 09:23:38 -070065 surface->getCanvas()->concat(matrix);
66 SkPaint whitePaint;
67 whitePaint.setColor(SK_ColorWHITE);
68 surface->getCanvas()->drawPath(path, whitePaint);
69 SkPixmap pixmap;
70 surface->getCanvas()->peekPixels(&pixmap);
71#if defined(SK_BUILD_FOR_WIN)
72 // The static constexpr version in #else causes cl.exe to crash.
73 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
74#else
75 static constexpr uint8_t kZeros[kRes] = {0};
76#endif
bsalomon164fd9f2016-08-26 06:45:06 -070077 for (int y = 0; y < kRes; ++y) {
bsalomon9fb42032016-05-13 09:23:38 -070078 const uint8_t* row = pixmap.addr8(0, y);
79 if (0 != memcmp(kZeros, row, kRes)) {
80 return false;
81 }
82 }
83#ifdef SK_BUILD_FOR_WIN
84 free(const_cast<uint8_t*>(kZeros));
85#endif
86 return true;
87}
bsalomon72dc51c2016-04-27 06:46:23 -070088
Brian Salomon4f40caf2017-09-01 09:00:45 -040089static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
90 SkPath path;
91 shape.asPath(&path);
92 if (shape.style().hasNonDashPathEffect()) {
93 return false;
94 }
95 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
96 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
97 strokeRecStyle == SkStrokeRec::kHairline_Style ||
98 (shape.style().isSimpleFill() && path.isConvex());
99}
100
101static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
102 const Key& keyA, const Key& keyB) {
103 // GrShape only respects the input winding direction and start point for rrect shapes
104 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
105 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
106 // key will differ. GrShape will have canonicalized the direction and start point for the shape
107 // without the path effect. If *both* have path effects then they should have both preserved
108 // the direction and starting point.
109
110 // The asRRect() output params are all initialized just to silence compiler warnings about
111 // uninitialized variables.
112 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
113 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
114 unsigned startA = ~0U, startB = ~0U;
115 bool invertedA = true, invertedB = true;
116
117 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
118 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
119 bool aHasPE = a.style().hasPathEffect();
120 bool bHasPE = b.style().hasPathEffect();
121 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
122 // GrShape will close paths with simple fill style.
123 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
124 SkPath pathA, pathB;
125 a.asPath(&pathA);
126 b.asPath(&pathB);
127
128 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
129 // non-inverse fill type (or vice versa).
130 bool ignoreInversenessDifference = false;
131 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
132 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
133 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
134 bool canDropInverse1 = s1->style().isDashed();
135 bool canDropInverse2 = s2->style().isDashed();
136 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
137 }
138 bool ignoreWindingVsEvenOdd = false;
139 if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
140 SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
141 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
142 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
143 if (aCanChange != bCanChange) {
144 ignoreWindingVsEvenOdd = true;
145 }
146 }
147 if (allowSameRRectButDiffStartAndDir) {
148 REPORTER_ASSERT(r, rrectA == rrectB);
149 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
150 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
151 } else {
152 SkPath pA = pathA;
153 SkPath pB = pathB;
154 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
155 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
156 if (ignoreInversenessDifference) {
157 pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
158 pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
159 }
160 if (ignoreWindingVsEvenOdd) {
161 pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
162 : SkPath::kEvenOdd_FillType);
163 pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
164 : SkPath::kEvenOdd_FillType);
165 }
166 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
167 REPORTER_ASSERT(r, keyA == keyB);
168 } else {
169 REPORTER_ASSERT(r, keyA != keyB);
170 }
171 if (allowedClosednessDiff) {
172 // GrShape will close paths with simple fill style. Make the non-filled path closed
173 // so that the comparision will succeed. Make sure both are closed before comparing.
174 pA.close();
175 pB.close();
176 }
177 REPORTER_ASSERT(r, pA == pB);
178 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
179 if (aIsRRect) {
180 REPORTER_ASSERT(r, rrectA == rrectB);
181 REPORTER_ASSERT(r, dirA == dirB);
182 REPORTER_ASSERT(r, startA == startB);
183 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
184 }
185 }
186 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
187 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
188 // closedness can affect convexity.
189 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
190 if (a.knownToBeConvex()) {
191 REPORTER_ASSERT(r, pathA.isConvex());
192 }
193 if (b.knownToBeConvex()) {
194 REPORTER_ASSERT(r, pathB.isConvex());
195 }
196 REPORTER_ASSERT(r, a.bounds() == b.bounds());
197 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
198 // Init these to suppress warnings.
199 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
200 bool invertedLine[2] {true, true};
201 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
202 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
203 // doesn't (since the PE can set any fill type on its output path).
204 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
205 // then they may disagree about inverseness.
206 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
207 a.style().isDashed() == b.style().isDashed()) {
208 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
209 b.mayBeInverseFilledAfterStyling());
210 }
211 if (a.asLine(nullptr, nullptr)) {
212 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
213 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
214 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
215 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
216 }
217 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
218}
219
Brian Osmanb379dcd2017-10-04 15:44:05 -0400220static void check_original_path_ids(skiatest::Reporter* r, const GrShape& base, const GrShape& pe,
221 const GrShape& peStroke, const GrShape& full) {
222 bool baseIsPath = base.testingOnly_isPath();
223 bool peIsPath = pe.testingOnly_isPath();
224 bool peStrokeIsPath = peStroke.testingOnly_isPath();
225 bool fullIsPath = full.testingOnly_isPath();
226
227 REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath);
228
229 uint32_t baseID = base.testingOnly_getOriginalGenerationID();
230 uint32_t peID = pe.testingOnly_getOriginalGenerationID();
231 uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID();
232 uint32_t fullID = full.testingOnly_getOriginalGenerationID();
233
234 // All empty paths have the same gen ID
235 uint32_t emptyID = SkPath().getGenerationID();
236
237 // If we started with a real path, then our genID should match that path's gen ID (and not be
238 // empty). If we started with a simple shape, our original path should have been reset.
239 REPORTER_ASSERT(r, baseIsPath == (baseID != emptyID));
240
241 // For the derived shapes, if they're simple types, their original paths should have been reset
242 REPORTER_ASSERT(r, peIsPath || (peID == emptyID));
243 REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID));
244 REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID));
245
246 if (!peIsPath) {
247 // If the path effect produces a simple shape, then there are no unbroken chains to test
248 return;
249 }
250
251 // From here on, we know that the path effect produced a shape that was a "real" path
252
253 if (baseIsPath) {
254 REPORTER_ASSERT(r, baseID == peID);
255 }
256
257 if (peStrokeIsPath) {
258 REPORTER_ASSERT(r, peID == peStrokeID);
259 REPORTER_ASSERT(r, peStrokeID == fullID);
260 }
261
262 if (baseIsPath && peStrokeIsPath) {
263 REPORTER_ASSERT(r, baseID == peStrokeID);
264 REPORTER_ASSERT(r, baseID == fullID);
265 }
266}
267
Brian Salomon4f40caf2017-09-01 09:00:45 -0400268void test_inversions(skiatest::Reporter* r, const GrShape& shape, const Key& shapeKey) {
269 GrShape preserve = GrShape::MakeFilled(shape, GrShape::FillInversion::kPreserve);
270 Key preserveKey;
271 make_key(&preserveKey, preserve);
272
273 GrShape flip = GrShape::MakeFilled(shape, GrShape::FillInversion::kFlip);
274 Key flipKey;
275 make_key(&flipKey, flip);
276
277 GrShape inverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceInverted);
278 Key invertedKey;
279 make_key(&invertedKey, inverted);
280
281 GrShape noninverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceNoninverted);
282 Key noninvertedKey;
283 make_key(&noninvertedKey, noninverted);
284
285 if (invertedKey.count() || noninvertedKey.count()) {
286 REPORTER_ASSERT(r, invertedKey != noninvertedKey);
287 }
288 if (shape.style().isSimpleFill()) {
289 check_equivalence(r, shape, preserve, shapeKey, preserveKey);
290 }
291 if (shape.inverseFilled()) {
292 check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
293 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
294 } else {
295 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
296 check_equivalence(r, flip, inverted, flipKey, invertedKey);
297 }
298
299 GrShape doubleFlip = GrShape::MakeFilled(flip, GrShape::FillInversion::kFlip);
300 Key doubleFlipKey;
301 make_key(&doubleFlipKey, doubleFlip);
302 // It can be the case that the double flip has no key but preserve does. This happens when the
303 // original shape has an inherited style key. That gets dropped on the first inversion flip.
304 if (preserveKey.count() && !doubleFlipKey.count()) {
305 preserveKey.reset();
306 }
307 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
308}
309
bsalomon9fb42032016-05-13 09:23:38 -0700310namespace {
bsalomona395f7c2016-08-24 17:47:40 -0700311/**
312 * Geo is a factory for creating a GrShape from another representation. It also answers some
313 * questions about expected behavior for GrShape given the inputs.
314 */
315class Geo {
316public:
Mike Kleinfc6c37b2016-09-27 09:34:10 -0400317 virtual ~Geo() {}
bsalomona395f7c2016-08-24 17:47:40 -0700318 virtual GrShape makeShape(const SkPaint&) const = 0;
319 virtual SkPath path() const = 0;
320 // These functions allow tests to check for special cases where style gets
321 // applied by GrShape in its constructor (without calling GrShape::applyStyle).
322 // These unfortunately rely on knowing details of GrShape's implementation.
323 // These predicates are factored out here to avoid littering the rest of the
324 // test code with GrShape implementation details.
325 virtual bool fillChangesGeom() const { return false; }
326 virtual bool strokeIsConvertedToFill() const { return false; }
327 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
328 // Is this something we expect GrShape to recognize as something simpler than a path.
329 virtual bool isNonPath(const SkPaint& paint) const { return true; }
330};
331
332class RectGeo : public Geo {
333public:
334 RectGeo(const SkRect& rect) : fRect(rect) {}
335
336 SkPath path() const override {
337 SkPath path;
338 path.addRect(fRect);
339 return path;
340 }
341
342 GrShape makeShape(const SkPaint& paint) const override {
343 return GrShape(fRect, paint);
344 }
345
346 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
347 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
348 // Converted to an outset rectangle.
349 return paint.getStrokeJoin() == SkPaint::kMiter_Join &&
350 paint.getStrokeMiter() >= SK_ScalarSqrt2;
351 }
352
353private:
354 SkRect fRect;
355};
356
357class RRectGeo : public Geo {
358public:
359 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
360
361 GrShape makeShape(const SkPaint& paint) const override {
362 return GrShape(fRRect, paint);
363 }
364
365 SkPath path() const override {
366 SkPath path;
367 path.addRRect(fRRect);
368 return path;
369 }
370
371 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
372 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
373 if (fRRect.isRect()) {
374 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
375 }
376 return false;
377 }
378
379private:
380 SkRRect fRRect;
381};
382
383class PathGeo : public Geo {
384public:
385 enum class Invert { kNo, kYes };
386
387 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
388 SkASSERT(!path.isInverseFillType());
389 if (Invert::kYes == invert) {
390 if (fPath.getFillType() == SkPath::kEvenOdd_FillType) {
391 fPath.setFillType(SkPath::kInverseEvenOdd_FillType);
392 } else {
393 SkASSERT(fPath.getFillType() == SkPath::kWinding_FillType);
394 fPath.setFillType(SkPath::kInverseWinding_FillType);
395 }
396 }
397 }
398
399 GrShape makeShape(const SkPaint& paint) const override {
400 return GrShape(fPath, paint);
401 }
402
403 SkPath path() const override { return fPath; }
404
405 bool fillChangesGeom() const override {
406 // unclosed rects get closed. Lines get turned into empty geometry
Brian Salomon085c0862017-08-31 15:44:51 -0400407 return this->isUnclosedRect() || fPath.isLine(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -0700408 }
409
410 bool strokeIsConvertedToFill() const override {
411 return this->isAxisAlignedLine();
412 }
413
414 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
415 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
416 if (this->isAxisAlignedLine()) {
417 // The fill is ignored (zero area) and the stroke is converted to a rrect.
418 return true;
419 }
420 SkRect rect;
421 unsigned start;
422 SkPath::Direction dir;
423 if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
424 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
425 }
426 return false;
427 }
428
429 bool isNonPath(const SkPaint& paint) const override {
430 return fPath.isLine(nullptr) || fPath.isEmpty();
431 }
432
433private:
434 bool isAxisAlignedLine() const {
435 SkPoint pts[2];
436 if (!fPath.isLine(pts)) {
437 return false;
438 }
439 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
440 }
441
442 bool isUnclosedRect() const {
443 bool closed;
444 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
445 }
446
447 SkPath fPath;
448};
449
450class RRectPathGeo : public PathGeo {
451public:
452 enum class RRectForStroke { kNo, kYes };
453
454 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
455 Invert invert)
456 : PathGeo(path, invert)
457 , fRRect(equivalentRRect)
458 , fRRectForStroke(rrectForStroke) {}
459
460 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
461 Invert invert)
462 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
463
464 bool isNonPath(const SkPaint& paint) const override {
465 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
466 return true;
467 }
468 return false;
469 }
470
471 const SkRRect& rrect() const { return fRRect; }
472
473private:
474 SkRRect fRRect;
475 RRectForStroke fRRectForStroke;
476};
477
bsalomon47cc7692016-04-26 12:56:00 -0700478class TestCase {
479public:
bsalomona395f7c2016-08-24 17:47:40 -0700480 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
481 SkScalar scale = SK_Scalar1) : fBase(geo.makeShape(paint)) {
bsalomon97fd2d42016-05-09 13:02:01 -0700482 this->init(r, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700483 }
484
bsalomona395f7c2016-08-24 17:47:40 -0700485 template<typename... ShapeArgs>
486 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs)
487 : fBase(shapeArgs...) {
488 this->init(r, SK_Scalar1);
489 }
490
bsalomon70493962016-06-10 08:05:14 -0700491 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
492 : fBase(shape) {
493 this->init(r, scale);
494 }
495
bsalomon47cc7692016-04-26 12:56:00 -0700496 struct SelfExpectations {
497 bool fPEHasEffect;
498 bool fPEHasValidKey;
499 bool fStrokeApplies;
500 };
501
502 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
503
504 enum ComparisonExpecation {
505 kAllDifferent_ComparisonExpecation,
506 kSameUpToPE_ComparisonExpecation,
507 kSameUpToStroke_ComparisonExpecation,
508 kAllSame_ComparisonExpecation,
509 };
510
511 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
512
bsalomon72dc51c2016-04-27 06:46:23 -0700513 const GrShape& baseShape() const { return fBase; }
514 const GrShape& appliedPathEffectShape() const { return fAppliedPE; }
515 const GrShape& appliedFullStyleShape() const { return fAppliedFull; }
516
517 // The returned array's count will be 0 if the key shape has no key.
518 const Key& baseKey() const { return fBaseKey; }
519 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
520 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700521 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700522
bsalomon47cc7692016-04-26 12:56:00 -0700523private:
bsalomon9fb42032016-05-13 09:23:38 -0700524 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
525 SkPath path;
526 shape.asPath(&path);
527 // If the bounds are empty, the path ought to be as well.
bsalomon0ae36a22016-07-18 07:31:13 -0700528 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
bsalomon9fb42032016-05-13 09:23:38 -0700529 REPORTER_ASSERT(r, path.isEmpty());
530 return;
531 }
532 if (path.isEmpty()) {
533 return;
534 }
bsalomon70493962016-06-10 08:05:14 -0700535 // The bounds API explicitly calls out that it does not consider inverseness.
536 SkPath p = path;
537 p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType()));
538 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
bsalomon9fb42032016-05-13 09:23:38 -0700539 }
540
bsalomon97fd2d42016-05-09 13:02:01 -0700541 void init(skiatest::Reporter* r, SkScalar scale) {
542 fAppliedPE = fBase.applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
543 fAppliedPEThenStroke = fAppliedPE.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec,
544 scale);
545 fAppliedFull = fBase.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700546
bsalomon72dc51c2016-04-27 06:46:23 -0700547 make_key(&fBaseKey, fBase);
548 make_key(&fAppliedPEKey, fAppliedPE);
549 make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke);
550 make_key(&fAppliedFullKey, fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700551
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400552 // All shapes should report the same "original" path, so that path renderers can get to it
553 // if necessary.
Brian Osmanb379dcd2017-10-04 15:44:05 -0400554 check_original_path_ids(r, fBase, fAppliedPE, fAppliedPEThenStroke, fAppliedFull);
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400555
bsalomonfb083272016-05-04 08:27:41 -0700556 // Applying the path effect and then the stroke should always be the same as applying
557 // both in one go.
558 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
559 SkPath a, b;
560 fAppliedPEThenStroke.asPath(&a);
561 fAppliedFull.asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700562 // If the output of the path effect is a rrect then it is possible for a and b to be
563 // different paths that fill identically. The reason is that fAppliedFull will do this:
564 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
565 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
566 // now that there is no longer a path effect, the direction and starting index get
567 // canonicalized before the stroke.
bsalomon70493962016-06-10 08:05:14 -0700568 if (fAppliedPE.asRRect(nullptr, nullptr, nullptr, nullptr)) {
bsalomonee295642016-06-06 14:01:25 -0700569 REPORTER_ASSERT(r, paths_fill_same(a, b));
570 } else {
571 REPORTER_ASSERT(r, a == b);
572 }
bsalomon7c73a532016-05-11 15:15:56 -0700573 REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty());
574
575 SkPath path;
576 fBase.asPath(&path);
577 REPORTER_ASSERT(r, path.isEmpty() == fBase.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700578 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700579 fAppliedPE.asPath(&path);
580 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700581 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700582 fAppliedFull.asPath(&path);
583 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700584 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull.segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700585
bsalomon9fb42032016-05-13 09:23:38 -0700586 CheckBounds(r, fBase, fBase.bounds());
587 CheckBounds(r, fAppliedPE, fAppliedPE.bounds());
588 CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds());
589 CheckBounds(r, fAppliedFull, fAppliedFull.bounds());
bsalomon0a0f67e2016-06-28 11:56:42 -0700590 SkRect styledBounds = fBase.styledBounds();
bsalomon9fb42032016-05-13 09:23:38 -0700591 CheckBounds(r, fAppliedFull, styledBounds);
bsalomon0a0f67e2016-06-28 11:56:42 -0700592 styledBounds = fAppliedPE.styledBounds();
bsalomon9fb42032016-05-13 09:23:38 -0700593 CheckBounds(r, fAppliedFull, styledBounds);
594
bsalomonfb083272016-05-04 08:27:41 -0700595 // Check that the same path is produced when style is applied by GrShape and GrStyle.
596 SkPath preStyle;
597 SkPath postPathEffect;
598 SkPath postAllStyle;
599
600 fBase.asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700601 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
bsalomon97fd2d42016-05-09 13:02:01 -0700602 if (fBase.style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
603 scale)) {
bsalomon1a0b9ed2016-05-06 11:07:03 -0700604 // run postPathEffect through GrShape to get any geometry reductions that would have
605 // occurred to fAppliedPE.
606 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
607
bsalomonfb083272016-05-04 08:27:41 -0700608 SkPath testPath;
609 fAppliedPE.asPath(&testPath);
610 REPORTER_ASSERT(r, testPath == postPathEffect);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700611 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE.style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700612 }
613 SkStrokeRec::InitStyle fillOrHairline;
bsalomon97fd2d42016-05-09 13:02:01 -0700614 if (fBase.style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomonfb083272016-05-04 08:27:41 -0700615 SkPath testPath;
616 fAppliedFull.asPath(&testPath);
bsalomon1b28c1a2016-06-20 12:28:17 -0700617 if (fBase.style().hasPathEffect()) {
618 // Because GrShape always does two-stage application when there is a path effect
619 // there may be a reduction/canonicalization step between the path effect and
620 // strokerec not reflected in postAllStyle since it applied both the path effect
621 // and strokerec without analyzing the intermediate path.
622 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
623 } else {
624 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape
625 // would apply.
626 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
627 REPORTER_ASSERT(r, testPath == postAllStyle);
628 }
629
bsalomonfb083272016-05-04 08:27:41 -0700630 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
631 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleFill());
632 } else {
633 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline());
634 }
635 }
Brian Salomon4f40caf2017-09-01 09:00:45 -0400636 test_inversions(r, fBase, fBaseKey);
637 test_inversions(r, fAppliedPE, fAppliedPEKey);
638 test_inversions(r, fAppliedFull, fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700639 }
640
641 GrShape fBase;
642 GrShape fAppliedPE;
643 GrShape fAppliedPEThenStroke;
644 GrShape fAppliedFull;
645
646 Key fBaseKey;
647 Key fAppliedPEKey;
648 Key fAppliedPEThenStrokeKey;
649 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700650};
651
652void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700653 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700654 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700655 if (expectations.fPEHasEffect) {
656 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700657 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700658 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700659 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700660 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
661 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700662 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700663 }
664 } else {
665 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700666 SkPath a, b;
bsalomon72dc51c2016-04-27 06:46:23 -0700667 fBase.asPath(&a);
668 fAppliedPE.asPath(&b);
669 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700670 if (expectations.fStrokeApplies) {
671 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
672 } else {
673 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
674 }
675 }
676}
677
bsalomonee295642016-06-06 14:01:25 -0700678void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700679 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700680 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700681 switch (expectation) {
682 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700683 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
684 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
685 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700686 break;
687 case kSameUpToPE_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700688 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
689 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
690 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700691 break;
692 case kSameUpToStroke_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700693 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
694 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
695 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700696 break;
697 case kAllSame_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700698 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
699 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
700 check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey,
701 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700702 break;
703 }
704}
705} // namespace
706
707static sk_sp<SkPathEffect> make_dash() {
708 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
709 static const SkScalar kPhase = 0.75;
710 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
711}
712
713static sk_sp<SkPathEffect> make_null_dash() {
714 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
715 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
716}
717
Mike Klein43344282017-08-16 11:56:22 -0400718// We make enough TestCases, and they're large enough, that on Google3 builds we exceed
719// the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
720template <typename... Args>
721static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
722 return std::unique_ptr<TestCase>{ new TestCase(std::forward<Args>(args)...) };
723}
724
bsalomona395f7c2016-08-24 17:47:40 -0700725static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700726 sk_sp<SkPathEffect> dashPE = make_dash();
727
728 TestCase::SelfExpectations expectations;
729 SkPaint fill;
730
bsalomonfb083272016-05-04 08:27:41 -0700731 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700732 expectations.fPEHasEffect = false;
733 expectations.fPEHasValidKey = false;
734 expectations.fStrokeApplies = false;
735 fillCase.testExpectations(reporter, expectations);
736 // Test that another GrShape instance built from the same primitive is the same.
Mike Klein43344282017-08-16 11:56:22 -0400737 make_TestCase(geo, fill, reporter)
738 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700739
740 SkPaint stroke2RoundBevel;
741 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
742 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
743 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
744 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700745 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700746 expectations.fPEHasValidKey = true;
747 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700748 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomon47cc7692016-04-26 12:56:00 -0700749 stroke2RoundBevelCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400750 make_TestCase(geo, stroke2RoundBevel, reporter)
751 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700752
753 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
754 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700755 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700756 expectations.fPEHasValidKey = true;
757 expectations.fPEHasEffect = true;
758 expectations.fStrokeApplies = true;
759 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400760 make_TestCase(geo, stroke2RoundBevelDash, reporter)
761 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700762
bsalomona395f7c2016-08-24 17:47:40 -0700763 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700764 fillCase.compare(reporter, stroke2RoundBevelCase,
765 TestCase::kAllDifferent_ComparisonExpecation);
766 fillCase.compare(reporter, stroke2RoundBevelDashCase,
767 TestCase::kAllDifferent_ComparisonExpecation);
768 } else {
769 fillCase.compare(reporter, stroke2RoundBevelCase,
770 TestCase::kSameUpToStroke_ComparisonExpecation);
771 fillCase.compare(reporter, stroke2RoundBevelDashCase,
772 TestCase::kSameUpToPE_ComparisonExpecation);
773 }
bsalomona395f7c2016-08-24 17:47:40 -0700774 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700775 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
776 TestCase::kAllDifferent_ComparisonExpecation);
777 } else {
778 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
779 TestCase::kSameUpToPE_ComparisonExpecation);
780 }
bsalomon72dc51c2016-04-27 06:46:23 -0700781
bsalomonf0cf3552016-05-05 08:28:30 -0700782 // Stroke and fill cases
783 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
784 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
785 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
786 expectations.fPEHasValidKey = true;
787 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700788 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700789 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400790 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
791 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700792
793 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
794 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
795 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
796 expectations.fPEHasValidKey = true;
bsalomona0587862016-06-09 06:03:38 -0700797 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700798 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700799 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400800 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
bsalomonf0cf3552016-05-05 08:28:30 -0700801 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomona0587862016-06-09 06:03:38 -0700802 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
803 TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700804
bsalomon72dc51c2016-04-27 06:46:23 -0700805 SkPaint hairline;
806 hairline.setStyle(SkPaint::kStroke_Style);
807 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700808 TestCase hairlineCase(geo, hairline, reporter);
bsalomon487f8d32016-07-20 07:15:44 -0700809 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
810 // in the line and unclosed rect cases).
bsalomona395f7c2016-08-24 17:47:40 -0700811 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -0700812 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
813 } else {
814 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
815 }
bsalomon9ad5d7c2016-05-04 08:44:15 -0700816 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
817 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
818 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700819
bsalomon0ae36a22016-07-18 07:31:13 -0700820}
821
bsalomona395f7c2016-08-24 17:47:40 -0700822static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon97fd2d42016-05-09 13:02:01 -0700823 sk_sp<SkPathEffect> dashPE = make_dash();
824
825 static const SkScalar kS1 = 1.f;
826 static const SkScalar kS2 = 2.f;
827
828 SkPaint fill;
829 TestCase fillCase1(geo, fill, reporter, kS1);
830 TestCase fillCase2(geo, fill, reporter, kS2);
831 // Scale doesn't affect fills.
832 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
833
834 SkPaint hairline;
835 hairline.setStyle(SkPaint::kStroke_Style);
836 hairline.setStrokeWidth(0.f);
837 TestCase hairlineCase1(geo, hairline, reporter, kS1);
838 TestCase hairlineCase2(geo, hairline, reporter, kS2);
839 // Scale doesn't affect hairlines.
840 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
841
842 SkPaint stroke;
843 stroke.setStyle(SkPaint::kStroke_Style);
844 stroke.setStrokeWidth(2.f);
845 TestCase strokeCase1(geo, stroke, reporter, kS1);
846 TestCase strokeCase2(geo, stroke, reporter, kS2);
bsalomon0ae36a22016-07-18 07:31:13 -0700847 // Scale affects the stroke
bsalomona395f7c2016-08-24 17:47:40 -0700848 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700849 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
bsalomon0ae36a22016-07-18 07:31:13 -0700850 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
851 } else {
852 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
853 }
bsalomon97fd2d42016-05-09 13:02:01 -0700854
855 SkPaint strokeDash = stroke;
856 strokeDash.setPathEffect(make_dash());
857 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
858 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
859 // Scale affects the dash and the stroke.
bsalomon487f8d32016-07-20 07:15:44 -0700860 strokeDashCase1.compare(reporter, strokeDashCase2,
861 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700862
863 // Stroke and fill cases
864 SkPaint strokeAndFill = stroke;
865 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
866 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
867 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
bsalomona0587862016-06-09 06:03:38 -0700868 SkPaint strokeAndFillDash = strokeDash;
869 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
870 // Dash is ignored for stroke and fill
871 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
872 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
bsalomon487f8d32016-07-20 07:15:44 -0700873 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
874 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
875 // geometries should agree.
bsalomona395f7c2016-08-24 17:47:40 -0700876 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
bsalomon487f8d32016-07-20 07:15:44 -0700877 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
bsalomon97fd2d42016-05-09 13:02:01 -0700878 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
879 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700880 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
881 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700882 } else {
883 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
884 TestCase::kSameUpToStroke_ComparisonExpecation);
885 }
bsalomona0587862016-06-09 06:03:38 -0700886 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
887 TestCase::kAllSame_ComparisonExpecation);
888 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
889 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700890}
891
bsalomona395f7c2016-08-24 17:47:40 -0700892template <typename T>
893static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700894 std::function<void(SkPaint*, T)> setter, T a, T b,
895 bool paramAffectsStroke,
896 bool paramAffectsDashAndStroke) {
897 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
898 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700899 SkPaint strokeA;
900 strokeA.setStyle(SkPaint::kStroke_Style);
901 strokeA.setStrokeWidth(2.f);
902 setter(&strokeA, a);
903 SkPaint strokeB;
904 strokeB.setStyle(SkPaint::kStroke_Style);
905 strokeB.setStrokeWidth(2.f);
906 setter(&strokeB, b);
907
bsalomonfb083272016-05-04 08:27:41 -0700908 TestCase strokeACase(geo, strokeA, reporter);
909 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700910 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700911 // If stroking is immediately incorporated into a geometric transformation then the base
912 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700913 if (geo.strokeIsConvertedToFill()) {
bsalomon0ae36a22016-07-18 07:31:13 -0700914 strokeACase.compare(reporter, strokeBCase,
915 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700916 } else {
917 strokeACase.compare(reporter, strokeBCase,
918 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700919 }
bsalomon06077562016-05-04 13:50:29 -0700920 } else {
921 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
922 }
bsalomon47cc7692016-04-26 12:56:00 -0700923
bsalomonf0cf3552016-05-05 08:28:30 -0700924 SkPaint strokeAndFillA = strokeA;
925 SkPaint strokeAndFillB = strokeB;
926 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
927 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
928 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
929 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
930 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700931 // If stroking is immediately incorporated into a geometric transformation then the base
932 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700933 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
934 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700935 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700936 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700937 } else {
938 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700939 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700940 }
bsalomonf0cf3552016-05-05 08:28:30 -0700941 } else {
942 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
943 TestCase::kAllSame_ComparisonExpecation);
944 }
945
bsalomon47cc7692016-04-26 12:56:00 -0700946 // Make sure stroking params don't affect fill style.
947 SkPaint fillA = strokeA, fillB = strokeB;
948 fillA.setStyle(SkPaint::kFill_Style);
949 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -0700950 TestCase fillACase(geo, fillA, reporter);
951 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700952 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
953
954 // Make sure just applying the dash but not stroke gives the same key for both stroking
955 // variations.
956 SkPaint dashA = strokeA, dashB = strokeB;
957 dashA.setPathEffect(make_dash());
958 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700959 TestCase dashACase(geo, dashA, reporter);
960 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700961 if (paramAffectsDashAndStroke) {
bsalomon487f8d32016-07-20 07:15:44 -0700962 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon06077562016-05-04 13:50:29 -0700963 } else {
964 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
965 }
bsalomon47cc7692016-04-26 12:56:00 -0700966}
967
bsalomona395f7c2016-08-24 17:47:40 -0700968template <typename T>
969static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700970 std::function<void(SkPaint*, T)> setter, T a, T b) {
971 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
972};
973
bsalomona395f7c2016-08-24 17:47:40 -0700974static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
975 SkPaint hairline;
976 hairline.setStrokeWidth(0);
977 hairline.setStyle(SkPaint::kStroke_Style);
978 GrShape shape = geo.makeShape(hairline);
bsalomon06077562016-05-04 13:50:29 -0700979 // The cap should only affect shapes that may be open.
980 bool affectsStroke = !shape.knownToBeClosed();
981 // Dashing adds ends that need caps.
982 bool affectsDashAndStroke = true;
bsalomona395f7c2016-08-24 17:47:40 -0700983 test_stroke_param_impl<SkPaint::Cap>(
bsalomon06077562016-05-04 13:50:29 -0700984 reporter,
985 geo,
986 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
987 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
988 affectsStroke,
989 affectsDashAndStroke);
990};
991
bsalomon0ae36a22016-07-18 07:31:13 -0700992static bool shape_known_not_to_have_joins(const GrShape& shape) {
993 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
994}
995
bsalomona395f7c2016-08-24 17:47:40 -0700996static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
997 SkPaint hairline;
998 hairline.setStrokeWidth(0);
999 hairline.setStyle(SkPaint::kStroke_Style);
1000 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001001 // GrShape recognizes certain types don't have joins and will prevent the join type from
1002 // affecting the style key.
1003 // Dashing doesn't add additional joins. However, GrShape currently loses track of this
1004 // after applying the dash.
1005 bool affectsStroke = !shape_known_not_to_have_joins(shape);
bsalomona395f7c2016-08-24 17:47:40 -07001006 test_stroke_param_impl<SkPaint::Join>(
bsalomon0ae36a22016-07-18 07:31:13 -07001007 reporter,
1008 geo,
1009 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
1010 SkPaint::kRound_Join, SkPaint::kBevel_Join,
1011 affectsStroke, true);
1012};
1013
bsalomona395f7c2016-08-24 17:47:40 -07001014static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon06077562016-05-04 13:50:29 -07001015 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1016 p->setStrokeJoin(SkPaint::kMiter_Join);
1017 p->setStrokeMiter(miter);
1018 };
bsalomon47cc7692016-04-26 12:56:00 -07001019
bsalomon06077562016-05-04 13:50:29 -07001020 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1021 p->setStrokeJoin(SkPaint::kRound_Join);
1022 p->setStrokeMiter(miter);
1023 };
bsalomon47cc7692016-04-26 12:56:00 -07001024
bsalomona395f7c2016-08-24 17:47:40 -07001025 SkPaint hairline;
1026 hairline.setStrokeWidth(0);
1027 hairline.setStyle(SkPaint::kStroke_Style);
1028 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001029 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
1030
bsalomon06077562016-05-04 13:50:29 -07001031 // The miter limit should affect stroked and dashed-stroked cases when the join type is
1032 // miter.
bsalomona395f7c2016-08-24 17:47:40 -07001033 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001034 reporter,
1035 geo,
1036 setMiterJoinAndLimit,
1037 0.5f, 0.75f,
bsalomon0ae36a22016-07-18 07:31:13 -07001038 mayHaveJoins,
bsalomon06077562016-05-04 13:50:29 -07001039 true);
bsalomon47cc7692016-04-26 12:56:00 -07001040
bsalomon06077562016-05-04 13:50:29 -07001041 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
1042 // not miter.
bsalomona395f7c2016-08-24 17:47:40 -07001043 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001044 reporter,
1045 geo,
1046 setOtherJoinAndLimit,
1047 0.5f, 0.75f,
1048 false,
1049 false);
bsalomon47cc7692016-04-26 12:56:00 -07001050}
1051
bsalomona395f7c2016-08-24 17:47:40 -07001052static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001053 // A dash with no stroke should have no effect
1054 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
1055 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
1056 SkPaint dashFill;
1057 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -07001058 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001059
bsalomonfb083272016-05-04 08:27:41 -07001060 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001061 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
1062 }
1063}
1064
bsalomona395f7c2016-08-24 17:47:40 -07001065void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001066 SkPaint fill;
1067 SkPaint stroke;
1068 stroke.setStyle(SkPaint::kStroke_Style);
1069 stroke.setStrokeWidth(1.f);
1070 SkPaint dash;
1071 dash.setStyle(SkPaint::kStroke_Style);
1072 dash.setStrokeWidth(1.f);
1073 dash.setPathEffect(make_dash());
1074 SkPaint nullDash;
1075 nullDash.setStyle(SkPaint::kStroke_Style);
1076 nullDash.setStrokeWidth(1.f);
1077 nullDash.setPathEffect(make_null_dash());
1078
bsalomonfb083272016-05-04 08:27:41 -07001079 TestCase fillCase(geo, fill, reporter);
1080 TestCase strokeCase(geo, stroke, reporter);
1081 TestCase dashCase(geo, dash, reporter);
1082 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001083
bsalomon487f8d32016-07-20 07:15:44 -07001084 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
bsalomon47cc7692016-04-26 12:56:00 -07001085 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -07001086 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
1087 // on construction in order to determine how to compare the fill and stroke.
bsalomona395f7c2016-08-24 17:47:40 -07001088 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001089 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
1090 } else {
1091 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1092 }
1093 // 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 -07001094 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001095 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
1096 } else {
1097 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
1098 }
bsalomon47cc7692016-04-26 12:56:00 -07001099}
1100
bsalomona395f7c2016-08-24 17:47:40 -07001101void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001102 /**
1103 * This path effect takes any input path and turns it into a rrect. It passes through stroke
1104 * info.
1105 */
1106 class RRectPathEffect : SkPathEffect {
1107 public:
1108 static const SkRRect& RRect() {
1109 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1110 return kRRect;
1111 }
1112
1113 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1114 const SkRect* cullR) const override {
1115 dst->reset();
1116 dst->addRRect(RRect());
1117 return true;
1118 }
1119 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1120 *dst = RRect().getBounds();
1121 }
1122 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1123 Factory getFactory() const override { return nullptr; }
1124 void toString(SkString*) const override {}
1125 private:
1126 RRectPathEffect() {}
1127 };
1128
1129 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -07001130 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001131
1132 SkPaint pe;
1133 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001134 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001135
1136 SkPaint peStroke;
1137 peStroke.setPathEffect(RRectPathEffect::Make());
1138 peStroke.setStrokeWidth(2.f);
1139 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001140 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001141
bsalomon487f8d32016-07-20 07:15:44 -07001142 // Check whether constructing the filled case would cause the base shape to have a different
1143 // geometry (because of a geometric transformation upon initial GrShape construction).
bsalomona395f7c2016-08-24 17:47:40 -07001144 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -07001145 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1146 fillGeoCase.compare(reporter, geoPEStrokeCase,
1147 TestCase::kAllDifferent_ComparisonExpecation);
1148 } else {
1149 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1150 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1151 }
bsalomon72dc51c2016-04-27 06:46:23 -07001152 geoPECase.compare(reporter, geoPEStrokeCase,
1153 TestCase::kSameUpToStroke_ComparisonExpecation);
1154
bsalomona395f7c2016-08-24 17:47:40 -07001155 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
bsalomon72dc51c2016-04-27 06:46:23 -07001156 SkPaint stroke = peStroke;
1157 stroke.setPathEffect(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -07001158 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001159
1160 SkRRect rrect;
1161 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1162 // geoPECase, so the full style should be the same as just the PE.
bsalomon70493962016-06-10 08:05:14 -07001163 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1164 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001165 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1166 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1167
bsalomon70493962016-06-10 08:05:14 -07001168 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1169 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001170 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1171 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1172
1173 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomon70493962016-06-10 08:05:14 -07001174 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1175 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001176 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1177 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1178
bsalomon70493962016-06-10 08:05:14 -07001179 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1180 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001181 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1182 rrectStrokeCase.appliedFullStyleKey());
1183}
1184
bsalomona395f7c2016-08-24 17:47:40 -07001185void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001186 /**
1187 * This path effect just adds two lineTos to the input path.
1188 */
1189 class AddLineTosPathEffect : SkPathEffect {
1190 public:
1191 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1192 const SkRect* cullR) const override {
1193 *dst = src;
bsalomon67fa4e32016-09-21 08:26:57 -07001194 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1195 for (int i = 0; i < 100; ++i) {
1196 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1197 }
bsalomon72dc51c2016-04-27 06:46:23 -07001198 return true;
1199 }
1200 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1201 *dst = src;
Mike Reed3c2d09f2017-08-28 13:32:37 -04001202 dst->growToInclude({0, 0});
1203 dst->growToInclude({100, 100});
bsalomon72dc51c2016-04-27 06:46:23 -07001204 }
1205 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1206 Factory getFactory() const override { return nullptr; }
1207 void toString(SkString*) const override {}
1208 private:
1209 AddLineTosPathEffect() {}
1210 };
1211
bsalomon9ad5d7c2016-05-04 08:44:15 -07001212 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -07001213 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1214 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1215 SkPaint peStroke;
1216 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1217 peStroke.setStrokeWidth(2.f);
1218 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001219 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001220 TestCase::SelfExpectations expectations;
1221 expectations.fPEHasEffect = true;
1222 expectations.fPEHasValidKey = false;
1223 expectations.fStrokeApplies = true;
1224 geoPEStrokeCase.testExpectations(reporter, expectations);
1225}
1226
bsalomona395f7c2016-08-24 17:47:40 -07001227void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon9ad5d7c2016-05-04 08:44:15 -07001228 /**
1229 * This path effect just changes the stroke rec to hairline.
1230 */
1231 class MakeHairlinePathEffect : SkPathEffect {
1232 public:
1233 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1234 const SkRect* cullR) const override {
1235 *dst = src;
1236 strokeRec->setHairlineStyle();
1237 return true;
1238 }
bsalomon70493962016-06-10 08:05:14 -07001239 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001240 static sk_sp<SkPathEffect> Make() {
1241 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1242 }
1243 Factory getFactory() const override { return nullptr; }
1244 void toString(SkString*) const override {}
1245 private:
1246 MakeHairlinePathEffect() {}
1247 };
1248
1249 SkPaint fill;
1250 SkPaint pe;
1251 pe.setPathEffect(MakeHairlinePathEffect::Make());
1252
1253 TestCase peCase(geo, pe, reporter);
1254
bsalomonee295642016-06-06 14:01:25 -07001255 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -07001256 peCase.baseShape().asPath(&a);
1257 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -07001258 peCase.appliedFullStyleShape().asPath(&c);
bsalomona395f7c2016-08-24 17:47:40 -07001259 if (geo.isNonPath(pe)) {
bsalomonee295642016-06-06 14:01:25 -07001260 // RRect types can have a change in start index or direction after the PE is applied. This
1261 // is because once the PE is applied, GrShape may canonicalize the dir and index since it
1262 // is not germane to the styling any longer.
1263 // Instead we just check that the paths would fill the same both before and after styling.
1264 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1265 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -07001266 } else {
bsalomona4817af2016-06-23 11:48:26 -07001267 // The base shape cannot perform canonicalization on the path's fill type because of an
1268 // unknown path effect. However, after the path effect is applied the resulting hairline
1269 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1270 // don't distinguish between even/odd and non-zero winding.
1271 a.setFillType(b.getFillType());
bsalomonee295642016-06-06 14:01:25 -07001272 REPORTER_ASSERT(reporter, a == b);
1273 REPORTER_ASSERT(reporter, a == c);
bsalomon67fa4e32016-09-21 08:26:57 -07001274 // If the resulting path is small enough then it will have a key.
1275 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1276 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomonaa840642016-09-23 12:09:16 -07001277 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1278 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001279 }
bsalomonee295642016-06-06 14:01:25 -07001280 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1281 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001282}
1283
bsalomona395f7c2016-08-24 17:47:40 -07001284void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1285 SkPath vPath = geo.path();
bsalomon4eeccc92016-04-27 13:30:25 -07001286 vPath.setIsVolatile(true);
1287
1288 SkPaint dashAndStroke;
1289 dashAndStroke.setPathEffect(make_dash());
1290 dashAndStroke.setStrokeWidth(2.f);
1291 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001292 TestCase volatileCase(reporter, vPath, dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001293 // We expect a shape made from a volatile path to have a key iff the shape is recognized
bsalomonaa840642016-09-23 12:09:16 -07001294 // as a specialized geometry.
1295 if (geo.isNonPath(dashAndStroke)) {
bsalomon4eeccc92016-04-27 13:30:25 -07001296 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1297 // In this case all the keys should be identical to the non-volatile case.
bsalomona395f7c2016-08-24 17:47:40 -07001298 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001299 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1300 } else {
1301 // None of the keys should be valid.
1302 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1303 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1304 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1305 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1306 }
1307}
1308
bsalomona395f7c2016-08-24 17:47:40 -07001309void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon409ed732016-04-27 12:36:02 -07001310 /**
Brian Salomon085c0862017-08-31 15:44:51 -04001311 * This path effect returns an empty path (possibly inverted)
bsalomon409ed732016-04-27 12:36:02 -07001312 */
1313 class EmptyPathEffect : SkPathEffect {
1314 public:
1315 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1316 const SkRect* cullR) const override {
1317 dst->reset();
Brian Salomon085c0862017-08-31 15:44:51 -04001318 if (fInvert) {
1319 dst->toggleInverseFillType();
1320 }
bsalomon409ed732016-04-27 12:36:02 -07001321 return true;
1322 }
1323 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1324 dst->setEmpty();
1325 }
Brian Salomon085c0862017-08-31 15:44:51 -04001326 static sk_sp<SkPathEffect> Make(bool invert) {
1327 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
1328 }
bsalomon409ed732016-04-27 12:36:02 -07001329 Factory getFactory() const override { return nullptr; }
1330 void toString(SkString*) const override {}
1331 private:
Brian Salomon085c0862017-08-31 15:44:51 -04001332 bool fInvert;
1333 EmptyPathEffect(bool invert) : fInvert(invert) {}
bsalomon409ed732016-04-27 12:36:02 -07001334 };
1335
1336 SkPath emptyPath;
1337 GrShape emptyShape(emptyPath);
1338 Key emptyKey;
1339 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -07001340 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001341
Brian Salomon085c0862017-08-31 15:44:51 -04001342 emptyPath.toggleInverseFillType();
1343 GrShape invertedEmptyShape(emptyPath);
1344 Key invertedEmptyKey;
1345 make_key(&invertedEmptyKey, invertedEmptyShape);
1346 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
1347
1348 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
1349
bsalomon409ed732016-04-27 12:36:02 -07001350 SkPaint pe;
Brian Salomon085c0862017-08-31 15:44:51 -04001351 pe.setPathEffect(EmptyPathEffect::Make(false));
1352 TestCase geoPECase(geo, pe, reporter);
1353 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
1354 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
1355 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
1356 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
1357 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
1358 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
1359 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001360
1361 SkPaint peStroke;
Brian Salomon085c0862017-08-31 15:44:51 -04001362 peStroke.setPathEffect(EmptyPathEffect::Make(false));
bsalomon409ed732016-04-27 12:36:02 -07001363 peStroke.setStrokeWidth(2.f);
1364 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001365 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001366 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1367 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1368 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001369 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1370 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001371 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
1372 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
1373 pe.setPathEffect(EmptyPathEffect::Make(true));
1374
1375 TestCase geoPEInvertCase(geo, pe, reporter);
1376 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
1377 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
1378 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1379 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
1380 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
1381 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
1382 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
1383
1384 peStroke.setPathEffect(EmptyPathEffect::Make(true));
1385 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
1386 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
1387 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
1388 REPORTER_ASSERT(reporter,
1389 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1390 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
1391 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
1392 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
1393 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001394}
1395
bsalomona395f7c2016-08-24 17:47:40 -07001396void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
bsalomond6723842016-06-07 12:20:15 -07001397 /**
bsalomon0ae36a22016-07-18 07:31:13 -07001398 * This path effect always fails to apply.
bsalomond6723842016-06-07 12:20:15 -07001399 */
1400 class FailurePathEffect : SkPathEffect {
1401 public:
1402 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1403 const SkRect* cullR) const override {
1404 return false;
1405 }
1406 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1407 *dst = src;
1408 }
1409 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1410 Factory getFactory() const override { return nullptr; }
1411 void toString(SkString*) const override {}
1412 private:
1413 FailurePathEffect() {}
1414 };
1415
1416 SkPaint fill;
1417 TestCase fillCase(geo, fill, reporter);
1418
1419 SkPaint pe;
1420 pe.setPathEffect(FailurePathEffect::Make());
1421 TestCase peCase(geo, pe, reporter);
1422
1423 SkPaint stroke;
1424 stroke.setStrokeWidth(2.f);
1425 stroke.setStyle(SkPaint::kStroke_Style);
1426 TestCase strokeCase(geo, stroke, reporter);
1427
1428 SkPaint peStroke = stroke;
1429 peStroke.setPathEffect(FailurePathEffect::Make());
1430 TestCase peStrokeCase(geo, peStroke, reporter);
1431
1432 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1433 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1434 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its
1435 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from
1436 // closing it but after the effect fails we can (for the fill+pe case). This causes different
1437 // routes through GrShape to have equivalent but different representations of the path (closed
1438 // or not) but that fill the same.
1439 SkPath a;
1440 SkPath b;
1441 fillCase.appliedPathEffectShape().asPath(&a);
1442 peCase.appliedPathEffectShape().asPath(&b);
1443 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1444
1445 fillCase.appliedFullStyleShape().asPath(&a);
1446 peCase.appliedFullStyleShape().asPath(&b);
1447 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1448
1449 strokeCase.appliedPathEffectShape().asPath(&a);
1450 peStrokeCase.appliedPathEffectShape().asPath(&b);
1451 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1452
1453 strokeCase.appliedFullStyleShape().asPath(&a);
1454 peStrokeCase.appliedFullStyleShape().asPath(&b);
1455 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1456}
1457
Mike Klein43344282017-08-16 11:56:22 -04001458DEF_TEST(GrShape_empty_shape, reporter) {
bsalomon409ed732016-04-27 12:36:02 -07001459 SkPath emptyPath;
Brian Salomon085c0862017-08-31 15:44:51 -04001460 SkPath invertedEmptyPath;
1461 invertedEmptyPath.toggleInverseFillType();
bsalomon409ed732016-04-27 12:36:02 -07001462 SkPaint fill;
bsalomona395f7c2016-08-24 17:47:40 -07001463 TestCase fillEmptyCase(reporter, emptyPath, fill);
bsalomon7c73a532016-05-11 15:15:56 -07001464 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1465 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1466 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001467 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
1468 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
1469 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
1470 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
1471 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
1472 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
1473 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
1474 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
1475 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
1476 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001477
1478 Key emptyKey(fillEmptyCase.baseKey());
1479 REPORTER_ASSERT(reporter, emptyKey.count());
Brian Salomon085c0862017-08-31 15:44:51 -04001480 Key inverseEmptyKey(fillInvertedEmptyCase.baseKey());
1481 REPORTER_ASSERT(reporter, inverseEmptyKey.count());
bsalomon409ed732016-04-27 12:36:02 -07001482 TestCase::SelfExpectations expectations;
1483 expectations.fStrokeApplies = false;
1484 expectations.fPEHasEffect = false;
1485 // This will test whether applying style preserves emptiness
1486 fillEmptyCase.testExpectations(reporter, expectations);
Brian Salomon085c0862017-08-31 15:44:51 -04001487 fillInvertedEmptyCase.testExpectations(reporter, expectations);
bsalomon409ed732016-04-27 12:36:02 -07001488
1489 // Stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001490 SkPaint stroke;
1491 stroke.setStrokeWidth(2.f);
1492 stroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon085c0862017-08-31 15:44:51 -04001493 TestCase strokeEmptyCase(reporter, emptyPath, stroke);
bsalomon409ed732016-04-27 12:36:02 -07001494 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001495 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
1496 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
1497 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001498
1499 // Dashing and stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001500 SkPaint dashAndStroke;
1501 dashAndStroke.setPathEffect(make_dash());
1502 dashAndStroke.setStrokeWidth(2.f);
1503 dashAndStroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon085c0862017-08-31 15:44:51 -04001504 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
bsalomon409ed732016-04-27 12:36:02 -07001505 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1506 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001507 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
1508 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1509 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
1510 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001511
1512 // A shape made from an empty rrect should behave the same as an empty path.
1513 SkRRect emptyRRect = SkRRect::MakeRect(SkRect::MakeEmpty());
1514 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
bsalomona395f7c2016-08-24 17:47:40 -07001515 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001516 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1517 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001518 static constexpr SkPath::Direction kDir = SkPath::kCCW_Direction;
1519 static constexpr int kStart = 0;
1520 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
1521 GrStyle(dashAndStroke));
1522 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1523 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
1524 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001525
1526 // Same for a rect.
1527 SkRect emptyRect = SkRect::MakeEmpty();
bsalomona395f7c2016-08-24 17:47:40 -07001528 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001529 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1530 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001531 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
1532 kStart, true, GrStyle(dashAndStroke));
1533 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1534 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
1535 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001536}
1537
bsalomon70493962016-06-10 08:05:14 -07001538// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1539// canonical point in these cases.
1540unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1541 switch (rrect.getType()) {
1542 case SkRRect::kRect_Type:
1543 return (s + 1) & 0b110;
1544 case SkRRect::kOval_Type:
1545 return s & 0b110;
1546 default:
1547 return s;
1548 }
1549}
1550
1551void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
bsalomoncadb5a22016-06-10 18:28:06 -07001552 enum Style {
bsalomon70493962016-06-10 08:05:14 -07001553 kFill,
1554 kStroke,
1555 kHairline,
1556 kStrokeAndFill
1557 };
1558
1559 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1560 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1561 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1562 strokeRecs[kFill].setFillStyle();
1563 strokeRecs[kStroke].setStrokeStyle(2.f);
1564 strokeRecs[kHairline].setHairlineStyle();
1565 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
bsalomon487f8d32016-07-20 07:15:44 -07001566 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1567 // applyStyle() is called.
1568 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
bsalomon70493962016-06-10 08:05:14 -07001569 sk_sp<SkPathEffect> dashEffect = make_dash();
1570
bsalomoncadb5a22016-06-10 18:28:06 -07001571 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1572
1573 auto index = [](bool inverted,
1574 SkPath::Direction dir,
1575 unsigned start,
1576 Style style,
1577 bool dash) -> int {
1578 return inverted * (2 * 8 * kStyleCnt * 2) +
1579 dir * ( 8 * kStyleCnt * 2) +
1580 start * ( kStyleCnt * 2) +
1581 style * ( 2) +
1582 dash;
1583 };
1584 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1);
1585 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1586 SkAutoTArray<GrShape> shapes(cnt);
1587 for (bool inverted : {false, true}) {
1588 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1589 for (unsigned start = 0; start < 8; ++start) {
1590 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1591 for (bool dash : {false, true}) {
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001592 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
bsalomoncadb5a22016-06-10 18:28:06 -07001593 shapes[index(inverted, dir, start, style, dash)] =
1594 GrShape(rrect, dir, start, SkToBool(inverted),
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001595 GrStyle(strokeRecs[style], std::move(pe)));
bsalomon70493962016-06-10 08:05:14 -07001596 }
1597 }
1598 }
1599 }
1600 }
1601
bsalomonfd32df72016-06-14 14:37:21 -07001602 // Get the keys for some example shape instances that we'll use for comparision against the
1603 // rest.
1604 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction;
1605 static constexpr unsigned kExamplesStart = 0;
1606 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1607 false)];
bsalomon70493962016-06-10 08:05:14 -07001608 Key exampleFillCaseKey;
1609 make_key(&exampleFillCaseKey, exampleFillCase);
1610
bsalomonfd32df72016-06-14 14:37:21 -07001611 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart,
1612 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001613 Key exampleStrokeAndFillCaseKey;
1614 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1615
bsalomonfd32df72016-06-14 14:37:21 -07001616 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill,
1617 false)];
bsalomon70493962016-06-10 08:05:14 -07001618 Key exampleInvFillCaseKey;
1619 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1620
bsalomonfd32df72016-06-14 14:37:21 -07001621 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart,
1622 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001623 Key exampleInvStrokeAndFillCaseKey;
1624 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1625
bsalomonfd32df72016-06-14 14:37:21 -07001626 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke,
1627 false)];
bsalomon70493962016-06-10 08:05:14 -07001628 Key exampleStrokeCaseKey;
1629 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1630
bsalomonfd32df72016-06-14 14:37:21 -07001631 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke,
1632 false)];
1633 Key exampleInvStrokeCaseKey;
1634 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1635
1636 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1637 kHairline, false)];
bsalomon70493962016-06-10 08:05:14 -07001638 Key exampleHairlineCaseKey;
1639 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1640
bsalomonfd32df72016-06-14 14:37:21 -07001641 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1642 kHairline, false)];
1643 Key exampleInvHairlineCaseKey;
1644 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1645
bsalomon70493962016-06-10 08:05:14 -07001646 // These are dummy initializations to suppress warnings.
bsalomoncadb5a22016-06-10 18:28:06 -07001647 SkRRect queryRR = SkRRect::MakeEmpty();
1648 SkPath::Direction queryDir = SkPath::kCW_Direction;
1649 unsigned queryStart = ~0U;
1650 bool queryInverted = true;
bsalomon70493962016-06-10 08:05:14 -07001651
bsalomoncadb5a22016-06-10 18:28:06 -07001652 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1653 REPORTER_ASSERT(r, queryRR == rrect);
1654 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1655 REPORTER_ASSERT(r, 0 == queryStart);
1656 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001657
bsalomoncadb5a22016-06-10 18:28:06 -07001658 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1659 &queryInverted));
1660 REPORTER_ASSERT(r, queryRR == rrect);
1661 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1662 REPORTER_ASSERT(r, 0 == queryStart);
1663 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001664
bsalomoncadb5a22016-06-10 18:28:06 -07001665 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1666 &queryInverted));
1667 REPORTER_ASSERT(r, queryRR == rrect);
1668 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1669 REPORTER_ASSERT(r, 0 == queryStart);
1670 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001671
bsalomoncadb5a22016-06-10 18:28:06 -07001672 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1673 &queryInverted));
1674 REPORTER_ASSERT(r, queryRR == rrect);
1675 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1676 REPORTER_ASSERT(r, 0 == queryStart);
1677 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001678
bsalomoncadb5a22016-06-10 18:28:06 -07001679 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1680 &queryInverted));
1681 REPORTER_ASSERT(r, queryRR == rrect);
1682 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1683 REPORTER_ASSERT(r, 0 == queryStart);
1684 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001685
bsalomonfd32df72016-06-14 14:37:21 -07001686 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1687 &queryInverted));
1688 REPORTER_ASSERT(r, queryRR == rrect);
1689 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1690 REPORTER_ASSERT(r, 0 == queryStart);
1691 REPORTER_ASSERT(r, queryInverted);
1692
bsalomoncadb5a22016-06-10 18:28:06 -07001693 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1694 REPORTER_ASSERT(r, queryRR == rrect);
1695 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1696 REPORTER_ASSERT(r, 0 == queryStart);
1697 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001698
bsalomonfd32df72016-06-14 14:37:21 -07001699 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1700 &queryInverted));
1701 REPORTER_ASSERT(r, queryRR == rrect);
1702 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1703 REPORTER_ASSERT(r, 0 == queryStart);
1704 REPORTER_ASSERT(r, queryInverted);
1705
bsalomon70493962016-06-10 08:05:14 -07001706 // Remember that the key reflects the geometry before styling is applied.
1707 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1708 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1709 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1710 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001711 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001712 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001713 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001714 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001715 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1716 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001717
bsalomoncadb5a22016-06-10 18:28:06 -07001718 for (bool inverted : {false, true}) {
1719 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1720 for (unsigned start = 0; start < 8; ++start) {
1721 for (bool dash : {false, true}) {
1722 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001723 Key fillCaseKey;
1724 make_key(&fillCaseKey, fillCase);
1725
bsalomoncadb5a22016-06-10 18:28:06 -07001726 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1727 kStrokeAndFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001728 Key strokeAndFillCaseKey;
1729 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1730
1731 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1732 // ignore dashing.
1733 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1734 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1735 TestCase a(fillCase, r);
1736 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1737 TestCase c(strokeAndFillCase, r);
1738 TestCase d(inverted ? exampleInvStrokeAndFillCase
1739 : exampleStrokeAndFillCase, r);
1740 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1741 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1742
bsalomoncadb5a22016-06-10 18:28:06 -07001743 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)];
1744 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline,
1745 dash)];
bsalomon70493962016-06-10 08:05:14 -07001746
1747 TestCase e(strokeCase, r);
bsalomon70493962016-06-10 08:05:14 -07001748 TestCase g(hairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001749
bsalomonfd32df72016-06-14 14:37:21 -07001750 // Both hairline and stroke shapes must respect the dashing.
bsalomon70493962016-06-10 08:05:14 -07001751 if (dash) {
bsalomonfd32df72016-06-14 14:37:21 -07001752 // Dashing always ignores the inverseness. skbug.com/5421
1753 TestCase f(exampleStrokeCase, r);
1754 TestCase h(exampleHairlineCase, r);
bsalomoncadb5a22016-06-10 18:28:06 -07001755 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
bsalomon70493962016-06-10 08:05:14 -07001756 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1757 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1758
bsalomoncadb5a22016-06-10 18:28:06 -07001759 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1760 &queryInverted));
1761 REPORTER_ASSERT(r, queryRR == rrect);
1762 REPORTER_ASSERT(r, queryDir == dir);
1763 REPORTER_ASSERT(r, queryStart == expectedStart);
1764 REPORTER_ASSERT(r, !queryInverted);
1765 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1766 &queryInverted));
1767 REPORTER_ASSERT(r, queryRR == rrect);
1768 REPORTER_ASSERT(r, queryDir == dir);
1769 REPORTER_ASSERT(r, queryStart == expectedStart);
1770 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001771
1772 // The pre-style case for the dash will match the non-dash example iff the
1773 // dir and start match (dir=cw, start=0).
bsalomoncadb5a22016-06-10 18:28:06 -07001774 if (0 == expectedStart && SkPath::kCW_Direction == dir) {
bsalomon70493962016-06-10 08:05:14 -07001775 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1776 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1777 } else {
1778 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1779 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1780 }
1781 } else {
bsalomonfd32df72016-06-14 14:37:21 -07001782 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1783 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001784 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1785 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1786 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1787 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1788 }
1789 }
1790 }
1791 }
1792 }
1793}
1794
Mike Klein43344282017-08-16 11:56:22 -04001795DEF_TEST(GrShape_lines, r) {
bsalomon0a0f67e2016-06-28 11:56:42 -07001796 static constexpr SkPoint kA { 1, 1};
1797 static constexpr SkPoint kB { 5, -9};
1798 static constexpr SkPoint kC {-3, 17};
1799
1800 SkPath lineAB;
1801 lineAB.moveTo(kA);
1802 lineAB.lineTo(kB);
1803
1804 SkPath lineBA;
1805 lineBA.moveTo(kB);
1806 lineBA.lineTo(kA);
1807
1808 SkPath lineAC;
1809 lineAC.moveTo(kB);
1810 lineAC.lineTo(kC);
1811
1812 SkPath invLineAB = lineAB;
1813 invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType);
1814
1815 SkPaint fill;
1816 SkPaint stroke;
1817 stroke.setStyle(SkPaint::kStroke_Style);
1818 stroke.setStrokeWidth(2.f);
1819 SkPaint hairline;
1820 hairline.setStyle(SkPaint::kStroke_Style);
1821 hairline.setStrokeWidth(0.f);
1822 SkPaint dash = stroke;
1823 dash.setPathEffect(make_dash());
1824
bsalomona395f7c2016-08-24 17:47:40 -07001825 TestCase fillAB(r, lineAB, fill);
1826 TestCase fillEmpty(r, SkPath(), fill);
bsalomon0a0f67e2016-06-28 11:56:42 -07001827 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1828 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1829
Brian Salomon085c0862017-08-31 15:44:51 -04001830 SkPath path;
1831 path.toggleInverseFillType();
1832 TestCase fillEmptyInverted(r, path, fill);
1833 TestCase fillABInverted(r, invLineAB, fill);
1834 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
1835 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
1836
bsalomona395f7c2016-08-24 17:47:40 -07001837 TestCase strokeAB(r, lineAB, stroke);
1838 TestCase strokeBA(r, lineBA, stroke);
1839 TestCase strokeAC(r, lineAC, stroke);
bsalomon0a0f67e2016-06-28 11:56:42 -07001840
bsalomona395f7c2016-08-24 17:47:40 -07001841 TestCase hairlineAB(r, lineAB, hairline);
1842 TestCase hairlineBA(r, lineBA, hairline);
1843 TestCase hairlineAC(r, lineAC, hairline);
bsalomon0a0f67e2016-06-28 11:56:42 -07001844
bsalomona395f7c2016-08-24 17:47:40 -07001845 TestCase dashAB(r, lineAB, dash);
1846 TestCase dashBA(r, lineBA, dash);
1847 TestCase dashAC(r, lineAC, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001848
1849 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1850
1851 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1852 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1853
1854 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1855 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1856
1857 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1858 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1859
1860 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1861
1862 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1863 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1864 bool canonicalizeAsAB;
1865 SkPoint canonicalPts[2] {kA, kB};
1866 // Init these to suppress warnings.
1867 bool inverted = true;
1868 SkPoint pts[2] {{0, 0}, {0, 0}};
1869 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1870 if (pts[0] == kA && pts[1] == kB) {
1871 canonicalizeAsAB = true;
1872 } else if (pts[1] == kA && pts[0] == kB) {
1873 canonicalizeAsAB = false;
1874 SkTSwap(canonicalPts[0], canonicalPts[1]);
1875 } else {
1876 ERRORF(r, "Should return pts (a,b) or (b, a)");
1877 return;
1878 };
1879
1880 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1881 TestCase::kSameUpToPE_ComparisonExpecation);
1882 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1883 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1884 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1885 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1886 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1887 pts[0] == kA && pts[1] == kB);
1888 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1889 pts[0] == kB && pts[1] == kA);
1890
1891
bsalomona395f7c2016-08-24 17:47:40 -07001892 TestCase strokeInvAB(r, invLineAB, stroke);
1893 TestCase hairlineInvAB(r, invLineAB, hairline);
1894 TestCase dashInvAB(r, invLineAB, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001895 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1896 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1897 // Dashing ignores inverse.
1898 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1899
1900 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1901 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1902 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1903 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1904 // Dashing ignores inverse.
1905 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1906 pts[0] == kA && pts[1] == kB);
1907
1908}
1909
Mike Klein43344282017-08-16 11:56:22 -04001910DEF_TEST(GrShape_stroked_lines, r) {
bsalomon0ae36a22016-07-18 07:31:13 -07001911 // Paints to try
1912 SkPaint buttCap;
1913 buttCap.setStyle(SkPaint::kStroke_Style);
1914 buttCap.setStrokeWidth(4);
1915 buttCap.setStrokeCap(SkPaint::kButt_Cap);
1916
1917 SkPaint squareCap = buttCap;
1918 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
1919
1920 SkPaint roundCap = buttCap;
1921 roundCap.setStrokeCap(SkPaint::kRound_Cap);
1922
1923 // vertical
1924 SkPath linePath;
1925 linePath.moveTo(4, 4);
1926 linePath.lineTo(4, 5);
1927
1928 SkPaint fill;
1929
Mike Klein43344282017-08-16 11:56:22 -04001930 make_TestCase(r, linePath, buttCap)->compare(
1931 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
1932 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001933
Mike Klein43344282017-08-16 11:56:22 -04001934 make_TestCase(r, linePath, squareCap)->compare(
1935 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
1936 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001937
Mike Klein43344282017-08-16 11:56:22 -04001938 make_TestCase(r, linePath, roundCap)->compare(r,
bsalomona395f7c2016-08-24 17:47:40 -07001939 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
bsalomon0ae36a22016-07-18 07:31:13 -07001940 TestCase::kAllSame_ComparisonExpecation);
1941
1942 // horizontal
1943 linePath.reset();
1944 linePath.moveTo(4, 4);
1945 linePath.lineTo(5, 4);
1946
Mike Klein43344282017-08-16 11:56:22 -04001947 make_TestCase(r, linePath, buttCap)->compare(
1948 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
1949 TestCase::kAllSame_ComparisonExpecation);
1950 make_TestCase(r, linePath, squareCap)->compare(
1951 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
1952 TestCase::kAllSame_ComparisonExpecation);
1953 make_TestCase(r, linePath, roundCap)->compare(
1954 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
1955 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001956
1957 // point
1958 linePath.reset();
1959 linePath.moveTo(4, 4);
1960 linePath.lineTo(4, 4);
1961
Mike Klein43344282017-08-16 11:56:22 -04001962 make_TestCase(r, linePath, buttCap)->compare(
1963 r, TestCase(r, SkRect::MakeEmpty(), fill),
1964 TestCase::kAllSame_ComparisonExpecation);
1965 make_TestCase(r, linePath, squareCap)->compare(
1966 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
1967 TestCase::kAllSame_ComparisonExpecation);
1968 make_TestCase(r, linePath, roundCap)->compare(
1969 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
1970 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001971}
1972
Mike Klein43344282017-08-16 11:56:22 -04001973DEF_TEST(GrShape_short_path_keys, r) {
bsalomon67fa4e32016-09-21 08:26:57 -07001974 SkPaint paints[4];
1975 paints[1].setStyle(SkPaint::kStroke_Style);
1976 paints[1].setStrokeWidth(5.f);
1977 paints[2].setStyle(SkPaint::kStroke_Style);
1978 paints[2].setStrokeWidth(0.f);
1979 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
1980 paints[3].setStrokeWidth(5.f);
1981
bsalomonaa840642016-09-23 12:09:16 -07001982 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
bsalomon67fa4e32016-09-21 08:26:57 -07001983 TestCase::ComparisonExpecation expectation) {
bsalomonaa840642016-09-23 12:09:16 -07001984 SkPath volatileA = pathA;
1985 SkPath volatileB = pathB;
1986 volatileA.setIsVolatile(true);
1987 volatileB.setIsVolatile(true);
bsalomon67fa4e32016-09-21 08:26:57 -07001988 for (const SkPaint& paint : paints) {
bsalomonaa840642016-09-23 12:09:16 -07001989 REPORTER_ASSERT(r, !GrShape(volatileA, paint).hasUnstyledKey());
1990 REPORTER_ASSERT(r, !GrShape(volatileB, paint).hasUnstyledKey());
bsalomon67fa4e32016-09-21 08:26:57 -07001991 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
bsalomonaa840642016-09-23 12:09:16 -07001992 TestCase caseA(PathGeo(pathA, invert), paint, r);
1993 TestCase caseB(PathGeo(pathB, invert), paint, r);
1994 caseA.compare(r, caseB, expectation);
bsalomon67fa4e32016-09-21 08:26:57 -07001995 }
1996 }
1997 };
1998
1999 SkPath pathA;
2000 SkPath pathB;
2001
2002 // Two identical paths
2003 pathA.lineTo(10.f, 10.f);
2004 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2005
2006 pathB.lineTo(10.f, 10.f);
2007 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002008 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002009
2010 // Give path b a different point
2011 pathB.reset();
2012 pathB.lineTo(10.f, 10.f);
2013 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002014 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002015
2016 // Give path b a different conic weight
2017 pathB.reset();
2018 pathB.lineTo(10.f, 10.f);
2019 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
bsalomonaa840642016-09-23 12:09:16 -07002020 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002021
2022 // Give path b an extra lineTo verb
2023 pathB.reset();
2024 pathB.lineTo(10.f, 10.f);
2025 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2026 pathB.lineTo(50.f, 50.f);
bsalomonaa840642016-09-23 12:09:16 -07002027 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002028
2029 // Give path b a close
2030 pathB.reset();
2031 pathB.lineTo(10.f, 10.f);
2032 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2033 pathB.close();
bsalomonaa840642016-09-23 12:09:16 -07002034 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002035}
2036
bsalomon47cc7692016-04-26 12:56:00 -07002037DEF_TEST(GrShape, reporter) {
bsalomona395f7c2016-08-24 17:47:40 -07002038 SkTArray<std::unique_ptr<Geo>> geos;
2039 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
2040
bsalomonee295642016-06-06 14:01:25 -07002041 for (auto r : { SkRect::MakeWH(10, 20),
2042 SkRect::MakeWH(-10, -20),
2043 SkRect::MakeWH(-10, 20),
2044 SkRect::MakeWH(10, -20)}) {
bsalomona395f7c2016-08-24 17:47:40 -07002045 geos.emplace_back(new RectGeo(r));
2046 SkPath rectPath;
2047 rectPath.addRect(r);
2048 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2049 PathGeo::Invert::kNo));
2050 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2051 PathGeo::Invert::kYes));
2052 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2053 PathGeo::Invert::kNo));
bsalomonee295642016-06-06 14:01:25 -07002054 }
bsalomon47cc7692016-04-26 12:56:00 -07002055 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -07002056 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
2057 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomona395f7c2016-08-24 17:47:40 -07002058 geos.emplace_back(new RRectGeo(rr));
bsalomon70493962016-06-10 08:05:14 -07002059 test_rrect(reporter, rr);
bsalomona395f7c2016-08-24 17:47:40 -07002060 SkPath rectPath;
2061 rectPath.addRRect(rr);
2062 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2063 PathGeo::Invert::kNo));
2064 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2065 PathGeo::Invert::kYes));
2066 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
2067 RRectPathGeo::RRectForStroke::kYes,
2068 PathGeo::Invert::kNo));
bsalomon72dc51c2016-04-27 06:46:23 -07002069 }
2070
Mike Klein43344282017-08-16 11:56:22 -04002071 {
2072 SkPath openRectPath;
2073 openRectPath.moveTo(0, 0);
2074 openRectPath.lineTo(10, 0);
2075 openRectPath.lineTo(10, 10);
2076 openRectPath.lineTo(0, 10);
2077 geos.emplace_back(new RRectPathGeo(
2078 openRectPath, SkRect::MakeWH(10, 10),
2079 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2080 geos.emplace_back(new RRectPathGeo(
2081 openRectPath, SkRect::MakeWH(10, 10),
2082 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
2083 rrectPathGeos.emplace_back(new RRectPathGeo(
2084 openRectPath, SkRect::MakeWH(10, 10),
2085 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2086 }
bsalomon72dc51c2016-04-27 06:46:23 -07002087
Mike Klein43344282017-08-16 11:56:22 -04002088 {
2089 SkPath quadPath;
2090 quadPath.quadTo(10, 10, 5, 8);
2091 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
2092 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
2093 }
bsalomon398e3f42016-06-13 10:22:48 -07002094
Mike Klein43344282017-08-16 11:56:22 -04002095 {
2096 SkPath linePath;
2097 linePath.lineTo(10, 10);
2098 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
2099 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
2100 }
bsalomon72dc51c2016-04-27 06:46:23 -07002101
bsalomon0ae36a22016-07-18 07:31:13 -07002102 // Horizontal and vertical paths become rrects when stroked.
Mike Klein43344282017-08-16 11:56:22 -04002103 {
2104 SkPath vLinePath;
2105 vLinePath.lineTo(0, 10);
2106 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
2107 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
2108 }
bsalomon0ae36a22016-07-18 07:31:13 -07002109
Mike Klein43344282017-08-16 11:56:22 -04002110 {
2111 SkPath hLinePath;
2112 hLinePath.lineTo(10, 0);
2113 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
2114 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
2115 }
bsalomon0ae36a22016-07-18 07:31:13 -07002116
bsalomona395f7c2016-08-24 17:47:40 -07002117 for (int i = 0; i < geos.count(); ++i) {
2118 test_basic(reporter, *geos[i]);
2119 test_scale(reporter, *geos[i]);
2120 test_dash_fill(reporter, *geos[i]);
2121 test_null_dash(reporter, *geos[i]);
2122 // Test modifying various stroke params.
2123 test_stroke_param<SkScalar>(
2124 reporter, *geos[i],
bsalomon70493962016-06-10 08:05:14 -07002125 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
2126 SkIntToScalar(2), SkIntToScalar(4));
bsalomona395f7c2016-08-24 17:47:40 -07002127 test_stroke_join(reporter, *geos[i]);
2128 test_stroke_cap(reporter, *geos[i]);
2129 test_miter_limit(reporter, *geos[i]);
2130 test_path_effect_makes_rrect(reporter, *geos[i]);
2131 test_unknown_path_effect(reporter, *geos[i]);
2132 test_path_effect_makes_empty_shape(reporter, *geos[i]);
2133 test_path_effect_fails(reporter, *geos[i]);
2134 test_make_hairline_path_effect(reporter, *geos[i]);
2135 test_volatile_path(reporter, *geos[i]);
bsalomon70493962016-06-10 08:05:14 -07002136 }
bsalomonfd32df72016-06-14 14:37:21 -07002137
bsalomona395f7c2016-08-24 17:47:40 -07002138 for (int i = 0; i < rrectPathGeos.count(); ++i) {
2139 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
bsalomon72dc51c2016-04-27 06:46:23 -07002140 SkPaint fillPaint;
bsalomona395f7c2016-08-24 17:47:40 -07002141 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002142 SkRRect rrect;
bsalomona395f7c2016-08-24 17:47:40 -07002143 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
bsalomon70493962016-06-10 08:05:14 -07002144 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2145 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002146 if (rrgeo.isNonPath(fillPaint)) {
2147 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
2148 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2149 TestCase fillRRectCase(reporter, rrect, fillPaint);
bsalomon70493962016-06-10 08:05:14 -07002150 fillPathCase2.compare(reporter, fillRRectCase,
2151 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002152 }
bsalomon72dc51c2016-04-27 06:46:23 -07002153 SkPaint strokePaint;
2154 strokePaint.setStrokeWidth(3.f);
2155 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07002156 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
2157 if (rrgeo.isNonPath(strokePaint)) {
bsalomon0ae36a22016-07-18 07:31:13 -07002158 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2159 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002160 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2161 TestCase strokeRRectCase(reporter, rrect, strokePaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002162 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07002163 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002164 }
bsalomon47cc7692016-04-26 12:56:00 -07002165 }
bsalomon409ed732016-04-27 12:36:02 -07002166
bsalomon4eeccc92016-04-27 13:30:25 -07002167 // Test a volatile empty path.
bsalomona395f7c2016-08-24 17:47:40 -07002168 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
bsalomon47cc7692016-04-26 12:56:00 -07002169}
2170
2171#endif