blob: ae75f1c8861f1e4cb70205d59f48c3fa38d930c7 [file] [log] [blame]
bsalomon47cc7692016-04-26 12:56:00 -07001/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include <initializer_list>
9#include <functional>
10#include "Test.h"
11#if SK_SUPPORT_GPU
12#include "GrShape.h"
bsalomon9fb42032016-05-13 09:23:38 -070013#include "SkCanvas.h"
bsalomon47cc7692016-04-26 12:56:00 -070014#include "SkDashPathEffect.h"
bsalomon9fb42032016-05-13 09:23:38 -070015#include "SkPath.h"
bsalomonee295642016-06-06 14:01:25 -070016#include "SkPathOps.h"
Mike Reed185ffe92018-01-08 17:09:54 -050017#include "SkRectPriv.h"
bsalomon9fb42032016-05-13 09:23:38 -070018#include "SkSurface.h"
Mike Reedebfce6d2016-12-12 10:02:12 -050019#include "SkClipOpPriv.h"
bsalomon47cc7692016-04-26 12:56:00 -070020
Brian Osmanf6f7cf62017-09-25 16:49:55 -040021uint32_t GrShape::testingOnly_getOriginalGenerationID() const {
Brian Salomonda6d0722018-01-03 13:54:35 -050022 if (const auto* lp = this->originalPathForListeners()) {
23 return lp->getGenerationID();
24 }
25 return SkPath().getGenerationID();
Brian Osmanf6f7cf62017-09-25 16:49:55 -040026}
27
Brian Osmanb379dcd2017-10-04 15:44:05 -040028bool GrShape::testingOnly_isPath() const {
29 return Type::kPath == fType;
30}
31
Brian Salomonda6d0722018-01-03 13:54:35 -050032bool GrShape::testingOnly_isNonVolatilePath() const {
33 return Type::kPath == fType && !fPathData.fPath.isVolatile();
34}
35
bsalomon72dc51c2016-04-27 06:46:23 -070036using Key = SkTArray<uint32_t>;
37
38static bool make_key(Key* key, const GrShape& shape) {
39 int size = shape.unstyledKeySize();
40 if (size <= 0) {
41 key->reset(0);
42 return false;
43 }
44 SkASSERT(size);
45 key->reset(size);
46 shape.writeUnstyledKey(key->begin());
47 return true;
48}
49
bsalomonee295642016-06-06 14:01:25 -070050static bool paths_fill_same(const SkPath& a, const SkPath& b) {
51 SkPath pathXor;
52 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
53 return pathXor.isEmpty();
54}
55
bsalomon9fb42032016-05-13 09:23:38 -070056static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
bsalomon164fd9f2016-08-26 06:45:06 -070057 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
58 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
59 // rendering within the bounds (with a tolerance). Then we render the path and check that
60 // everything got clipped out.
bsalomon9fb42032016-05-13 09:23:38 -070061 static constexpr int kRes = 2000;
62 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
63 static constexpr int kTol = 0;
64 GR_STATIC_ASSERT(kRes % 4 == 0);
65 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
66 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
67 surface->getCanvas()->clear(0x0);
68 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
69 SkMatrix matrix;
70 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit);
71 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
Mike Reedc1f77742016-12-09 09:00:50 -050072 surface->getCanvas()->clipRect(clip, kDifference_SkClipOp);
bsalomon9fb42032016-05-13 09:23:38 -070073 surface->getCanvas()->concat(matrix);
74 SkPaint whitePaint;
75 whitePaint.setColor(SK_ColorWHITE);
76 surface->getCanvas()->drawPath(path, whitePaint);
77 SkPixmap pixmap;
78 surface->getCanvas()->peekPixels(&pixmap);
79#if defined(SK_BUILD_FOR_WIN)
80 // The static constexpr version in #else causes cl.exe to crash.
81 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
82#else
83 static constexpr uint8_t kZeros[kRes] = {0};
84#endif
bsalomon164fd9f2016-08-26 06:45:06 -070085 for (int y = 0; y < kRes; ++y) {
bsalomon9fb42032016-05-13 09:23:38 -070086 const uint8_t* row = pixmap.addr8(0, y);
87 if (0 != memcmp(kZeros, row, kRes)) {
88 return false;
89 }
90 }
91#ifdef SK_BUILD_FOR_WIN
92 free(const_cast<uint8_t*>(kZeros));
93#endif
94 return true;
95}
bsalomon72dc51c2016-04-27 06:46:23 -070096
Brian Salomon4f40caf2017-09-01 09:00:45 -040097static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
98 SkPath path;
99 shape.asPath(&path);
100 if (shape.style().hasNonDashPathEffect()) {
101 return false;
102 }
103 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
104 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
105 strokeRecStyle == SkStrokeRec::kHairline_Style ||
106 (shape.style().isSimpleFill() && path.isConvex());
107}
108
109static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
110 const Key& keyA, const Key& keyB) {
111 // GrShape only respects the input winding direction and start point for rrect shapes
112 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
113 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
114 // key will differ. GrShape will have canonicalized the direction and start point for the shape
115 // without the path effect. If *both* have path effects then they should have both preserved
116 // the direction and starting point.
117
118 // The asRRect() output params are all initialized just to silence compiler warnings about
119 // uninitialized variables.
120 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
121 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
122 unsigned startA = ~0U, startB = ~0U;
123 bool invertedA = true, invertedB = true;
124
125 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
126 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
127 bool aHasPE = a.style().hasPathEffect();
128 bool bHasPE = b.style().hasPathEffect();
129 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
130 // GrShape will close paths with simple fill style.
131 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
132 SkPath pathA, pathB;
133 a.asPath(&pathA);
134 b.asPath(&pathB);
135
136 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
137 // non-inverse fill type (or vice versa).
138 bool ignoreInversenessDifference = false;
139 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
140 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
141 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
142 bool canDropInverse1 = s1->style().isDashed();
143 bool canDropInverse2 = s2->style().isDashed();
144 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
145 }
146 bool ignoreWindingVsEvenOdd = false;
147 if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
148 SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
149 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
150 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
151 if (aCanChange != bCanChange) {
152 ignoreWindingVsEvenOdd = true;
153 }
154 }
155 if (allowSameRRectButDiffStartAndDir) {
156 REPORTER_ASSERT(r, rrectA == rrectB);
157 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
158 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
159 } else {
160 SkPath pA = pathA;
161 SkPath pB = pathB;
162 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
163 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
164 if (ignoreInversenessDifference) {
165 pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
166 pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
167 }
168 if (ignoreWindingVsEvenOdd) {
169 pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
170 : SkPath::kEvenOdd_FillType);
171 pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
172 : SkPath::kEvenOdd_FillType);
173 }
174 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
175 REPORTER_ASSERT(r, keyA == keyB);
176 } else {
177 REPORTER_ASSERT(r, keyA != keyB);
178 }
179 if (allowedClosednessDiff) {
180 // GrShape will close paths with simple fill style. Make the non-filled path closed
181 // so that the comparision will succeed. Make sure both are closed before comparing.
182 pA.close();
183 pB.close();
184 }
185 REPORTER_ASSERT(r, pA == pB);
186 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
187 if (aIsRRect) {
188 REPORTER_ASSERT(r, rrectA == rrectB);
189 REPORTER_ASSERT(r, dirA == dirB);
190 REPORTER_ASSERT(r, startA == startB);
191 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
192 }
193 }
194 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
195 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
196 // closedness can affect convexity.
197 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
198 if (a.knownToBeConvex()) {
199 REPORTER_ASSERT(r, pathA.isConvex());
200 }
201 if (b.knownToBeConvex()) {
202 REPORTER_ASSERT(r, pathB.isConvex());
203 }
204 REPORTER_ASSERT(r, a.bounds() == b.bounds());
205 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
206 // Init these to suppress warnings.
207 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
208 bool invertedLine[2] {true, true};
209 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
210 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
211 // doesn't (since the PE can set any fill type on its output path).
212 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
213 // then they may disagree about inverseness.
214 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
215 a.style().isDashed() == b.style().isDashed()) {
216 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
217 b.mayBeInverseFilledAfterStyling());
218 }
219 if (a.asLine(nullptr, nullptr)) {
220 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
221 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
222 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
223 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
224 }
225 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
226}
227
Brian Osmanb379dcd2017-10-04 15:44:05 -0400228static void check_original_path_ids(skiatest::Reporter* r, const GrShape& base, const GrShape& pe,
229 const GrShape& peStroke, const GrShape& full) {
Brian Salomonda6d0722018-01-03 13:54:35 -0500230 bool baseIsNonVolatilePath = base.testingOnly_isNonVolatilePath();
Brian Osmanb379dcd2017-10-04 15:44:05 -0400231 bool peIsPath = pe.testingOnly_isPath();
232 bool peStrokeIsPath = peStroke.testingOnly_isPath();
233 bool fullIsPath = full.testingOnly_isPath();
234
235 REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath);
236
237 uint32_t baseID = base.testingOnly_getOriginalGenerationID();
238 uint32_t peID = pe.testingOnly_getOriginalGenerationID();
239 uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID();
240 uint32_t fullID = full.testingOnly_getOriginalGenerationID();
241
242 // All empty paths have the same gen ID
243 uint32_t emptyID = SkPath().getGenerationID();
244
245 // If we started with a real path, then our genID should match that path's gen ID (and not be
Brian Salomonda6d0722018-01-03 13:54:35 -0500246 // empty). If we started with a simple shape or a volatile path, our original path should have
247 // been reset.
248 REPORTER_ASSERT(r, baseIsNonVolatilePath == (baseID != emptyID));
Brian Osmanb379dcd2017-10-04 15:44:05 -0400249
250 // For the derived shapes, if they're simple types, their original paths should have been reset
251 REPORTER_ASSERT(r, peIsPath || (peID == emptyID));
252 REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID));
253 REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID));
254
255 if (!peIsPath) {
256 // If the path effect produces a simple shape, then there are no unbroken chains to test
257 return;
258 }
259
260 // From here on, we know that the path effect produced a shape that was a "real" path
261
Brian Salomonda6d0722018-01-03 13:54:35 -0500262 if (baseIsNonVolatilePath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400263 REPORTER_ASSERT(r, baseID == peID);
264 }
265
266 if (peStrokeIsPath) {
267 REPORTER_ASSERT(r, peID == peStrokeID);
268 REPORTER_ASSERT(r, peStrokeID == fullID);
269 }
270
Brian Salomonda6d0722018-01-03 13:54:35 -0500271 if (baseIsNonVolatilePath && peStrokeIsPath) {
Brian Osmanb379dcd2017-10-04 15:44:05 -0400272 REPORTER_ASSERT(r, baseID == peStrokeID);
273 REPORTER_ASSERT(r, baseID == fullID);
274 }
275}
276
Brian Salomon4f40caf2017-09-01 09:00:45 -0400277void test_inversions(skiatest::Reporter* r, const GrShape& shape, const Key& shapeKey) {
278 GrShape preserve = GrShape::MakeFilled(shape, GrShape::FillInversion::kPreserve);
279 Key preserveKey;
280 make_key(&preserveKey, preserve);
281
282 GrShape flip = GrShape::MakeFilled(shape, GrShape::FillInversion::kFlip);
283 Key flipKey;
284 make_key(&flipKey, flip);
285
286 GrShape inverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceInverted);
287 Key invertedKey;
288 make_key(&invertedKey, inverted);
289
290 GrShape noninverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceNoninverted);
291 Key noninvertedKey;
292 make_key(&noninvertedKey, noninverted);
293
294 if (invertedKey.count() || noninvertedKey.count()) {
295 REPORTER_ASSERT(r, invertedKey != noninvertedKey);
296 }
297 if (shape.style().isSimpleFill()) {
298 check_equivalence(r, shape, preserve, shapeKey, preserveKey);
299 }
300 if (shape.inverseFilled()) {
301 check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
302 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
303 } else {
304 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
305 check_equivalence(r, flip, inverted, flipKey, invertedKey);
306 }
307
308 GrShape doubleFlip = GrShape::MakeFilled(flip, GrShape::FillInversion::kFlip);
309 Key doubleFlipKey;
310 make_key(&doubleFlipKey, doubleFlip);
311 // It can be the case that the double flip has no key but preserve does. This happens when the
312 // original shape has an inherited style key. That gets dropped on the first inversion flip.
313 if (preserveKey.count() && !doubleFlipKey.count()) {
314 preserveKey.reset();
315 }
316 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
317}
318
bsalomon9fb42032016-05-13 09:23:38 -0700319namespace {
bsalomona395f7c2016-08-24 17:47:40 -0700320/**
321 * Geo is a factory for creating a GrShape from another representation. It also answers some
322 * questions about expected behavior for GrShape given the inputs.
323 */
324class Geo {
325public:
Mike Kleinfc6c37b2016-09-27 09:34:10 -0400326 virtual ~Geo() {}
bsalomona395f7c2016-08-24 17:47:40 -0700327 virtual GrShape makeShape(const SkPaint&) const = 0;
328 virtual SkPath path() const = 0;
329 // These functions allow tests to check for special cases where style gets
330 // applied by GrShape in its constructor (without calling GrShape::applyStyle).
331 // These unfortunately rely on knowing details of GrShape's implementation.
332 // These predicates are factored out here to avoid littering the rest of the
333 // test code with GrShape implementation details.
334 virtual bool fillChangesGeom() const { return false; }
335 virtual bool strokeIsConvertedToFill() const { return false; }
336 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
337 // Is this something we expect GrShape to recognize as something simpler than a path.
338 virtual bool isNonPath(const SkPaint& paint) const { return true; }
339};
340
341class RectGeo : public Geo {
342public:
343 RectGeo(const SkRect& rect) : fRect(rect) {}
344
345 SkPath path() const override {
346 SkPath path;
347 path.addRect(fRect);
348 return path;
349 }
350
351 GrShape makeShape(const SkPaint& paint) const override {
352 return GrShape(fRect, paint);
353 }
354
355 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
356 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
357 // Converted to an outset rectangle.
358 return paint.getStrokeJoin() == SkPaint::kMiter_Join &&
359 paint.getStrokeMiter() >= SK_ScalarSqrt2;
360 }
361
362private:
363 SkRect fRect;
364};
365
366class RRectGeo : public Geo {
367public:
368 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
369
370 GrShape makeShape(const SkPaint& paint) const override {
371 return GrShape(fRRect, paint);
372 }
373
374 SkPath path() const override {
375 SkPath path;
376 path.addRRect(fRRect);
377 return path;
378 }
379
380 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
381 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
382 if (fRRect.isRect()) {
383 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
384 }
385 return false;
386 }
387
388private:
389 SkRRect fRRect;
390};
391
392class PathGeo : public Geo {
393public:
394 enum class Invert { kNo, kYes };
395
396 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
397 SkASSERT(!path.isInverseFillType());
398 if (Invert::kYes == invert) {
399 if (fPath.getFillType() == SkPath::kEvenOdd_FillType) {
400 fPath.setFillType(SkPath::kInverseEvenOdd_FillType);
401 } else {
402 SkASSERT(fPath.getFillType() == SkPath::kWinding_FillType);
403 fPath.setFillType(SkPath::kInverseWinding_FillType);
404 }
405 }
406 }
407
408 GrShape makeShape(const SkPaint& paint) const override {
409 return GrShape(fPath, paint);
410 }
411
412 SkPath path() const override { return fPath; }
413
414 bool fillChangesGeom() const override {
415 // unclosed rects get closed. Lines get turned into empty geometry
Brian Salomon085c0862017-08-31 15:44:51 -0400416 return this->isUnclosedRect() || fPath.isLine(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -0700417 }
418
419 bool strokeIsConvertedToFill() const override {
420 return this->isAxisAlignedLine();
421 }
422
423 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
424 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
425 if (this->isAxisAlignedLine()) {
426 // The fill is ignored (zero area) and the stroke is converted to a rrect.
427 return true;
428 }
429 SkRect rect;
430 unsigned start;
431 SkPath::Direction dir;
432 if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
433 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
434 }
435 return false;
436 }
437
438 bool isNonPath(const SkPaint& paint) const override {
439 return fPath.isLine(nullptr) || fPath.isEmpty();
440 }
441
442private:
443 bool isAxisAlignedLine() const {
444 SkPoint pts[2];
445 if (!fPath.isLine(pts)) {
446 return false;
447 }
448 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
449 }
450
451 bool isUnclosedRect() const {
452 bool closed;
453 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
454 }
455
456 SkPath fPath;
457};
458
459class RRectPathGeo : public PathGeo {
460public:
461 enum class RRectForStroke { kNo, kYes };
462
463 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
464 Invert invert)
465 : PathGeo(path, invert)
466 , fRRect(equivalentRRect)
467 , fRRectForStroke(rrectForStroke) {}
468
469 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
470 Invert invert)
471 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
472
473 bool isNonPath(const SkPaint& paint) const override {
474 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
475 return true;
476 }
477 return false;
478 }
479
480 const SkRRect& rrect() const { return fRRect; }
481
482private:
483 SkRRect fRRect;
484 RRectForStroke fRRectForStroke;
485};
486
bsalomon47cc7692016-04-26 12:56:00 -0700487class TestCase {
488public:
bsalomona395f7c2016-08-24 17:47:40 -0700489 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
490 SkScalar scale = SK_Scalar1) : fBase(geo.makeShape(paint)) {
bsalomon97fd2d42016-05-09 13:02:01 -0700491 this->init(r, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700492 }
493
bsalomona395f7c2016-08-24 17:47:40 -0700494 template<typename... ShapeArgs>
495 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs)
496 : fBase(shapeArgs...) {
497 this->init(r, SK_Scalar1);
498 }
499
bsalomon70493962016-06-10 08:05:14 -0700500 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
501 : fBase(shape) {
502 this->init(r, scale);
503 }
504
bsalomon47cc7692016-04-26 12:56:00 -0700505 struct SelfExpectations {
506 bool fPEHasEffect;
507 bool fPEHasValidKey;
508 bool fStrokeApplies;
509 };
510
511 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
512
513 enum ComparisonExpecation {
514 kAllDifferent_ComparisonExpecation,
515 kSameUpToPE_ComparisonExpecation,
516 kSameUpToStroke_ComparisonExpecation,
517 kAllSame_ComparisonExpecation,
518 };
519
520 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
521
bsalomon72dc51c2016-04-27 06:46:23 -0700522 const GrShape& baseShape() const { return fBase; }
523 const GrShape& appliedPathEffectShape() const { return fAppliedPE; }
524 const GrShape& appliedFullStyleShape() const { return fAppliedFull; }
525
526 // The returned array's count will be 0 if the key shape has no key.
527 const Key& baseKey() const { return fBaseKey; }
528 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
529 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700530 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700531
bsalomon47cc7692016-04-26 12:56:00 -0700532private:
bsalomon9fb42032016-05-13 09:23:38 -0700533 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
534 SkPath path;
535 shape.asPath(&path);
536 // If the bounds are empty, the path ought to be as well.
bsalomon0ae36a22016-07-18 07:31:13 -0700537 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
bsalomon9fb42032016-05-13 09:23:38 -0700538 REPORTER_ASSERT(r, path.isEmpty());
539 return;
540 }
541 if (path.isEmpty()) {
542 return;
543 }
bsalomon70493962016-06-10 08:05:14 -0700544 // The bounds API explicitly calls out that it does not consider inverseness.
545 SkPath p = path;
546 p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType()));
547 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
bsalomon9fb42032016-05-13 09:23:38 -0700548 }
549
bsalomon97fd2d42016-05-09 13:02:01 -0700550 void init(skiatest::Reporter* r, SkScalar scale) {
551 fAppliedPE = fBase.applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
552 fAppliedPEThenStroke = fAppliedPE.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec,
553 scale);
554 fAppliedFull = fBase.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700555
bsalomon72dc51c2016-04-27 06:46:23 -0700556 make_key(&fBaseKey, fBase);
557 make_key(&fAppliedPEKey, fAppliedPE);
558 make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke);
559 make_key(&fAppliedFullKey, fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700560
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400561 // All shapes should report the same "original" path, so that path renderers can get to it
562 // if necessary.
Brian Osmanb379dcd2017-10-04 15:44:05 -0400563 check_original_path_ids(r, fBase, fAppliedPE, fAppliedPEThenStroke, fAppliedFull);
Brian Osmanf6f7cf62017-09-25 16:49:55 -0400564
bsalomonfb083272016-05-04 08:27:41 -0700565 // Applying the path effect and then the stroke should always be the same as applying
566 // both in one go.
567 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
568 SkPath a, b;
569 fAppliedPEThenStroke.asPath(&a);
570 fAppliedFull.asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700571 // If the output of the path effect is a rrect then it is possible for a and b to be
572 // different paths that fill identically. The reason is that fAppliedFull will do this:
573 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
574 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
575 // now that there is no longer a path effect, the direction and starting index get
576 // canonicalized before the stroke.
bsalomon70493962016-06-10 08:05:14 -0700577 if (fAppliedPE.asRRect(nullptr, nullptr, nullptr, nullptr)) {
bsalomonee295642016-06-06 14:01:25 -0700578 REPORTER_ASSERT(r, paths_fill_same(a, b));
579 } else {
580 REPORTER_ASSERT(r, a == b);
581 }
bsalomon7c73a532016-05-11 15:15:56 -0700582 REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty());
583
584 SkPath path;
585 fBase.asPath(&path);
586 REPORTER_ASSERT(r, path.isEmpty() == fBase.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700587 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700588 fAppliedPE.asPath(&path);
589 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700590 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700591 fAppliedFull.asPath(&path);
592 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700593 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull.segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700594
bsalomon9fb42032016-05-13 09:23:38 -0700595 CheckBounds(r, fBase, fBase.bounds());
596 CheckBounds(r, fAppliedPE, fAppliedPE.bounds());
597 CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds());
598 CheckBounds(r, fAppliedFull, fAppliedFull.bounds());
bsalomon0a0f67e2016-06-28 11:56:42 -0700599 SkRect styledBounds = fBase.styledBounds();
bsalomon9fb42032016-05-13 09:23:38 -0700600 CheckBounds(r, fAppliedFull, styledBounds);
bsalomon0a0f67e2016-06-28 11:56:42 -0700601 styledBounds = fAppliedPE.styledBounds();
bsalomon9fb42032016-05-13 09:23:38 -0700602 CheckBounds(r, fAppliedFull, styledBounds);
603
bsalomonfb083272016-05-04 08:27:41 -0700604 // Check that the same path is produced when style is applied by GrShape and GrStyle.
605 SkPath preStyle;
606 SkPath postPathEffect;
607 SkPath postAllStyle;
608
609 fBase.asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700610 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
bsalomon97fd2d42016-05-09 13:02:01 -0700611 if (fBase.style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
612 scale)) {
bsalomon1a0b9ed2016-05-06 11:07:03 -0700613 // run postPathEffect through GrShape to get any geometry reductions that would have
614 // occurred to fAppliedPE.
615 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
616
bsalomonfb083272016-05-04 08:27:41 -0700617 SkPath testPath;
618 fAppliedPE.asPath(&testPath);
619 REPORTER_ASSERT(r, testPath == postPathEffect);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700620 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE.style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700621 }
622 SkStrokeRec::InitStyle fillOrHairline;
bsalomon97fd2d42016-05-09 13:02:01 -0700623 if (fBase.style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomonfb083272016-05-04 08:27:41 -0700624 SkPath testPath;
625 fAppliedFull.asPath(&testPath);
bsalomon1b28c1a2016-06-20 12:28:17 -0700626 if (fBase.style().hasPathEffect()) {
627 // Because GrShape always does two-stage application when there is a path effect
628 // there may be a reduction/canonicalization step between the path effect and
629 // strokerec not reflected in postAllStyle since it applied both the path effect
630 // and strokerec without analyzing the intermediate path.
631 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
632 } else {
633 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape
634 // would apply.
635 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
636 REPORTER_ASSERT(r, testPath == postAllStyle);
637 }
638
bsalomonfb083272016-05-04 08:27:41 -0700639 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
640 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleFill());
641 } else {
642 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline());
643 }
644 }
Brian Salomon4f40caf2017-09-01 09:00:45 -0400645 test_inversions(r, fBase, fBaseKey);
646 test_inversions(r, fAppliedPE, fAppliedPEKey);
647 test_inversions(r, fAppliedFull, fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700648 }
649
650 GrShape fBase;
651 GrShape fAppliedPE;
652 GrShape fAppliedPEThenStroke;
653 GrShape fAppliedFull;
654
655 Key fBaseKey;
656 Key fAppliedPEKey;
657 Key fAppliedPEThenStrokeKey;
658 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700659};
660
661void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700662 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700663 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700664 if (expectations.fPEHasEffect) {
665 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700666 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700667 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700668 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700669 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
670 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700671 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700672 }
673 } else {
674 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700675 SkPath a, b;
bsalomon72dc51c2016-04-27 06:46:23 -0700676 fBase.asPath(&a);
677 fAppliedPE.asPath(&b);
678 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700679 if (expectations.fStrokeApplies) {
680 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
681 } else {
682 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
683 }
684 }
685}
686
bsalomonee295642016-06-06 14:01:25 -0700687void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700688 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700689 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700690 switch (expectation) {
691 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700692 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
693 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
694 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700695 break;
696 case kSameUpToPE_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700697 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
698 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
699 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700700 break;
701 case kSameUpToStroke_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700702 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
703 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
704 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700705 break;
706 case kAllSame_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700707 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
708 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
709 check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey,
710 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700711 break;
712 }
713}
714} // namespace
715
716static sk_sp<SkPathEffect> make_dash() {
717 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
718 static const SkScalar kPhase = 0.75;
719 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
720}
721
722static sk_sp<SkPathEffect> make_null_dash() {
723 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
724 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
725}
726
Mike Klein43344282017-08-16 11:56:22 -0400727// We make enough TestCases, and they're large enough, that on Google3 builds we exceed
728// the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
729template <typename... Args>
730static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
731 return std::unique_ptr<TestCase>{ new TestCase(std::forward<Args>(args)...) };
732}
733
bsalomona395f7c2016-08-24 17:47:40 -0700734static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700735 sk_sp<SkPathEffect> dashPE = make_dash();
736
737 TestCase::SelfExpectations expectations;
738 SkPaint fill;
739
bsalomonfb083272016-05-04 08:27:41 -0700740 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700741 expectations.fPEHasEffect = false;
742 expectations.fPEHasValidKey = false;
743 expectations.fStrokeApplies = false;
744 fillCase.testExpectations(reporter, expectations);
745 // Test that another GrShape instance built from the same primitive is the same.
Mike Klein43344282017-08-16 11:56:22 -0400746 make_TestCase(geo, fill, reporter)
747 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700748
749 SkPaint stroke2RoundBevel;
750 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
751 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
752 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
753 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700754 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700755 expectations.fPEHasValidKey = true;
756 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700757 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomon47cc7692016-04-26 12:56:00 -0700758 stroke2RoundBevelCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400759 make_TestCase(geo, stroke2RoundBevel, reporter)
760 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700761
762 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
763 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700764 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700765 expectations.fPEHasValidKey = true;
766 expectations.fPEHasEffect = true;
767 expectations.fStrokeApplies = true;
768 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400769 make_TestCase(geo, stroke2RoundBevelDash, reporter)
770 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700771
bsalomona395f7c2016-08-24 17:47:40 -0700772 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700773 fillCase.compare(reporter, stroke2RoundBevelCase,
774 TestCase::kAllDifferent_ComparisonExpecation);
775 fillCase.compare(reporter, stroke2RoundBevelDashCase,
776 TestCase::kAllDifferent_ComparisonExpecation);
777 } else {
778 fillCase.compare(reporter, stroke2RoundBevelCase,
779 TestCase::kSameUpToStroke_ComparisonExpecation);
780 fillCase.compare(reporter, stroke2RoundBevelDashCase,
781 TestCase::kSameUpToPE_ComparisonExpecation);
782 }
bsalomona395f7c2016-08-24 17:47:40 -0700783 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700784 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
785 TestCase::kAllDifferent_ComparisonExpecation);
786 } else {
787 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
788 TestCase::kSameUpToPE_ComparisonExpecation);
789 }
bsalomon72dc51c2016-04-27 06:46:23 -0700790
bsalomonf0cf3552016-05-05 08:28:30 -0700791 // Stroke and fill cases
792 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
793 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
794 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
795 expectations.fPEHasValidKey = true;
796 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700797 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700798 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400799 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
800 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700801
802 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
803 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
804 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
805 expectations.fPEHasValidKey = true;
bsalomona0587862016-06-09 06:03:38 -0700806 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700807 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700808 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400809 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
bsalomonf0cf3552016-05-05 08:28:30 -0700810 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomona0587862016-06-09 06:03:38 -0700811 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
812 TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700813
bsalomon72dc51c2016-04-27 06:46:23 -0700814 SkPaint hairline;
815 hairline.setStyle(SkPaint::kStroke_Style);
816 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700817 TestCase hairlineCase(geo, hairline, reporter);
bsalomon487f8d32016-07-20 07:15:44 -0700818 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
819 // in the line and unclosed rect cases).
bsalomona395f7c2016-08-24 17:47:40 -0700820 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -0700821 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
822 } else {
823 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
824 }
bsalomon9ad5d7c2016-05-04 08:44:15 -0700825 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
826 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
827 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700828
bsalomon0ae36a22016-07-18 07:31:13 -0700829}
830
bsalomona395f7c2016-08-24 17:47:40 -0700831static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon97fd2d42016-05-09 13:02:01 -0700832 sk_sp<SkPathEffect> dashPE = make_dash();
833
834 static const SkScalar kS1 = 1.f;
835 static const SkScalar kS2 = 2.f;
836
837 SkPaint fill;
838 TestCase fillCase1(geo, fill, reporter, kS1);
839 TestCase fillCase2(geo, fill, reporter, kS2);
840 // Scale doesn't affect fills.
841 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
842
843 SkPaint hairline;
844 hairline.setStyle(SkPaint::kStroke_Style);
845 hairline.setStrokeWidth(0.f);
846 TestCase hairlineCase1(geo, hairline, reporter, kS1);
847 TestCase hairlineCase2(geo, hairline, reporter, kS2);
848 // Scale doesn't affect hairlines.
849 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
850
851 SkPaint stroke;
852 stroke.setStyle(SkPaint::kStroke_Style);
853 stroke.setStrokeWidth(2.f);
854 TestCase strokeCase1(geo, stroke, reporter, kS1);
855 TestCase strokeCase2(geo, stroke, reporter, kS2);
bsalomon0ae36a22016-07-18 07:31:13 -0700856 // Scale affects the stroke
bsalomona395f7c2016-08-24 17:47:40 -0700857 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700858 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
bsalomon0ae36a22016-07-18 07:31:13 -0700859 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
860 } else {
861 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
862 }
bsalomon97fd2d42016-05-09 13:02:01 -0700863
864 SkPaint strokeDash = stroke;
865 strokeDash.setPathEffect(make_dash());
866 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
867 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
868 // Scale affects the dash and the stroke.
bsalomon487f8d32016-07-20 07:15:44 -0700869 strokeDashCase1.compare(reporter, strokeDashCase2,
870 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700871
872 // Stroke and fill cases
873 SkPaint strokeAndFill = stroke;
874 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
875 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
876 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
bsalomona0587862016-06-09 06:03:38 -0700877 SkPaint strokeAndFillDash = strokeDash;
878 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
879 // Dash is ignored for stroke and fill
880 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
881 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
bsalomon487f8d32016-07-20 07:15:44 -0700882 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
883 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
884 // geometries should agree.
bsalomona395f7c2016-08-24 17:47:40 -0700885 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
bsalomon487f8d32016-07-20 07:15:44 -0700886 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
bsalomon97fd2d42016-05-09 13:02:01 -0700887 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
888 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700889 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
890 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700891 } else {
892 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
893 TestCase::kSameUpToStroke_ComparisonExpecation);
894 }
bsalomona0587862016-06-09 06:03:38 -0700895 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
896 TestCase::kAllSame_ComparisonExpecation);
897 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
898 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700899}
900
bsalomona395f7c2016-08-24 17:47:40 -0700901template <typename T>
902static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700903 std::function<void(SkPaint*, T)> setter, T a, T b,
904 bool paramAffectsStroke,
905 bool paramAffectsDashAndStroke) {
906 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
907 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700908 SkPaint strokeA;
909 strokeA.setStyle(SkPaint::kStroke_Style);
910 strokeA.setStrokeWidth(2.f);
911 setter(&strokeA, a);
912 SkPaint strokeB;
913 strokeB.setStyle(SkPaint::kStroke_Style);
914 strokeB.setStrokeWidth(2.f);
915 setter(&strokeB, b);
916
bsalomonfb083272016-05-04 08:27:41 -0700917 TestCase strokeACase(geo, strokeA, reporter);
918 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700919 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700920 // If stroking is immediately incorporated into a geometric transformation then the base
921 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700922 if (geo.strokeIsConvertedToFill()) {
bsalomon0ae36a22016-07-18 07:31:13 -0700923 strokeACase.compare(reporter, strokeBCase,
924 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700925 } else {
926 strokeACase.compare(reporter, strokeBCase,
927 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700928 }
bsalomon06077562016-05-04 13:50:29 -0700929 } else {
930 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
931 }
bsalomon47cc7692016-04-26 12:56:00 -0700932
bsalomonf0cf3552016-05-05 08:28:30 -0700933 SkPaint strokeAndFillA = strokeA;
934 SkPaint strokeAndFillB = strokeB;
935 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
936 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
937 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
938 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
939 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700940 // If stroking is immediately incorporated into a geometric transformation then the base
941 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700942 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
943 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700944 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700945 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700946 } else {
947 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700948 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700949 }
bsalomonf0cf3552016-05-05 08:28:30 -0700950 } else {
951 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
952 TestCase::kAllSame_ComparisonExpecation);
953 }
954
bsalomon47cc7692016-04-26 12:56:00 -0700955 // Make sure stroking params don't affect fill style.
956 SkPaint fillA = strokeA, fillB = strokeB;
957 fillA.setStyle(SkPaint::kFill_Style);
958 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -0700959 TestCase fillACase(geo, fillA, reporter);
960 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700961 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
962
963 // Make sure just applying the dash but not stroke gives the same key for both stroking
964 // variations.
965 SkPaint dashA = strokeA, dashB = strokeB;
966 dashA.setPathEffect(make_dash());
967 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700968 TestCase dashACase(geo, dashA, reporter);
969 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700970 if (paramAffectsDashAndStroke) {
bsalomon487f8d32016-07-20 07:15:44 -0700971 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon06077562016-05-04 13:50:29 -0700972 } else {
973 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
974 }
bsalomon47cc7692016-04-26 12:56:00 -0700975}
976
bsalomona395f7c2016-08-24 17:47:40 -0700977template <typename T>
978static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700979 std::function<void(SkPaint*, T)> setter, T a, T b) {
980 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
981};
982
bsalomona395f7c2016-08-24 17:47:40 -0700983static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
984 SkPaint hairline;
985 hairline.setStrokeWidth(0);
986 hairline.setStyle(SkPaint::kStroke_Style);
987 GrShape shape = geo.makeShape(hairline);
bsalomon06077562016-05-04 13:50:29 -0700988 // The cap should only affect shapes that may be open.
989 bool affectsStroke = !shape.knownToBeClosed();
990 // Dashing adds ends that need caps.
991 bool affectsDashAndStroke = true;
bsalomona395f7c2016-08-24 17:47:40 -0700992 test_stroke_param_impl<SkPaint::Cap>(
bsalomon06077562016-05-04 13:50:29 -0700993 reporter,
994 geo,
995 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
996 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
997 affectsStroke,
998 affectsDashAndStroke);
999};
1000
bsalomon0ae36a22016-07-18 07:31:13 -07001001static bool shape_known_not_to_have_joins(const GrShape& shape) {
1002 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
1003}
1004
bsalomona395f7c2016-08-24 17:47:40 -07001005static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
1006 SkPaint hairline;
1007 hairline.setStrokeWidth(0);
1008 hairline.setStyle(SkPaint::kStroke_Style);
1009 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001010 // GrShape recognizes certain types don't have joins and will prevent the join type from
1011 // affecting the style key.
1012 // Dashing doesn't add additional joins. However, GrShape currently loses track of this
1013 // after applying the dash.
1014 bool affectsStroke = !shape_known_not_to_have_joins(shape);
bsalomona395f7c2016-08-24 17:47:40 -07001015 test_stroke_param_impl<SkPaint::Join>(
bsalomon0ae36a22016-07-18 07:31:13 -07001016 reporter,
1017 geo,
1018 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
1019 SkPaint::kRound_Join, SkPaint::kBevel_Join,
1020 affectsStroke, true);
1021};
1022
bsalomona395f7c2016-08-24 17:47:40 -07001023static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon06077562016-05-04 13:50:29 -07001024 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1025 p->setStrokeJoin(SkPaint::kMiter_Join);
1026 p->setStrokeMiter(miter);
1027 };
bsalomon47cc7692016-04-26 12:56:00 -07001028
bsalomon06077562016-05-04 13:50:29 -07001029 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1030 p->setStrokeJoin(SkPaint::kRound_Join);
1031 p->setStrokeMiter(miter);
1032 };
bsalomon47cc7692016-04-26 12:56:00 -07001033
bsalomona395f7c2016-08-24 17:47:40 -07001034 SkPaint hairline;
1035 hairline.setStrokeWidth(0);
1036 hairline.setStyle(SkPaint::kStroke_Style);
1037 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -07001038 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
1039
bsalomon06077562016-05-04 13:50:29 -07001040 // The miter limit should affect stroked and dashed-stroked cases when the join type is
1041 // miter.
bsalomona395f7c2016-08-24 17:47:40 -07001042 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001043 reporter,
1044 geo,
1045 setMiterJoinAndLimit,
1046 0.5f, 0.75f,
bsalomon0ae36a22016-07-18 07:31:13 -07001047 mayHaveJoins,
bsalomon06077562016-05-04 13:50:29 -07001048 true);
bsalomon47cc7692016-04-26 12:56:00 -07001049
bsalomon06077562016-05-04 13:50:29 -07001050 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
1051 // not miter.
bsalomona395f7c2016-08-24 17:47:40 -07001052 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -07001053 reporter,
1054 geo,
1055 setOtherJoinAndLimit,
1056 0.5f, 0.75f,
1057 false,
1058 false);
bsalomon47cc7692016-04-26 12:56:00 -07001059}
1060
bsalomona395f7c2016-08-24 17:47:40 -07001061static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001062 // A dash with no stroke should have no effect
1063 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
1064 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
1065 SkPaint dashFill;
1066 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -07001067 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001068
bsalomonfb083272016-05-04 08:27:41 -07001069 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001070 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
1071 }
1072}
1073
bsalomona395f7c2016-08-24 17:47:40 -07001074void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -07001075 SkPaint fill;
1076 SkPaint stroke;
1077 stroke.setStyle(SkPaint::kStroke_Style);
1078 stroke.setStrokeWidth(1.f);
1079 SkPaint dash;
1080 dash.setStyle(SkPaint::kStroke_Style);
1081 dash.setStrokeWidth(1.f);
1082 dash.setPathEffect(make_dash());
1083 SkPaint nullDash;
1084 nullDash.setStyle(SkPaint::kStroke_Style);
1085 nullDash.setStrokeWidth(1.f);
1086 nullDash.setPathEffect(make_null_dash());
1087
bsalomonfb083272016-05-04 08:27:41 -07001088 TestCase fillCase(geo, fill, reporter);
1089 TestCase strokeCase(geo, stroke, reporter);
1090 TestCase dashCase(geo, dash, reporter);
1091 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001092
bsalomon487f8d32016-07-20 07:15:44 -07001093 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
bsalomon47cc7692016-04-26 12:56:00 -07001094 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -07001095 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
1096 // on construction in order to determine how to compare the fill and stroke.
bsalomona395f7c2016-08-24 17:47:40 -07001097 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001098 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
1099 } else {
1100 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1101 }
1102 // In the null dash case we may immediately convert to a fill, but not for the normal dash case.
bsalomona395f7c2016-08-24 17:47:40 -07001103 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -07001104 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
1105 } else {
1106 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
1107 }
bsalomon47cc7692016-04-26 12:56:00 -07001108}
1109
bsalomona395f7c2016-08-24 17:47:40 -07001110void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001111 /**
1112 * This path effect takes any input path and turns it into a rrect. It passes through stroke
1113 * info.
1114 */
1115 class RRectPathEffect : SkPathEffect {
1116 public:
1117 static const SkRRect& RRect() {
1118 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1119 return kRRect;
1120 }
1121
1122 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1123 const SkRect* cullR) const override {
1124 dst->reset();
1125 dst->addRRect(RRect());
1126 return true;
1127 }
1128 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1129 *dst = RRect().getBounds();
1130 }
1131 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1132 Factory getFactory() const override { return nullptr; }
1133 void toString(SkString*) const override {}
1134 private:
1135 RRectPathEffect() {}
1136 };
1137
1138 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -07001139 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001140
1141 SkPaint pe;
1142 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001143 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001144
1145 SkPaint peStroke;
1146 peStroke.setPathEffect(RRectPathEffect::Make());
1147 peStroke.setStrokeWidth(2.f);
1148 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001149 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001150
bsalomon487f8d32016-07-20 07:15:44 -07001151 // Check whether constructing the filled case would cause the base shape to have a different
1152 // geometry (because of a geometric transformation upon initial GrShape construction).
bsalomona395f7c2016-08-24 17:47:40 -07001153 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -07001154 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1155 fillGeoCase.compare(reporter, geoPEStrokeCase,
1156 TestCase::kAllDifferent_ComparisonExpecation);
1157 } else {
1158 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1159 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1160 }
bsalomon72dc51c2016-04-27 06:46:23 -07001161 geoPECase.compare(reporter, geoPEStrokeCase,
1162 TestCase::kSameUpToStroke_ComparisonExpecation);
1163
bsalomona395f7c2016-08-24 17:47:40 -07001164 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
bsalomon72dc51c2016-04-27 06:46:23 -07001165 SkPaint stroke = peStroke;
1166 stroke.setPathEffect(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -07001167 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001168
1169 SkRRect rrect;
1170 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1171 // geoPECase, so the full style should be the same as just the PE.
bsalomon70493962016-06-10 08:05:14 -07001172 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1173 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001174 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1175 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1176
bsalomon70493962016-06-10 08:05:14 -07001177 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1178 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001179 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1180 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1181
1182 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomon70493962016-06-10 08:05:14 -07001183 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1184 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001185 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1186 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1187
bsalomon70493962016-06-10 08:05:14 -07001188 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1189 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001190 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1191 rrectStrokeCase.appliedFullStyleKey());
1192}
1193
bsalomona395f7c2016-08-24 17:47:40 -07001194void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001195 /**
1196 * This path effect just adds two lineTos to the input path.
1197 */
1198 class AddLineTosPathEffect : SkPathEffect {
1199 public:
1200 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1201 const SkRect* cullR) const override {
1202 *dst = src;
bsalomon67fa4e32016-09-21 08:26:57 -07001203 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1204 for (int i = 0; i < 100; ++i) {
1205 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1206 }
bsalomon72dc51c2016-04-27 06:46:23 -07001207 return true;
1208 }
1209 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1210 *dst = src;
Mike Reed185ffe92018-01-08 17:09:54 -05001211 SkRectPriv::GrowToInclude(dst, {0, 0});
1212 SkRectPriv::GrowToInclude(dst, {100, 100});
bsalomon72dc51c2016-04-27 06:46:23 -07001213 }
1214 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1215 Factory getFactory() const override { return nullptr; }
1216 void toString(SkString*) const override {}
1217 private:
1218 AddLineTosPathEffect() {}
1219 };
1220
bsalomon9ad5d7c2016-05-04 08:44:15 -07001221 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -07001222 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1223 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1224 SkPaint peStroke;
1225 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1226 peStroke.setStrokeWidth(2.f);
1227 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001228 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001229 TestCase::SelfExpectations expectations;
1230 expectations.fPEHasEffect = true;
1231 expectations.fPEHasValidKey = false;
1232 expectations.fStrokeApplies = true;
1233 geoPEStrokeCase.testExpectations(reporter, expectations);
1234}
1235
bsalomona395f7c2016-08-24 17:47:40 -07001236void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon9ad5d7c2016-05-04 08:44:15 -07001237 /**
1238 * This path effect just changes the stroke rec to hairline.
1239 */
1240 class MakeHairlinePathEffect : SkPathEffect {
1241 public:
1242 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1243 const SkRect* cullR) const override {
1244 *dst = src;
1245 strokeRec->setHairlineStyle();
1246 return true;
1247 }
bsalomon70493962016-06-10 08:05:14 -07001248 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001249 static sk_sp<SkPathEffect> Make() {
1250 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1251 }
1252 Factory getFactory() const override { return nullptr; }
1253 void toString(SkString*) const override {}
1254 private:
1255 MakeHairlinePathEffect() {}
1256 };
1257
1258 SkPaint fill;
1259 SkPaint pe;
1260 pe.setPathEffect(MakeHairlinePathEffect::Make());
1261
1262 TestCase peCase(geo, pe, reporter);
1263
bsalomonee295642016-06-06 14:01:25 -07001264 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -07001265 peCase.baseShape().asPath(&a);
1266 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -07001267 peCase.appliedFullStyleShape().asPath(&c);
bsalomona395f7c2016-08-24 17:47:40 -07001268 if (geo.isNonPath(pe)) {
bsalomonee295642016-06-06 14:01:25 -07001269 // RRect types can have a change in start index or direction after the PE is applied. This
1270 // is because once the PE is applied, GrShape may canonicalize the dir and index since it
1271 // is not germane to the styling any longer.
1272 // Instead we just check that the paths would fill the same both before and after styling.
1273 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1274 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -07001275 } else {
bsalomona4817af2016-06-23 11:48:26 -07001276 // The base shape cannot perform canonicalization on the path's fill type because of an
1277 // unknown path effect. However, after the path effect is applied the resulting hairline
1278 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1279 // don't distinguish between even/odd and non-zero winding.
1280 a.setFillType(b.getFillType());
bsalomonee295642016-06-06 14:01:25 -07001281 REPORTER_ASSERT(reporter, a == b);
1282 REPORTER_ASSERT(reporter, a == c);
bsalomon67fa4e32016-09-21 08:26:57 -07001283 // If the resulting path is small enough then it will have a key.
1284 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1285 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomonaa840642016-09-23 12:09:16 -07001286 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1287 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001288 }
bsalomonee295642016-06-06 14:01:25 -07001289 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1290 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001291}
1292
bsalomona395f7c2016-08-24 17:47:40 -07001293void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1294 SkPath vPath = geo.path();
bsalomon4eeccc92016-04-27 13:30:25 -07001295 vPath.setIsVolatile(true);
1296
1297 SkPaint dashAndStroke;
1298 dashAndStroke.setPathEffect(make_dash());
1299 dashAndStroke.setStrokeWidth(2.f);
1300 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001301 TestCase volatileCase(reporter, vPath, dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001302 // We expect a shape made from a volatile path to have a key iff the shape is recognized
bsalomonaa840642016-09-23 12:09:16 -07001303 // as a specialized geometry.
1304 if (geo.isNonPath(dashAndStroke)) {
bsalomon4eeccc92016-04-27 13:30:25 -07001305 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1306 // In this case all the keys should be identical to the non-volatile case.
bsalomona395f7c2016-08-24 17:47:40 -07001307 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001308 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1309 } else {
1310 // None of the keys should be valid.
1311 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1312 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1313 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1314 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1315 }
1316}
1317
bsalomona395f7c2016-08-24 17:47:40 -07001318void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon409ed732016-04-27 12:36:02 -07001319 /**
Brian Salomon085c0862017-08-31 15:44:51 -04001320 * This path effect returns an empty path (possibly inverted)
bsalomon409ed732016-04-27 12:36:02 -07001321 */
1322 class EmptyPathEffect : SkPathEffect {
1323 public:
1324 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1325 const SkRect* cullR) const override {
1326 dst->reset();
Brian Salomon085c0862017-08-31 15:44:51 -04001327 if (fInvert) {
1328 dst->toggleInverseFillType();
1329 }
bsalomon409ed732016-04-27 12:36:02 -07001330 return true;
1331 }
1332 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1333 dst->setEmpty();
1334 }
Brian Salomon085c0862017-08-31 15:44:51 -04001335 static sk_sp<SkPathEffect> Make(bool invert) {
1336 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
1337 }
bsalomon409ed732016-04-27 12:36:02 -07001338 Factory getFactory() const override { return nullptr; }
1339 void toString(SkString*) const override {}
1340 private:
Brian Salomon085c0862017-08-31 15:44:51 -04001341 bool fInvert;
1342 EmptyPathEffect(bool invert) : fInvert(invert) {}
bsalomon409ed732016-04-27 12:36:02 -07001343 };
1344
1345 SkPath emptyPath;
1346 GrShape emptyShape(emptyPath);
1347 Key emptyKey;
1348 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -07001349 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001350
Brian Salomon085c0862017-08-31 15:44:51 -04001351 emptyPath.toggleInverseFillType();
1352 GrShape invertedEmptyShape(emptyPath);
1353 Key invertedEmptyKey;
1354 make_key(&invertedEmptyKey, invertedEmptyShape);
1355 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
1356
1357 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
1358
bsalomon409ed732016-04-27 12:36:02 -07001359 SkPaint pe;
Brian Salomon085c0862017-08-31 15:44:51 -04001360 pe.setPathEffect(EmptyPathEffect::Make(false));
1361 TestCase geoPECase(geo, pe, reporter);
1362 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
1363 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
1364 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
1365 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
1366 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
1367 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
1368 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001369
1370 SkPaint peStroke;
Brian Salomon085c0862017-08-31 15:44:51 -04001371 peStroke.setPathEffect(EmptyPathEffect::Make(false));
bsalomon409ed732016-04-27 12:36:02 -07001372 peStroke.setStrokeWidth(2.f);
1373 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001374 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001375 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1376 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1377 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001378 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1379 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001380 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
1381 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
1382 pe.setPathEffect(EmptyPathEffect::Make(true));
1383
1384 TestCase geoPEInvertCase(geo, pe, reporter);
1385 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
1386 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
1387 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1388 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
1389 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
1390 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
1391 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
1392
1393 peStroke.setPathEffect(EmptyPathEffect::Make(true));
1394 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
1395 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
1396 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
1397 REPORTER_ASSERT(reporter,
1398 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1399 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
1400 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
1401 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
1402 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001403}
1404
bsalomona395f7c2016-08-24 17:47:40 -07001405void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
bsalomond6723842016-06-07 12:20:15 -07001406 /**
bsalomon0ae36a22016-07-18 07:31:13 -07001407 * This path effect always fails to apply.
bsalomond6723842016-06-07 12:20:15 -07001408 */
1409 class FailurePathEffect : SkPathEffect {
1410 public:
1411 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1412 const SkRect* cullR) const override {
1413 return false;
1414 }
1415 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1416 *dst = src;
1417 }
1418 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1419 Factory getFactory() const override { return nullptr; }
1420 void toString(SkString*) const override {}
1421 private:
1422 FailurePathEffect() {}
1423 };
1424
1425 SkPaint fill;
1426 TestCase fillCase(geo, fill, reporter);
1427
1428 SkPaint pe;
1429 pe.setPathEffect(FailurePathEffect::Make());
1430 TestCase peCase(geo, pe, reporter);
1431
1432 SkPaint stroke;
1433 stroke.setStrokeWidth(2.f);
1434 stroke.setStyle(SkPaint::kStroke_Style);
1435 TestCase strokeCase(geo, stroke, reporter);
1436
1437 SkPaint peStroke = stroke;
1438 peStroke.setPathEffect(FailurePathEffect::Make());
1439 TestCase peStrokeCase(geo, peStroke, reporter);
1440
1441 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1442 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1443 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its
1444 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from
1445 // closing it but after the effect fails we can (for the fill+pe case). This causes different
1446 // routes through GrShape to have equivalent but different representations of the path (closed
1447 // or not) but that fill the same.
1448 SkPath a;
1449 SkPath b;
1450 fillCase.appliedPathEffectShape().asPath(&a);
1451 peCase.appliedPathEffectShape().asPath(&b);
1452 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1453
1454 fillCase.appliedFullStyleShape().asPath(&a);
1455 peCase.appliedFullStyleShape().asPath(&b);
1456 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1457
1458 strokeCase.appliedPathEffectShape().asPath(&a);
1459 peStrokeCase.appliedPathEffectShape().asPath(&b);
1460 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1461
1462 strokeCase.appliedFullStyleShape().asPath(&a);
1463 peStrokeCase.appliedFullStyleShape().asPath(&b);
1464 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1465}
1466
Mike Klein43344282017-08-16 11:56:22 -04001467DEF_TEST(GrShape_empty_shape, reporter) {
bsalomon409ed732016-04-27 12:36:02 -07001468 SkPath emptyPath;
Brian Salomon085c0862017-08-31 15:44:51 -04001469 SkPath invertedEmptyPath;
1470 invertedEmptyPath.toggleInverseFillType();
bsalomon409ed732016-04-27 12:36:02 -07001471 SkPaint fill;
bsalomona395f7c2016-08-24 17:47:40 -07001472 TestCase fillEmptyCase(reporter, emptyPath, fill);
bsalomon7c73a532016-05-11 15:15:56 -07001473 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1474 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1475 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
Brian Salomon085c0862017-08-31 15:44:51 -04001476 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
1477 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
1478 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
1479 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
1480 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
1481 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
1482 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
1483 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
1484 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
1485 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
bsalomon409ed732016-04-27 12:36:02 -07001486
1487 Key emptyKey(fillEmptyCase.baseKey());
1488 REPORTER_ASSERT(reporter, emptyKey.count());
Brian Salomon085c0862017-08-31 15:44:51 -04001489 Key inverseEmptyKey(fillInvertedEmptyCase.baseKey());
1490 REPORTER_ASSERT(reporter, inverseEmptyKey.count());
bsalomon409ed732016-04-27 12:36:02 -07001491 TestCase::SelfExpectations expectations;
1492 expectations.fStrokeApplies = false;
1493 expectations.fPEHasEffect = false;
1494 // This will test whether applying style preserves emptiness
1495 fillEmptyCase.testExpectations(reporter, expectations);
Brian Salomon085c0862017-08-31 15:44:51 -04001496 fillInvertedEmptyCase.testExpectations(reporter, expectations);
bsalomon409ed732016-04-27 12:36:02 -07001497
1498 // Stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001499 SkPaint stroke;
1500 stroke.setStrokeWidth(2.f);
1501 stroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001502 stroke.setStrokeJoin(SkPaint::kRound_Join);
1503 stroke.setStrokeCap(SkPaint::kRound_Cap);
Brian Salomon085c0862017-08-31 15:44:51 -04001504 TestCase strokeEmptyCase(reporter, emptyPath, stroke);
bsalomon409ed732016-04-27 12:36:02 -07001505 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001506 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
1507 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
1508 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001509
1510 // Dashing and stroking an empty path should have no effect
bsalomon409ed732016-04-27 12:36:02 -07001511 SkPaint dashAndStroke;
1512 dashAndStroke.setPathEffect(make_dash());
1513 dashAndStroke.setStrokeWidth(2.f);
1514 dashAndStroke.setStyle(SkPaint::kStroke_Style);
Brian Salomon085c0862017-08-31 15:44:51 -04001515 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
bsalomon409ed732016-04-27 12:36:02 -07001516 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1517 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon085c0862017-08-31 15:44:51 -04001518 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
1519 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1520 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
1521 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001522
Brian Salomon2fad74a2017-12-20 13:28:55 -05001523 // A shape made from an empty rrect should behave the same as an empty path when filled but not
1524 // when stroked. However, dashing an empty rrect produces an empty path leaving nothing to
1525 // stroke - so equivalent to filling an empty path.
1526 SkRRect emptyRRect = SkRRect::MakeEmpty();
bsalomon5e410b42016-04-28 09:30:46 -07001527 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001528
1529 TestCase fillEmptyRRectCase(reporter, emptyRRect, fill);
1530 fillEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1531
1532 TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke);
1533 strokeEmptyRRectCase.compare(reporter, strokeEmptyCase,
1534 TestCase::kAllDifferent_ComparisonExpecation);
1535
bsalomona395f7c2016-08-24 17:47:40 -07001536 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001537 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1538 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001539
Brian Salomon085c0862017-08-31 15:44:51 -04001540 static constexpr SkPath::Direction kDir = SkPath::kCCW_Direction;
1541 static constexpr int kStart = 0;
Brian Salomon2fad74a2017-12-20 13:28:55 -05001542
1543 TestCase fillInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, GrStyle(fill));
1544 fillInvertedEmptyRRectCase.compare(reporter, fillInvertedEmptyCase,
1545 TestCase::kAllSame_ComparisonExpecation);
1546
1547 TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true,
1548 GrStyle(stroke));
1549 strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase,
1550 TestCase::kAllDifferent_ComparisonExpecation);
1551
Brian Salomon085c0862017-08-31 15:44:51 -04001552 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
1553 GrStyle(dashAndStroke));
Brian Salomon085c0862017-08-31 15:44:51 -04001554 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
1555 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001556
1557 // Same for a rect.
1558 SkRect emptyRect = SkRect::MakeEmpty();
Brian Salomon2fad74a2017-12-20 13:28:55 -05001559 TestCase fillEmptyRectCase(reporter, emptyRect, fill);
1560 fillEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1561
bsalomona395f7c2016-08-24 17:47:40 -07001562 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001563 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1564 TestCase::kAllSame_ComparisonExpecation);
Brian Salomon2fad74a2017-12-20 13:28:55 -05001565
Brian Salomon085c0862017-08-31 15:44:51 -04001566 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
1567 kStart, true, GrStyle(dashAndStroke));
1568 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1569 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
1570 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001571}
1572
bsalomon70493962016-06-10 08:05:14 -07001573// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1574// canonical point in these cases.
1575unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1576 switch (rrect.getType()) {
1577 case SkRRect::kRect_Type:
1578 return (s + 1) & 0b110;
1579 case SkRRect::kOval_Type:
1580 return s & 0b110;
1581 default:
1582 return s;
1583 }
1584}
1585
1586void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
bsalomoncadb5a22016-06-10 18:28:06 -07001587 enum Style {
bsalomon70493962016-06-10 08:05:14 -07001588 kFill,
1589 kStroke,
1590 kHairline,
1591 kStrokeAndFill
1592 };
1593
1594 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1595 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1596 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1597 strokeRecs[kFill].setFillStyle();
1598 strokeRecs[kStroke].setStrokeStyle(2.f);
1599 strokeRecs[kHairline].setHairlineStyle();
1600 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
bsalomon487f8d32016-07-20 07:15:44 -07001601 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1602 // applyStyle() is called.
1603 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
bsalomon70493962016-06-10 08:05:14 -07001604 sk_sp<SkPathEffect> dashEffect = make_dash();
1605
bsalomoncadb5a22016-06-10 18:28:06 -07001606 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1607
1608 auto index = [](bool inverted,
1609 SkPath::Direction dir,
1610 unsigned start,
1611 Style style,
1612 bool dash) -> int {
1613 return inverted * (2 * 8 * kStyleCnt * 2) +
1614 dir * ( 8 * kStyleCnt * 2) +
1615 start * ( kStyleCnt * 2) +
1616 style * ( 2) +
1617 dash;
1618 };
1619 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1);
1620 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1621 SkAutoTArray<GrShape> shapes(cnt);
1622 for (bool inverted : {false, true}) {
1623 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1624 for (unsigned start = 0; start < 8; ++start) {
1625 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1626 for (bool dash : {false, true}) {
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001627 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
bsalomoncadb5a22016-06-10 18:28:06 -07001628 shapes[index(inverted, dir, start, style, dash)] =
1629 GrShape(rrect, dir, start, SkToBool(inverted),
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001630 GrStyle(strokeRecs[style], std::move(pe)));
bsalomon70493962016-06-10 08:05:14 -07001631 }
1632 }
1633 }
1634 }
1635 }
1636
bsalomonfd32df72016-06-14 14:37:21 -07001637 // Get the keys for some example shape instances that we'll use for comparision against the
1638 // rest.
1639 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction;
1640 static constexpr unsigned kExamplesStart = 0;
1641 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1642 false)];
bsalomon70493962016-06-10 08:05:14 -07001643 Key exampleFillCaseKey;
1644 make_key(&exampleFillCaseKey, exampleFillCase);
1645
bsalomonfd32df72016-06-14 14:37:21 -07001646 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart,
1647 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001648 Key exampleStrokeAndFillCaseKey;
1649 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1650
bsalomonfd32df72016-06-14 14:37:21 -07001651 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill,
1652 false)];
bsalomon70493962016-06-10 08:05:14 -07001653 Key exampleInvFillCaseKey;
1654 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1655
bsalomonfd32df72016-06-14 14:37:21 -07001656 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart,
1657 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001658 Key exampleInvStrokeAndFillCaseKey;
1659 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1660
bsalomonfd32df72016-06-14 14:37:21 -07001661 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke,
1662 false)];
bsalomon70493962016-06-10 08:05:14 -07001663 Key exampleStrokeCaseKey;
1664 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1665
bsalomonfd32df72016-06-14 14:37:21 -07001666 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke,
1667 false)];
1668 Key exampleInvStrokeCaseKey;
1669 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1670
1671 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1672 kHairline, false)];
bsalomon70493962016-06-10 08:05:14 -07001673 Key exampleHairlineCaseKey;
1674 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1675
bsalomonfd32df72016-06-14 14:37:21 -07001676 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1677 kHairline, false)];
1678 Key exampleInvHairlineCaseKey;
1679 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1680
bsalomon70493962016-06-10 08:05:14 -07001681 // These are dummy initializations to suppress warnings.
bsalomoncadb5a22016-06-10 18:28:06 -07001682 SkRRect queryRR = SkRRect::MakeEmpty();
1683 SkPath::Direction queryDir = SkPath::kCW_Direction;
1684 unsigned queryStart = ~0U;
1685 bool queryInverted = true;
bsalomon70493962016-06-10 08:05:14 -07001686
bsalomoncadb5a22016-06-10 18:28:06 -07001687 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1688 REPORTER_ASSERT(r, queryRR == rrect);
1689 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1690 REPORTER_ASSERT(r, 0 == queryStart);
1691 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001692
bsalomoncadb5a22016-06-10 18:28:06 -07001693 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1694 &queryInverted));
1695 REPORTER_ASSERT(r, queryRR == rrect);
1696 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1697 REPORTER_ASSERT(r, 0 == queryStart);
1698 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001699
bsalomoncadb5a22016-06-10 18:28:06 -07001700 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1701 &queryInverted));
1702 REPORTER_ASSERT(r, queryRR == rrect);
1703 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1704 REPORTER_ASSERT(r, 0 == queryStart);
1705 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001706
bsalomoncadb5a22016-06-10 18:28:06 -07001707 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1708 &queryInverted));
1709 REPORTER_ASSERT(r, queryRR == rrect);
1710 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1711 REPORTER_ASSERT(r, 0 == queryStart);
1712 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001713
bsalomoncadb5a22016-06-10 18:28:06 -07001714 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1715 &queryInverted));
1716 REPORTER_ASSERT(r, queryRR == rrect);
1717 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1718 REPORTER_ASSERT(r, 0 == queryStart);
1719 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001720
bsalomonfd32df72016-06-14 14:37:21 -07001721 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1722 &queryInverted));
1723 REPORTER_ASSERT(r, queryRR == rrect);
1724 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1725 REPORTER_ASSERT(r, 0 == queryStart);
1726 REPORTER_ASSERT(r, queryInverted);
1727
bsalomoncadb5a22016-06-10 18:28:06 -07001728 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1729 REPORTER_ASSERT(r, queryRR == rrect);
1730 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1731 REPORTER_ASSERT(r, 0 == queryStart);
1732 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001733
bsalomonfd32df72016-06-14 14:37:21 -07001734 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1735 &queryInverted));
1736 REPORTER_ASSERT(r, queryRR == rrect);
1737 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1738 REPORTER_ASSERT(r, 0 == queryStart);
1739 REPORTER_ASSERT(r, queryInverted);
1740
bsalomon70493962016-06-10 08:05:14 -07001741 // Remember that the key reflects the geometry before styling is applied.
1742 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1743 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1744 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1745 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001746 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001747 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001748 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001749 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001750 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1751 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001752
bsalomoncadb5a22016-06-10 18:28:06 -07001753 for (bool inverted : {false, true}) {
1754 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1755 for (unsigned start = 0; start < 8; ++start) {
1756 for (bool dash : {false, true}) {
1757 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001758 Key fillCaseKey;
1759 make_key(&fillCaseKey, fillCase);
1760
bsalomoncadb5a22016-06-10 18:28:06 -07001761 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1762 kStrokeAndFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001763 Key strokeAndFillCaseKey;
1764 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1765
1766 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1767 // ignore dashing.
1768 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1769 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1770 TestCase a(fillCase, r);
1771 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1772 TestCase c(strokeAndFillCase, r);
1773 TestCase d(inverted ? exampleInvStrokeAndFillCase
1774 : exampleStrokeAndFillCase, r);
1775 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1776 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1777
bsalomoncadb5a22016-06-10 18:28:06 -07001778 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)];
1779 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline,
1780 dash)];
bsalomon70493962016-06-10 08:05:14 -07001781
1782 TestCase e(strokeCase, r);
bsalomon70493962016-06-10 08:05:14 -07001783 TestCase g(hairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001784
bsalomonfd32df72016-06-14 14:37:21 -07001785 // Both hairline and stroke shapes must respect the dashing.
bsalomon70493962016-06-10 08:05:14 -07001786 if (dash) {
bsalomonfd32df72016-06-14 14:37:21 -07001787 // Dashing always ignores the inverseness. skbug.com/5421
1788 TestCase f(exampleStrokeCase, r);
1789 TestCase h(exampleHairlineCase, r);
bsalomoncadb5a22016-06-10 18:28:06 -07001790 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
bsalomon70493962016-06-10 08:05:14 -07001791 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1792 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1793
bsalomoncadb5a22016-06-10 18:28:06 -07001794 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1795 &queryInverted));
1796 REPORTER_ASSERT(r, queryRR == rrect);
1797 REPORTER_ASSERT(r, queryDir == dir);
1798 REPORTER_ASSERT(r, queryStart == expectedStart);
1799 REPORTER_ASSERT(r, !queryInverted);
1800 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1801 &queryInverted));
1802 REPORTER_ASSERT(r, queryRR == rrect);
1803 REPORTER_ASSERT(r, queryDir == dir);
1804 REPORTER_ASSERT(r, queryStart == expectedStart);
1805 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001806
1807 // The pre-style case for the dash will match the non-dash example iff the
1808 // dir and start match (dir=cw, start=0).
bsalomoncadb5a22016-06-10 18:28:06 -07001809 if (0 == expectedStart && SkPath::kCW_Direction == dir) {
bsalomon70493962016-06-10 08:05:14 -07001810 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1811 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1812 } else {
1813 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1814 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1815 }
1816 } else {
bsalomonfd32df72016-06-14 14:37:21 -07001817 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1818 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001819 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1820 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1821 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1822 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1823 }
1824 }
1825 }
1826 }
1827 }
1828}
1829
Mike Klein43344282017-08-16 11:56:22 -04001830DEF_TEST(GrShape_lines, r) {
bsalomon0a0f67e2016-06-28 11:56:42 -07001831 static constexpr SkPoint kA { 1, 1};
1832 static constexpr SkPoint kB { 5, -9};
1833 static constexpr SkPoint kC {-3, 17};
1834
1835 SkPath lineAB;
1836 lineAB.moveTo(kA);
1837 lineAB.lineTo(kB);
1838
1839 SkPath lineBA;
1840 lineBA.moveTo(kB);
1841 lineBA.lineTo(kA);
1842
1843 SkPath lineAC;
1844 lineAC.moveTo(kB);
1845 lineAC.lineTo(kC);
1846
1847 SkPath invLineAB = lineAB;
1848 invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType);
1849
1850 SkPaint fill;
1851 SkPaint stroke;
1852 stroke.setStyle(SkPaint::kStroke_Style);
1853 stroke.setStrokeWidth(2.f);
1854 SkPaint hairline;
1855 hairline.setStyle(SkPaint::kStroke_Style);
1856 hairline.setStrokeWidth(0.f);
1857 SkPaint dash = stroke;
1858 dash.setPathEffect(make_dash());
1859
bsalomona395f7c2016-08-24 17:47:40 -07001860 TestCase fillAB(r, lineAB, fill);
1861 TestCase fillEmpty(r, SkPath(), fill);
bsalomon0a0f67e2016-06-28 11:56:42 -07001862 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1863 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1864
Brian Salomon085c0862017-08-31 15:44:51 -04001865 SkPath path;
1866 path.toggleInverseFillType();
1867 TestCase fillEmptyInverted(r, path, fill);
1868 TestCase fillABInverted(r, invLineAB, fill);
1869 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
1870 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
1871
bsalomona395f7c2016-08-24 17:47:40 -07001872 TestCase strokeAB(r, lineAB, stroke);
1873 TestCase strokeBA(r, lineBA, stroke);
1874 TestCase strokeAC(r, lineAC, stroke);
bsalomon0a0f67e2016-06-28 11:56:42 -07001875
bsalomona395f7c2016-08-24 17:47:40 -07001876 TestCase hairlineAB(r, lineAB, hairline);
1877 TestCase hairlineBA(r, lineBA, hairline);
1878 TestCase hairlineAC(r, lineAC, hairline);
bsalomon0a0f67e2016-06-28 11:56:42 -07001879
bsalomona395f7c2016-08-24 17:47:40 -07001880 TestCase dashAB(r, lineAB, dash);
1881 TestCase dashBA(r, lineBA, dash);
1882 TestCase dashAC(r, lineAC, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001883
1884 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1885
1886 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1887 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1888
1889 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1890 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1891
1892 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1893 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1894
1895 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1896
1897 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1898 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1899 bool canonicalizeAsAB;
1900 SkPoint canonicalPts[2] {kA, kB};
1901 // Init these to suppress warnings.
1902 bool inverted = true;
1903 SkPoint pts[2] {{0, 0}, {0, 0}};
1904 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1905 if (pts[0] == kA && pts[1] == kB) {
1906 canonicalizeAsAB = true;
1907 } else if (pts[1] == kA && pts[0] == kB) {
1908 canonicalizeAsAB = false;
1909 SkTSwap(canonicalPts[0], canonicalPts[1]);
1910 } else {
1911 ERRORF(r, "Should return pts (a,b) or (b, a)");
1912 return;
1913 };
1914
1915 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1916 TestCase::kSameUpToPE_ComparisonExpecation);
1917 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1918 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1919 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1920 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1921 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1922 pts[0] == kA && pts[1] == kB);
1923 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1924 pts[0] == kB && pts[1] == kA);
1925
1926
bsalomona395f7c2016-08-24 17:47:40 -07001927 TestCase strokeInvAB(r, invLineAB, stroke);
1928 TestCase hairlineInvAB(r, invLineAB, hairline);
1929 TestCase dashInvAB(r, invLineAB, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001930 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1931 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1932 // Dashing ignores inverse.
1933 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1934
1935 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1936 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1937 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1938 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1939 // Dashing ignores inverse.
1940 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1941 pts[0] == kA && pts[1] == kB);
1942
1943}
1944
Mike Klein43344282017-08-16 11:56:22 -04001945DEF_TEST(GrShape_stroked_lines, r) {
Brian Salomon72f78c32017-12-21 11:56:42 -05001946 static constexpr SkScalar kIntervals1[] = {1.f, 0.f};
1947 auto dash1 = SkDashPathEffect::Make(kIntervals1, SK_ARRAY_COUNT(kIntervals1), 0.f);
1948 REPORTER_ASSERT(r, dash1);
1949 static constexpr SkScalar kIntervals2[] = {10.f, 0.f, 5.f, 0.f};
1950 auto dash2 = SkDashPathEffect::Make(kIntervals2, SK_ARRAY_COUNT(kIntervals2), 10.f);
1951 REPORTER_ASSERT(r, dash2);
bsalomon0ae36a22016-07-18 07:31:13 -07001952
Brian Salomon72f78c32017-12-21 11:56:42 -05001953 sk_sp<SkPathEffect> pathEffects[] = {nullptr, std::move(dash1), std::move(dash2)};
bsalomon0ae36a22016-07-18 07:31:13 -07001954
Brian Salomon72f78c32017-12-21 11:56:42 -05001955 for (const auto& pe : pathEffects) {
1956 // Paints to try
1957 SkPaint buttCap;
1958 buttCap.setStyle(SkPaint::kStroke_Style);
1959 buttCap.setStrokeWidth(4);
1960 buttCap.setStrokeCap(SkPaint::kButt_Cap);
1961 buttCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07001962
Brian Salomon72f78c32017-12-21 11:56:42 -05001963 SkPaint squareCap = buttCap;
1964 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
1965 squareCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07001966
Brian Salomon72f78c32017-12-21 11:56:42 -05001967 SkPaint roundCap = buttCap;
1968 roundCap.setStrokeCap(SkPaint::kRound_Cap);
1969 roundCap.setPathEffect(pe);
bsalomon0ae36a22016-07-18 07:31:13 -07001970
Brian Salomon72f78c32017-12-21 11:56:42 -05001971 // vertical
1972 SkPath linePath;
1973 linePath.moveTo(4, 4);
1974 linePath.lineTo(4, 5);
bsalomon0ae36a22016-07-18 07:31:13 -07001975
Brian Salomon72f78c32017-12-21 11:56:42 -05001976 SkPaint fill;
bsalomon0ae36a22016-07-18 07:31:13 -07001977
Brian Salomon72f78c32017-12-21 11:56:42 -05001978 make_TestCase(r, linePath, buttCap)->compare(
1979 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
1980 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001981
Brian Salomon72f78c32017-12-21 11:56:42 -05001982 make_TestCase(r, linePath, squareCap)->compare(
1983 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
1984 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001985
Brian Salomon72f78c32017-12-21 11:56:42 -05001986 make_TestCase(r, linePath, roundCap)->compare(r,
1987 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
1988 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001989
Brian Salomon72f78c32017-12-21 11:56:42 -05001990 // horizontal
1991 linePath.reset();
1992 linePath.moveTo(4, 4);
1993 linePath.lineTo(5, 4);
bsalomon0ae36a22016-07-18 07:31:13 -07001994
Brian Salomon72f78c32017-12-21 11:56:42 -05001995 make_TestCase(r, linePath, buttCap)->compare(
1996 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
1997 TestCase::kAllSame_ComparisonExpecation);
1998 make_TestCase(r, linePath, squareCap)->compare(
1999 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
2000 TestCase::kAllSame_ComparisonExpecation);
2001 make_TestCase(r, linePath, roundCap)->compare(
2002 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
2003 TestCase::kAllSame_ComparisonExpecation);
2004
2005 // point
2006 linePath.reset();
2007 linePath.moveTo(4, 4);
2008 linePath.lineTo(4, 4);
2009
2010 make_TestCase(r, linePath, buttCap)->compare(
2011 r, TestCase(r, SkRect::MakeEmpty(), fill),
2012 TestCase::kAllSame_ComparisonExpecation);
2013 make_TestCase(r, linePath, squareCap)->compare(
2014 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
2015 TestCase::kAllSame_ComparisonExpecation);
2016 make_TestCase(r, linePath, roundCap)->compare(
2017 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
2018 TestCase::kAllSame_ComparisonExpecation);
2019 }
bsalomon0ae36a22016-07-18 07:31:13 -07002020}
2021
Mike Klein43344282017-08-16 11:56:22 -04002022DEF_TEST(GrShape_short_path_keys, r) {
bsalomon67fa4e32016-09-21 08:26:57 -07002023 SkPaint paints[4];
2024 paints[1].setStyle(SkPaint::kStroke_Style);
2025 paints[1].setStrokeWidth(5.f);
2026 paints[2].setStyle(SkPaint::kStroke_Style);
2027 paints[2].setStrokeWidth(0.f);
2028 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
2029 paints[3].setStrokeWidth(5.f);
2030
bsalomonaa840642016-09-23 12:09:16 -07002031 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
bsalomon67fa4e32016-09-21 08:26:57 -07002032 TestCase::ComparisonExpecation expectation) {
bsalomonaa840642016-09-23 12:09:16 -07002033 SkPath volatileA = pathA;
2034 SkPath volatileB = pathB;
2035 volatileA.setIsVolatile(true);
2036 volatileB.setIsVolatile(true);
bsalomon67fa4e32016-09-21 08:26:57 -07002037 for (const SkPaint& paint : paints) {
bsalomonaa840642016-09-23 12:09:16 -07002038 REPORTER_ASSERT(r, !GrShape(volatileA, paint).hasUnstyledKey());
2039 REPORTER_ASSERT(r, !GrShape(volatileB, paint).hasUnstyledKey());
bsalomon67fa4e32016-09-21 08:26:57 -07002040 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
bsalomonaa840642016-09-23 12:09:16 -07002041 TestCase caseA(PathGeo(pathA, invert), paint, r);
2042 TestCase caseB(PathGeo(pathB, invert), paint, r);
2043 caseA.compare(r, caseB, expectation);
bsalomon67fa4e32016-09-21 08:26:57 -07002044 }
2045 }
2046 };
2047
2048 SkPath pathA;
2049 SkPath pathB;
2050
2051 // Two identical paths
2052 pathA.lineTo(10.f, 10.f);
2053 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2054
2055 pathB.lineTo(10.f, 10.f);
2056 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002057 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002058
2059 // Give path b a different point
2060 pathB.reset();
2061 pathB.lineTo(10.f, 10.f);
2062 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07002063 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002064
2065 // Give path b a different conic weight
2066 pathB.reset();
2067 pathB.lineTo(10.f, 10.f);
2068 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
bsalomonaa840642016-09-23 12:09:16 -07002069 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002070
2071 // Give path b an extra lineTo verb
2072 pathB.reset();
2073 pathB.lineTo(10.f, 10.f);
2074 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2075 pathB.lineTo(50.f, 50.f);
bsalomonaa840642016-09-23 12:09:16 -07002076 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002077
2078 // Give path b a close
2079 pathB.reset();
2080 pathB.lineTo(10.f, 10.f);
2081 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2082 pathB.close();
bsalomonaa840642016-09-23 12:09:16 -07002083 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07002084}
2085
bsalomon47cc7692016-04-26 12:56:00 -07002086DEF_TEST(GrShape, reporter) {
bsalomona395f7c2016-08-24 17:47:40 -07002087 SkTArray<std::unique_ptr<Geo>> geos;
2088 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
2089
bsalomonee295642016-06-06 14:01:25 -07002090 for (auto r : { SkRect::MakeWH(10, 20),
2091 SkRect::MakeWH(-10, -20),
2092 SkRect::MakeWH(-10, 20),
2093 SkRect::MakeWH(10, -20)}) {
bsalomona395f7c2016-08-24 17:47:40 -07002094 geos.emplace_back(new RectGeo(r));
2095 SkPath rectPath;
2096 rectPath.addRect(r);
2097 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2098 PathGeo::Invert::kNo));
2099 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2100 PathGeo::Invert::kYes));
2101 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2102 PathGeo::Invert::kNo));
bsalomonee295642016-06-06 14:01:25 -07002103 }
bsalomon47cc7692016-04-26 12:56:00 -07002104 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -07002105 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
2106 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomona395f7c2016-08-24 17:47:40 -07002107 geos.emplace_back(new RRectGeo(rr));
bsalomon70493962016-06-10 08:05:14 -07002108 test_rrect(reporter, rr);
bsalomona395f7c2016-08-24 17:47:40 -07002109 SkPath rectPath;
2110 rectPath.addRRect(rr);
2111 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2112 PathGeo::Invert::kNo));
2113 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2114 PathGeo::Invert::kYes));
2115 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
2116 RRectPathGeo::RRectForStroke::kYes,
2117 PathGeo::Invert::kNo));
bsalomon72dc51c2016-04-27 06:46:23 -07002118 }
2119
Mike Klein43344282017-08-16 11:56:22 -04002120 {
2121 SkPath openRectPath;
2122 openRectPath.moveTo(0, 0);
2123 openRectPath.lineTo(10, 0);
2124 openRectPath.lineTo(10, 10);
2125 openRectPath.lineTo(0, 10);
2126 geos.emplace_back(new RRectPathGeo(
2127 openRectPath, SkRect::MakeWH(10, 10),
2128 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2129 geos.emplace_back(new RRectPathGeo(
2130 openRectPath, SkRect::MakeWH(10, 10),
2131 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
2132 rrectPathGeos.emplace_back(new RRectPathGeo(
2133 openRectPath, SkRect::MakeWH(10, 10),
2134 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2135 }
bsalomon72dc51c2016-04-27 06:46:23 -07002136
Mike Klein43344282017-08-16 11:56:22 -04002137 {
2138 SkPath quadPath;
2139 quadPath.quadTo(10, 10, 5, 8);
2140 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
2141 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
2142 }
bsalomon398e3f42016-06-13 10:22:48 -07002143
Mike Klein43344282017-08-16 11:56:22 -04002144 {
2145 SkPath linePath;
2146 linePath.lineTo(10, 10);
2147 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
2148 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
2149 }
bsalomon72dc51c2016-04-27 06:46:23 -07002150
bsalomon0ae36a22016-07-18 07:31:13 -07002151 // Horizontal and vertical paths become rrects when stroked.
Mike Klein43344282017-08-16 11:56:22 -04002152 {
2153 SkPath vLinePath;
2154 vLinePath.lineTo(0, 10);
2155 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
2156 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
2157 }
bsalomon0ae36a22016-07-18 07:31:13 -07002158
Mike Klein43344282017-08-16 11:56:22 -04002159 {
2160 SkPath hLinePath;
2161 hLinePath.lineTo(10, 0);
2162 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
2163 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
2164 }
bsalomon0ae36a22016-07-18 07:31:13 -07002165
bsalomona395f7c2016-08-24 17:47:40 -07002166 for (int i = 0; i < geos.count(); ++i) {
2167 test_basic(reporter, *geos[i]);
2168 test_scale(reporter, *geos[i]);
2169 test_dash_fill(reporter, *geos[i]);
2170 test_null_dash(reporter, *geos[i]);
2171 // Test modifying various stroke params.
2172 test_stroke_param<SkScalar>(
2173 reporter, *geos[i],
bsalomon70493962016-06-10 08:05:14 -07002174 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
2175 SkIntToScalar(2), SkIntToScalar(4));
bsalomona395f7c2016-08-24 17:47:40 -07002176 test_stroke_join(reporter, *geos[i]);
2177 test_stroke_cap(reporter, *geos[i]);
2178 test_miter_limit(reporter, *geos[i]);
2179 test_path_effect_makes_rrect(reporter, *geos[i]);
2180 test_unknown_path_effect(reporter, *geos[i]);
2181 test_path_effect_makes_empty_shape(reporter, *geos[i]);
2182 test_path_effect_fails(reporter, *geos[i]);
2183 test_make_hairline_path_effect(reporter, *geos[i]);
2184 test_volatile_path(reporter, *geos[i]);
bsalomon70493962016-06-10 08:05:14 -07002185 }
bsalomonfd32df72016-06-14 14:37:21 -07002186
bsalomona395f7c2016-08-24 17:47:40 -07002187 for (int i = 0; i < rrectPathGeos.count(); ++i) {
2188 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
bsalomon72dc51c2016-04-27 06:46:23 -07002189 SkPaint fillPaint;
bsalomona395f7c2016-08-24 17:47:40 -07002190 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002191 SkRRect rrect;
bsalomona395f7c2016-08-24 17:47:40 -07002192 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
bsalomon70493962016-06-10 08:05:14 -07002193 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2194 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002195 if (rrgeo.isNonPath(fillPaint)) {
2196 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
2197 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2198 TestCase fillRRectCase(reporter, rrect, fillPaint);
bsalomon70493962016-06-10 08:05:14 -07002199 fillPathCase2.compare(reporter, fillRRectCase,
2200 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002201 }
bsalomon72dc51c2016-04-27 06:46:23 -07002202 SkPaint strokePaint;
2203 strokePaint.setStrokeWidth(3.f);
2204 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07002205 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
2206 if (rrgeo.isNonPath(strokePaint)) {
bsalomon0ae36a22016-07-18 07:31:13 -07002207 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2208 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07002209 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2210 TestCase strokeRRectCase(reporter, rrect, strokePaint);
bsalomon72dc51c2016-04-27 06:46:23 -07002211 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07002212 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07002213 }
bsalomon47cc7692016-04-26 12:56:00 -07002214 }
bsalomon409ed732016-04-27 12:36:02 -07002215
bsalomon4eeccc92016-04-27 13:30:25 -07002216 // Test a volatile empty path.
bsalomona395f7c2016-08-24 17:47:40 -07002217 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
bsalomon47cc7692016-04-26 12:56:00 -07002218}
2219
2220#endif