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 <initializer_list>
9 #include <functional>
10 #include "Test.h"
11 #if SK_SUPPORT_GPU
12 #include "GrShape.h"
13 #include "SkCanvas.h"
14 #include "SkDashPathEffect.h"
15 #include "SkPath.h"
16 #include "SkPathOps.h"
17 #include "SkSurface.h"
18 #include "SkClipOpPriv.h"
19
20 using Key = SkTArray<uint32_t>;
21
make_key(Key * key,const GrShape & shape)22 static bool make_key(Key* key, const GrShape& shape) {
23 int size = shape.unstyledKeySize();
24 if (size <= 0) {
25 key->reset(0);
26 return false;
27 }
28 SkASSERT(size);
29 key->reset(size);
30 shape.writeUnstyledKey(key->begin());
31 return true;
32 }
33
paths_fill_same(const SkPath & a,const SkPath & b)34 static bool paths_fill_same(const SkPath& a, const SkPath& b) {
35 SkPath pathXor;
36 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
37 return pathXor.isEmpty();
38 }
39
test_bounds_by_rasterizing(const SkPath & path,const SkRect & bounds)40 static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
41 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
42 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
43 // rendering within the bounds (with a tolerance). Then we render the path and check that
44 // everything got clipped out.
45 static constexpr int kRes = 2000;
46 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
47 static constexpr int kTol = 0;
48 GR_STATIC_ASSERT(kRes % 4 == 0);
49 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
50 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
51 surface->getCanvas()->clear(0x0);
52 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
53 SkMatrix matrix;
54 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit);
55 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
56 surface->getCanvas()->clipRect(clip, kDifference_SkClipOp);
57 surface->getCanvas()->concat(matrix);
58 SkPaint whitePaint;
59 whitePaint.setColor(SK_ColorWHITE);
60 surface->getCanvas()->drawPath(path, whitePaint);
61 SkPixmap pixmap;
62 surface->getCanvas()->peekPixels(&pixmap);
63 #if defined(SK_BUILD_FOR_WIN)
64 // The static constexpr version in #else causes cl.exe to crash.
65 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
66 #else
67 static constexpr uint8_t kZeros[kRes] = {0};
68 #endif
69 for (int y = 0; y < kRes; ++y) {
70 const uint8_t* row = pixmap.addr8(0, y);
71 if (0 != memcmp(kZeros, row, kRes)) {
72 return false;
73 }
74 }
75 #ifdef SK_BUILD_FOR_WIN
76 free(const_cast<uint8_t*>(kZeros));
77 #endif
78 return true;
79 }
80
81 namespace {
82 /**
83 * Geo is a factory for creating a GrShape from another representation. It also answers some
84 * questions about expected behavior for GrShape given the inputs.
85 */
86 class Geo {
87 public:
~Geo()88 virtual ~Geo() {}
89 virtual GrShape makeShape(const SkPaint&) const = 0;
90 virtual SkPath path() const = 0;
91 // These functions allow tests to check for special cases where style gets
92 // applied by GrShape in its constructor (without calling GrShape::applyStyle).
93 // These unfortunately rely on knowing details of GrShape's implementation.
94 // These predicates are factored out here to avoid littering the rest of the
95 // test code with GrShape implementation details.
fillChangesGeom() const96 virtual bool fillChangesGeom() const { return false; }
strokeIsConvertedToFill() const97 virtual bool strokeIsConvertedToFill() const { return false; }
strokeAndFillIsConvertedToFill(const SkPaint &) const98 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
99 // Is this something we expect GrShape to recognize as something simpler than a path.
isNonPath(const SkPaint & paint) const100 virtual bool isNonPath(const SkPaint& paint) const { return true; }
101 };
102
103 class RectGeo : public Geo {
104 public:
RectGeo(const SkRect & rect)105 RectGeo(const SkRect& rect) : fRect(rect) {}
106
path() const107 SkPath path() const override {
108 SkPath path;
109 path.addRect(fRect);
110 return path;
111 }
112
makeShape(const SkPaint & paint) const113 GrShape makeShape(const SkPaint& paint) const override {
114 return GrShape(fRect, paint);
115 }
116
strokeAndFillIsConvertedToFill(const SkPaint & paint) const117 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
118 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
119 // Converted to an outset rectangle.
120 return paint.getStrokeJoin() == SkPaint::kMiter_Join &&
121 paint.getStrokeMiter() >= SK_ScalarSqrt2;
122 }
123
124 private:
125 SkRect fRect;
126 };
127
128 class RRectGeo : public Geo {
129 public:
RRectGeo(const SkRRect & rrect)130 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
131
makeShape(const SkPaint & paint) const132 GrShape makeShape(const SkPaint& paint) const override {
133 return GrShape(fRRect, paint);
134 }
135
path() const136 SkPath path() const override {
137 SkPath path;
138 path.addRRect(fRRect);
139 return path;
140 }
141
strokeAndFillIsConvertedToFill(const SkPaint & paint) const142 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
143 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
144 if (fRRect.isRect()) {
145 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
146 }
147 return false;
148 }
149
150 private:
151 SkRRect fRRect;
152 };
153
154 class PathGeo : public Geo {
155 public:
156 enum class Invert { kNo, kYes };
157
PathGeo(const SkPath & path,Invert invert)158 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
159 SkASSERT(!path.isInverseFillType());
160 if (Invert::kYes == invert) {
161 if (fPath.getFillType() == SkPath::kEvenOdd_FillType) {
162 fPath.setFillType(SkPath::kInverseEvenOdd_FillType);
163 } else {
164 SkASSERT(fPath.getFillType() == SkPath::kWinding_FillType);
165 fPath.setFillType(SkPath::kInverseWinding_FillType);
166 }
167 }
168 }
169
makeShape(const SkPaint & paint) const170 GrShape makeShape(const SkPaint& paint) const override {
171 return GrShape(fPath, paint);
172 }
173
path() const174 SkPath path() const override { return fPath; }
175
fillChangesGeom() const176 bool fillChangesGeom() const override {
177 // unclosed rects get closed. Lines get turned into empty geometry
178 return this->isUnclosedRect() || (fPath.isLine(nullptr) && !fPath.isInverseFillType());
179 }
180
strokeIsConvertedToFill() const181 bool strokeIsConvertedToFill() const override {
182 return this->isAxisAlignedLine();
183 }
184
strokeAndFillIsConvertedToFill(const SkPaint & paint) const185 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
186 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
187 if (this->isAxisAlignedLine()) {
188 // The fill is ignored (zero area) and the stroke is converted to a rrect.
189 return true;
190 }
191 SkRect rect;
192 unsigned start;
193 SkPath::Direction dir;
194 if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
195 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
196 }
197 return false;
198 }
199
isNonPath(const SkPaint & paint) const200 bool isNonPath(const SkPaint& paint) const override {
201 return fPath.isLine(nullptr) || fPath.isEmpty();
202 }
203
204 private:
isAxisAlignedLine() const205 bool isAxisAlignedLine() const {
206 SkPoint pts[2];
207 if (!fPath.isLine(pts)) {
208 return false;
209 }
210 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
211 }
212
isUnclosedRect() const213 bool isUnclosedRect() const {
214 bool closed;
215 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
216 }
217
218 SkPath fPath;
219 };
220
221 class RRectPathGeo : public PathGeo {
222 public:
223 enum class RRectForStroke { kNo, kYes };
224
RRectPathGeo(const SkPath & path,const SkRRect & equivalentRRect,RRectForStroke rrectForStroke,Invert invert)225 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
226 Invert invert)
227 : PathGeo(path, invert)
228 , fRRect(equivalentRRect)
229 , fRRectForStroke(rrectForStroke) {}
230
RRectPathGeo(const SkPath & path,const SkRect & equivalentRect,RRectForStroke rrectForStroke,Invert invert)231 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
232 Invert invert)
233 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
234
isNonPath(const SkPaint & paint) const235 bool isNonPath(const SkPaint& paint) const override {
236 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
237 return true;
238 }
239 return false;
240 }
241
rrect() const242 const SkRRect& rrect() const { return fRRect; }
243
244 private:
245 SkRRect fRRect;
246 RRectForStroke fRRectForStroke;
247 };
248
249 class TestCase {
250 public:
TestCase(const Geo & geo,const SkPaint & paint,skiatest::Reporter * r,SkScalar scale=SK_Scalar1)251 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
252 SkScalar scale = SK_Scalar1) : fBase(geo.makeShape(paint)) {
253 this->init(r, scale);
254 }
255
256 template<typename... ShapeArgs>
TestCase(skiatest::Reporter * r,ShapeArgs...shapeArgs)257 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs)
258 : fBase(shapeArgs...) {
259 this->init(r, SK_Scalar1);
260 }
261
TestCase(const GrShape & shape,skiatest::Reporter * r,SkScalar scale=SK_Scalar1)262 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
263 : fBase(shape) {
264 this->init(r, scale);
265 }
266
267 struct SelfExpectations {
268 bool fPEHasEffect;
269 bool fPEHasValidKey;
270 bool fStrokeApplies;
271 };
272
273 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
274
275 enum ComparisonExpecation {
276 kAllDifferent_ComparisonExpecation,
277 kSameUpToPE_ComparisonExpecation,
278 kSameUpToStroke_ComparisonExpecation,
279 kAllSame_ComparisonExpecation,
280 };
281
282 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
283
baseShape() const284 const GrShape& baseShape() const { return fBase; }
appliedPathEffectShape() const285 const GrShape& appliedPathEffectShape() const { return fAppliedPE; }
appliedFullStyleShape() const286 const GrShape& appliedFullStyleShape() const { return fAppliedFull; }
287
288 // The returned array's count will be 0 if the key shape has no key.
baseKey() const289 const Key& baseKey() const { return fBaseKey; }
appliedPathEffectKey() const290 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
appliedFullStyleKey() const291 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
appliedPathEffectThenStrokeKey() const292 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
293
294 private:
CheckBounds(skiatest::Reporter * r,const GrShape & shape,const SkRect & bounds)295 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
296 SkPath path;
297 shape.asPath(&path);
298 // If the bounds are empty, the path ought to be as well.
299 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
300 REPORTER_ASSERT(r, path.isEmpty());
301 return;
302 }
303 if (path.isEmpty()) {
304 return;
305 }
306 // The bounds API explicitly calls out that it does not consider inverseness.
307 SkPath p = path;
308 p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType()));
309 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
310 }
311
init(skiatest::Reporter * r,SkScalar scale)312 void init(skiatest::Reporter* r, SkScalar scale) {
313 fAppliedPE = fBase.applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
314 fAppliedPEThenStroke = fAppliedPE.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec,
315 scale);
316 fAppliedFull = fBase.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
317
318 make_key(&fBaseKey, fBase);
319 make_key(&fAppliedPEKey, fAppliedPE);
320 make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke);
321 make_key(&fAppliedFullKey, fAppliedFull);
322
323 // Applying the path effect and then the stroke should always be the same as applying
324 // both in one go.
325 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
326 SkPath a, b;
327 fAppliedPEThenStroke.asPath(&a);
328 fAppliedFull.asPath(&b);
329 // If the output of the path effect is a rrect then it is possible for a and b to be
330 // different paths that fill identically. The reason is that fAppliedFull will do this:
331 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
332 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
333 // now that there is no longer a path effect, the direction and starting index get
334 // canonicalized before the stroke.
335 if (fAppliedPE.asRRect(nullptr, nullptr, nullptr, nullptr)) {
336 REPORTER_ASSERT(r, paths_fill_same(a, b));
337 } else {
338 REPORTER_ASSERT(r, a == b);
339 }
340 REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty());
341
342 SkPath path;
343 fBase.asPath(&path);
344 REPORTER_ASSERT(r, path.isEmpty() == fBase.isEmpty());
345 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase.segmentMask());
346 fAppliedPE.asPath(&path);
347 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE.isEmpty());
348 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE.segmentMask());
349 fAppliedFull.asPath(&path);
350 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull.isEmpty());
351 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull.segmentMask());
352
353 CheckBounds(r, fBase, fBase.bounds());
354 CheckBounds(r, fAppliedPE, fAppliedPE.bounds());
355 CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds());
356 CheckBounds(r, fAppliedFull, fAppliedFull.bounds());
357 SkRect styledBounds = fBase.styledBounds();
358 CheckBounds(r, fAppliedFull, styledBounds);
359 styledBounds = fAppliedPE.styledBounds();
360 CheckBounds(r, fAppliedFull, styledBounds);
361
362 // Check that the same path is produced when style is applied by GrShape and GrStyle.
363 SkPath preStyle;
364 SkPath postPathEffect;
365 SkPath postAllStyle;
366
367 fBase.asPath(&preStyle);
368 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
369 if (fBase.style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
370 scale)) {
371 // run postPathEffect through GrShape to get any geometry reductions that would have
372 // occurred to fAppliedPE.
373 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
374
375 SkPath testPath;
376 fAppliedPE.asPath(&testPath);
377 REPORTER_ASSERT(r, testPath == postPathEffect);
378 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE.style().strokeRec()));
379 }
380 SkStrokeRec::InitStyle fillOrHairline;
381 if (fBase.style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
382 SkPath testPath;
383 fAppliedFull.asPath(&testPath);
384 if (fBase.style().hasPathEffect()) {
385 // Because GrShape always does two-stage application when there is a path effect
386 // there may be a reduction/canonicalization step between the path effect and
387 // strokerec not reflected in postAllStyle since it applied both the path effect
388 // and strokerec without analyzing the intermediate path.
389 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
390 } else {
391 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape
392 // would apply.
393 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
394 REPORTER_ASSERT(r, testPath == postAllStyle);
395 }
396
397 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
398 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleFill());
399 } else {
400 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline());
401 }
402 }
403 }
404
405 GrShape fBase;
406 GrShape fAppliedPE;
407 GrShape fAppliedPEThenStroke;
408 GrShape fAppliedFull;
409
410 Key fBaseKey;
411 Key fAppliedPEKey;
412 Key fAppliedPEThenStrokeKey;
413 Key fAppliedFullKey;
414 };
415
testExpectations(skiatest::Reporter * reporter,SelfExpectations expectations) const416 void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
417 // The base's key should always be valid (unless the path is volatile)
418 REPORTER_ASSERT(reporter, fBaseKey.count());
419 if (expectations.fPEHasEffect) {
420 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
421 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
422 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
423 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
424 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
425 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
426 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
427 }
428 } else {
429 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
430 SkPath a, b;
431 fBase.asPath(&a);
432 fAppliedPE.asPath(&b);
433 REPORTER_ASSERT(reporter, a == b);
434 if (expectations.fStrokeApplies) {
435 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
436 } else {
437 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
438 }
439 }
440 }
441
can_interchange_winding_and_even_odd_fill(const GrShape & shape)442 static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
443 SkPath path;
444 shape.asPath(&path);
445 if (shape.style().hasNonDashPathEffect()) {
446 return false;
447 }
448 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
449 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
450 strokeRecStyle == SkStrokeRec::kHairline_Style ||
451 (shape.style().isSimpleFill() && path.isConvex());
452 }
453
check_equivalence(skiatest::Reporter * r,const GrShape & a,const GrShape & b,const Key & keyA,const Key & keyB)454 static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
455 const Key& keyA, const Key& keyB) {
456 // GrShape only respects the input winding direction and start point for rrect shapes
457 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
458 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
459 // key will differ. GrShape will have canonicalized the direction and start point for the shape
460 // without the path effect. If *both* have path effects then they should have both preserved
461 // the direction and starting point.
462
463 // The asRRect() output params are all initialized just to silence compiler warnings about
464 // uninitialized variables.
465 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
466 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
467 unsigned startA = ~0U, startB = ~0U;
468 bool invertedA = true, invertedB = true;
469
470 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
471 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
472 bool aHasPE = a.style().hasPathEffect();
473 bool bHasPE = b.style().hasPathEffect();
474 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
475 // GrShape will close paths with simple fill style.
476 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
477 SkPath pathA, pathB;
478 a.asPath(&pathA);
479 b.asPath(&pathB);
480
481 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
482 // non-inverse fill type (or vice versa).
483 bool ignoreInversenessDifference = false;
484 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
485 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
486 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
487 bool canDropInverse1 = s1->style().isDashed();
488 bool canDropInverse2 = s2->style().isDashed();
489 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
490 }
491 bool ignoreWindingVsEvenOdd = false;
492 if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
493 SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
494 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
495 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
496 if (aCanChange != bCanChange) {
497 ignoreWindingVsEvenOdd = true;
498 }
499 }
500 if (allowSameRRectButDiffStartAndDir) {
501 REPORTER_ASSERT(r, rrectA == rrectB);
502 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
503 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
504 } else {
505 SkPath pA = pathA;
506 SkPath pB = pathB;
507 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
508 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
509 if (ignoreInversenessDifference) {
510 pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
511 pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
512 }
513 if (ignoreWindingVsEvenOdd) {
514 pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
515 : SkPath::kEvenOdd_FillType);
516 pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
517 : SkPath::kEvenOdd_FillType);
518 }
519 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
520 REPORTER_ASSERT(r, keyA == keyB);
521 } else {
522 REPORTER_ASSERT(r, keyA != keyB);
523 }
524 if (allowedClosednessDiff) {
525 // GrShape will close paths with simple fill style. Make the non-filled path closed
526 // so that the comparision will succeed. Make sure both are closed before comparing.
527 pA.close();
528 pB.close();
529 }
530 REPORTER_ASSERT(r, pA == pB);
531 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
532 if (aIsRRect) {
533 REPORTER_ASSERT(r, rrectA == rrectB);
534 REPORTER_ASSERT(r, dirA == dirB);
535 REPORTER_ASSERT(r, startA == startB);
536 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
537 }
538 }
539 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
540 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
541 // closedness can affect convexity.
542 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
543 if (a.knownToBeConvex()) {
544 REPORTER_ASSERT(r, pathA.isConvex());
545 }
546 if (b.knownToBeConvex()) {
547 REPORTER_ASSERT(r, pathB.isConvex());
548 }
549 REPORTER_ASSERT(r, a.bounds() == b.bounds());
550 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
551 // Init these to suppress warnings.
552 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
553 bool invertedLine[2] {true, true};
554 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
555 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
556 // doesn't (since the PE can set any fill type on its output path).
557 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
558 // then they may disagree about inverseness.
559 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
560 a.style().isDashed() == b.style().isDashed()) {
561 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
562 b.mayBeInverseFilledAfterStyling());
563 }
564 if (a.asLine(nullptr, nullptr)) {
565 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
566 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
567 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
568 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
569 }
570 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
571 }
572
compare(skiatest::Reporter * r,const TestCase & that,ComparisonExpecation expectation) const573 void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
574 ComparisonExpecation expectation) const {
575 SkPath a, b;
576 switch (expectation) {
577 case kAllDifferent_ComparisonExpecation:
578 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
579 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
580 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
581 break;
582 case kSameUpToPE_ComparisonExpecation:
583 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
584 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
585 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
586 break;
587 case kSameUpToStroke_ComparisonExpecation:
588 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
589 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
590 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
591 break;
592 case kAllSame_ComparisonExpecation:
593 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
594 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
595 check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey,
596 that.fAppliedFullKey);
597 break;
598 }
599 }
600 } // namespace
601
make_dash()602 static sk_sp<SkPathEffect> make_dash() {
603 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
604 static const SkScalar kPhase = 0.75;
605 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
606 }
607
make_null_dash()608 static sk_sp<SkPathEffect> make_null_dash() {
609 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
610 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
611 }
612
test_basic(skiatest::Reporter * reporter,const Geo & geo)613 static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
614 sk_sp<SkPathEffect> dashPE = make_dash();
615
616 TestCase::SelfExpectations expectations;
617 SkPaint fill;
618
619 TestCase fillCase(geo, fill, reporter);
620 expectations.fPEHasEffect = false;
621 expectations.fPEHasValidKey = false;
622 expectations.fStrokeApplies = false;
623 fillCase.testExpectations(reporter, expectations);
624 // Test that another GrShape instance built from the same primitive is the same.
625 TestCase(geo, fill, reporter).compare(reporter, fillCase,
626 TestCase::kAllSame_ComparisonExpecation);
627
628 SkPaint stroke2RoundBevel;
629 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
630 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
631 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
632 stroke2RoundBevel.setStrokeWidth(2.f);
633 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
634 expectations.fPEHasValidKey = true;
635 expectations.fPEHasEffect = false;
636 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
637 stroke2RoundBevelCase.testExpectations(reporter, expectations);
638 TestCase(geo, stroke2RoundBevel, reporter).compare(reporter, stroke2RoundBevelCase,
639 TestCase::kAllSame_ComparisonExpecation);
640
641 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
642 stroke2RoundBevelDash.setPathEffect(make_dash());
643 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
644 expectations.fPEHasValidKey = true;
645 expectations.fPEHasEffect = true;
646 expectations.fStrokeApplies = true;
647 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
648 TestCase(geo, stroke2RoundBevelDash, reporter).compare(reporter, stroke2RoundBevelDashCase,
649 TestCase::kAllSame_ComparisonExpecation);
650
651 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
652 fillCase.compare(reporter, stroke2RoundBevelCase,
653 TestCase::kAllDifferent_ComparisonExpecation);
654 fillCase.compare(reporter, stroke2RoundBevelDashCase,
655 TestCase::kAllDifferent_ComparisonExpecation);
656 } else {
657 fillCase.compare(reporter, stroke2RoundBevelCase,
658 TestCase::kSameUpToStroke_ComparisonExpecation);
659 fillCase.compare(reporter, stroke2RoundBevelDashCase,
660 TestCase::kSameUpToPE_ComparisonExpecation);
661 }
662 if (geo.strokeIsConvertedToFill()) {
663 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
664 TestCase::kAllDifferent_ComparisonExpecation);
665 } else {
666 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
667 TestCase::kSameUpToPE_ComparisonExpecation);
668 }
669
670 // Stroke and fill cases
671 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
672 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
673 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
674 expectations.fPEHasValidKey = true;
675 expectations.fPEHasEffect = false;
676 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
677 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
678 TestCase(geo, stroke2RoundBevelAndFill, reporter).compare(reporter,
679 stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
680
681 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
682 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
683 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
684 expectations.fPEHasValidKey = true;
685 expectations.fPEHasEffect = false;
686 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
687 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
688 TestCase(geo, stroke2RoundBevelAndFillDash, reporter).compare(
689 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
690 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
691 TestCase::kAllSame_ComparisonExpecation);
692
693 SkPaint hairline;
694 hairline.setStyle(SkPaint::kStroke_Style);
695 hairline.setStrokeWidth(0.f);
696 TestCase hairlineCase(geo, hairline, reporter);
697 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
698 // in the line and unclosed rect cases).
699 if (geo.fillChangesGeom()) {
700 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
701 } else {
702 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
703 }
704 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
705 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
706 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
707
708 }
709
test_scale(skiatest::Reporter * reporter,const Geo & geo)710 static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
711 sk_sp<SkPathEffect> dashPE = make_dash();
712
713 static const SkScalar kS1 = 1.f;
714 static const SkScalar kS2 = 2.f;
715
716 SkPaint fill;
717 TestCase fillCase1(geo, fill, reporter, kS1);
718 TestCase fillCase2(geo, fill, reporter, kS2);
719 // Scale doesn't affect fills.
720 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
721
722 SkPaint hairline;
723 hairline.setStyle(SkPaint::kStroke_Style);
724 hairline.setStrokeWidth(0.f);
725 TestCase hairlineCase1(geo, hairline, reporter, kS1);
726 TestCase hairlineCase2(geo, hairline, reporter, kS2);
727 // Scale doesn't affect hairlines.
728 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
729
730 SkPaint stroke;
731 stroke.setStyle(SkPaint::kStroke_Style);
732 stroke.setStrokeWidth(2.f);
733 TestCase strokeCase1(geo, stroke, reporter, kS1);
734 TestCase strokeCase2(geo, stroke, reporter, kS2);
735 // Scale affects the stroke
736 if (geo.strokeIsConvertedToFill()) {
737 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
738 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
739 } else {
740 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
741 }
742
743 SkPaint strokeDash = stroke;
744 strokeDash.setPathEffect(make_dash());
745 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
746 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
747 // Scale affects the dash and the stroke.
748 strokeDashCase1.compare(reporter, strokeDashCase2,
749 TestCase::kSameUpToPE_ComparisonExpecation);
750
751 // Stroke and fill cases
752 SkPaint strokeAndFill = stroke;
753 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
754 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
755 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
756 SkPaint strokeAndFillDash = strokeDash;
757 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
758 // Dash is ignored for stroke and fill
759 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
760 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
761 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
762 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
763 // geometries should agree.
764 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
765 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
766 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
767 TestCase::kAllSame_ComparisonExpecation);
768 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
769 TestCase::kAllSame_ComparisonExpecation);
770 } else {
771 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
772 TestCase::kSameUpToStroke_ComparisonExpecation);
773 }
774 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
775 TestCase::kAllSame_ComparisonExpecation);
776 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
777 TestCase::kAllSame_ComparisonExpecation);
778 }
779
780 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)781 static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
782 std::function<void(SkPaint*, T)> setter, T a, T b,
783 bool paramAffectsStroke,
784 bool paramAffectsDashAndStroke) {
785 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
786 // that it can override the stroke width.
787 SkPaint strokeA;
788 strokeA.setStyle(SkPaint::kStroke_Style);
789 strokeA.setStrokeWidth(2.f);
790 setter(&strokeA, a);
791 SkPaint strokeB;
792 strokeB.setStyle(SkPaint::kStroke_Style);
793 strokeB.setStrokeWidth(2.f);
794 setter(&strokeB, b);
795
796 TestCase strokeACase(geo, strokeA, reporter);
797 TestCase strokeBCase(geo, strokeB, reporter);
798 if (paramAffectsStroke) {
799 // If stroking is immediately incorporated into a geometric transformation then the base
800 // shapes will differ.
801 if (geo.strokeIsConvertedToFill()) {
802 strokeACase.compare(reporter, strokeBCase,
803 TestCase::kAllDifferent_ComparisonExpecation);
804 } else {
805 strokeACase.compare(reporter, strokeBCase,
806 TestCase::kSameUpToStroke_ComparisonExpecation);
807 }
808 } else {
809 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
810 }
811
812 SkPaint strokeAndFillA = strokeA;
813 SkPaint strokeAndFillB = strokeB;
814 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
815 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
816 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
817 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
818 if (paramAffectsStroke) {
819 // If stroking is immediately incorporated into a geometric transformation then the base
820 // shapes will differ.
821 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
822 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
823 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
824 TestCase::kAllDifferent_ComparisonExpecation);
825 } else {
826 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
827 TestCase::kSameUpToStroke_ComparisonExpecation);
828 }
829 } else {
830 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
831 TestCase::kAllSame_ComparisonExpecation);
832 }
833
834 // Make sure stroking params don't affect fill style.
835 SkPaint fillA = strokeA, fillB = strokeB;
836 fillA.setStyle(SkPaint::kFill_Style);
837 fillB.setStyle(SkPaint::kFill_Style);
838 TestCase fillACase(geo, fillA, reporter);
839 TestCase fillBCase(geo, fillB, reporter);
840 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
841
842 // Make sure just applying the dash but not stroke gives the same key for both stroking
843 // variations.
844 SkPaint dashA = strokeA, dashB = strokeB;
845 dashA.setPathEffect(make_dash());
846 dashB.setPathEffect(make_dash());
847 TestCase dashACase(geo, dashA, reporter);
848 TestCase dashBCase(geo, dashB, reporter);
849 if (paramAffectsDashAndStroke) {
850 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
851 } else {
852 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
853 }
854 }
855
856 template <typename T>
test_stroke_param(skiatest::Reporter * reporter,const Geo & geo,std::function<void (SkPaint *,T)> setter,T a,T b)857 static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
858 std::function<void(SkPaint*, T)> setter, T a, T b) {
859 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
860 };
861
test_stroke_cap(skiatest::Reporter * reporter,const Geo & geo)862 static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
863 SkPaint hairline;
864 hairline.setStrokeWidth(0);
865 hairline.setStyle(SkPaint::kStroke_Style);
866 GrShape shape = geo.makeShape(hairline);
867 // The cap should only affect shapes that may be open.
868 bool affectsStroke = !shape.knownToBeClosed();
869 // Dashing adds ends that need caps.
870 bool affectsDashAndStroke = true;
871 test_stroke_param_impl<SkPaint::Cap>(
872 reporter,
873 geo,
874 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
875 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
876 affectsStroke,
877 affectsDashAndStroke);
878 };
879
shape_known_not_to_have_joins(const GrShape & shape)880 static bool shape_known_not_to_have_joins(const GrShape& shape) {
881 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
882 }
883
test_stroke_join(skiatest::Reporter * reporter,const Geo & geo)884 static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
885 SkPaint hairline;
886 hairline.setStrokeWidth(0);
887 hairline.setStyle(SkPaint::kStroke_Style);
888 GrShape shape = geo.makeShape(hairline);
889 // GrShape recognizes certain types don't have joins and will prevent the join type from
890 // affecting the style key.
891 // Dashing doesn't add additional joins. However, GrShape currently loses track of this
892 // after applying the dash.
893 bool affectsStroke = !shape_known_not_to_have_joins(shape);
894 test_stroke_param_impl<SkPaint::Join>(
895 reporter,
896 geo,
897 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
898 SkPaint::kRound_Join, SkPaint::kBevel_Join,
899 affectsStroke, true);
900 };
901
test_miter_limit(skiatest::Reporter * reporter,const Geo & geo)902 static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
903 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
904 p->setStrokeJoin(SkPaint::kMiter_Join);
905 p->setStrokeMiter(miter);
906 };
907
908 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
909 p->setStrokeJoin(SkPaint::kRound_Join);
910 p->setStrokeMiter(miter);
911 };
912
913 SkPaint hairline;
914 hairline.setStrokeWidth(0);
915 hairline.setStyle(SkPaint::kStroke_Style);
916 GrShape shape = geo.makeShape(hairline);
917 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
918
919 // The miter limit should affect stroked and dashed-stroked cases when the join type is
920 // miter.
921 test_stroke_param_impl<SkScalar>(
922 reporter,
923 geo,
924 setMiterJoinAndLimit,
925 0.5f, 0.75f,
926 mayHaveJoins,
927 true);
928
929 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
930 // not miter.
931 test_stroke_param_impl<SkScalar>(
932 reporter,
933 geo,
934 setOtherJoinAndLimit,
935 0.5f, 0.75f,
936 false,
937 false);
938 }
939
test_dash_fill(skiatest::Reporter * reporter,const Geo & geo)940 static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
941 // A dash with no stroke should have no effect
942 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
943 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
944 SkPaint dashFill;
945 dashFill.setPathEffect((*md)());
946 TestCase dashFillCase(geo, dashFill, reporter);
947
948 TestCase fillCase(geo, SkPaint(), reporter);
949 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
950 }
951 }
952
test_null_dash(skiatest::Reporter * reporter,const Geo & geo)953 void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
954 SkPaint fill;
955 SkPaint stroke;
956 stroke.setStyle(SkPaint::kStroke_Style);
957 stroke.setStrokeWidth(1.f);
958 SkPaint dash;
959 dash.setStyle(SkPaint::kStroke_Style);
960 dash.setStrokeWidth(1.f);
961 dash.setPathEffect(make_dash());
962 SkPaint nullDash;
963 nullDash.setStyle(SkPaint::kStroke_Style);
964 nullDash.setStrokeWidth(1.f);
965 nullDash.setPathEffect(make_null_dash());
966
967 TestCase fillCase(geo, fill, reporter);
968 TestCase strokeCase(geo, stroke, reporter);
969 TestCase dashCase(geo, dash, reporter);
970 TestCase nullDashCase(geo, nullDash, reporter);
971
972 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
973 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
974 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
975 // on construction in order to determine how to compare the fill and stroke.
976 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
977 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
978 } else {
979 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
980 }
981 // In the null dash case we may immediately convert to a fill, but not for the normal dash case.
982 if (geo.strokeIsConvertedToFill()) {
983 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
984 } else {
985 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
986 }
987 }
988
test_path_effect_makes_rrect(skiatest::Reporter * reporter,const Geo & geo)989 void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
990 /**
991 * This path effect takes any input path and turns it into a rrect. It passes through stroke
992 * info.
993 */
994 class RRectPathEffect : SkPathEffect {
995 public:
996 static const SkRRect& RRect() {
997 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
998 return kRRect;
999 }
1000
1001 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1002 const SkRect* cullR) const override {
1003 dst->reset();
1004 dst->addRRect(RRect());
1005 return true;
1006 }
1007 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1008 *dst = RRect().getBounds();
1009 }
1010 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1011 Factory getFactory() const override { return nullptr; }
1012 void toString(SkString*) const override {}
1013 private:
1014 RRectPathEffect() {}
1015 };
1016
1017 SkPaint fill;
1018 TestCase fillGeoCase(geo, fill, reporter);
1019
1020 SkPaint pe;
1021 pe.setPathEffect(RRectPathEffect::Make());
1022 TestCase geoPECase(geo, pe, reporter);
1023
1024 SkPaint peStroke;
1025 peStroke.setPathEffect(RRectPathEffect::Make());
1026 peStroke.setStrokeWidth(2.f);
1027 peStroke.setStyle(SkPaint::kStroke_Style);
1028 TestCase geoPEStrokeCase(geo, peStroke, reporter);
1029
1030 // Check whether constructing the filled case would cause the base shape to have a different
1031 // geometry (because of a geometric transformation upon initial GrShape construction).
1032 if (geo.fillChangesGeom()) {
1033 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1034 fillGeoCase.compare(reporter, geoPEStrokeCase,
1035 TestCase::kAllDifferent_ComparisonExpecation);
1036 } else {
1037 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1038 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1039 }
1040 geoPECase.compare(reporter, geoPEStrokeCase,
1041 TestCase::kSameUpToStroke_ComparisonExpecation);
1042
1043 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
1044 SkPaint stroke = peStroke;
1045 stroke.setPathEffect(nullptr);
1046 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
1047
1048 SkRRect rrect;
1049 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1050 // geoPECase, so the full style should be the same as just the PE.
1051 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1052 nullptr));
1053 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1054 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1055
1056 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1057 nullptr));
1058 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1059 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1060
1061 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
1062 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1063 nullptr, nullptr));
1064 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1065 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1066
1067 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1068 nullptr, nullptr));
1069 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1070 rrectStrokeCase.appliedFullStyleKey());
1071 }
1072
test_unknown_path_effect(skiatest::Reporter * reporter,const Geo & geo)1073 void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
1074 /**
1075 * This path effect just adds two lineTos to the input path.
1076 */
1077 class AddLineTosPathEffect : SkPathEffect {
1078 public:
1079 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1080 const SkRect* cullR) const override {
1081 *dst = src;
1082 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1083 for (int i = 0; i < 100; ++i) {
1084 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1085 }
1086 return true;
1087 }
1088 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1089 *dst = src;
1090 dst->growToInclude(0, 0);
1091 dst->growToInclude(100, 100);
1092 }
1093 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1094 Factory getFactory() const override { return nullptr; }
1095 void toString(SkString*) const override {}
1096 private:
1097 AddLineTosPathEffect() {}
1098 };
1099
1100 // This path effect should make the keys invalid when it is applied. We only produce a path
1101 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1102 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1103 SkPaint peStroke;
1104 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1105 peStroke.setStrokeWidth(2.f);
1106 peStroke.setStyle(SkPaint::kStroke_Style);
1107 TestCase geoPEStrokeCase(geo, peStroke, reporter);
1108 TestCase::SelfExpectations expectations;
1109 expectations.fPEHasEffect = true;
1110 expectations.fPEHasValidKey = false;
1111 expectations.fStrokeApplies = true;
1112 geoPEStrokeCase.testExpectations(reporter, expectations);
1113 }
1114
test_make_hairline_path_effect(skiatest::Reporter * reporter,const Geo & geo)1115 void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
1116 /**
1117 * This path effect just changes the stroke rec to hairline.
1118 */
1119 class MakeHairlinePathEffect : SkPathEffect {
1120 public:
1121 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1122 const SkRect* cullR) const override {
1123 *dst = src;
1124 strokeRec->setHairlineStyle();
1125 return true;
1126 }
1127 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; }
1128 static sk_sp<SkPathEffect> Make() {
1129 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1130 }
1131 Factory getFactory() const override { return nullptr; }
1132 void toString(SkString*) const override {}
1133 private:
1134 MakeHairlinePathEffect() {}
1135 };
1136
1137 SkPaint fill;
1138 SkPaint pe;
1139 pe.setPathEffect(MakeHairlinePathEffect::Make());
1140
1141 TestCase peCase(geo, pe, reporter);
1142
1143 SkPath a, b, c;
1144 peCase.baseShape().asPath(&a);
1145 peCase.appliedPathEffectShape().asPath(&b);
1146 peCase.appliedFullStyleShape().asPath(&c);
1147 if (geo.isNonPath(pe)) {
1148 // RRect types can have a change in start index or direction after the PE is applied. This
1149 // is because once the PE is applied, GrShape may canonicalize the dir and index since it
1150 // is not germane to the styling any longer.
1151 // Instead we just check that the paths would fill the same both before and after styling.
1152 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1153 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
1154 } else {
1155 // The base shape cannot perform canonicalization on the path's fill type because of an
1156 // unknown path effect. However, after the path effect is applied the resulting hairline
1157 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1158 // don't distinguish between even/odd and non-zero winding.
1159 a.setFillType(b.getFillType());
1160 REPORTER_ASSERT(reporter, a == b);
1161 REPORTER_ASSERT(reporter, a == c);
1162 // If the resulting path is small enough then it will have a key.
1163 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1164 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
1165 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1166 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
1167 }
1168 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1169 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
1170 }
1171
test_volatile_path(skiatest::Reporter * reporter,const Geo & geo)1172 void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1173 SkPath vPath = geo.path();
1174 vPath.setIsVolatile(true);
1175
1176 SkPaint dashAndStroke;
1177 dashAndStroke.setPathEffect(make_dash());
1178 dashAndStroke.setStrokeWidth(2.f);
1179 dashAndStroke.setStyle(SkPaint::kStroke_Style);
1180 TestCase volatileCase(reporter, vPath, dashAndStroke);
1181 // We expect a shape made from a volatile path to have a key iff the shape is recognized
1182 // as a specialized geometry.
1183 if (geo.isNonPath(dashAndStroke)) {
1184 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1185 // In this case all the keys should be identical to the non-volatile case.
1186 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
1187 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1188 } else {
1189 // None of the keys should be valid.
1190 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1191 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1192 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1193 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1194 }
1195 }
1196
test_path_effect_makes_empty_shape(skiatest::Reporter * reporter,const Geo & geo)1197 void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
1198 /**
1199 * This path effect returns an empty path.
1200 */
1201 class EmptyPathEffect : SkPathEffect {
1202 public:
1203 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1204 const SkRect* cullR) const override {
1205 dst->reset();
1206 return true;
1207 }
1208 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1209 dst->setEmpty();
1210 }
1211 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new EmptyPathEffect); }
1212 Factory getFactory() const override { return nullptr; }
1213 void toString(SkString*) const override {}
1214 private:
1215 EmptyPathEffect() {}
1216 };
1217
1218 SkPath emptyPath;
1219 GrShape emptyShape(emptyPath);
1220 Key emptyKey;
1221 make_key(&emptyKey, emptyShape);
1222 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
1223
1224 SkPaint pe;
1225 pe.setPathEffect(EmptyPathEffect::Make());
1226 TestCase geoCase(geo, pe, reporter);
1227 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleKey() == emptyKey);
1228 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectKey() == emptyKey);
1229 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectThenStrokeKey() == emptyKey);
1230 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectShape().isEmpty());
1231 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleShape().isEmpty());
1232
1233 SkPaint peStroke;
1234 peStroke.setPathEffect(EmptyPathEffect::Make());
1235 peStroke.setStrokeWidth(2.f);
1236 peStroke.setStyle(SkPaint::kStroke_Style);
1237 TestCase geoPEStrokeCase(geo, peStroke, reporter);
1238 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1239 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1240 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
1241 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1242 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
1243 }
1244
test_path_effect_fails(skiatest::Reporter * reporter,const Geo & geo)1245 void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
1246 /**
1247 * This path effect always fails to apply.
1248 */
1249 class FailurePathEffect : SkPathEffect {
1250 public:
1251 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1252 const SkRect* cullR) const override {
1253 return false;
1254 }
1255 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1256 *dst = src;
1257 }
1258 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1259 Factory getFactory() const override { return nullptr; }
1260 void toString(SkString*) const override {}
1261 private:
1262 FailurePathEffect() {}
1263 };
1264
1265 SkPaint fill;
1266 TestCase fillCase(geo, fill, reporter);
1267
1268 SkPaint pe;
1269 pe.setPathEffect(FailurePathEffect::Make());
1270 TestCase peCase(geo, pe, reporter);
1271
1272 SkPaint stroke;
1273 stroke.setStrokeWidth(2.f);
1274 stroke.setStyle(SkPaint::kStroke_Style);
1275 TestCase strokeCase(geo, stroke, reporter);
1276
1277 SkPaint peStroke = stroke;
1278 peStroke.setPathEffect(FailurePathEffect::Make());
1279 TestCase peStrokeCase(geo, peStroke, reporter);
1280
1281 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1282 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1283 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its
1284 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from
1285 // closing it but after the effect fails we can (for the fill+pe case). This causes different
1286 // routes through GrShape to have equivalent but different representations of the path (closed
1287 // or not) but that fill the same.
1288 SkPath a;
1289 SkPath b;
1290 fillCase.appliedPathEffectShape().asPath(&a);
1291 peCase.appliedPathEffectShape().asPath(&b);
1292 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1293
1294 fillCase.appliedFullStyleShape().asPath(&a);
1295 peCase.appliedFullStyleShape().asPath(&b);
1296 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1297
1298 strokeCase.appliedPathEffectShape().asPath(&a);
1299 peStrokeCase.appliedPathEffectShape().asPath(&b);
1300 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1301
1302 strokeCase.appliedFullStyleShape().asPath(&a);
1303 peStrokeCase.appliedFullStyleShape().asPath(&b);
1304 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1305 }
1306
test_empty_shape(skiatest::Reporter * reporter)1307 void test_empty_shape(skiatest::Reporter* reporter) {
1308 SkPath emptyPath;
1309 SkPaint fill;
1310 TestCase fillEmptyCase(reporter, emptyPath, fill);
1311 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1312 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1313 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
1314
1315 Key emptyKey(fillEmptyCase.baseKey());
1316 REPORTER_ASSERT(reporter, emptyKey.count());
1317 TestCase::SelfExpectations expectations;
1318 expectations.fStrokeApplies = false;
1319 expectations.fPEHasEffect = false;
1320 // This will test whether applying style preserves emptiness
1321 fillEmptyCase.testExpectations(reporter, expectations);
1322
1323 // Stroking an empty path should have no effect
1324 SkPath emptyPath2;
1325 SkPaint stroke;
1326 stroke.setStrokeWidth(2.f);
1327 stroke.setStyle(SkPaint::kStroke_Style);
1328 TestCase strokeEmptyCase(reporter, emptyPath2, stroke);
1329 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1330
1331 // Dashing and stroking an empty path should have no effect
1332 SkPath emptyPath3;
1333 SkPaint dashAndStroke;
1334 dashAndStroke.setPathEffect(make_dash());
1335 dashAndStroke.setStrokeWidth(2.f);
1336 dashAndStroke.setStyle(SkPaint::kStroke_Style);
1337 TestCase dashAndStrokeEmptyCase(reporter, emptyPath3, dashAndStroke);
1338 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1339 TestCase::kAllSame_ComparisonExpecation);
1340
1341 // A shape made from an empty rrect should behave the same as an empty path.
1342 SkRRect emptyRRect = SkRRect::MakeRect(SkRect::MakeEmpty());
1343 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
1344 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
1345 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1346 TestCase::kAllSame_ComparisonExpecation);
1347
1348 // Same for a rect.
1349 SkRect emptyRect = SkRect::MakeEmpty();
1350 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
1351 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1352 TestCase::kAllSame_ComparisonExpecation);
1353 }
1354
1355 // rect and oval types have rrect start indices that collapse to the same point. Here we select the
1356 // canonical point in these cases.
canonicalize_rrect_start(int s,const SkRRect & rrect)1357 unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1358 switch (rrect.getType()) {
1359 case SkRRect::kRect_Type:
1360 return (s + 1) & 0b110;
1361 case SkRRect::kOval_Type:
1362 return s & 0b110;
1363 default:
1364 return s;
1365 }
1366 }
1367
test_rrect(skiatest::Reporter * r,const SkRRect & rrect)1368 void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
1369 enum Style {
1370 kFill,
1371 kStroke,
1372 kHairline,
1373 kStrokeAndFill
1374 };
1375
1376 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1377 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1378 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1379 strokeRecs[kFill].setFillStyle();
1380 strokeRecs[kStroke].setStrokeStyle(2.f);
1381 strokeRecs[kHairline].setHairlineStyle();
1382 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
1383 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1384 // applyStyle() is called.
1385 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
1386 sk_sp<SkPathEffect> dashEffect = make_dash();
1387
1388 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1389
1390 auto index = [](bool inverted,
1391 SkPath::Direction dir,
1392 unsigned start,
1393 Style style,
1394 bool dash) -> int {
1395 return inverted * (2 * 8 * kStyleCnt * 2) +
1396 dir * ( 8 * kStyleCnt * 2) +
1397 start * ( kStyleCnt * 2) +
1398 style * ( 2) +
1399 dash;
1400 };
1401 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1);
1402 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1403 SkAutoTArray<GrShape> shapes(cnt);
1404 for (bool inverted : {false, true}) {
1405 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1406 for (unsigned start = 0; start < 8; ++start) {
1407 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1408 for (bool dash : {false, true}) {
1409 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
1410 shapes[index(inverted, dir, start, style, dash)] =
1411 GrShape(rrect, dir, start, SkToBool(inverted),
1412 GrStyle(strokeRecs[style], std::move(pe)));
1413 }
1414 }
1415 }
1416 }
1417 }
1418
1419 // Get the keys for some example shape instances that we'll use for comparision against the
1420 // rest.
1421 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction;
1422 static constexpr unsigned kExamplesStart = 0;
1423 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1424 false)];
1425 Key exampleFillCaseKey;
1426 make_key(&exampleFillCaseKey, exampleFillCase);
1427
1428 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart,
1429 kStrokeAndFill, false)];
1430 Key exampleStrokeAndFillCaseKey;
1431 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1432
1433 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill,
1434 false)];
1435 Key exampleInvFillCaseKey;
1436 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1437
1438 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart,
1439 kStrokeAndFill, false)];
1440 Key exampleInvStrokeAndFillCaseKey;
1441 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1442
1443 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke,
1444 false)];
1445 Key exampleStrokeCaseKey;
1446 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1447
1448 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke,
1449 false)];
1450 Key exampleInvStrokeCaseKey;
1451 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1452
1453 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1454 kHairline, false)];
1455 Key exampleHairlineCaseKey;
1456 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1457
1458 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1459 kHairline, false)];
1460 Key exampleInvHairlineCaseKey;
1461 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1462
1463 // These are dummy initializations to suppress warnings.
1464 SkRRect queryRR = SkRRect::MakeEmpty();
1465 SkPath::Direction queryDir = SkPath::kCW_Direction;
1466 unsigned queryStart = ~0U;
1467 bool queryInverted = true;
1468
1469 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1470 REPORTER_ASSERT(r, queryRR == rrect);
1471 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1472 REPORTER_ASSERT(r, 0 == queryStart);
1473 REPORTER_ASSERT(r, !queryInverted);
1474
1475 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1476 &queryInverted));
1477 REPORTER_ASSERT(r, queryRR == rrect);
1478 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1479 REPORTER_ASSERT(r, 0 == queryStart);
1480 REPORTER_ASSERT(r, queryInverted);
1481
1482 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1483 &queryInverted));
1484 REPORTER_ASSERT(r, queryRR == rrect);
1485 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1486 REPORTER_ASSERT(r, 0 == queryStart);
1487 REPORTER_ASSERT(r, !queryInverted);
1488
1489 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1490 &queryInverted));
1491 REPORTER_ASSERT(r, queryRR == rrect);
1492 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1493 REPORTER_ASSERT(r, 0 == queryStart);
1494 REPORTER_ASSERT(r, queryInverted);
1495
1496 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1497 &queryInverted));
1498 REPORTER_ASSERT(r, queryRR == rrect);
1499 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1500 REPORTER_ASSERT(r, 0 == queryStart);
1501 REPORTER_ASSERT(r, !queryInverted);
1502
1503 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1504 &queryInverted));
1505 REPORTER_ASSERT(r, queryRR == rrect);
1506 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1507 REPORTER_ASSERT(r, 0 == queryStart);
1508 REPORTER_ASSERT(r, queryInverted);
1509
1510 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1511 REPORTER_ASSERT(r, queryRR == rrect);
1512 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1513 REPORTER_ASSERT(r, 0 == queryStart);
1514 REPORTER_ASSERT(r, !queryInverted);
1515
1516 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1517 &queryInverted));
1518 REPORTER_ASSERT(r, queryRR == rrect);
1519 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1520 REPORTER_ASSERT(r, 0 == queryStart);
1521 REPORTER_ASSERT(r, queryInverted);
1522
1523 // Remember that the key reflects the geometry before styling is applied.
1524 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1525 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1526 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1527 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
1528 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
1529 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
1530 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
1531 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
1532 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1533 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
1534
1535 for (bool inverted : {false, true}) {
1536 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1537 for (unsigned start = 0; start < 8; ++start) {
1538 for (bool dash : {false, true}) {
1539 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)];
1540 Key fillCaseKey;
1541 make_key(&fillCaseKey, fillCase);
1542
1543 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1544 kStrokeAndFill, dash)];
1545 Key strokeAndFillCaseKey;
1546 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1547
1548 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1549 // ignore dashing.
1550 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1551 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1552 TestCase a(fillCase, r);
1553 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1554 TestCase c(strokeAndFillCase, r);
1555 TestCase d(inverted ? exampleInvStrokeAndFillCase
1556 : exampleStrokeAndFillCase, r);
1557 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1558 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1559
1560 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)];
1561 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline,
1562 dash)];
1563
1564 TestCase e(strokeCase, r);
1565 TestCase g(hairlineCase, r);
1566
1567 // Both hairline and stroke shapes must respect the dashing.
1568 if (dash) {
1569 // Dashing always ignores the inverseness. skbug.com/5421
1570 TestCase f(exampleStrokeCase, r);
1571 TestCase h(exampleHairlineCase, r);
1572 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
1573 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1574 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1575
1576 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1577 &queryInverted));
1578 REPORTER_ASSERT(r, queryRR == rrect);
1579 REPORTER_ASSERT(r, queryDir == dir);
1580 REPORTER_ASSERT(r, queryStart == expectedStart);
1581 REPORTER_ASSERT(r, !queryInverted);
1582 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1583 &queryInverted));
1584 REPORTER_ASSERT(r, queryRR == rrect);
1585 REPORTER_ASSERT(r, queryDir == dir);
1586 REPORTER_ASSERT(r, queryStart == expectedStart);
1587 REPORTER_ASSERT(r, !queryInverted);
1588
1589 // The pre-style case for the dash will match the non-dash example iff the
1590 // dir and start match (dir=cw, start=0).
1591 if (0 == expectedStart && SkPath::kCW_Direction == dir) {
1592 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1593 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1594 } else {
1595 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1596 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1597 }
1598 } else {
1599 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1600 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
1601 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1602 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1603 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1604 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1605 }
1606 }
1607 }
1608 }
1609 }
1610 }
1611
test_lines(skiatest::Reporter * r)1612 void test_lines(skiatest::Reporter* r) {
1613 static constexpr SkPoint kA { 1, 1};
1614 static constexpr SkPoint kB { 5, -9};
1615 static constexpr SkPoint kC {-3, 17};
1616
1617 SkPath lineAB;
1618 lineAB.moveTo(kA);
1619 lineAB.lineTo(kB);
1620
1621 SkPath lineBA;
1622 lineBA.moveTo(kB);
1623 lineBA.lineTo(kA);
1624
1625 SkPath lineAC;
1626 lineAC.moveTo(kB);
1627 lineAC.lineTo(kC);
1628
1629 SkPath invLineAB = lineAB;
1630 invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType);
1631
1632 SkPaint fill;
1633 SkPaint stroke;
1634 stroke.setStyle(SkPaint::kStroke_Style);
1635 stroke.setStrokeWidth(2.f);
1636 SkPaint hairline;
1637 hairline.setStyle(SkPaint::kStroke_Style);
1638 hairline.setStrokeWidth(0.f);
1639 SkPaint dash = stroke;
1640 dash.setPathEffect(make_dash());
1641
1642 TestCase fillAB(r, lineAB, fill);
1643 TestCase fillEmpty(r, SkPath(), fill);
1644 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1645 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1646
1647 TestCase strokeAB(r, lineAB, stroke);
1648 TestCase strokeBA(r, lineBA, stroke);
1649 TestCase strokeAC(r, lineAC, stroke);
1650
1651 TestCase hairlineAB(r, lineAB, hairline);
1652 TestCase hairlineBA(r, lineBA, hairline);
1653 TestCase hairlineAC(r, lineAC, hairline);
1654
1655 TestCase dashAB(r, lineAB, dash);
1656 TestCase dashBA(r, lineBA, dash);
1657 TestCase dashAC(r, lineAC, dash);
1658
1659 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1660
1661 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1662 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1663
1664 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1665 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1666
1667 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1668 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1669
1670 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1671
1672 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1673 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1674 bool canonicalizeAsAB;
1675 SkPoint canonicalPts[2] {kA, kB};
1676 // Init these to suppress warnings.
1677 bool inverted = true;
1678 SkPoint pts[2] {{0, 0}, {0, 0}};
1679 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1680 if (pts[0] == kA && pts[1] == kB) {
1681 canonicalizeAsAB = true;
1682 } else if (pts[1] == kA && pts[0] == kB) {
1683 canonicalizeAsAB = false;
1684 SkTSwap(canonicalPts[0], canonicalPts[1]);
1685 } else {
1686 ERRORF(r, "Should return pts (a,b) or (b, a)");
1687 return;
1688 };
1689
1690 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1691 TestCase::kSameUpToPE_ComparisonExpecation);
1692 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1693 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1694 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1695 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1696 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1697 pts[0] == kA && pts[1] == kB);
1698 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1699 pts[0] == kB && pts[1] == kA);
1700
1701
1702 TestCase strokeInvAB(r, invLineAB, stroke);
1703 TestCase hairlineInvAB(r, invLineAB, hairline);
1704 TestCase dashInvAB(r, invLineAB, dash);
1705 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1706 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1707 // Dashing ignores inverse.
1708 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1709
1710 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1711 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1712 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1713 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1714 // Dashing ignores inverse.
1715 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1716 pts[0] == kA && pts[1] == kB);
1717
1718 }
1719
test_stroked_lines(skiatest::Reporter * r)1720 static void test_stroked_lines(skiatest::Reporter* r) {
1721 // Paints to try
1722 SkPaint buttCap;
1723 buttCap.setStyle(SkPaint::kStroke_Style);
1724 buttCap.setStrokeWidth(4);
1725 buttCap.setStrokeCap(SkPaint::kButt_Cap);
1726
1727 SkPaint squareCap = buttCap;
1728 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
1729
1730 SkPaint roundCap = buttCap;
1731 roundCap.setStrokeCap(SkPaint::kRound_Cap);
1732
1733 // vertical
1734 SkPath linePath;
1735 linePath.moveTo(4, 4);
1736 linePath.lineTo(4, 5);
1737
1738 SkPaint fill;
1739
1740 TestCase(r, linePath, buttCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
1741 TestCase::kAllSame_ComparisonExpecation);
1742
1743 TestCase(r, linePath, squareCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
1744 TestCase::kAllSame_ComparisonExpecation);
1745
1746 TestCase(r, linePath, roundCap).compare(r,
1747 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
1748 TestCase::kAllSame_ComparisonExpecation);
1749
1750 // horizontal
1751 linePath.reset();
1752 linePath.moveTo(4, 4);
1753 linePath.lineTo(5, 4);
1754
1755 TestCase(r, linePath, buttCap).compare(r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
1756 TestCase::kAllSame_ComparisonExpecation);
1757 TestCase(r, linePath, squareCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
1758 TestCase::kAllSame_ComparisonExpecation);
1759 TestCase(r, linePath, roundCap).compare(r,
1760 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
1761 TestCase::kAllSame_ComparisonExpecation);
1762
1763 // point
1764 linePath.reset();
1765 linePath.moveTo(4, 4);
1766 linePath.lineTo(4, 4);
1767
1768 TestCase(r, linePath, buttCap).compare(r, TestCase(r, SkRect::MakeEmpty(), fill),
1769 TestCase::kAllSame_ComparisonExpecation);
1770 TestCase(r, linePath, squareCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
1771 TestCase::kAllSame_ComparisonExpecation);
1772 TestCase(r, linePath, roundCap).compare(r,
1773 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
1774 TestCase::kAllSame_ComparisonExpecation);
1775 }
1776
test_short_path_keys(skiatest::Reporter * r)1777 static void test_short_path_keys(skiatest::Reporter* r) {
1778 SkPaint paints[4];
1779 paints[1].setStyle(SkPaint::kStroke_Style);
1780 paints[1].setStrokeWidth(5.f);
1781 paints[2].setStyle(SkPaint::kStroke_Style);
1782 paints[2].setStrokeWidth(0.f);
1783 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
1784 paints[3].setStrokeWidth(5.f);
1785
1786 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
1787 TestCase::ComparisonExpecation expectation) {
1788 SkPath volatileA = pathA;
1789 SkPath volatileB = pathB;
1790 volatileA.setIsVolatile(true);
1791 volatileB.setIsVolatile(true);
1792 for (const SkPaint& paint : paints) {
1793 REPORTER_ASSERT(r, !GrShape(volatileA, paint).hasUnstyledKey());
1794 REPORTER_ASSERT(r, !GrShape(volatileB, paint).hasUnstyledKey());
1795 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
1796 TestCase caseA(PathGeo(pathA, invert), paint, r);
1797 TestCase caseB(PathGeo(pathB, invert), paint, r);
1798 caseA.compare(r, caseB, expectation);
1799 }
1800 }
1801 };
1802
1803 SkPath pathA;
1804 SkPath pathB;
1805
1806 // Two identical paths
1807 pathA.lineTo(10.f, 10.f);
1808 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
1809
1810 pathB.lineTo(10.f, 10.f);
1811 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
1812 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
1813
1814 // Give path b a different point
1815 pathB.reset();
1816 pathB.lineTo(10.f, 10.f);
1817 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
1818 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
1819
1820 // Give path b a different conic weight
1821 pathB.reset();
1822 pathB.lineTo(10.f, 10.f);
1823 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
1824 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
1825
1826 // Give path b an extra lineTo verb
1827 pathB.reset();
1828 pathB.lineTo(10.f, 10.f);
1829 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
1830 pathB.lineTo(50.f, 50.f);
1831 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
1832
1833 // Give path b a close
1834 pathB.reset();
1835 pathB.lineTo(10.f, 10.f);
1836 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
1837 pathB.close();
1838 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
1839 }
1840
DEF_TEST(GrShape,reporter)1841 DEF_TEST(GrShape, reporter) {
1842 SkTArray<std::unique_ptr<Geo>> geos;
1843 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
1844
1845 for (auto r : { SkRect::MakeWH(10, 20),
1846 SkRect::MakeWH(-10, -20),
1847 SkRect::MakeWH(-10, 20),
1848 SkRect::MakeWH(10, -20)}) {
1849 geos.emplace_back(new RectGeo(r));
1850 SkPath rectPath;
1851 rectPath.addRect(r);
1852 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
1853 PathGeo::Invert::kNo));
1854 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
1855 PathGeo::Invert::kYes));
1856 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
1857 PathGeo::Invert::kNo));
1858 }
1859 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
1860 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
1861 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
1862 geos.emplace_back(new RRectGeo(rr));
1863 test_rrect(reporter, rr);
1864 SkPath rectPath;
1865 rectPath.addRRect(rr);
1866 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
1867 PathGeo::Invert::kNo));
1868 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
1869 PathGeo::Invert::kYes));
1870 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
1871 RRectPathGeo::RRectForStroke::kYes,
1872 PathGeo::Invert::kNo));
1873 }
1874
1875 SkPath openRectPath;
1876 openRectPath.moveTo(0, 0);
1877 openRectPath.lineTo(10, 0);
1878 openRectPath.lineTo(10, 10);
1879 openRectPath.lineTo(0, 10);
1880 geos.emplace_back(new RRectPathGeo(openRectPath, SkRect::MakeWH(10, 10),
1881 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
1882 geos.emplace_back(new RRectPathGeo(openRectPath, SkRect::MakeWH(10, 10),
1883 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
1884 rrectPathGeos.emplace_back(new RRectPathGeo(openRectPath, SkRect::MakeWH(10, 10),
1885 RRectPathGeo::RRectForStroke::kNo,
1886 PathGeo::Invert::kNo));
1887
1888 SkPath quadPath;
1889 quadPath.quadTo(10, 10, 5, 8);
1890 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
1891 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
1892
1893 SkPath linePath;
1894 linePath.lineTo(10, 10);
1895 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
1896 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
1897
1898 // Horizontal and vertical paths become rrects when stroked.
1899 SkPath vLinePath;
1900 vLinePath.lineTo(0, 10);
1901 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
1902 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
1903
1904 SkPath hLinePath;
1905 hLinePath.lineTo(10, 0);
1906 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
1907 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
1908
1909 for (int i = 0; i < geos.count(); ++i) {
1910 test_basic(reporter, *geos[i]);
1911 test_scale(reporter, *geos[i]);
1912 test_dash_fill(reporter, *geos[i]);
1913 test_null_dash(reporter, *geos[i]);
1914 // Test modifying various stroke params.
1915 test_stroke_param<SkScalar>(
1916 reporter, *geos[i],
1917 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
1918 SkIntToScalar(2), SkIntToScalar(4));
1919 test_stroke_join(reporter, *geos[i]);
1920 test_stroke_cap(reporter, *geos[i]);
1921 test_miter_limit(reporter, *geos[i]);
1922 test_path_effect_makes_rrect(reporter, *geos[i]);
1923 test_unknown_path_effect(reporter, *geos[i]);
1924 test_path_effect_makes_empty_shape(reporter, *geos[i]);
1925 test_path_effect_fails(reporter, *geos[i]);
1926 test_make_hairline_path_effect(reporter, *geos[i]);
1927 test_volatile_path(reporter, *geos[i]);
1928 }
1929
1930 for (int i = 0; i < rrectPathGeos.count(); ++i) {
1931 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
1932 SkPaint fillPaint;
1933 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
1934 SkRRect rrect;
1935 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
1936 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
1937 nullptr));
1938 if (rrgeo.isNonPath(fillPaint)) {
1939 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
1940 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
1941 TestCase fillRRectCase(reporter, rrect, fillPaint);
1942 fillPathCase2.compare(reporter, fillRRectCase,
1943 TestCase::kAllSame_ComparisonExpecation);
1944 }
1945 SkPaint strokePaint;
1946 strokePaint.setStrokeWidth(3.f);
1947 strokePaint.setStyle(SkPaint::kStroke_Style);
1948 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
1949 if (rrgeo.isNonPath(strokePaint)) {
1950 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
1951 nullptr));
1952 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
1953 TestCase strokeRRectCase(reporter, rrect, strokePaint);
1954 strokePathCase.compare(reporter, strokeRRectCase,
1955 TestCase::kAllSame_ComparisonExpecation);
1956 }
1957 }
1958
1959 // Test a volatile empty path.
1960 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
1961
1962 test_empty_shape(reporter);
1963
1964 test_lines(reporter);
1965
1966 test_stroked_lines(reporter);
1967
1968 test_short_path_keys(reporter);
1969 }
1970
1971 #endif
1972