1 /*
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 "include/core/SkArc.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkClipOp.h"
11 #include "include/core/SkColor.h"
12 #include "include/core/SkImageInfo.h"
13 #include "include/core/SkMatrix.h"
14 #include "include/core/SkPaint.h"
15 #include "include/core/SkPath.h"
16 #include "include/core/SkPathEffect.h"
17 #include "include/core/SkPathTypes.h"
18 #include "include/core/SkPixmap.h"
19 #include "include/core/SkPoint.h"
20 #include "include/core/SkRRect.h"
21 #include "include/core/SkRect.h"
22 #include "include/core/SkRefCnt.h"
23 #include "include/core/SkScalar.h"
24 #include "include/core/SkStrokeRec.h"
25 #include "include/core/SkSurface.h"
26 #include "include/core/SkTypes.h"
27 #include "include/effects/SkDashPathEffect.h"
28 #include "include/pathops/SkPathOps.h"
29 #include "include/private/base/SkTArray.h"
30 #include "include/private/base/SkTemplates.h"
31 #include "include/private/base/SkTo.h"
32 #include "src/core/SkPathEffectBase.h"
33 #include "src/core/SkPathPriv.h"
34 #include "src/core/SkRectPriv.h"
35 #include "src/gpu/ganesh/GrStyle.h"
36 #include "src/gpu/ganesh/geometry/GrShape.h"
37 #include "src/gpu/ganesh/geometry/GrStyledShape.h"
38 #include "tests/Test.h"
39
40 #include <cstdint>
41 #include <cstring>
42 #include <functional>
43 #include <initializer_list>
44 #include <memory>
45 #include <string>
46 #include <utility>
47
48 using namespace skia_private;
49
testingOnly_getOriginalGenerationID() const50 uint32_t GrStyledShape::testingOnly_getOriginalGenerationID() const {
51 if (const auto* lp = this->originalPathForListeners()) {
52 return lp->getGenerationID();
53 }
54 return SkPath().getGenerationID();
55 }
56
testingOnly_isPath() const57 bool GrStyledShape::testingOnly_isPath() const {
58 return fShape.isPath();
59 }
60
testingOnly_isNonVolatilePath() const61 bool GrStyledShape::testingOnly_isNonVolatilePath() const {
62 return fShape.isPath() && !fShape.path().isVolatile();
63 }
64
65 using Key = TArray<uint32_t>;
66
make_key(Key * key,const GrStyledShape & shape)67 static bool make_key(Key* key, const GrStyledShape& shape) {
68 int size = shape.unstyledKeySize();
69 if (size <= 0) {
70 key->reset(0);
71 return false;
72 }
73 SkASSERT(size);
74 key->reset(size);
75 shape.writeUnstyledKey(key->begin());
76 return true;
77 }
78
paths_fill_same(const SkPath & a,const SkPath & b)79 static bool paths_fill_same(const SkPath& a, const SkPath& b) {
80 SkPath pathXor;
81 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
82 return pathXor.isEmpty();
83 }
84
test_bounds_by_rasterizing(const SkPath & path,const SkRect & bounds)85 static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
86 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
87 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
88 // rendering within the bounds (with a tolerance). Then we render the path and check that
89 // everything got clipped out.
90 static constexpr int kRes = 2000;
91 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
92 static constexpr int kTol = 2;
93 static_assert(kRes % 4 == 0);
94 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
95 sk_sp<SkSurface> surface = SkSurfaces::Raster(info);
96 surface->getCanvas()->clear(0x0);
97 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
98 SkMatrix matrix = SkMatrix::RectToRect(bounds, clip);
99 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
100 surface->getCanvas()->clipRect(clip, SkClipOp::kDifference);
101 surface->getCanvas()->concat(matrix);
102 SkPaint whitePaint;
103 whitePaint.setColor(SK_ColorWHITE);
104 surface->getCanvas()->drawPath(path, whitePaint);
105 SkPixmap pixmap;
106 surface->getCanvas()->peekPixels(&pixmap);
107 #if defined(SK_BUILD_FOR_WIN)
108 // The static constexpr version in #else causes cl.exe to crash.
109 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
110 #else
111 static constexpr uint8_t kZeros[kRes] = {0};
112 #endif
113 for (int y = 0; y < kRes; ++y) {
114 const uint8_t* row = pixmap.addr8(0, y);
115 if (0 != memcmp(kZeros, row, kRes)) {
116 return false;
117 }
118 }
119 #ifdef SK_BUILD_FOR_WIN
120 free(const_cast<uint8_t*>(kZeros));
121 #endif
122 return true;
123 }
124
can_interchange_winding_and_even_odd_fill(const GrStyledShape & shape)125 static bool can_interchange_winding_and_even_odd_fill(const GrStyledShape& shape) {
126 SkPath path;
127 shape.asPath(&path);
128 if (shape.style().hasNonDashPathEffect()) {
129 return false;
130 }
131 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
132 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
133 strokeRecStyle == SkStrokeRec::kHairline_Style ||
134 (shape.style().isSimpleFill() && path.isConvex());
135 }
136
check_equivalence(skiatest::Reporter * r,const GrStyledShape & a,const GrStyledShape & b,const Key & keyA,const Key & keyB)137 static void check_equivalence(skiatest::Reporter* r, const GrStyledShape& a, const GrStyledShape& b,
138 const Key& keyA, const Key& keyB) {
139 // GrStyledShape only respects the input winding direction and start point for rrect shapes
140 // when there is a path effect. Thus, if there are two GrStyledShapes representing the same
141 // rrect but one has a path effect in its style and the other doesn't then asPath() and the
142 // unstyled key will differ. GrStyledShape will have canonicalized the direction and start point
143 // for the shape without the path effect. If *both* have path effects then they should have both
144 // preserved the direction and starting point.
145
146 // The asRRect() output params are all initialized just to silence compiler warnings about
147 // uninitialized variables.
148 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
149 bool invertedA = true, invertedB = true;
150
151 bool aIsRRect = a.asRRect(&rrectA, &invertedA);
152 bool bIsRRect = b.asRRect(&rrectB, &invertedB);
153 bool aHasPE = a.style().hasPathEffect();
154 bool bHasPE = b.style().hasPathEffect();
155 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
156 // GrStyledShape will close paths with simple fill style.
157 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
158 SkPath pathA, pathB;
159 a.asPath(&pathA);
160 b.asPath(&pathB);
161
162 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
163 // non-inverse fill type (or vice versa).
164 bool ignoreInversenessDifference = false;
165 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
166 const GrStyledShape* s1 = pathA.isInverseFillType() ? &a : &b;
167 const GrStyledShape* s2 = pathA.isInverseFillType() ? &b : &a;
168 bool canDropInverse1 = s1->style().isDashed();
169 bool canDropInverse2 = s2->style().isDashed();
170 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
171 }
172 bool ignoreWindingVsEvenOdd = false;
173 if (SkPathFillType_ConvertToNonInverse(pathA.getFillType()) !=
174 SkPathFillType_ConvertToNonInverse(pathB.getFillType())) {
175 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
176 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
177 if (aCanChange != bCanChange) {
178 ignoreWindingVsEvenOdd = true;
179 }
180 }
181 if (allowSameRRectButDiffStartAndDir) {
182 REPORTER_ASSERT(r, rrectA == rrectB);
183 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
184 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
185 } else {
186 SkPath pA = pathA;
187 SkPath pB = pathB;
188 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
189 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
190 if (ignoreInversenessDifference) {
191 pA.setFillType(SkPathFillType_ConvertToNonInverse(pathA.getFillType()));
192 pB.setFillType(SkPathFillType_ConvertToNonInverse(pathB.getFillType()));
193 }
194 if (ignoreWindingVsEvenOdd) {
195 pA.setFillType(pA.isInverseFillType() ? SkPathFillType::kInverseEvenOdd
196 : SkPathFillType::kEvenOdd);
197 pB.setFillType(pB.isInverseFillType() ? SkPathFillType::kInverseEvenOdd
198 : SkPathFillType::kEvenOdd);
199 }
200 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
201 REPORTER_ASSERT(r, keyA == keyB);
202 } else {
203 REPORTER_ASSERT(r, keyA != keyB);
204 }
205 if (allowedClosednessDiff) {
206 // GrStyledShape will close paths with simple fill style. Make the non-filled path
207 // closed so that the comparision will succeed. Make sure both are closed before
208 // comparing.
209 pA.close();
210 pB.close();
211 }
212 REPORTER_ASSERT(r, pA == pB);
213 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
214 if (aIsRRect) {
215 REPORTER_ASSERT(r, rrectA == rrectB);
216 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
217 }
218 }
219 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
220 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
221 // closedness can affect convexity.
222 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
223 if (a.knownToBeConvex()) {
224 REPORTER_ASSERT(r, pathA.isConvex());
225 }
226 if (b.knownToBeConvex()) {
227 REPORTER_ASSERT(r, pathB.isConvex());
228 }
229 REPORTER_ASSERT(r, a.bounds() == b.bounds());
230 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
231 // Init these to suppress warnings.
232 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
233 bool invertedLine[2] {true, true};
234 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
235 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
236 // doesn't (since the PE can set any fill type on its output path).
237 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
238 // then they may disagree about inverseness.
239 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
240 a.style().isDashed() == b.style().isDashed()) {
241 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
242 b.mayBeInverseFilledAfterStyling());
243 }
244 if (a.asLine(nullptr, nullptr)) {
245 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
246 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
247 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
248 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
249 }
250 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
251 }
252
check_original_path_ids(skiatest::Reporter * r,const GrStyledShape & base,const GrStyledShape & pe,const GrStyledShape & peStroke,const GrStyledShape & full)253 static void check_original_path_ids(skiatest::Reporter* r, const GrStyledShape& base,
254 const GrStyledShape& pe, const GrStyledShape& peStroke,
255 const GrStyledShape& full) {
256 bool baseIsNonVolatilePath = base.testingOnly_isNonVolatilePath();
257 bool peIsPath = pe.testingOnly_isPath();
258 bool peStrokeIsPath = peStroke.testingOnly_isPath();
259 bool fullIsPath = full.testingOnly_isPath();
260
261 REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath);
262
263 uint32_t baseID = base.testingOnly_getOriginalGenerationID();
264 uint32_t peID = pe.testingOnly_getOriginalGenerationID();
265 uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID();
266 uint32_t fullID = full.testingOnly_getOriginalGenerationID();
267
268 // All empty paths have the same gen ID
269 uint32_t emptyID = SkPath().getGenerationID();
270
271 // If we started with a real path, then our genID should match that path's gen ID (and not be
272 // empty). If we started with a simple shape or a volatile path, our original path should have
273 // been reset.
274 REPORTER_ASSERT(r, baseIsNonVolatilePath == (baseID != emptyID));
275
276 // For the derived shapes, if they're simple types, their original paths should have been reset
277 REPORTER_ASSERT(r, peIsPath || (peID == emptyID));
278 REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID));
279 REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID));
280
281 if (!peIsPath) {
282 // If the path effect produces a simple shape, then there are no unbroken chains to test
283 return;
284 }
285
286 // From here on, we know that the path effect produced a shape that was a "real" path
287
288 if (baseIsNonVolatilePath) {
289 REPORTER_ASSERT(r, baseID == peID);
290 }
291
292 if (peStrokeIsPath) {
293 REPORTER_ASSERT(r, peID == peStrokeID);
294 REPORTER_ASSERT(r, peStrokeID == fullID);
295 }
296
297 if (baseIsNonVolatilePath && peStrokeIsPath) {
298 REPORTER_ASSERT(r, baseID == peStrokeID);
299 REPORTER_ASSERT(r, baseID == fullID);
300 }
301 }
302
test_inversions(skiatest::Reporter * r,const GrStyledShape & shape,const Key & shapeKey)303 void test_inversions(skiatest::Reporter* r, const GrStyledShape& shape, const Key& shapeKey) {
304 GrStyledShape preserve = GrStyledShape::MakeFilled(
305 shape, GrStyledShape::FillInversion::kPreserve);
306 Key preserveKey;
307 make_key(&preserveKey, preserve);
308
309 GrStyledShape flip = GrStyledShape::MakeFilled(shape, GrStyledShape::FillInversion::kFlip);
310 Key flipKey;
311 make_key(&flipKey, flip);
312
313 GrStyledShape inverted = GrStyledShape::MakeFilled(
314 shape, GrStyledShape::FillInversion::kForceInverted);
315 Key invertedKey;
316 make_key(&invertedKey, inverted);
317
318 GrStyledShape noninverted = GrStyledShape::MakeFilled(
319 shape, GrStyledShape::FillInversion::kForceNoninverted);
320 Key noninvertedKey;
321 make_key(&noninvertedKey, noninverted);
322
323 if (invertedKey.size() || noninvertedKey.size()) {
324 REPORTER_ASSERT(r, invertedKey != noninvertedKey);
325 }
326 if (shape.style().isSimpleFill()) {
327 check_equivalence(r, shape, preserve, shapeKey, preserveKey);
328 }
329 if (shape.inverseFilled()) {
330 check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
331 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
332 } else {
333 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
334 check_equivalence(r, flip, inverted, flipKey, invertedKey);
335 }
336
337 GrStyledShape doubleFlip = GrStyledShape::MakeFilled(flip, GrStyledShape::FillInversion::kFlip);
338 Key doubleFlipKey;
339 make_key(&doubleFlipKey, doubleFlip);
340 // It can be the case that the double flip has no key but preserve does. This happens when the
341 // original shape has an inherited style key. That gets dropped on the first inversion flip.
342 if (preserveKey.size() && !doubleFlipKey.size()) {
343 preserveKey.clear();
344 }
345 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
346 }
347
348 namespace {
349 /**
350 * Geo is a factory for creating a GrStyledShape from another representation. It also answers some
351 * questions about expected behavior for GrStyledShape given the inputs.
352 */
353 class Geo {
354 public:
~Geo()355 virtual ~Geo() {}
356 virtual GrStyledShape makeShape(const SkPaint&) const = 0;
357 virtual SkPath path() const = 0;
358 // These functions allow tests to check for special cases where style gets
359 // applied by GrStyledShape in its constructor (without calling GrStyledShape::applyStyle).
360 // These unfortunately rely on knowing details of GrStyledShape's implementation.
361 // These predicates are factored out here to avoid littering the rest of the
362 // test code with GrStyledShape implementation details.
fillChangesGeom() const363 virtual bool fillChangesGeom() const { return false; }
strokeIsConvertedToFill() const364 virtual bool strokeIsConvertedToFill() const { return false; }
strokeAndFillIsConvertedToFill(const SkPaint &) const365 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
366 // Is this something we expect GrStyledShape to recognize as something simpler than a path.
isNonPath(const SkPaint & paint) const367 virtual bool isNonPath(const SkPaint& paint) const { return true; }
368 };
369
370 class RectGeo : public Geo {
371 public:
RectGeo(const SkRect & rect)372 RectGeo(const SkRect& rect) : fRect(rect) {}
373
path() const374 SkPath path() const override {
375 SkPath path;
376 path.addRect(fRect);
377 return path;
378 }
379
makeShape(const SkPaint & paint) const380 GrStyledShape makeShape(const SkPaint& paint) const override {
381 return GrStyledShape(fRect, paint);
382 }
383
strokeAndFillIsConvertedToFill(const SkPaint & paint) const384 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
385 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
386 // Converted to an outset rectangle or round rect
387 return (paint.getStrokeJoin() == SkPaint::kMiter_Join &&
388 paint.getStrokeMiter() >= SK_ScalarSqrt2) ||
389 paint.getStrokeJoin() == SkPaint::kRound_Join;
390 }
391
392 private:
393 SkRect fRect;
394 };
395
396 class RRectGeo : public Geo {
397 public:
RRectGeo(const SkRRect & rrect)398 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
399
makeShape(const SkPaint & paint) const400 GrStyledShape makeShape(const SkPaint& paint) const override {
401 return GrStyledShape(fRRect, paint);
402 }
403
path() const404 SkPath path() const override {
405 SkPath path;
406 path.addRRect(fRRect);
407 return path;
408 }
409
strokeAndFillIsConvertedToFill(const SkPaint & paint) const410 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
411 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
412 if (fRRect.isRect()) {
413 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
414 }
415 return false;
416 }
417
418 private:
419 SkRRect fRRect;
420 };
421
422 class ArcGeo : public Geo {
423 public:
ArcGeo(const SkArc & arc)424 ArcGeo(const SkArc& arc) : fArc(arc) {}
425
path() const426 SkPath path() const override {
427 SkPath path;
428 SkPathPriv::CreateDrawArcPath(&path, fArc, false);
429 return path;
430 }
431
makeShape(const SkPaint & paint) const432 GrStyledShape makeShape(const SkPaint& paint) const override {
433 return GrStyledShape::MakeArc(fArc, GrStyle(paint));
434 }
435
436 // GrStyledShape specializes when created from arc params but it doesn't recognize arcs from
437 // SkPath.
isNonPath(const SkPaint & paint) const438 bool isNonPath(const SkPaint& paint) const override { return false; }
439
440 private:
441 SkArc fArc;
442 };
443
444 class PathGeo : public Geo {
445 public:
446 enum class Invert { kNo, kYes };
447
PathGeo(const SkPath & path,Invert invert)448 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
449 SkASSERT(!path.isInverseFillType());
450 if (Invert::kYes == invert) {
451 if (fPath.getFillType() == SkPathFillType::kEvenOdd) {
452 fPath.setFillType(SkPathFillType::kInverseEvenOdd);
453 } else {
454 SkASSERT(fPath.getFillType() == SkPathFillType::kWinding);
455 fPath.setFillType(SkPathFillType::kInverseWinding);
456 }
457 }
458 }
459
makeShape(const SkPaint & paint) const460 GrStyledShape makeShape(const SkPaint& paint) const override {
461 return GrStyledShape(fPath, paint);
462 }
463
path() const464 SkPath path() const override { return fPath; }
465
fillChangesGeom() const466 bool fillChangesGeom() const override {
467 // unclosed rects get closed. Lines get turned into empty geometry
468 return this->isUnclosedRect() || fPath.isLine(nullptr);
469 }
470
strokeIsConvertedToFill() const471 bool strokeIsConvertedToFill() const override {
472 return this->isAxisAlignedLine();
473 }
474
strokeAndFillIsConvertedToFill(const SkPaint & paint) const475 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
476 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
477 if (this->isAxisAlignedLine()) {
478 // The fill is ignored (zero area) and the stroke is converted to a rrect.
479 return true;
480 }
481 SkRect rect;
482 unsigned start;
483 SkPathDirection dir;
484 if (SkPathPriv::IsSimpleRect(fPath, false, &rect, &dir, &start)) {
485 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
486 }
487 return false;
488 }
489
isNonPath(const SkPaint & paint) const490 bool isNonPath(const SkPaint& paint) const override {
491 return fPath.isLine(nullptr) || fPath.isEmpty();
492 }
493
494 private:
isAxisAlignedLine() const495 bool isAxisAlignedLine() const {
496 SkPoint pts[2];
497 if (!fPath.isLine(pts)) {
498 return false;
499 }
500 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
501 }
502
isUnclosedRect() const503 bool isUnclosedRect() const {
504 bool closed;
505 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
506 }
507
508 SkPath fPath;
509 };
510
511 class RRectPathGeo : public PathGeo {
512 public:
513 enum class RRectForStroke { kNo, kYes };
514
RRectPathGeo(const SkPath & path,const SkRRect & equivalentRRect,RRectForStroke rrectForStroke,Invert invert)515 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
516 Invert invert)
517 : PathGeo(path, invert)
518 , fRRect(equivalentRRect)
519 , fRRectForStroke(rrectForStroke) {}
520
RRectPathGeo(const SkPath & path,const SkRect & equivalentRect,RRectForStroke rrectForStroke,Invert invert)521 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
522 Invert invert)
523 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
524
isNonPath(const SkPaint & paint) const525 bool isNonPath(const SkPaint& paint) const override {
526 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
527 return true;
528 }
529 return false;
530 }
531
rrect() const532 const SkRRect& rrect() const { return fRRect; }
533
534 private:
535 SkRRect fRRect;
536 RRectForStroke fRRectForStroke;
537 };
538
539 class TestCase {
540 public:
TestCase(const Geo & geo,const SkPaint & paint,skiatest::Reporter * r,SkScalar scale=SK_Scalar1)541 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
542 SkScalar scale = SK_Scalar1)
543 : fBase(new GrStyledShape(geo.makeShape(paint))) {
544 this->init(r, scale);
545 }
546
547 template <typename... ShapeArgs>
TestCase(skiatest::Reporter * r,ShapeArgs...shapeArgs)548 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs)
549 : fBase(new GrStyledShape(shapeArgs...)) {
550 this->init(r, SK_Scalar1);
551 }
552
TestCase(const GrStyledShape & shape,skiatest::Reporter * r,SkScalar scale=SK_Scalar1)553 TestCase(const GrStyledShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
554 : fBase(new GrStyledShape(shape)) {
555 this->init(r, scale);
556 }
557
558 struct SelfExpectations {
559 bool fPEHasEffect;
560 bool fPEHasValidKey;
561 bool fStrokeApplies;
562 };
563
564 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
565
566 enum ComparisonExpecation {
567 kAllDifferent_ComparisonExpecation,
568 kSameUpToPE_ComparisonExpecation,
569 kSameUpToStroke_ComparisonExpecation,
570 kAllSame_ComparisonExpecation,
571 };
572
573 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
574
baseShape() const575 const GrStyledShape& baseShape() const { return *fBase; }
appliedPathEffectShape() const576 const GrStyledShape& appliedPathEffectShape() const { return *fAppliedPE; }
appliedFullStyleShape() const577 const GrStyledShape& appliedFullStyleShape() const { return *fAppliedFull; }
578
579 // The returned array's count will be 0 if the key shape has no key.
baseKey() const580 const Key& baseKey() const { return fBaseKey; }
appliedPathEffectKey() const581 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
appliedFullStyleKey() const582 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
appliedPathEffectThenStrokeKey() const583 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
584
585 private:
CheckBounds(skiatest::Reporter * r,const GrStyledShape & shape,const SkRect & bounds)586 static void CheckBounds(skiatest::Reporter* r, const GrStyledShape& shape,
587 const SkRect& bounds) {
588 SkPath path;
589 shape.asPath(&path);
590 // If the bounds are empty, the path ought to be as well.
591 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
592 REPORTER_ASSERT(r, path.isEmpty());
593 return;
594 }
595 if (path.isEmpty()) {
596 return;
597 }
598 // The bounds API explicitly calls out that it does not consider inverseness.
599 SkPath p = path;
600 p.setFillType(SkPathFillType_ConvertToNonInverse(path.getFillType()));
601 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
602 }
603
init(skiatest::Reporter * r,SkScalar scale)604 void init(skiatest::Reporter* r, SkScalar scale) {
605 fAppliedPE = std::make_unique<GrStyledShape>();
606 fAppliedPEThenStroke = std::make_unique<GrStyledShape>();
607 fAppliedFull = std::make_unique<GrStyledShape>();
608
609 *fAppliedPE = fBase->applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
610 *fAppliedPEThenStroke =
611 fAppliedPE->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
612 *fAppliedFull = fBase->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
613
614 make_key(&fBaseKey, *fBase);
615 make_key(&fAppliedPEKey, *fAppliedPE);
616 make_key(&fAppliedPEThenStrokeKey, *fAppliedPEThenStroke);
617 make_key(&fAppliedFullKey, *fAppliedFull);
618
619 // All shapes should report the same "original" path, so that path renderers can get to it
620 // if necessary.
621 check_original_path_ids(r, *fBase, *fAppliedPE, *fAppliedPEThenStroke, *fAppliedFull);
622
623 // Applying the path effect and then the stroke should always be the same as applying
624 // both in one go.
625 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
626 SkPath a, b;
627 fAppliedPEThenStroke->asPath(&a);
628 fAppliedFull->asPath(&b);
629 // If the output of the path effect is a rrect then it is possible for a and b to be
630 // different paths that fill identically. The reason is that fAppliedFull will do this:
631 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
632 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
633 // now that there is no longer a path effect, the direction and starting index get
634 // canonicalized before the stroke.
635 if (fAppliedPE->asRRect(nullptr, nullptr)) {
636 REPORTER_ASSERT(r, paths_fill_same(a, b));
637 } else {
638 REPORTER_ASSERT(r, a == b);
639 }
640 REPORTER_ASSERT(r, fAppliedFull->isEmpty() == fAppliedPEThenStroke->isEmpty());
641
642 SkPath path;
643 fBase->asPath(&path);
644 REPORTER_ASSERT(r, path.isEmpty() == fBase->isEmpty());
645 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase->segmentMask());
646 fAppliedPE->asPath(&path);
647 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE->isEmpty());
648 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE->segmentMask());
649 fAppliedFull->asPath(&path);
650 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull->isEmpty());
651 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull->segmentMask());
652
653 CheckBounds(r, *fBase, fBase->bounds());
654 CheckBounds(r, *fAppliedPE, fAppliedPE->bounds());
655 CheckBounds(r, *fAppliedPEThenStroke, fAppliedPEThenStroke->bounds());
656 CheckBounds(r, *fAppliedFull, fAppliedFull->bounds());
657 SkRect styledBounds = fBase->styledBounds();
658 CheckBounds(r, *fAppliedFull, styledBounds);
659 styledBounds = fAppliedPE->styledBounds();
660 CheckBounds(r, *fAppliedFull, styledBounds);
661
662 // Check that the same path is produced when style is applied by GrStyledShape and GrStyle.
663 SkPath preStyle;
664 SkPath postPathEffect;
665 SkPath postAllStyle;
666
667 fBase->asPath(&preStyle);
668 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
669 if (fBase->style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
670 scale)) {
671 // run postPathEffect through GrStyledShape to get any geometry reductions that would
672 // have occurred to fAppliedPE.
673 GrStyledShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr))
674 .asPath(&postPathEffect);
675
676 SkPath testPath;
677 fAppliedPE->asPath(&testPath);
678 REPORTER_ASSERT(r, testPath == postPathEffect);
679 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE->style().strokeRec()));
680 }
681 SkStrokeRec::InitStyle fillOrHairline;
682 if (fBase->style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
683 SkPath testPath;
684 fAppliedFull->asPath(&testPath);
685 if (fBase->style().hasPathEffect()) {
686 // Because GrStyledShape always does two-stage application when there is a path
687 // effect there may be a reduction/canonicalization step between the path effect and
688 // strokerec not reflected in postAllStyle since it applied both the path effect
689 // and strokerec without analyzing the intermediate path.
690 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
691 } else {
692 // Make sure that postAllStyle sees any reductions/canonicalizations that
693 // GrStyledShape would apply.
694 GrStyledShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
695 REPORTER_ASSERT(r, testPath == postAllStyle);
696 }
697
698 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
699 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleFill());
700 } else {
701 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleHairline());
702 }
703 }
704 test_inversions(r, *fBase, fBaseKey);
705 test_inversions(r, *fAppliedPE, fAppliedPEKey);
706 test_inversions(r, *fAppliedFull, fAppliedFullKey);
707 }
708
709 std::unique_ptr<GrStyledShape> fBase;
710 std::unique_ptr<GrStyledShape> fAppliedPE;
711 std::unique_ptr<GrStyledShape> fAppliedPEThenStroke;
712 std::unique_ptr<GrStyledShape> fAppliedFull;
713
714 Key fBaseKey;
715 Key fAppliedPEKey;
716 Key fAppliedPEThenStrokeKey;
717 Key fAppliedFullKey;
718 };
719
testExpectations(skiatest::Reporter * reporter,SelfExpectations expectations) const720 void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
721 // The base's key should always be valid (unless the path is volatile)
722 REPORTER_ASSERT(reporter, fBaseKey.size());
723 if (expectations.fPEHasEffect) {
724 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
725 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.size()));
726 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
727 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.size()));
728 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
729 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
730 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.size()));
731 }
732 } else {
733 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
734 SkPath a, b;
735 fBase->asPath(&a);
736 fAppliedPE->asPath(&b);
737 REPORTER_ASSERT(reporter, a == b);
738 if (expectations.fStrokeApplies) {
739 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
740 } else {
741 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
742 }
743 }
744 }
745
compare(skiatest::Reporter * r,const TestCase & that,ComparisonExpecation expectation) const746 void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
747 ComparisonExpecation expectation) const {
748 SkPath a, b;
749 switch (expectation) {
750 case kAllDifferent_ComparisonExpecation:
751 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
752 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
753 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
754 break;
755 case kSameUpToPE_ComparisonExpecation:
756 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
757 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
758 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
759 break;
760 case kSameUpToStroke_ComparisonExpecation:
761 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
762 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
763 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
764 break;
765 case kAllSame_ComparisonExpecation:
766 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
767 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
768 check_equivalence(r, *fAppliedFull, *that.fAppliedFull, fAppliedFullKey,
769 that.fAppliedFullKey);
770 break;
771 }
772 }
773 } // namespace
774
make_dash()775 static sk_sp<SkPathEffect> make_dash() {
776 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
777 static const SkScalar kPhase = 0.75;
778 return SkDashPathEffect::Make(kIntervals, std::size(kIntervals), kPhase);
779 }
780
make_null_dash()781 static sk_sp<SkPathEffect> make_null_dash() {
782 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
783 return SkDashPathEffect::Make(kNullIntervals, std::size(kNullIntervals), 0.f);
784 }
785
786 // We make enough TestCases, and they're large enough, that on Google3 builds we exceed
787 // the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
788 template <typename... Args>
make_TestCase(Args &&...args)789 static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
790 return std::make_unique<TestCase>( std::forward<Args>(args)... );
791 }
792
test_basic(skiatest::Reporter * reporter,const Geo & geo)793 static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
794 sk_sp<SkPathEffect> dashPE = make_dash();
795
796 TestCase::SelfExpectations expectations;
797 SkPaint fill;
798
799 TestCase fillCase(geo, fill, reporter);
800 expectations.fPEHasEffect = false;
801 expectations.fPEHasValidKey = false;
802 expectations.fStrokeApplies = false;
803 fillCase.testExpectations(reporter, expectations);
804 // Test that another GrStyledShape instance built from the same primitive is the same.
805 make_TestCase(geo, fill, reporter)
806 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
807
808 SkPaint stroke2RoundBevel;
809 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
810 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
811 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
812 stroke2RoundBevel.setStrokeWidth(2.f);
813 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
814 expectations.fPEHasValidKey = true;
815 expectations.fPEHasEffect = false;
816 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
817 stroke2RoundBevelCase.testExpectations(reporter, expectations);
818 make_TestCase(geo, stroke2RoundBevel, reporter)
819 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
820
821 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
822 stroke2RoundBevelDash.setPathEffect(make_dash());
823 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
824 expectations.fPEHasValidKey = true;
825 expectations.fPEHasEffect = true;
826 expectations.fStrokeApplies = true;
827 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
828 make_TestCase(geo, stroke2RoundBevelDash, reporter)
829 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
830
831 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
832 fillCase.compare(reporter, stroke2RoundBevelCase,
833 TestCase::kAllDifferent_ComparisonExpecation);
834 fillCase.compare(reporter, stroke2RoundBevelDashCase,
835 TestCase::kAllDifferent_ComparisonExpecation);
836 } else {
837 fillCase.compare(reporter, stroke2RoundBevelCase,
838 TestCase::kSameUpToStroke_ComparisonExpecation);
839 fillCase.compare(reporter, stroke2RoundBevelDashCase,
840 TestCase::kSameUpToPE_ComparisonExpecation);
841 }
842 if (geo.strokeIsConvertedToFill()) {
843 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
844 TestCase::kAllDifferent_ComparisonExpecation);
845 } else {
846 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
847 TestCase::kSameUpToPE_ComparisonExpecation);
848 }
849
850 // Stroke and fill cases
851 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
852 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
853 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
854 expectations.fPEHasValidKey = true;
855 expectations.fPEHasEffect = false;
856 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
857 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
858 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
859 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
860
861 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
862 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
863 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
864 expectations.fPEHasValidKey = true;
865 expectations.fPEHasEffect = false;
866 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
867 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
868 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
869 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
870 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
871 TestCase::kAllSame_ComparisonExpecation);
872
873 SkPaint hairline;
874 hairline.setStyle(SkPaint::kStroke_Style);
875 hairline.setStrokeWidth(0.f);
876 TestCase hairlineCase(geo, hairline, reporter);
877 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
878 // in the line and unclosed rect cases).
879 if (geo.fillChangesGeom()) {
880 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
881 } else {
882 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
883 }
884 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
885 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
886 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
887
888 }
889
test_scale(skiatest::Reporter * reporter,const Geo & geo)890 static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
891 sk_sp<SkPathEffect> dashPE = make_dash();
892
893 static const SkScalar kS1 = 1.f;
894 static const SkScalar kS2 = 2.f;
895
896 SkPaint fill;
897 TestCase fillCase1(geo, fill, reporter, kS1);
898 TestCase fillCase2(geo, fill, reporter, kS2);
899 // Scale doesn't affect fills.
900 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
901
902 SkPaint hairline;
903 hairline.setStyle(SkPaint::kStroke_Style);
904 hairline.setStrokeWidth(0.f);
905 TestCase hairlineCase1(geo, hairline, reporter, kS1);
906 TestCase hairlineCase2(geo, hairline, reporter, kS2);
907 // Scale doesn't affect hairlines.
908 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
909
910 SkPaint stroke;
911 stroke.setStyle(SkPaint::kStroke_Style);
912 stroke.setStrokeWidth(2.f);
913 TestCase strokeCase1(geo, stroke, reporter, kS1);
914 TestCase strokeCase2(geo, stroke, reporter, kS2);
915 // Scale affects the stroke
916 if (geo.strokeIsConvertedToFill()) {
917 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
918 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
919 } else {
920 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
921 }
922
923 SkPaint strokeDash = stroke;
924 strokeDash.setPathEffect(make_dash());
925 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
926 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
927 // Scale affects the dash and the stroke.
928 strokeDashCase1.compare(reporter, strokeDashCase2,
929 TestCase::kSameUpToPE_ComparisonExpecation);
930
931 // Stroke and fill cases
932 SkPaint strokeAndFill = stroke;
933 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
934 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
935 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
936 SkPaint strokeAndFillDash = strokeDash;
937 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
938 // Dash is ignored for stroke and fill
939 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
940 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
941 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
942 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
943 // geometries should agree.
944 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
945 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
946 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
947 TestCase::kAllSame_ComparisonExpecation);
948 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
949 TestCase::kAllSame_ComparisonExpecation);
950 } else {
951 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
952 TestCase::kSameUpToStroke_ComparisonExpecation);
953 }
954 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
955 TestCase::kAllSame_ComparisonExpecation);
956 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
957 TestCase::kAllSame_ComparisonExpecation);
958 }
959
960 template <typename T>
test_stroke_param_impl(skiatest::Reporter * reporter,const Geo & geo,std::function<void (SkPaint *,T)> setter,T a,T b,bool paramAffectsStroke,bool paramAffectsDashAndStroke)961 static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
962 std::function<void(SkPaint*, T)> setter, T a, T b,
963 bool paramAffectsStroke,
964 bool paramAffectsDashAndStroke) {
965 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
966 // that it can override the stroke width.
967 SkPaint strokeA;
968 strokeA.setStyle(SkPaint::kStroke_Style);
969 strokeA.setStrokeWidth(2.f);
970 setter(&strokeA, a);
971 SkPaint strokeB;
972 strokeB.setStyle(SkPaint::kStroke_Style);
973 strokeB.setStrokeWidth(2.f);
974 setter(&strokeB, b);
975
976 TestCase strokeACase(geo, strokeA, reporter);
977 TestCase strokeBCase(geo, strokeB, reporter);
978 if (paramAffectsStroke) {
979 // If stroking is immediately incorporated into a geometric transformation then the base
980 // shapes will differ.
981 if (geo.strokeIsConvertedToFill()) {
982 strokeACase.compare(reporter, strokeBCase,
983 TestCase::kAllDifferent_ComparisonExpecation);
984 } else {
985 strokeACase.compare(reporter, strokeBCase,
986 TestCase::kSameUpToStroke_ComparisonExpecation);
987 }
988 } else {
989 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
990 }
991
992 SkPaint strokeAndFillA = strokeA;
993 SkPaint strokeAndFillB = strokeB;
994 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
995 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
996 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
997 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
998 if (paramAffectsStroke) {
999 // If stroking is immediately incorporated into a geometric transformation then the base
1000 // shapes will differ.
1001 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
1002 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
1003 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
1004 TestCase::kAllDifferent_ComparisonExpecation);
1005 } else {
1006 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
1007 TestCase::kSameUpToStroke_ComparisonExpecation);
1008 }
1009 } else {
1010 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
1011 TestCase::kAllSame_ComparisonExpecation);
1012 }
1013
1014 // Make sure stroking params don't affect fill style.
1015 SkPaint fillA = strokeA, fillB = strokeB;
1016 fillA.setStyle(SkPaint::kFill_Style);
1017 fillB.setStyle(SkPaint::kFill_Style);
1018 TestCase fillACase(geo, fillA, reporter);
1019 TestCase fillBCase(geo, fillB, reporter);
1020 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
1021
1022 // Make sure just applying the dash but not stroke gives the same key for both stroking
1023 // variations.
1024 SkPaint dashA = strokeA, dashB = strokeB;
1025 dashA.setPathEffect(make_dash());
1026 dashB.setPathEffect(make_dash());
1027 TestCase dashACase(geo, dashA, reporter);
1028 TestCase dashBCase(geo, dashB, reporter);
1029 if (paramAffectsDashAndStroke) {
1030 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1031 } else {
1032 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
1033 }
1034 }
1035
1036 template <typename T>
test_stroke_param(skiatest::Reporter * reporter,const Geo & geo,std::function<void (SkPaint *,T)> setter,T a,T b)1037 static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
1038 std::function<void(SkPaint*, T)> setter, T a, T b) {
1039 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
1040 }
1041
test_stroke_cap(skiatest::Reporter * reporter,const Geo & geo)1042 static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
1043 SkPaint hairline;
1044 hairline.setStrokeWidth(0);
1045 hairline.setStyle(SkPaint::kStroke_Style);
1046 GrStyledShape shape = geo.makeShape(hairline);
1047 // The cap should only affect shapes that may be open.
1048 bool affectsStroke = !shape.knownToBeClosed();
1049 // Dashing adds ends that need caps.
1050 bool affectsDashAndStroke = true;
1051 test_stroke_param_impl<SkPaint::Cap>(
1052 reporter,
1053 geo,
1054 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
1055 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
1056 affectsStroke,
1057 affectsDashAndStroke);
1058 }
1059
shape_known_not_to_have_joins(const GrStyledShape & shape)1060 static bool shape_known_not_to_have_joins(const GrStyledShape& shape) {
1061 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
1062 }
1063
test_stroke_join(skiatest::Reporter * reporter,const Geo & geo)1064 static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
1065 SkPaint hairline;
1066 hairline.setStrokeWidth(0);
1067 hairline.setStyle(SkPaint::kStroke_Style);
1068 GrStyledShape shape = geo.makeShape(hairline);
1069 // GrStyledShape recognizes certain types don't have joins and will prevent the join type from
1070 // affecting the style key.
1071 // Dashing doesn't add additional joins. However, GrStyledShape currently loses track of this
1072 // after applying the dash.
1073 bool affectsStroke = !shape_known_not_to_have_joins(shape);
1074 test_stroke_param_impl<SkPaint::Join>(
1075 reporter,
1076 geo,
1077 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
1078 SkPaint::kRound_Join, SkPaint::kBevel_Join,
1079 affectsStroke, true);
1080 }
1081
test_miter_limit(skiatest::Reporter * reporter,const Geo & geo)1082 static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
1083 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1084 p->setStrokeJoin(SkPaint::kMiter_Join);
1085 p->setStrokeMiter(miter);
1086 };
1087
1088 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1089 p->setStrokeJoin(SkPaint::kRound_Join);
1090 p->setStrokeMiter(miter);
1091 };
1092
1093 SkPaint hairline;
1094 hairline.setStrokeWidth(0);
1095 hairline.setStyle(SkPaint::kStroke_Style);
1096 GrStyledShape shape = geo.makeShape(hairline);
1097 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
1098
1099 // The miter limit should affect stroked and dashed-stroked cases when the join type is
1100 // miter.
1101 test_stroke_param_impl<SkScalar>(
1102 reporter,
1103 geo,
1104 setMiterJoinAndLimit,
1105 0.5f, 0.75f,
1106 mayHaveJoins,
1107 true);
1108
1109 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
1110 // not miter.
1111 test_stroke_param_impl<SkScalar>(
1112 reporter,
1113 geo,
1114 setOtherJoinAndLimit,
1115 0.5f, 0.75f,
1116 false,
1117 false);
1118 }
1119
test_dash_fill(skiatest::Reporter * reporter,const Geo & geo)1120 static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
1121 // A dash with no stroke should have no effect
1122 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
1123 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
1124 SkPaint dashFill;
1125 dashFill.setPathEffect((*md)());
1126 TestCase dashFillCase(geo, dashFill, reporter);
1127
1128 TestCase fillCase(geo, SkPaint(), reporter);
1129 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
1130 }
1131 }
1132
test_null_dash(skiatest::Reporter * reporter,const Geo & geo)1133 void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
1134 SkPaint fill;
1135 SkPaint stroke;
1136 stroke.setStyle(SkPaint::kStroke_Style);
1137 stroke.setStrokeWidth(1.f);
1138 SkPaint dash;
1139 dash.setStyle(SkPaint::kStroke_Style);
1140 dash.setStrokeWidth(1.f);
1141 dash.setPathEffect(make_dash());
1142 SkPaint nullDash;
1143 nullDash.setStyle(SkPaint::kStroke_Style);
1144 nullDash.setStrokeWidth(1.f);
1145 nullDash.setPathEffect(make_null_dash());
1146
1147 TestCase fillCase(geo, fill, reporter);
1148 TestCase strokeCase(geo, stroke, reporter);
1149 TestCase dashCase(geo, dash, reporter);
1150 TestCase nullDashCase(geo, nullDash, reporter);
1151
1152 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
1153 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
1154 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
1155 // on construction in order to determine how to compare the fill and stroke.
1156 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
1157 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
1158 } else {
1159 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1160 }
1161 // In the null dash case we may immediately convert to a fill, but not for the normal dash case.
1162 if (geo.strokeIsConvertedToFill()) {
1163 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
1164 } else {
1165 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
1166 }
1167 }
1168
test_path_effect_makes_rrect(skiatest::Reporter * reporter,const Geo & geo)1169 void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
1170 /**
1171 * This path effect takes any input path and turns it into a rrect. It passes through stroke
1172 * info.
1173 */
1174 class RRectPathEffect : SkPathEffectBase {
1175 public:
1176 static const SkRRect& RRect() {
1177 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1178 return kRRect;
1179 }
1180
1181 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1182 Factory getFactory() const override { return nullptr; }
1183 const char* getTypeName() const override { return nullptr; }
1184
1185 protected:
1186 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1187 const SkRect* cullR, const SkMatrix&) const override {
1188 dst->reset();
1189 dst->addRRect(RRect());
1190 return true;
1191 }
1192
1193 bool computeFastBounds(SkRect* bounds) const override {
1194 if (bounds) {
1195 *bounds = RRect().getBounds();
1196 }
1197 return true;
1198 }
1199
1200 private:
1201 RRectPathEffect() {}
1202 };
1203
1204 SkPaint fill;
1205 TestCase fillGeoCase(geo, fill, reporter);
1206
1207 SkPaint pe;
1208 pe.setPathEffect(RRectPathEffect::Make());
1209 TestCase geoPECase(geo, pe, reporter);
1210
1211 SkPaint peStroke;
1212 peStroke.setPathEffect(RRectPathEffect::Make());
1213 peStroke.setStrokeWidth(2.f);
1214 peStroke.setStyle(SkPaint::kStroke_Style);
1215 TestCase geoPEStrokeCase(geo, peStroke, reporter);
1216
1217 // Check whether constructing the filled case would cause the base shape to have a different
1218 // geometry (because of a geometric transformation upon initial GrStyledShape construction).
1219 if (geo.fillChangesGeom()) {
1220 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1221 fillGeoCase.compare(reporter, geoPEStrokeCase,
1222 TestCase::kAllDifferent_ComparisonExpecation);
1223 } else {
1224 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1225 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1226 }
1227 geoPECase.compare(reporter, geoPEStrokeCase,
1228 TestCase::kSameUpToStroke_ComparisonExpecation);
1229
1230 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
1231 SkPaint stroke = peStroke;
1232 stroke.setPathEffect(nullptr);
1233 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
1234
1235 SkRRect rrect;
1236 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1237 // geoPECase, so the full style should be the same as just the PE.
1238 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr));
1239 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1240 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1241
1242 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr));
1243 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1244 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1245
1246 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
1247 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr));
1248 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1249 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1250
1251 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr));
1252 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1253 rrectStrokeCase.appliedFullStyleKey());
1254 }
1255
test_unknown_path_effect(skiatest::Reporter * reporter,const Geo & geo)1256 void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
1257 /**
1258 * This path effect just adds two lineTos to the input path.
1259 */
1260 class AddLineTosPathEffect : SkPathEffectBase {
1261 public:
1262 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1263 Factory getFactory() const override { return nullptr; }
1264 const char* getTypeName() const override { return nullptr; }
1265
1266 protected:
1267 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1268 const SkRect* cullR, const SkMatrix&) const override {
1269 *dst = src;
1270 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1271 for (int i = 0; i < 100; ++i) {
1272 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1273 }
1274 return true;
1275 }
1276 bool computeFastBounds(SkRect* bounds) const override {
1277 if (bounds) {
1278 SkRectPriv::GrowToInclude(bounds, {0, 0});
1279 SkRectPriv::GrowToInclude(bounds, {100, 100});
1280 }
1281 return true;
1282 }
1283 private:
1284 AddLineTosPathEffect() {}
1285 };
1286
1287 // This path effect should make the keys invalid when it is applied. We only produce a path
1288 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1289 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1290 SkPaint peStroke;
1291 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1292 peStroke.setStrokeWidth(2.f);
1293 peStroke.setStyle(SkPaint::kStroke_Style);
1294 TestCase geoPEStrokeCase(geo, peStroke, reporter);
1295 TestCase::SelfExpectations expectations;
1296 expectations.fPEHasEffect = true;
1297 expectations.fPEHasValidKey = false;
1298 expectations.fStrokeApplies = true;
1299 geoPEStrokeCase.testExpectations(reporter, expectations);
1300 }
1301
test_make_hairline_path_effect(skiatest::Reporter * reporter,const Geo & geo)1302 void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
1303 /**
1304 * This path effect just changes the stroke rec to hairline.
1305 */
1306 class MakeHairlinePathEffect : SkPathEffectBase {
1307 public:
1308 static sk_sp<SkPathEffect> Make() {
1309 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1310 }
1311 Factory getFactory() const override { return nullptr; }
1312 const char* getTypeName() const override { return nullptr; }
1313
1314 protected:
1315 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1316 const SkRect* cullR, const SkMatrix&) const override {
1317 *dst = src;
1318 strokeRec->setHairlineStyle();
1319 return true;
1320 }
1321 private:
1322 bool computeFastBounds(SkRect* bounds) const override { return true; }
1323
1324 MakeHairlinePathEffect() {}
1325 };
1326
1327 SkPaint fill;
1328 SkPaint pe;
1329 pe.setPathEffect(MakeHairlinePathEffect::Make());
1330
1331 TestCase peCase(geo, pe, reporter);
1332
1333 SkPath a, b, c;
1334 peCase.baseShape().asPath(&a);
1335 peCase.appliedPathEffectShape().asPath(&b);
1336 peCase.appliedFullStyleShape().asPath(&c);
1337 if (geo.isNonPath(pe)) {
1338 // RRect types can have a change in start index or direction after the PE is applied. This
1339 // is because once the PE is applied, GrStyledShape may canonicalize the dir and index since
1340 // it is not germane to the styling any longer.
1341 // Instead we just check that the paths would fill the same both before and after styling.
1342 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1343 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
1344 } else {
1345 // The base shape cannot perform canonicalization on the path's fill type because of an
1346 // unknown path effect. However, after the path effect is applied the resulting hairline
1347 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1348 // don't distinguish between even/odd and non-zero winding.
1349 a.setFillType(b.getFillType());
1350 REPORTER_ASSERT(reporter, a == b);
1351 REPORTER_ASSERT(reporter, a == c);
1352 // If the resulting path is small enough then it will have a key.
1353 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1354 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
1355 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1356 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
1357 }
1358 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1359 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
1360 }
1361
test_volatile_path(skiatest::Reporter * reporter,const Geo & geo)1362 void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1363 SkPath vPath = geo.path();
1364 vPath.setIsVolatile(true);
1365
1366 SkPaint dashAndStroke;
1367 dashAndStroke.setPathEffect(make_dash());
1368 dashAndStroke.setStrokeWidth(2.f);
1369 dashAndStroke.setStyle(SkPaint::kStroke_Style);
1370 TestCase volatileCase(reporter, vPath, dashAndStroke);
1371 // We expect a shape made from a volatile path to have a key iff the shape is recognized
1372 // as a specialized geometry.
1373 if (geo.isNonPath(dashAndStroke)) {
1374 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().size()));
1375 // In this case all the keys should be identical to the non-volatile case.
1376 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
1377 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1378 } else {
1379 // None of the keys should be valid.
1380 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().empty()));
1381 REPORTER_ASSERT(reporter, SkToBool(volatileCase.appliedPathEffectKey().empty()));
1382 REPORTER_ASSERT(reporter, SkToBool(volatileCase.appliedFullStyleKey().empty()));
1383 REPORTER_ASSERT(reporter, SkToBool(volatileCase.appliedPathEffectThenStrokeKey().empty()));
1384 }
1385 }
1386
test_path_effect_makes_empty_shape(skiatest::Reporter * reporter,const Geo & geo)1387 void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
1388 /**
1389 * This path effect returns an empty path (possibly inverted)
1390 */
1391 class EmptyPathEffect : SkPathEffectBase {
1392 public:
1393 static sk_sp<SkPathEffect> Make(bool invert) {
1394 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
1395 }
1396 Factory getFactory() const override { return nullptr; }
1397 const char* getTypeName() const override { return nullptr; }
1398 protected:
1399 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1400 const SkRect* cullR, const SkMatrix&) const override {
1401 dst->reset();
1402 if (fInvert) {
1403 dst->toggleInverseFillType();
1404 }
1405 return true;
1406 }
1407 bool computeFastBounds(SkRect* bounds) const override {
1408 if (bounds) {
1409 *bounds = { 0, 0, 0, 0 };
1410 }
1411 return true;
1412 }
1413 private:
1414 bool fInvert;
1415 EmptyPathEffect(bool invert) : fInvert(invert) {}
1416 };
1417
1418 SkPath emptyPath;
1419 GrStyledShape emptyShape(emptyPath);
1420 Key emptyKey;
1421 make_key(&emptyKey, emptyShape);
1422 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
1423
1424 emptyPath.toggleInverseFillType();
1425 GrStyledShape invertedEmptyShape(emptyPath);
1426 Key invertedEmptyKey;
1427 make_key(&invertedEmptyKey, invertedEmptyShape);
1428 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
1429
1430 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
1431
1432 SkPaint pe;
1433 pe.setPathEffect(EmptyPathEffect::Make(false));
1434 TestCase geoPECase(geo, pe, reporter);
1435 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
1436 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
1437 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
1438 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
1439 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
1440 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
1441 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
1442
1443 SkPaint peStroke;
1444 peStroke.setPathEffect(EmptyPathEffect::Make(false));
1445 peStroke.setStrokeWidth(2.f);
1446 peStroke.setStyle(SkPaint::kStroke_Style);
1447 TestCase geoPEStrokeCase(geo, peStroke, reporter);
1448 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1449 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1450 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
1451 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1452 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
1453 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
1454 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
1455 pe.setPathEffect(EmptyPathEffect::Make(true));
1456
1457 TestCase geoPEInvertCase(geo, pe, reporter);
1458 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
1459 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
1460 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1461 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
1462 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
1463 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
1464 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
1465
1466 peStroke.setPathEffect(EmptyPathEffect::Make(true));
1467 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
1468 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
1469 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
1470 REPORTER_ASSERT(reporter,
1471 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1472 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
1473 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
1474 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
1475 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
1476 }
1477
test_path_effect_fails(skiatest::Reporter * reporter,const Geo & geo)1478 void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
1479 /**
1480 * This path effect always fails to apply.
1481 */
1482 class FailurePathEffect : SkPathEffectBase {
1483 public:
1484 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1485 Factory getFactory() const override { return nullptr; }
1486 const char* getTypeName() const override { return nullptr; }
1487 protected:
1488 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1489 const SkRect* cullR, const SkMatrix&) const override {
1490 return false;
1491 }
1492 private:
1493 bool computeFastBounds(SkRect* bounds) const override { return false; }
1494
1495 FailurePathEffect() {}
1496 };
1497
1498 SkPaint fill;
1499 TestCase fillCase(geo, fill, reporter);
1500
1501 SkPaint pe;
1502 pe.setPathEffect(FailurePathEffect::Make());
1503 TestCase peCase(geo, pe, reporter);
1504
1505 SkPaint stroke;
1506 stroke.setStrokeWidth(2.f);
1507 stroke.setStyle(SkPaint::kStroke_Style);
1508 TestCase strokeCase(geo, stroke, reporter);
1509
1510 SkPaint peStroke = stroke;
1511 peStroke.setPathEffect(FailurePathEffect::Make());
1512 TestCase peStrokeCase(geo, peStroke, reporter);
1513
1514 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1515 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1516 // path effect, but then when the path effect fails we can key it. 2) GrStyledShape will change
1517 // its mind about whether a unclosed rect is actually rect. The path effect initially bars us
1518 // from closing it but after the effect fails we can (for the fill+pe case). This causes
1519 // different routes through GrStyledShape to have equivalent but different representations of
1520 // the path (closed or not) but that fill the same.
1521 SkPath a;
1522 SkPath b;
1523 fillCase.appliedPathEffectShape().asPath(&a);
1524 peCase.appliedPathEffectShape().asPath(&b);
1525 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1526
1527 fillCase.appliedFullStyleShape().asPath(&a);
1528 peCase.appliedFullStyleShape().asPath(&b);
1529 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1530
1531 strokeCase.appliedPathEffectShape().asPath(&a);
1532 peStrokeCase.appliedPathEffectShape().asPath(&b);
1533 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1534
1535 strokeCase.appliedFullStyleShape().asPath(&a);
1536 peStrokeCase.appliedFullStyleShape().asPath(&b);
1537 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1538 }
1539
DEF_TEST(GrStyledShape_empty_shape,reporter)1540 DEF_TEST(GrStyledShape_empty_shape, reporter) {
1541 SkPath emptyPath;
1542 SkPath invertedEmptyPath;
1543 invertedEmptyPath.toggleInverseFillType();
1544 SkPaint fill;
1545 TestCase fillEmptyCase(reporter, emptyPath, fill);
1546 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1547 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1548 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
1549 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
1550 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
1551 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
1552 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
1553 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
1554 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
1555 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
1556 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
1557 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
1558 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
1559
1560 const Key& emptyKey = fillEmptyCase.baseKey();
1561 REPORTER_ASSERT(reporter, emptyKey.size());
1562 const Key& inverseEmptyKey = fillInvertedEmptyCase.baseKey();
1563 REPORTER_ASSERT(reporter, inverseEmptyKey.size());
1564 TestCase::SelfExpectations expectations;
1565 expectations.fStrokeApplies = false;
1566 expectations.fPEHasEffect = false;
1567 // This will test whether applying style preserves emptiness
1568 fillEmptyCase.testExpectations(reporter, expectations);
1569 fillInvertedEmptyCase.testExpectations(reporter, expectations);
1570
1571 // Stroking an empty path should have no effect
1572 SkPaint stroke;
1573 stroke.setStrokeWidth(2.f);
1574 stroke.setStyle(SkPaint::kStroke_Style);
1575 stroke.setStrokeJoin(SkPaint::kRound_Join);
1576 stroke.setStrokeCap(SkPaint::kRound_Cap);
1577 TestCase strokeEmptyCase(reporter, emptyPath, stroke);
1578 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1579 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
1580 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
1581 TestCase::kAllSame_ComparisonExpecation);
1582
1583 // Dashing and stroking an empty path should have no effect
1584 SkPaint dashAndStroke;
1585 dashAndStroke.setPathEffect(make_dash());
1586 dashAndStroke.setStrokeWidth(2.f);
1587 dashAndStroke.setStyle(SkPaint::kStroke_Style);
1588 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
1589 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1590 TestCase::kAllSame_ComparisonExpecation);
1591 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
1592 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1593 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
1594 TestCase::kAllSame_ComparisonExpecation);
1595
1596 // A shape made from an empty rrect should behave the same as an empty path when filled and
1597 // when stroked. The shape is closed so it does not produce caps when stroked. When dashed there
1598 // is no path to dash along, making it equivalent as well.
1599 SkRRect emptyRRect = SkRRect::MakeEmpty();
1600 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
1601
1602 TestCase fillEmptyRRectCase(reporter, emptyRRect, fill);
1603 fillEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1604
1605 TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke);
1606 strokeEmptyRRectCase.compare(reporter, strokeEmptyCase,
1607 TestCase::kAllSame_ComparisonExpecation);
1608
1609 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
1610 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1611 TestCase::kAllSame_ComparisonExpecation);
1612
1613 static constexpr SkPathDirection kDir = SkPathDirection::kCCW;
1614 static constexpr int kStart = 0;
1615
1616 TestCase fillInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, GrStyle(fill));
1617 fillInvertedEmptyRRectCase.compare(reporter, fillInvertedEmptyCase,
1618 TestCase::kAllSame_ComparisonExpecation);
1619
1620 TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true,
1621 GrStyle(stroke));
1622 strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase,
1623 TestCase::kAllSame_ComparisonExpecation);
1624
1625 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
1626 GrStyle(dashAndStroke));
1627 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
1628 TestCase::kAllSame_ComparisonExpecation);
1629
1630 // Same for a rect.
1631 SkRect emptyRect = SkRect::MakeEmpty();
1632 TestCase fillEmptyRectCase(reporter, emptyRect, fill);
1633 fillEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1634
1635 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
1636 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1637 TestCase::kAllSame_ComparisonExpecation);
1638
1639 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
1640 kStart, true, GrStyle(dashAndStroke));
1641 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1642 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
1643 TestCase::kAllSame_ComparisonExpecation);
1644 }
1645
1646 // rect and oval types have rrect start indices that collapse to the same point. Here we select the
1647 // canonical point in these cases.
canonicalize_rrect_start(int s,const SkRRect & rrect)1648 unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1649 switch (rrect.getType()) {
1650 case SkRRect::kRect_Type:
1651 return (s + 1) & 0b110;
1652 case SkRRect::kOval_Type:
1653 return s & 0b110;
1654 default:
1655 return s;
1656 }
1657 }
1658
test_rrect(skiatest::Reporter * r,const SkRRect & rrect)1659 void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
1660 enum Style {
1661 kFill,
1662 kStroke,
1663 kHairline,
1664 kStrokeAndFill
1665 };
1666
1667 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1668 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1669 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1670 strokeRecs[kFill].setFillStyle();
1671 strokeRecs[kStroke].setStrokeStyle(2.f);
1672 strokeRecs[kHairline].setHairlineStyle();
1673 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
1674 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1675 // applyStyle() is called.
1676 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
1677 sk_sp<SkPathEffect> dashEffect = make_dash();
1678
1679 static constexpr size_t kStyleCnt = std::size(strokeRecs);
1680
1681 auto index = [](bool inverted,
1682 SkPathDirection dir,
1683 unsigned start,
1684 Style style,
1685 bool dash) -> int {
1686 return inverted * (2 * 8 * kStyleCnt * 2) +
1687 (int)dir * ( 8 * kStyleCnt * 2) +
1688 start * ( kStyleCnt * 2) +
1689 style * ( 2) +
1690 dash;
1691 };
1692 static const SkPathDirection kSecondDirection = static_cast<SkPathDirection>(1);
1693 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1694 AutoTArray<GrStyledShape> shapes(cnt);
1695 for (bool inverted : {false, true}) {
1696 for (SkPathDirection dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
1697 for (unsigned start = 0; start < 8; ++start) {
1698 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1699 for (bool dash : {false, true}) {
1700 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
1701 shapes[index(inverted, dir, start, style, dash)] =
1702 GrStyledShape(rrect, dir, start, SkToBool(inverted),
1703 GrStyle(strokeRecs[style], std::move(pe)));
1704 }
1705 }
1706 }
1707 }
1708 }
1709
1710 // Get the keys for some example shape instances that we'll use for comparision against the
1711 // rest.
1712 static constexpr SkPathDirection kExamplesDir = SkPathDirection::kCW;
1713 static constexpr unsigned kExamplesStart = 0;
1714 const GrStyledShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1715 false)];
1716 Key exampleFillCaseKey;
1717 make_key(&exampleFillCaseKey, exampleFillCase);
1718
1719 const GrStyledShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir,
1720 kExamplesStart, kStrokeAndFill, false)];
1721 Key exampleStrokeAndFillCaseKey;
1722 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1723
1724 const GrStyledShape& exampleInvFillCase = shapes[index(true, kExamplesDir,
1725 kExamplesStart, kFill, false)];
1726 Key exampleInvFillCaseKey;
1727 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1728
1729 const GrStyledShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir,
1730 kExamplesStart, kStrokeAndFill,
1731 false)];
1732 Key exampleInvStrokeAndFillCaseKey;
1733 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1734
1735 const GrStyledShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart,
1736 kStroke, false)];
1737 Key exampleStrokeCaseKey;
1738 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1739
1740 const GrStyledShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart,
1741 kStroke, false)];
1742 Key exampleInvStrokeCaseKey;
1743 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1744
1745 const GrStyledShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1746 kHairline, false)];
1747 Key exampleHairlineCaseKey;
1748 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1749
1750 const GrStyledShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1751 kHairline, false)];
1752 Key exampleInvHairlineCaseKey;
1753 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1754
1755 // These initializations suppress warnings.
1756 SkRRect queryRR = SkRRect::MakeEmpty();
1757 bool queryInverted = true;
1758
1759 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryInverted));
1760 REPORTER_ASSERT(r, queryRR == rrect);
1761 REPORTER_ASSERT(r, !queryInverted);
1762
1763 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryInverted));
1764 REPORTER_ASSERT(r, queryRR == rrect);
1765 REPORTER_ASSERT(r, queryInverted);
1766
1767 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryInverted));
1768 REPORTER_ASSERT(r, queryRR == rrect);
1769 REPORTER_ASSERT(r, !queryInverted);
1770
1771 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryInverted));
1772 REPORTER_ASSERT(r, queryRR == rrect);
1773 REPORTER_ASSERT(r, queryInverted);
1774
1775 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryInverted));
1776 REPORTER_ASSERT(r, queryRR == rrect);
1777 REPORTER_ASSERT(r, !queryInverted);
1778
1779 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryInverted));
1780 REPORTER_ASSERT(r, queryRR == rrect);
1781 REPORTER_ASSERT(r, queryInverted);
1782
1783 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryInverted));
1784 REPORTER_ASSERT(r, queryRR == rrect);
1785 REPORTER_ASSERT(r, !queryInverted);
1786
1787 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryInverted));
1788 REPORTER_ASSERT(r, queryRR == rrect);
1789 REPORTER_ASSERT(r, queryInverted);
1790
1791 // Remember that the key reflects the geometry before styling is applied.
1792 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1793 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1794 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1795 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
1796 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
1797 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
1798 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
1799 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
1800 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1801 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
1802
1803 for (bool inverted : {false, true}) {
1804 for (SkPathDirection dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
1805 for (unsigned start = 0; start < 8; ++start) {
1806 for (bool dash : {false, true}) {
1807 const GrStyledShape& fillCase = shapes[index(inverted, dir, start, kFill,
1808 dash)];
1809 Key fillCaseKey;
1810 make_key(&fillCaseKey, fillCase);
1811
1812 const GrStyledShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1813 kStrokeAndFill, dash)];
1814 Key strokeAndFillCaseKey;
1815 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1816
1817 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1818 // ignore dashing.
1819 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1820 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1821 TestCase a(fillCase, r);
1822 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1823 TestCase c(strokeAndFillCase, r);
1824 TestCase d(inverted ? exampleInvStrokeAndFillCase
1825 : exampleStrokeAndFillCase, r);
1826 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1827 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1828
1829 const GrStyledShape& strokeCase = shapes[index(inverted, dir, start, kStroke,
1830 dash)];
1831 const GrStyledShape& hairlineCase = shapes[index(inverted, dir, start,
1832 kHairline, dash)];
1833
1834 TestCase e(strokeCase, r);
1835 TestCase g(hairlineCase, r);
1836
1837 // Both hairline and stroke shapes must respect the dashing.
1838 if (dash) {
1839 // Dashing always ignores the inverseness. skbug.com/5421
1840 TestCase f(exampleStrokeCase, r);
1841 TestCase h(exampleHairlineCase, r);
1842 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
1843 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1844 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1845
1846 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryInverted));
1847 REPORTER_ASSERT(r, queryRR == rrect);
1848 REPORTER_ASSERT(r, !queryInverted);
1849 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryInverted));
1850 REPORTER_ASSERT(r, queryRR == rrect);
1851 REPORTER_ASSERT(r, !queryInverted);
1852
1853 // The pre-style case for the dash will match the non-dash example iff the
1854 // dir and start match (dir=cw, start=0).
1855 if (0 == expectedStart && SkPathDirection::kCW == dir) {
1856 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1857 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1858 } else {
1859 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1860 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1861 }
1862 } else {
1863 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1864 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
1865 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1866 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1867 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1868 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1869 }
1870 }
1871 }
1872 }
1873 }
1874 }
1875
DEF_TEST(GrStyledShape_lines,r)1876 DEF_TEST(GrStyledShape_lines, r) {
1877 static constexpr SkPoint kA { 1, 1};
1878 static constexpr SkPoint kB { 5, -9};
1879 static constexpr SkPoint kC {-3, 17};
1880
1881 SkPath lineAB = SkPath::Line(kA, kB);
1882 SkPath lineBA = SkPath::Line(kB, kA);
1883 SkPath lineAC = SkPath::Line(kB, kC);
1884 SkPath invLineAB = lineAB;
1885
1886 invLineAB.setFillType(SkPathFillType::kInverseEvenOdd);
1887
1888 SkPaint fill;
1889 SkPaint stroke;
1890 stroke.setStyle(SkPaint::kStroke_Style);
1891 stroke.setStrokeWidth(2.f);
1892 SkPaint hairline;
1893 hairline.setStyle(SkPaint::kStroke_Style);
1894 hairline.setStrokeWidth(0.f);
1895 SkPaint dash = stroke;
1896 dash.setPathEffect(make_dash());
1897
1898 TestCase fillAB(r, lineAB, fill);
1899 TestCase fillEmpty(r, SkPath(), fill);
1900 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1901 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1902
1903 SkPath path;
1904 path.toggleInverseFillType();
1905 TestCase fillEmptyInverted(r, path, fill);
1906 TestCase fillABInverted(r, invLineAB, fill);
1907 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
1908 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
1909
1910 TestCase strokeAB(r, lineAB, stroke);
1911 TestCase strokeBA(r, lineBA, stroke);
1912 TestCase strokeAC(r, lineAC, stroke);
1913
1914 TestCase hairlineAB(r, lineAB, hairline);
1915 TestCase hairlineBA(r, lineBA, hairline);
1916 TestCase hairlineAC(r, lineAC, hairline);
1917
1918 TestCase dashAB(r, lineAB, dash);
1919 TestCase dashBA(r, lineBA, dash);
1920 TestCase dashAC(r, lineAC, dash);
1921
1922 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1923
1924 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1925 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1926
1927 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1928 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1929
1930 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1931 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1932
1933 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1934
1935 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1936 // GrStyledShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1937 bool canonicalizeAsAB;
1938 SkPoint canonicalPts[2] {kA, kB};
1939 // Init these to suppress warnings.
1940 bool inverted = true;
1941 SkPoint pts[2] {{0, 0}, {0, 0}};
1942 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1943 if (pts[0] == kA && pts[1] == kB) {
1944 canonicalizeAsAB = true;
1945 } else if (pts[1] == kA && pts[0] == kB) {
1946 canonicalizeAsAB = false;
1947 using std::swap;
1948 swap(canonicalPts[0], canonicalPts[1]);
1949 } else {
1950 ERRORF(r, "Should return pts (a,b) or (b, a)");
1951 return;
1952 }
1953
1954 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1955 TestCase::kSameUpToPE_ComparisonExpecation);
1956 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1957 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1958 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1959 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1960 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1961 pts[0] == kA && pts[1] == kB);
1962 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1963 pts[0] == kB && pts[1] == kA);
1964
1965
1966 TestCase strokeInvAB(r, invLineAB, stroke);
1967 TestCase hairlineInvAB(r, invLineAB, hairline);
1968 TestCase dashInvAB(r, invLineAB, dash);
1969 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1970 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1971 // Dashing ignores inverse.
1972 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1973
1974 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1975 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1976 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1977 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1978 // Dashing ignores inverse.
1979 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1980 pts[0] == kA && pts[1] == kB);
1981
1982 }
1983
DEF_TEST(GrStyledShape_stroked_lines,r)1984 DEF_TEST(GrStyledShape_stroked_lines, r) {
1985 static constexpr SkScalar kIntervals1[] = {1.f, 0.f};
1986 auto dash1 = SkDashPathEffect::Make(kIntervals1, std::size(kIntervals1), 0.f);
1987 REPORTER_ASSERT(r, dash1);
1988 static constexpr SkScalar kIntervals2[] = {10.f, 0.f, 5.f, 0.f};
1989 auto dash2 = SkDashPathEffect::Make(kIntervals2, std::size(kIntervals2), 10.f);
1990 REPORTER_ASSERT(r, dash2);
1991
1992 sk_sp<SkPathEffect> pathEffects[] = {nullptr, std::move(dash1), std::move(dash2)};
1993
1994 for (const auto& pe : pathEffects) {
1995 // Paints to try
1996 SkPaint buttCap;
1997 buttCap.setStyle(SkPaint::kStroke_Style);
1998 buttCap.setStrokeWidth(4);
1999 buttCap.setStrokeCap(SkPaint::kButt_Cap);
2000 buttCap.setPathEffect(pe);
2001
2002 SkPaint squareCap = buttCap;
2003 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
2004 squareCap.setPathEffect(pe);
2005
2006 SkPaint roundCap = buttCap;
2007 roundCap.setStrokeCap(SkPaint::kRound_Cap);
2008 roundCap.setPathEffect(pe);
2009
2010 // vertical
2011 SkPath linePath;
2012 linePath.moveTo(4, 4);
2013 linePath.lineTo(4, 5);
2014
2015 SkPaint fill;
2016
2017 make_TestCase(r, linePath, buttCap)->compare(
2018 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
2019 TestCase::kAllSame_ComparisonExpecation);
2020
2021 make_TestCase(r, linePath, squareCap)->compare(
2022 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
2023 TestCase::kAllSame_ComparisonExpecation);
2024
2025 make_TestCase(r, linePath, roundCap)->compare(r,
2026 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
2027 TestCase::kAllSame_ComparisonExpecation);
2028
2029 // horizontal
2030 linePath.reset();
2031 linePath.moveTo(4, 4);
2032 linePath.lineTo(5, 4);
2033
2034 make_TestCase(r, linePath, buttCap)->compare(
2035 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
2036 TestCase::kAllSame_ComparisonExpecation);
2037 make_TestCase(r, linePath, squareCap)->compare(
2038 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
2039 TestCase::kAllSame_ComparisonExpecation);
2040 make_TestCase(r, linePath, roundCap)->compare(
2041 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
2042 TestCase::kAllSame_ComparisonExpecation);
2043
2044 // point
2045 linePath.reset();
2046 linePath.moveTo(4, 4);
2047 linePath.lineTo(4, 4);
2048
2049 make_TestCase(r, linePath, buttCap)->compare(
2050 r, TestCase(r, SkRect::MakeEmpty(), fill),
2051 TestCase::kAllSame_ComparisonExpecation);
2052 make_TestCase(r, linePath, squareCap)->compare(
2053 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
2054 TestCase::kAllSame_ComparisonExpecation);
2055 make_TestCase(r, linePath, roundCap)->compare(
2056 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
2057 TestCase::kAllSame_ComparisonExpecation);
2058 }
2059 }
2060
DEF_TEST(GrStyledShape_short_path_keys,r)2061 DEF_TEST(GrStyledShape_short_path_keys, r) {
2062 SkPaint paints[4];
2063 paints[1].setStyle(SkPaint::kStroke_Style);
2064 paints[1].setStrokeWidth(5.f);
2065 paints[2].setStyle(SkPaint::kStroke_Style);
2066 paints[2].setStrokeWidth(0.f);
2067 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
2068 paints[3].setStrokeWidth(5.f);
2069
2070 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
2071 TestCase::ComparisonExpecation expectation) {
2072 SkPath volatileA = pathA;
2073 SkPath volatileB = pathB;
2074 volatileA.setIsVolatile(true);
2075 volatileB.setIsVolatile(true);
2076 for (const SkPaint& paint : paints) {
2077 REPORTER_ASSERT(r, !GrStyledShape(volatileA, paint).hasUnstyledKey());
2078 REPORTER_ASSERT(r, !GrStyledShape(volatileB, paint).hasUnstyledKey());
2079 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
2080 TestCase caseA(PathGeo(pathA, invert), paint, r);
2081 TestCase caseB(PathGeo(pathB, invert), paint, r);
2082 caseA.compare(r, caseB, expectation);
2083 }
2084 }
2085 };
2086
2087 SkPath pathA;
2088 SkPath pathB;
2089
2090 // Two identical paths
2091 pathA.lineTo(10.f, 10.f);
2092 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2093
2094 pathB.lineTo(10.f, 10.f);
2095 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2096 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
2097
2098 // Give path b a different point
2099 pathB.reset();
2100 pathB.lineTo(10.f, 10.f);
2101 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
2102 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
2103
2104 // Give path b a different conic weight
2105 pathB.reset();
2106 pathB.lineTo(10.f, 10.f);
2107 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2108 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
2109
2110 // Give path b an extra lineTo verb
2111 pathB.reset();
2112 pathB.lineTo(10.f, 10.f);
2113 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2114 pathB.lineTo(50.f, 50.f);
2115 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
2116
2117 // Give path b a close
2118 pathB.reset();
2119 pathB.lineTo(10.f, 10.f);
2120 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2121 pathB.close();
2122 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
2123 }
2124
DEF_TEST(GrStyledShape,reporter)2125 DEF_TEST(GrStyledShape, reporter) {
2126 TArray<std::unique_ptr<Geo>> geos;
2127 TArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
2128
2129 for (auto r : { SkRect::MakeWH(10, 20),
2130 SkRect::MakeWH(-10, -20),
2131 SkRect::MakeWH(-10, 20),
2132 SkRect::MakeWH(10, -20)}) {
2133 geos.emplace_back(new RectGeo(r));
2134 SkPath rectPath;
2135 rectPath.addRect(r);
2136 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2137 PathGeo::Invert::kNo));
2138 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2139 PathGeo::Invert::kYes));
2140 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2141 PathGeo::Invert::kNo));
2142 }
2143 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
2144 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
2145 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
2146 geos.emplace_back(new RRectGeo(rr));
2147 test_rrect(reporter, rr);
2148 SkPath rectPath;
2149 rectPath.addRRect(rr);
2150 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2151 PathGeo::Invert::kNo));
2152 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2153 PathGeo::Invert::kYes));
2154 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
2155 RRectPathGeo::RRectForStroke::kYes,
2156 PathGeo::Invert::kNo));
2157 }
2158
2159 // Arcs
2160 geos.emplace_back(
2161 new ArcGeo(SkArc::Make(SkRect::MakeWH(200, 100), 12.f, 110.f, SkArc::Type::kArc)));
2162 geos.emplace_back(
2163 new ArcGeo(SkArc::Make(SkRect::MakeWH(200, 100), 12.f, 110.f, SkArc::Type::kWedge)));
2164
2165 {
2166 SkPath openRectPath;
2167 openRectPath.moveTo(0, 0);
2168 openRectPath.lineTo(10, 0);
2169 openRectPath.lineTo(10, 10);
2170 openRectPath.lineTo(0, 10);
2171 geos.emplace_back(new RRectPathGeo(
2172 openRectPath, SkRect::MakeWH(10, 10),
2173 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2174 geos.emplace_back(new RRectPathGeo(
2175 openRectPath, SkRect::MakeWH(10, 10),
2176 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
2177 rrectPathGeos.emplace_back(new RRectPathGeo(
2178 openRectPath, SkRect::MakeWH(10, 10),
2179 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2180 }
2181
2182 {
2183 SkPath quadPath;
2184 quadPath.quadTo(10, 10, 5, 8);
2185 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
2186 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
2187 }
2188
2189 {
2190 SkPath linePath;
2191 linePath.lineTo(10, 10);
2192 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
2193 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
2194 }
2195
2196 // Horizontal and vertical paths become rrects when stroked.
2197 {
2198 SkPath vLinePath;
2199 vLinePath.lineTo(0, 10);
2200 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
2201 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
2202 }
2203
2204 {
2205 SkPath hLinePath;
2206 hLinePath.lineTo(10, 0);
2207 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
2208 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
2209 }
2210
2211 for (int i = 0; i < geos.size(); ++i) {
2212 test_basic(reporter, *geos[i]);
2213 test_scale(reporter, *geos[i]);
2214 test_dash_fill(reporter, *geos[i]);
2215 test_null_dash(reporter, *geos[i]);
2216 // Test modifying various stroke params.
2217 test_stroke_param<SkScalar>(
2218 reporter, *geos[i],
2219 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
2220 SkIntToScalar(2), SkIntToScalar(4));
2221 test_stroke_join(reporter, *geos[i]);
2222 test_stroke_cap(reporter, *geos[i]);
2223 test_miter_limit(reporter, *geos[i]);
2224 test_path_effect_makes_rrect(reporter, *geos[i]);
2225 test_unknown_path_effect(reporter, *geos[i]);
2226 test_path_effect_makes_empty_shape(reporter, *geos[i]);
2227 test_path_effect_fails(reporter, *geos[i]);
2228 test_make_hairline_path_effect(reporter, *geos[i]);
2229 test_volatile_path(reporter, *geos[i]);
2230 }
2231
2232 for (int i = 0; i < rrectPathGeos.size(); ++i) {
2233 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
2234 SkPaint fillPaint;
2235 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
2236 SkRRect rrect;
2237 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
2238 fillPathCase.baseShape().asRRect(&rrect, nullptr));
2239 if (rrgeo.isNonPath(fillPaint)) {
2240 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
2241 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2242 TestCase fillRRectCase(reporter, rrect, fillPaint);
2243 fillPathCase2.compare(reporter, fillRRectCase,
2244 TestCase::kAllSame_ComparisonExpecation);
2245 }
2246 SkPaint strokePaint;
2247 strokePaint.setStrokeWidth(3.f);
2248 strokePaint.setStyle(SkPaint::kStroke_Style);
2249 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
2250 if (rrgeo.isNonPath(strokePaint)) {
2251 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr));
2252 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2253 TestCase strokeRRectCase(reporter, rrect, strokePaint);
2254 strokePathCase.compare(reporter, strokeRRectCase,
2255 TestCase::kAllSame_ComparisonExpecation);
2256 }
2257 }
2258
2259 // Test a volatile empty path.
2260 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
2261 }
2262
DEF_TEST(GrStyledShape_arcs,reporter)2263 DEF_TEST(GrStyledShape_arcs, reporter) {
2264 SkStrokeRec roundStroke(SkStrokeRec::kFill_InitStyle);
2265 roundStroke.setStrokeStyle(2.f);
2266 roundStroke.setStrokeParams(SkPaint::kRound_Cap, SkPaint::kRound_Join, 1.f);
2267
2268 SkStrokeRec squareStroke(roundStroke);
2269 squareStroke.setStrokeParams(SkPaint::kSquare_Cap, SkPaint::kRound_Join, 1.f);
2270
2271 SkStrokeRec roundStrokeAndFill(roundStroke);
2272 roundStrokeAndFill.setStrokeStyle(2.f, true);
2273
2274 static constexpr SkScalar kIntervals[] = {1, 2};
2275 auto dash = SkDashPathEffect::Make(kIntervals, std::size(kIntervals), 1.5f);
2276
2277 TArray<GrStyle> styles;
2278 styles.push_back(GrStyle::SimpleFill());
2279 styles.push_back(GrStyle::SimpleHairline());
2280 styles.push_back(GrStyle(roundStroke, nullptr));
2281 styles.push_back(GrStyle(squareStroke, nullptr));
2282 styles.push_back(GrStyle(roundStrokeAndFill, nullptr));
2283 styles.push_back(GrStyle(roundStroke, dash));
2284
2285 for (const auto& style : styles) {
2286 // An empty rect never draws anything according to SkCanvas::drawArc() docs.
2287 TestCase emptyArc(
2288 GrStyledShape::MakeArc(SkArc::Make(SkRect::MakeEmpty(), 0, 90.f, SkArc::Type::kArc),
2289 style),
2290 reporter);
2291 TestCase emptyPath(reporter, SkPath(), style);
2292 emptyArc.compare(reporter, emptyPath, TestCase::kAllSame_ComparisonExpecation);
2293
2294 static constexpr SkRect kOval1{0, 0, 50, 50};
2295 static constexpr SkRect kOval2{50, 0, 100, 50};
2296 // Test that swapping starting and ending angle doesn't change the shape unless the arc
2297 // has a path effect. Also test that different ovals produce different shapes.
2298 TestCase arc1CW(
2299 GrStyledShape::MakeArc(SkArc::Make(kOval1, 0, 90.f, SkArc::Type::kArc), style),
2300 reporter);
2301 TestCase arc1CCW(
2302 GrStyledShape::MakeArc(SkArc::Make(kOval1, 90.f, -90.f, SkArc::Type::kArc), style),
2303 reporter);
2304
2305 TestCase arc1CWWithCenter(
2306 GrStyledShape::MakeArc(SkArc::Make(kOval1, 0, 90.f, SkArc::Type::kWedge), style),
2307 reporter);
2308 TestCase arc1CCWWithCenter(
2309 GrStyledShape::MakeArc(SkArc::Make(kOval1, 90.f, -90.f, SkArc::Type::kWedge),
2310 style),
2311 reporter);
2312
2313 TestCase arc2CW(
2314 GrStyledShape::MakeArc(SkArc::Make(kOval2, 0, 90.f, SkArc::Type::kArc), style),
2315 reporter);
2316 TestCase arc2CWWithCenter(
2317 GrStyledShape::MakeArc(SkArc::Make(kOval2, 0, 90.f, SkArc::Type::kWedge), style),
2318 reporter);
2319
2320 auto reversedExepectations = style.hasPathEffect()
2321 ? TestCase::kAllDifferent_ComparisonExpecation
2322 : TestCase::kAllSame_ComparisonExpecation;
2323 arc1CW.compare(reporter, arc1CCW, reversedExepectations);
2324 arc1CWWithCenter.compare(reporter, arc1CCWWithCenter, reversedExepectations);
2325 arc1CW.compare(reporter, arc2CW, TestCase::kAllDifferent_ComparisonExpecation);
2326 arc1CW.compare(reporter, arc1CWWithCenter, TestCase::kAllDifferent_ComparisonExpecation);
2327 arc1CWWithCenter.compare(reporter, arc2CWWithCenter,
2328 TestCase::kAllDifferent_ComparisonExpecation);
2329
2330 // Test that two arcs that start at the same angle but specified differently are equivalent.
2331 TestCase arc3A(
2332 GrStyledShape::MakeArc(SkArc::Make(kOval1, 224.f, 73.f, SkArc::Type::kArc), style),
2333 reporter);
2334 TestCase arc3B(GrStyledShape::MakeArc(
2335 SkArc::Make(kOval1, 224.f - 360.f, 73.f, SkArc::Type::kArc), style),
2336 reporter);
2337 arc3A.compare(reporter, arc3B, TestCase::kAllDifferent_ComparisonExpecation);
2338
2339 // Test that an arc that traverses the entire oval (and then some) is equivalent to the
2340 // oval itself unless there is a path effect.
2341 TestCase ovalArc(GrStyledShape::MakeArc(
2342 SkArc::Make(kOval1, 150.f, -790.f, SkArc::Type::kArc), style),
2343 reporter);
2344 TestCase oval(GrStyledShape(SkRRect::MakeOval(kOval1)), reporter);
2345 auto ovalExpectations = style.hasPathEffect() ? TestCase::kAllDifferent_ComparisonExpecation
2346 : TestCase::kAllSame_ComparisonExpecation;
2347 if (style.strokeRec().getWidth() >= 0 && style.strokeRec().getCap() != SkPaint::kButt_Cap) {
2348 ovalExpectations = TestCase::kAllDifferent_ComparisonExpecation;
2349 }
2350 ovalArc.compare(reporter, oval, ovalExpectations);
2351
2352 // If the the arc starts/ends at the center then it is then equivalent to the oval only for
2353 // simple fills.
2354 TestCase ovalArcWithCenter(
2355 GrStyledShape::MakeArc(SkArc::Make(kOval1, 304.f, 1225.f, SkArc::Type::kWedge),
2356 style),
2357 reporter);
2358 ovalExpectations = style.isSimpleFill() ? TestCase::kAllSame_ComparisonExpecation
2359 : TestCase::kAllDifferent_ComparisonExpecation;
2360 ovalArcWithCenter.compare(reporter, oval, ovalExpectations);
2361 }
2362 }
2363
DEF_TEST(GrShapeInversion,r)2364 DEF_TEST(GrShapeInversion, r) {
2365 SkPath path;
2366 SkScalar radii[] = {10.f, 10.f, 10.f, 10.f,
2367 10.f, 10.f, 10.f, 10.f};
2368 path.addRoundRect(SkRect::MakeWH(50, 50), radii);
2369 path.toggleInverseFillType();
2370
2371 GrShape inverseRRect(path);
2372 GrShape rrect(inverseRRect);
2373 rrect.setInverted(false);
2374
2375 REPORTER_ASSERT(r, inverseRRect.inverted() && inverseRRect.isPath());
2376 REPORTER_ASSERT(r, !rrect.inverted() && rrect.isPath());
2377
2378 // Invertedness should be preserved after simplification
2379 inverseRRect.simplify();
2380 rrect.simplify();
2381
2382 REPORTER_ASSERT(r, inverseRRect.inverted() && inverseRRect.isRRect());
2383 REPORTER_ASSERT(r, !rrect.inverted() && rrect.isRRect());
2384
2385 // Invertedness should be reset when calling reset().
2386 inverseRRect.reset();
2387 REPORTER_ASSERT(r, !inverseRRect.inverted() && inverseRRect.isEmpty());
2388 inverseRRect.setPath(path);
2389 inverseRRect.reset();
2390 REPORTER_ASSERT(r, !inverseRRect.inverted() && inverseRRect.isEmpty());
2391 }
2392