blob: 646c06c4443c309ae9a60e6715749c6810e8aea0 [file] [log] [blame]
bsalomon47cc7692016-04-26 12:56:00 -07001/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include <initializer_list>
9#include <functional>
10#include "Test.h"
11#if SK_SUPPORT_GPU
12#include "GrShape.h"
bsalomon9fb42032016-05-13 09:23:38 -070013#include "SkCanvas.h"
bsalomon47cc7692016-04-26 12:56:00 -070014#include "SkDashPathEffect.h"
bsalomon9fb42032016-05-13 09:23:38 -070015#include "SkPath.h"
bsalomonee295642016-06-06 14:01:25 -070016#include "SkPathOps.h"
bsalomon9fb42032016-05-13 09:23:38 -070017#include "SkSurface.h"
Mike Reedebfce6d2016-12-12 10:02:12 -050018#include "SkClipOpPriv.h"
bsalomon47cc7692016-04-26 12:56:00 -070019
bsalomon72dc51c2016-04-27 06:46:23 -070020using Key = SkTArray<uint32_t>;
21
22static bool make_key(Key* key, const GrShape& shape) {
23 int size = shape.unstyledKeySize();
24 if (size <= 0) {
25 key->reset(0);
26 return false;
27 }
28 SkASSERT(size);
29 key->reset(size);
30 shape.writeUnstyledKey(key->begin());
31 return true;
32}
33
bsalomonee295642016-06-06 14:01:25 -070034static bool paths_fill_same(const SkPath& a, const SkPath& b) {
35 SkPath pathXor;
36 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
37 return pathXor.isEmpty();
38}
39
bsalomon9fb42032016-05-13 09:23:38 -070040static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
bsalomon164fd9f2016-08-26 06:45:06 -070041 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
42 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
43 // rendering within the bounds (with a tolerance). Then we render the path and check that
44 // everything got clipped out.
bsalomon9fb42032016-05-13 09:23:38 -070045 static constexpr int kRes = 2000;
46 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
47 static constexpr int kTol = 0;
48 GR_STATIC_ASSERT(kRes % 4 == 0);
49 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
50 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
51 surface->getCanvas()->clear(0x0);
52 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
53 SkMatrix matrix;
54 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit);
55 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
Mike Reedc1f77742016-12-09 09:00:50 -050056 surface->getCanvas()->clipRect(clip, kDifference_SkClipOp);
bsalomon9fb42032016-05-13 09:23:38 -070057 surface->getCanvas()->concat(matrix);
58 SkPaint whitePaint;
59 whitePaint.setColor(SK_ColorWHITE);
60 surface->getCanvas()->drawPath(path, whitePaint);
61 SkPixmap pixmap;
62 surface->getCanvas()->peekPixels(&pixmap);
63#if defined(SK_BUILD_FOR_WIN)
64 // The static constexpr version in #else causes cl.exe to crash.
65 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
66#else
67 static constexpr uint8_t kZeros[kRes] = {0};
68#endif
bsalomon164fd9f2016-08-26 06:45:06 -070069 for (int y = 0; y < kRes; ++y) {
bsalomon9fb42032016-05-13 09:23:38 -070070 const uint8_t* row = pixmap.addr8(0, y);
71 if (0 != memcmp(kZeros, row, kRes)) {
72 return false;
73 }
74 }
75#ifdef SK_BUILD_FOR_WIN
76 free(const_cast<uint8_t*>(kZeros));
77#endif
78 return true;
79}
bsalomon72dc51c2016-04-27 06:46:23 -070080
bsalomon9fb42032016-05-13 09:23:38 -070081namespace {
bsalomona395f7c2016-08-24 17:47:40 -070082/**
83 * Geo is a factory for creating a GrShape from another representation. It also answers some
84 * questions about expected behavior for GrShape given the inputs.
85 */
86class Geo {
87public:
Mike Kleinfc6c37b2016-09-27 09:34:10 -040088 virtual ~Geo() {}
bsalomona395f7c2016-08-24 17:47:40 -070089 virtual GrShape makeShape(const SkPaint&) const = 0;
90 virtual SkPath path() const = 0;
91 // These functions allow tests to check for special cases where style gets
92 // applied by GrShape in its constructor (without calling GrShape::applyStyle).
93 // These unfortunately rely on knowing details of GrShape's implementation.
94 // These predicates are factored out here to avoid littering the rest of the
95 // test code with GrShape implementation details.
96 virtual bool fillChangesGeom() const { return false; }
97 virtual bool strokeIsConvertedToFill() const { return false; }
98 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
99 // Is this something we expect GrShape to recognize as something simpler than a path.
100 virtual bool isNonPath(const SkPaint& paint) const { return true; }
101};
102
103class RectGeo : public Geo {
104public:
105 RectGeo(const SkRect& rect) : fRect(rect) {}
106
107 SkPath path() const override {
108 SkPath path;
109 path.addRect(fRect);
110 return path;
111 }
112
113 GrShape makeShape(const SkPaint& paint) const override {
114 return GrShape(fRect, paint);
115 }
116
117 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
118 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
119 // Converted to an outset rectangle.
120 return paint.getStrokeJoin() == SkPaint::kMiter_Join &&
121 paint.getStrokeMiter() >= SK_ScalarSqrt2;
122 }
123
124private:
125 SkRect fRect;
126};
127
128class RRectGeo : public Geo {
129public:
130 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
131
132 GrShape makeShape(const SkPaint& paint) const override {
133 return GrShape(fRRect, paint);
134 }
135
136 SkPath path() const override {
137 SkPath path;
138 path.addRRect(fRRect);
139 return path;
140 }
141
142 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
143 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
144 if (fRRect.isRect()) {
145 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
146 }
147 return false;
148 }
149
150private:
151 SkRRect fRRect;
152};
153
154class PathGeo : public Geo {
155public:
156 enum class Invert { kNo, kYes };
157
158 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
159 SkASSERT(!path.isInverseFillType());
160 if (Invert::kYes == invert) {
161 if (fPath.getFillType() == SkPath::kEvenOdd_FillType) {
162 fPath.setFillType(SkPath::kInverseEvenOdd_FillType);
163 } else {
164 SkASSERT(fPath.getFillType() == SkPath::kWinding_FillType);
165 fPath.setFillType(SkPath::kInverseWinding_FillType);
166 }
167 }
168 }
169
170 GrShape makeShape(const SkPaint& paint) const override {
171 return GrShape(fPath, paint);
172 }
173
174 SkPath path() const override { return fPath; }
175
176 bool fillChangesGeom() const override {
177 // unclosed rects get closed. Lines get turned into empty geometry
178 return this->isUnclosedRect() || (fPath.isLine(nullptr) && !fPath.isInverseFillType());
179 }
180
181 bool strokeIsConvertedToFill() const override {
182 return this->isAxisAlignedLine();
183 }
184
185 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
186 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
187 if (this->isAxisAlignedLine()) {
188 // The fill is ignored (zero area) and the stroke is converted to a rrect.
189 return true;
190 }
191 SkRect rect;
192 unsigned start;
193 SkPath::Direction dir;
194 if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
195 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
196 }
197 return false;
198 }
199
200 bool isNonPath(const SkPaint& paint) const override {
201 return fPath.isLine(nullptr) || fPath.isEmpty();
202 }
203
204private:
205 bool isAxisAlignedLine() const {
206 SkPoint pts[2];
207 if (!fPath.isLine(pts)) {
208 return false;
209 }
210 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
211 }
212
213 bool isUnclosedRect() const {
214 bool closed;
215 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
216 }
217
218 SkPath fPath;
219};
220
221class RRectPathGeo : public PathGeo {
222public:
223 enum class RRectForStroke { kNo, kYes };
224
225 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
226 Invert invert)
227 : PathGeo(path, invert)
228 , fRRect(equivalentRRect)
229 , fRRectForStroke(rrectForStroke) {}
230
231 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
232 Invert invert)
233 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
234
235 bool isNonPath(const SkPaint& paint) const override {
236 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
237 return true;
238 }
239 return false;
240 }
241
242 const SkRRect& rrect() const { return fRRect; }
243
244private:
245 SkRRect fRRect;
246 RRectForStroke fRRectForStroke;
247};
248
bsalomon47cc7692016-04-26 12:56:00 -0700249class TestCase {
250public:
bsalomona395f7c2016-08-24 17:47:40 -0700251 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
252 SkScalar scale = SK_Scalar1) : fBase(geo.makeShape(paint)) {
bsalomon97fd2d42016-05-09 13:02:01 -0700253 this->init(r, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700254 }
255
bsalomona395f7c2016-08-24 17:47:40 -0700256 template<typename... ShapeArgs>
257 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs)
258 : fBase(shapeArgs...) {
259 this->init(r, SK_Scalar1);
260 }
261
bsalomon70493962016-06-10 08:05:14 -0700262 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
263 : fBase(shape) {
264 this->init(r, scale);
265 }
266
bsalomon47cc7692016-04-26 12:56:00 -0700267 struct SelfExpectations {
268 bool fPEHasEffect;
269 bool fPEHasValidKey;
270 bool fStrokeApplies;
271 };
272
273 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
274
275 enum ComparisonExpecation {
276 kAllDifferent_ComparisonExpecation,
277 kSameUpToPE_ComparisonExpecation,
278 kSameUpToStroke_ComparisonExpecation,
279 kAllSame_ComparisonExpecation,
280 };
281
282 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
283
bsalomon72dc51c2016-04-27 06:46:23 -0700284 const GrShape& baseShape() const { return fBase; }
285 const GrShape& appliedPathEffectShape() const { return fAppliedPE; }
286 const GrShape& appliedFullStyleShape() const { return fAppliedFull; }
287
288 // The returned array's count will be 0 if the key shape has no key.
289 const Key& baseKey() const { return fBaseKey; }
290 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
291 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700292 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700293
bsalomon47cc7692016-04-26 12:56:00 -0700294private:
bsalomon9fb42032016-05-13 09:23:38 -0700295 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
296 SkPath path;
297 shape.asPath(&path);
298 // If the bounds are empty, the path ought to be as well.
bsalomon0ae36a22016-07-18 07:31:13 -0700299 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
bsalomon9fb42032016-05-13 09:23:38 -0700300 REPORTER_ASSERT(r, path.isEmpty());
301 return;
302 }
303 if (path.isEmpty()) {
304 return;
305 }
bsalomon70493962016-06-10 08:05:14 -0700306 // The bounds API explicitly calls out that it does not consider inverseness.
307 SkPath p = path;
308 p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType()));
309 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
bsalomon9fb42032016-05-13 09:23:38 -0700310 }
311
bsalomon97fd2d42016-05-09 13:02:01 -0700312 void init(skiatest::Reporter* r, SkScalar scale) {
313 fAppliedPE = fBase.applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
314 fAppliedPEThenStroke = fAppliedPE.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec,
315 scale);
316 fAppliedFull = fBase.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700317
bsalomon72dc51c2016-04-27 06:46:23 -0700318 make_key(&fBaseKey, fBase);
319 make_key(&fAppliedPEKey, fAppliedPE);
320 make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke);
321 make_key(&fAppliedFullKey, fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700322
323 // Applying the path effect and then the stroke should always be the same as applying
324 // both in one go.
325 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
326 SkPath a, b;
327 fAppliedPEThenStroke.asPath(&a);
328 fAppliedFull.asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700329 // If the output of the path effect is a rrect then it is possible for a and b to be
330 // different paths that fill identically. The reason is that fAppliedFull will do this:
331 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
332 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
333 // now that there is no longer a path effect, the direction and starting index get
334 // canonicalized before the stroke.
bsalomon70493962016-06-10 08:05:14 -0700335 if (fAppliedPE.asRRect(nullptr, nullptr, nullptr, nullptr)) {
bsalomonee295642016-06-06 14:01:25 -0700336 REPORTER_ASSERT(r, paths_fill_same(a, b));
337 } else {
338 REPORTER_ASSERT(r, a == b);
339 }
bsalomon7c73a532016-05-11 15:15:56 -0700340 REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty());
341
342 SkPath path;
343 fBase.asPath(&path);
344 REPORTER_ASSERT(r, path.isEmpty() == fBase.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700345 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700346 fAppliedPE.asPath(&path);
347 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700348 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700349 fAppliedFull.asPath(&path);
350 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700351 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull.segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700352
bsalomon9fb42032016-05-13 09:23:38 -0700353 CheckBounds(r, fBase, fBase.bounds());
354 CheckBounds(r, fAppliedPE, fAppliedPE.bounds());
355 CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds());
356 CheckBounds(r, fAppliedFull, fAppliedFull.bounds());
bsalomon0a0f67e2016-06-28 11:56:42 -0700357 SkRect styledBounds = fBase.styledBounds();
bsalomon9fb42032016-05-13 09:23:38 -0700358 CheckBounds(r, fAppliedFull, styledBounds);
bsalomon0a0f67e2016-06-28 11:56:42 -0700359 styledBounds = fAppliedPE.styledBounds();
bsalomon9fb42032016-05-13 09:23:38 -0700360 CheckBounds(r, fAppliedFull, styledBounds);
361
bsalomonfb083272016-05-04 08:27:41 -0700362 // Check that the same path is produced when style is applied by GrShape and GrStyle.
363 SkPath preStyle;
364 SkPath postPathEffect;
365 SkPath postAllStyle;
366
367 fBase.asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700368 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
bsalomon97fd2d42016-05-09 13:02:01 -0700369 if (fBase.style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
370 scale)) {
bsalomon1a0b9ed2016-05-06 11:07:03 -0700371 // run postPathEffect through GrShape to get any geometry reductions that would have
372 // occurred to fAppliedPE.
373 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
374
bsalomonfb083272016-05-04 08:27:41 -0700375 SkPath testPath;
376 fAppliedPE.asPath(&testPath);
377 REPORTER_ASSERT(r, testPath == postPathEffect);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700378 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE.style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700379 }
380 SkStrokeRec::InitStyle fillOrHairline;
bsalomon97fd2d42016-05-09 13:02:01 -0700381 if (fBase.style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomonfb083272016-05-04 08:27:41 -0700382 SkPath testPath;
383 fAppliedFull.asPath(&testPath);
bsalomon1b28c1a2016-06-20 12:28:17 -0700384 if (fBase.style().hasPathEffect()) {
385 // Because GrShape always does two-stage application when there is a path effect
386 // there may be a reduction/canonicalization step between the path effect and
387 // strokerec not reflected in postAllStyle since it applied both the path effect
388 // and strokerec without analyzing the intermediate path.
389 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
390 } else {
391 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape
392 // would apply.
393 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
394 REPORTER_ASSERT(r, testPath == postAllStyle);
395 }
396
bsalomonfb083272016-05-04 08:27:41 -0700397 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
398 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleFill());
399 } else {
400 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline());
401 }
402 }
bsalomon47cc7692016-04-26 12:56:00 -0700403 }
404
405 GrShape fBase;
406 GrShape fAppliedPE;
407 GrShape fAppliedPEThenStroke;
408 GrShape fAppliedFull;
409
410 Key fBaseKey;
411 Key fAppliedPEKey;
412 Key fAppliedPEThenStrokeKey;
413 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700414};
415
416void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700417 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700418 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700419 if (expectations.fPEHasEffect) {
420 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700421 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700422 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700423 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700424 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
425 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700426 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700427 }
428 } else {
429 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700430 SkPath a, b;
bsalomon72dc51c2016-04-27 06:46:23 -0700431 fBase.asPath(&a);
432 fAppliedPE.asPath(&b);
433 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700434 if (expectations.fStrokeApplies) {
435 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
436 } else {
437 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
438 }
439 }
440}
441
bsalomonac5fcea2016-06-23 12:23:07 -0700442static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
443 SkPath path;
444 shape.asPath(&path);
445 if (shape.style().hasNonDashPathEffect()) {
446 return false;
447 }
448 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
449 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
450 strokeRecStyle == SkStrokeRec::kHairline_Style ||
451 (shape.style().isSimpleFill() && path.isConvex());
452}
453
454static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
455 const Key& keyA, const Key& keyB) {
bsalomonee295642016-06-06 14:01:25 -0700456 // GrShape only respects the input winding direction and start point for rrect shapes
457 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
458 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
459 // key will differ. GrShape will have canonicalized the direction and start point for the shape
460 // without the path effect. If *both* have path effects then they should have both preserved
461 // the direction and starting point.
462
463 // The asRRect() output params are all initialized just to silence compiler warnings about
464 // uninitialized variables.
465 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
466 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
467 unsigned startA = ~0U, startB = ~0U;
bsalomon70493962016-06-10 08:05:14 -0700468 bool invertedA = true, invertedB = true;
bsalomonee295642016-06-06 14:01:25 -0700469
bsalomon70493962016-06-10 08:05:14 -0700470 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
471 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
bsalomonee295642016-06-06 14:01:25 -0700472 bool aHasPE = a.style().hasPathEffect();
473 bool bHasPE = b.style().hasPathEffect();
474 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
bsalomon425c27f2016-06-23 13:18:45 -0700475 // GrShape will close paths with simple fill style.
476 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
bsalomonee295642016-06-06 14:01:25 -0700477 SkPath pathA, pathB;
478 a.asPath(&pathA);
479 b.asPath(&pathB);
bsalomon70493962016-06-10 08:05:14 -0700480
bsalomonfd32df72016-06-14 14:37:21 -0700481 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
482 // non-inverse fill type (or vice versa).
bsalomon70493962016-06-10 08:05:14 -0700483 bool ignoreInversenessDifference = false;
484 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
485 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
486 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
bsalomonfd32df72016-06-14 14:37:21 -0700487 bool canDropInverse1 = s1->style().isDashed();
488 bool canDropInverse2 = s2->style().isDashed();
489 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
bsalomon70493962016-06-10 08:05:14 -0700490 }
bsalomona4817af2016-06-23 11:48:26 -0700491 bool ignoreWindingVsEvenOdd = false;
492 if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
493 SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
bsalomonac5fcea2016-06-23 12:23:07 -0700494 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
495 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
bsalomona4817af2016-06-23 11:48:26 -0700496 if (aCanChange != bCanChange) {
497 ignoreWindingVsEvenOdd = true;
498 }
499 }
bsalomonee295642016-06-06 14:01:25 -0700500 if (allowSameRRectButDiffStartAndDir) {
501 REPORTER_ASSERT(r, rrectA == rrectB);
502 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
bsalomon70493962016-06-10 08:05:14 -0700503 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
bsalomonee295642016-06-06 14:01:25 -0700504 } else {
bsalomon70493962016-06-10 08:05:14 -0700505 SkPath pA = pathA;
506 SkPath pB = pathB;
bsalomon425c27f2016-06-23 13:18:45 -0700507 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
508 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
bsalomon70493962016-06-10 08:05:14 -0700509 if (ignoreInversenessDifference) {
510 pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
511 pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
bsalomona4817af2016-06-23 11:48:26 -0700512 }
513 if (ignoreWindingVsEvenOdd) {
514 pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
515 : SkPath::kEvenOdd_FillType);
516 pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
517 : SkPath::kEvenOdd_FillType);
518 }
519 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
bsalomon70493962016-06-10 08:05:14 -0700520 REPORTER_ASSERT(r, keyA == keyB);
bsalomona4817af2016-06-23 11:48:26 -0700521 } else {
522 REPORTER_ASSERT(r, keyA != keyB);
bsalomon70493962016-06-10 08:05:14 -0700523 }
bsalomon425c27f2016-06-23 13:18:45 -0700524 if (allowedClosednessDiff) {
bsalomon93f66bc2016-06-21 08:35:49 -0700525 // GrShape will close paths with simple fill style. Make the non-filled path closed
526 // so that the comparision will succeed. Make sure both are closed before comparing.
527 pA.close();
528 pB.close();
529 }
bsalomon70493962016-06-10 08:05:14 -0700530 REPORTER_ASSERT(r, pA == pB);
bsalomonee295642016-06-06 14:01:25 -0700531 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
532 if (aIsRRect) {
533 REPORTER_ASSERT(r, rrectA == rrectB);
534 REPORTER_ASSERT(r, dirA == dirB);
535 REPORTER_ASSERT(r, startA == startB);
bsalomon70493962016-06-10 08:05:14 -0700536 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
bsalomonee295642016-06-06 14:01:25 -0700537 }
538 }
539 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
bsalomon425c27f2016-06-23 13:18:45 -0700540 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
541 // closedness can affect convexity.
542 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
543 if (a.knownToBeConvex()) {
544 REPORTER_ASSERT(r, pathA.isConvex());
545 }
546 if (b.knownToBeConvex()) {
547 REPORTER_ASSERT(r, pathB.isConvex());
548 }
bsalomonee295642016-06-06 14:01:25 -0700549 REPORTER_ASSERT(r, a.bounds() == b.bounds());
bsalomon06115ee2016-06-07 06:28:51 -0700550 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
bsalomon0a0f67e2016-06-28 11:56:42 -0700551 // Init these to suppress warnings.
552 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
553 bool invertedLine[2] {true, true};
554 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
bsalomon425c27f2016-06-23 13:18:45 -0700555 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
556 // doesn't (since the PE can set any fill type on its output path).
557 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
558 // then they may disagree about inverseness.
559 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
560 a.style().isDashed() == b.style().isDashed()) {
561 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
562 b.mayBeInverseFilledAfterStyling());
563 }
bsalomon0a0f67e2016-06-28 11:56:42 -0700564 if (a.asLine(nullptr, nullptr)) {
bsalomon398e3f42016-06-13 10:22:48 -0700565 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
bsalomon0a0f67e2016-06-28 11:56:42 -0700566 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
567 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
568 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
bsalomon398e3f42016-06-13 10:22:48 -0700569 }
bsalomon425c27f2016-06-23 13:18:45 -0700570 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
bsalomonee295642016-06-06 14:01:25 -0700571}
572
573void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700574 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700575 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700576 switch (expectation) {
577 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700578 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
579 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
580 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700581 break;
582 case kSameUpToPE_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700583 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
584 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
585 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700586 break;
587 case kSameUpToStroke_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700588 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
589 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
590 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700591 break;
592 case kAllSame_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700593 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
594 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
595 check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey,
596 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700597 break;
598 }
599}
600} // namespace
601
602static sk_sp<SkPathEffect> make_dash() {
603 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
604 static const SkScalar kPhase = 0.75;
605 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
606}
607
608static sk_sp<SkPathEffect> make_null_dash() {
609 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
610 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
611}
612
Mike Klein43344282017-08-16 11:56:22 -0400613// We make enough TestCases, and they're large enough, that on Google3 builds we exceed
614// the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
615template <typename... Args>
616static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
617 return std::unique_ptr<TestCase>{ new TestCase(std::forward<Args>(args)...) };
618}
619
bsalomona395f7c2016-08-24 17:47:40 -0700620static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700621 sk_sp<SkPathEffect> dashPE = make_dash();
622
623 TestCase::SelfExpectations expectations;
624 SkPaint fill;
625
bsalomonfb083272016-05-04 08:27:41 -0700626 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700627 expectations.fPEHasEffect = false;
628 expectations.fPEHasValidKey = false;
629 expectations.fStrokeApplies = false;
630 fillCase.testExpectations(reporter, expectations);
631 // Test that another GrShape instance built from the same primitive is the same.
Mike Klein43344282017-08-16 11:56:22 -0400632 make_TestCase(geo, fill, reporter)
633 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700634
635 SkPaint stroke2RoundBevel;
636 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
637 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
638 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
639 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700640 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700641 expectations.fPEHasValidKey = true;
642 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700643 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomon47cc7692016-04-26 12:56:00 -0700644 stroke2RoundBevelCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400645 make_TestCase(geo, stroke2RoundBevel, reporter)
646 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700647
648 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
649 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700650 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700651 expectations.fPEHasValidKey = true;
652 expectations.fPEHasEffect = true;
653 expectations.fStrokeApplies = true;
654 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400655 make_TestCase(geo, stroke2RoundBevelDash, reporter)
656 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700657
bsalomona395f7c2016-08-24 17:47:40 -0700658 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700659 fillCase.compare(reporter, stroke2RoundBevelCase,
660 TestCase::kAllDifferent_ComparisonExpecation);
661 fillCase.compare(reporter, stroke2RoundBevelDashCase,
662 TestCase::kAllDifferent_ComparisonExpecation);
663 } else {
664 fillCase.compare(reporter, stroke2RoundBevelCase,
665 TestCase::kSameUpToStroke_ComparisonExpecation);
666 fillCase.compare(reporter, stroke2RoundBevelDashCase,
667 TestCase::kSameUpToPE_ComparisonExpecation);
668 }
bsalomona395f7c2016-08-24 17:47:40 -0700669 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700670 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
671 TestCase::kAllDifferent_ComparisonExpecation);
672 } else {
673 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
674 TestCase::kSameUpToPE_ComparisonExpecation);
675 }
bsalomon72dc51c2016-04-27 06:46:23 -0700676
bsalomonf0cf3552016-05-05 08:28:30 -0700677 // Stroke and fill cases
678 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
679 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
680 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
681 expectations.fPEHasValidKey = true;
682 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700683 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700684 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400685 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
686 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700687
688 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
689 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
690 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
691 expectations.fPEHasValidKey = true;
bsalomona0587862016-06-09 06:03:38 -0700692 expectations.fPEHasEffect = false;
bsalomona395f7c2016-08-24 17:47:40 -0700693 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
bsalomonf0cf3552016-05-05 08:28:30 -0700694 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
Mike Klein43344282017-08-16 11:56:22 -0400695 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
bsalomonf0cf3552016-05-05 08:28:30 -0700696 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomona0587862016-06-09 06:03:38 -0700697 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
698 TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700699
bsalomon72dc51c2016-04-27 06:46:23 -0700700 SkPaint hairline;
701 hairline.setStyle(SkPaint::kStroke_Style);
702 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700703 TestCase hairlineCase(geo, hairline, reporter);
bsalomon487f8d32016-07-20 07:15:44 -0700704 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
705 // in the line and unclosed rect cases).
bsalomona395f7c2016-08-24 17:47:40 -0700706 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -0700707 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
708 } else {
709 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
710 }
bsalomon9ad5d7c2016-05-04 08:44:15 -0700711 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
712 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
713 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700714
bsalomon0ae36a22016-07-18 07:31:13 -0700715}
716
bsalomona395f7c2016-08-24 17:47:40 -0700717static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon97fd2d42016-05-09 13:02:01 -0700718 sk_sp<SkPathEffect> dashPE = make_dash();
719
720 static const SkScalar kS1 = 1.f;
721 static const SkScalar kS2 = 2.f;
722
723 SkPaint fill;
724 TestCase fillCase1(geo, fill, reporter, kS1);
725 TestCase fillCase2(geo, fill, reporter, kS2);
726 // Scale doesn't affect fills.
727 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
728
729 SkPaint hairline;
730 hairline.setStyle(SkPaint::kStroke_Style);
731 hairline.setStrokeWidth(0.f);
732 TestCase hairlineCase1(geo, hairline, reporter, kS1);
733 TestCase hairlineCase2(geo, hairline, reporter, kS2);
734 // Scale doesn't affect hairlines.
735 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
736
737 SkPaint stroke;
738 stroke.setStyle(SkPaint::kStroke_Style);
739 stroke.setStrokeWidth(2.f);
740 TestCase strokeCase1(geo, stroke, reporter, kS1);
741 TestCase strokeCase2(geo, stroke, reporter, kS2);
bsalomon0ae36a22016-07-18 07:31:13 -0700742 // Scale affects the stroke
bsalomona395f7c2016-08-24 17:47:40 -0700743 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700744 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
bsalomon0ae36a22016-07-18 07:31:13 -0700745 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
746 } else {
747 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
748 }
bsalomon97fd2d42016-05-09 13:02:01 -0700749
750 SkPaint strokeDash = stroke;
751 strokeDash.setPathEffect(make_dash());
752 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
753 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
754 // Scale affects the dash and the stroke.
bsalomon487f8d32016-07-20 07:15:44 -0700755 strokeDashCase1.compare(reporter, strokeDashCase2,
756 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700757
758 // Stroke and fill cases
759 SkPaint strokeAndFill = stroke;
760 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
761 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
762 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
bsalomona0587862016-06-09 06:03:38 -0700763 SkPaint strokeAndFillDash = strokeDash;
764 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
765 // Dash is ignored for stroke and fill
766 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
767 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
bsalomon487f8d32016-07-20 07:15:44 -0700768 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
769 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
770 // geometries should agree.
bsalomona395f7c2016-08-24 17:47:40 -0700771 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
bsalomon487f8d32016-07-20 07:15:44 -0700772 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
bsalomon97fd2d42016-05-09 13:02:01 -0700773 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
774 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700775 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
776 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700777 } else {
778 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
779 TestCase::kSameUpToStroke_ComparisonExpecation);
780 }
bsalomona0587862016-06-09 06:03:38 -0700781 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
782 TestCase::kAllSame_ComparisonExpecation);
783 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
784 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700785}
786
bsalomona395f7c2016-08-24 17:47:40 -0700787template <typename T>
788static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700789 std::function<void(SkPaint*, T)> setter, T a, T b,
790 bool paramAffectsStroke,
791 bool paramAffectsDashAndStroke) {
792 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
793 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700794 SkPaint strokeA;
795 strokeA.setStyle(SkPaint::kStroke_Style);
796 strokeA.setStrokeWidth(2.f);
797 setter(&strokeA, a);
798 SkPaint strokeB;
799 strokeB.setStyle(SkPaint::kStroke_Style);
800 strokeB.setStrokeWidth(2.f);
801 setter(&strokeB, b);
802
bsalomonfb083272016-05-04 08:27:41 -0700803 TestCase strokeACase(geo, strokeA, reporter);
804 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700805 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700806 // If stroking is immediately incorporated into a geometric transformation then the base
807 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700808 if (geo.strokeIsConvertedToFill()) {
bsalomon0ae36a22016-07-18 07:31:13 -0700809 strokeACase.compare(reporter, strokeBCase,
810 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700811 } else {
812 strokeACase.compare(reporter, strokeBCase,
813 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700814 }
bsalomon06077562016-05-04 13:50:29 -0700815 } else {
816 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
817 }
bsalomon47cc7692016-04-26 12:56:00 -0700818
bsalomonf0cf3552016-05-05 08:28:30 -0700819 SkPaint strokeAndFillA = strokeA;
820 SkPaint strokeAndFillB = strokeB;
821 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
822 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
823 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
824 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
825 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700826 // If stroking is immediately incorporated into a geometric transformation then the base
827 // shapes will differ.
bsalomona395f7c2016-08-24 17:47:40 -0700828 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
829 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700830 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700831 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700832 } else {
833 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700834 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700835 }
bsalomonf0cf3552016-05-05 08:28:30 -0700836 } else {
837 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
838 TestCase::kAllSame_ComparisonExpecation);
839 }
840
bsalomon47cc7692016-04-26 12:56:00 -0700841 // Make sure stroking params don't affect fill style.
842 SkPaint fillA = strokeA, fillB = strokeB;
843 fillA.setStyle(SkPaint::kFill_Style);
844 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -0700845 TestCase fillACase(geo, fillA, reporter);
846 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700847 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
848
849 // Make sure just applying the dash but not stroke gives the same key for both stroking
850 // variations.
851 SkPaint dashA = strokeA, dashB = strokeB;
852 dashA.setPathEffect(make_dash());
853 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700854 TestCase dashACase(geo, dashA, reporter);
855 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700856 if (paramAffectsDashAndStroke) {
bsalomon487f8d32016-07-20 07:15:44 -0700857 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon06077562016-05-04 13:50:29 -0700858 } else {
859 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
860 }
bsalomon47cc7692016-04-26 12:56:00 -0700861}
862
bsalomona395f7c2016-08-24 17:47:40 -0700863template <typename T>
864static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
bsalomon06077562016-05-04 13:50:29 -0700865 std::function<void(SkPaint*, T)> setter, T a, T b) {
866 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
867};
868
bsalomona395f7c2016-08-24 17:47:40 -0700869static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
870 SkPaint hairline;
871 hairline.setStrokeWidth(0);
872 hairline.setStyle(SkPaint::kStroke_Style);
873 GrShape shape = geo.makeShape(hairline);
bsalomon06077562016-05-04 13:50:29 -0700874 // The cap should only affect shapes that may be open.
875 bool affectsStroke = !shape.knownToBeClosed();
876 // Dashing adds ends that need caps.
877 bool affectsDashAndStroke = true;
bsalomona395f7c2016-08-24 17:47:40 -0700878 test_stroke_param_impl<SkPaint::Cap>(
bsalomon06077562016-05-04 13:50:29 -0700879 reporter,
880 geo,
881 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
882 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
883 affectsStroke,
884 affectsDashAndStroke);
885};
886
bsalomon0ae36a22016-07-18 07:31:13 -0700887static bool shape_known_not_to_have_joins(const GrShape& shape) {
888 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
889}
890
bsalomona395f7c2016-08-24 17:47:40 -0700891static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
892 SkPaint hairline;
893 hairline.setStrokeWidth(0);
894 hairline.setStyle(SkPaint::kStroke_Style);
895 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -0700896 // GrShape recognizes certain types don't have joins and will prevent the join type from
897 // affecting the style key.
898 // Dashing doesn't add additional joins. However, GrShape currently loses track of this
899 // after applying the dash.
900 bool affectsStroke = !shape_known_not_to_have_joins(shape);
bsalomona395f7c2016-08-24 17:47:40 -0700901 test_stroke_param_impl<SkPaint::Join>(
bsalomon0ae36a22016-07-18 07:31:13 -0700902 reporter,
903 geo,
904 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
905 SkPaint::kRound_Join, SkPaint::kBevel_Join,
906 affectsStroke, true);
907};
908
bsalomona395f7c2016-08-24 17:47:40 -0700909static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon06077562016-05-04 13:50:29 -0700910 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
911 p->setStrokeJoin(SkPaint::kMiter_Join);
912 p->setStrokeMiter(miter);
913 };
bsalomon47cc7692016-04-26 12:56:00 -0700914
bsalomon06077562016-05-04 13:50:29 -0700915 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
916 p->setStrokeJoin(SkPaint::kRound_Join);
917 p->setStrokeMiter(miter);
918 };
bsalomon47cc7692016-04-26 12:56:00 -0700919
bsalomona395f7c2016-08-24 17:47:40 -0700920 SkPaint hairline;
921 hairline.setStrokeWidth(0);
922 hairline.setStyle(SkPaint::kStroke_Style);
923 GrShape shape = geo.makeShape(hairline);
bsalomon0ae36a22016-07-18 07:31:13 -0700924 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
925
bsalomon06077562016-05-04 13:50:29 -0700926 // The miter limit should affect stroked and dashed-stroked cases when the join type is
927 // miter.
bsalomona395f7c2016-08-24 17:47:40 -0700928 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -0700929 reporter,
930 geo,
931 setMiterJoinAndLimit,
932 0.5f, 0.75f,
bsalomon0ae36a22016-07-18 07:31:13 -0700933 mayHaveJoins,
bsalomon06077562016-05-04 13:50:29 -0700934 true);
bsalomon47cc7692016-04-26 12:56:00 -0700935
bsalomon06077562016-05-04 13:50:29 -0700936 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
937 // not miter.
bsalomona395f7c2016-08-24 17:47:40 -0700938 test_stroke_param_impl<SkScalar>(
bsalomon06077562016-05-04 13:50:29 -0700939 reporter,
940 geo,
941 setOtherJoinAndLimit,
942 0.5f, 0.75f,
943 false,
944 false);
bsalomon47cc7692016-04-26 12:56:00 -0700945}
946
bsalomona395f7c2016-08-24 17:47:40 -0700947static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700948 // A dash with no stroke should have no effect
949 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
950 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
951 SkPaint dashFill;
952 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -0700953 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700954
bsalomonfb083272016-05-04 08:27:41 -0700955 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700956 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
957 }
958}
959
bsalomona395f7c2016-08-24 17:47:40 -0700960void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700961 SkPaint fill;
962 SkPaint stroke;
963 stroke.setStyle(SkPaint::kStroke_Style);
964 stroke.setStrokeWidth(1.f);
965 SkPaint dash;
966 dash.setStyle(SkPaint::kStroke_Style);
967 dash.setStrokeWidth(1.f);
968 dash.setPathEffect(make_dash());
969 SkPaint nullDash;
970 nullDash.setStyle(SkPaint::kStroke_Style);
971 nullDash.setStrokeWidth(1.f);
972 nullDash.setPathEffect(make_null_dash());
973
bsalomonfb083272016-05-04 08:27:41 -0700974 TestCase fillCase(geo, fill, reporter);
975 TestCase strokeCase(geo, stroke, reporter);
976 TestCase dashCase(geo, dash, reporter);
977 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700978
bsalomon487f8d32016-07-20 07:15:44 -0700979 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
bsalomon47cc7692016-04-26 12:56:00 -0700980 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700981 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
982 // on construction in order to determine how to compare the fill and stroke.
bsalomona395f7c2016-08-24 17:47:40 -0700983 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700984 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
985 } else {
986 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
987 }
988 // 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 -0700989 if (geo.strokeIsConvertedToFill()) {
bsalomon487f8d32016-07-20 07:15:44 -0700990 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
991 } else {
992 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
993 }
bsalomon47cc7692016-04-26 12:56:00 -0700994}
995
bsalomona395f7c2016-08-24 17:47:40 -0700996void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -0700997 /**
998 * This path effect takes any input path and turns it into a rrect. It passes through stroke
999 * info.
1000 */
1001 class RRectPathEffect : SkPathEffect {
1002 public:
1003 static const SkRRect& RRect() {
1004 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1005 return kRRect;
1006 }
1007
1008 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1009 const SkRect* cullR) const override {
1010 dst->reset();
1011 dst->addRRect(RRect());
1012 return true;
1013 }
1014 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1015 *dst = RRect().getBounds();
1016 }
1017 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1018 Factory getFactory() const override { return nullptr; }
1019 void toString(SkString*) const override {}
1020 private:
1021 RRectPathEffect() {}
1022 };
1023
1024 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -07001025 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001026
1027 SkPaint pe;
1028 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001029 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001030
1031 SkPaint peStroke;
1032 peStroke.setPathEffect(RRectPathEffect::Make());
1033 peStroke.setStrokeWidth(2.f);
1034 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001035 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001036
bsalomon487f8d32016-07-20 07:15:44 -07001037 // Check whether constructing the filled case would cause the base shape to have a different
1038 // geometry (because of a geometric transformation upon initial GrShape construction).
bsalomona395f7c2016-08-24 17:47:40 -07001039 if (geo.fillChangesGeom()) {
bsalomon487f8d32016-07-20 07:15:44 -07001040 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1041 fillGeoCase.compare(reporter, geoPEStrokeCase,
1042 TestCase::kAllDifferent_ComparisonExpecation);
1043 } else {
1044 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1045 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1046 }
bsalomon72dc51c2016-04-27 06:46:23 -07001047 geoPECase.compare(reporter, geoPEStrokeCase,
1048 TestCase::kSameUpToStroke_ComparisonExpecation);
1049
bsalomona395f7c2016-08-24 17:47:40 -07001050 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
bsalomon72dc51c2016-04-27 06:46:23 -07001051 SkPaint stroke = peStroke;
1052 stroke.setPathEffect(nullptr);
bsalomona395f7c2016-08-24 17:47:40 -07001053 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001054
1055 SkRRect rrect;
1056 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1057 // geoPECase, so the full style should be the same as just the PE.
bsalomon70493962016-06-10 08:05:14 -07001058 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1059 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001060 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1061 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1062
bsalomon70493962016-06-10 08:05:14 -07001063 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1064 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001065 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1066 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1067
1068 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomon70493962016-06-10 08:05:14 -07001069 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1070 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001071 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1072 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1073
bsalomon70493962016-06-10 08:05:14 -07001074 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1075 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001076 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1077 rrectStrokeCase.appliedFullStyleKey());
1078}
1079
bsalomona395f7c2016-08-24 17:47:40 -07001080void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon72dc51c2016-04-27 06:46:23 -07001081 /**
1082 * This path effect just adds two lineTos to the input path.
1083 */
1084 class AddLineTosPathEffect : SkPathEffect {
1085 public:
1086 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1087 const SkRect* cullR) const override {
1088 *dst = src;
bsalomon67fa4e32016-09-21 08:26:57 -07001089 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1090 for (int i = 0; i < 100; ++i) {
1091 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1092 }
bsalomon72dc51c2016-04-27 06:46:23 -07001093 return true;
1094 }
1095 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1096 *dst = src;
1097 dst->growToInclude(0, 0);
bsalomon67fa4e32016-09-21 08:26:57 -07001098 dst->growToInclude(100, 100);
bsalomon72dc51c2016-04-27 06:46:23 -07001099 }
1100 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1101 Factory getFactory() const override { return nullptr; }
1102 void toString(SkString*) const override {}
1103 private:
1104 AddLineTosPathEffect() {}
1105 };
1106
bsalomon9ad5d7c2016-05-04 08:44:15 -07001107 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -07001108 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1109 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1110 SkPaint peStroke;
1111 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1112 peStroke.setStrokeWidth(2.f);
1113 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001114 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001115 TestCase::SelfExpectations expectations;
1116 expectations.fPEHasEffect = true;
1117 expectations.fPEHasValidKey = false;
1118 expectations.fStrokeApplies = true;
1119 geoPEStrokeCase.testExpectations(reporter, expectations);
1120}
1121
bsalomona395f7c2016-08-24 17:47:40 -07001122void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon9ad5d7c2016-05-04 08:44:15 -07001123 /**
1124 * This path effect just changes the stroke rec to hairline.
1125 */
1126 class MakeHairlinePathEffect : SkPathEffect {
1127 public:
1128 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1129 const SkRect* cullR) const override {
1130 *dst = src;
1131 strokeRec->setHairlineStyle();
1132 return true;
1133 }
bsalomon70493962016-06-10 08:05:14 -07001134 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001135 static sk_sp<SkPathEffect> Make() {
1136 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1137 }
1138 Factory getFactory() const override { return nullptr; }
1139 void toString(SkString*) const override {}
1140 private:
1141 MakeHairlinePathEffect() {}
1142 };
1143
1144 SkPaint fill;
1145 SkPaint pe;
1146 pe.setPathEffect(MakeHairlinePathEffect::Make());
1147
1148 TestCase peCase(geo, pe, reporter);
1149
bsalomonee295642016-06-06 14:01:25 -07001150 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -07001151 peCase.baseShape().asPath(&a);
1152 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -07001153 peCase.appliedFullStyleShape().asPath(&c);
bsalomona395f7c2016-08-24 17:47:40 -07001154 if (geo.isNonPath(pe)) {
bsalomonee295642016-06-06 14:01:25 -07001155 // RRect types can have a change in start index or direction after the PE is applied. This
1156 // is because once the PE is applied, GrShape may canonicalize the dir and index since it
1157 // is not germane to the styling any longer.
1158 // Instead we just check that the paths would fill the same both before and after styling.
1159 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1160 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -07001161 } else {
bsalomona4817af2016-06-23 11:48:26 -07001162 // The base shape cannot perform canonicalization on the path's fill type because of an
1163 // unknown path effect. However, after the path effect is applied the resulting hairline
1164 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1165 // don't distinguish between even/odd and non-zero winding.
1166 a.setFillType(b.getFillType());
bsalomonee295642016-06-06 14:01:25 -07001167 REPORTER_ASSERT(reporter, a == b);
1168 REPORTER_ASSERT(reporter, a == c);
bsalomon67fa4e32016-09-21 08:26:57 -07001169 // If the resulting path is small enough then it will have a key.
1170 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1171 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomonaa840642016-09-23 12:09:16 -07001172 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1173 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001174 }
bsalomonee295642016-06-06 14:01:25 -07001175 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1176 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001177}
1178
bsalomona395f7c2016-08-24 17:47:40 -07001179void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
1180 SkPath vPath = geo.path();
bsalomon4eeccc92016-04-27 13:30:25 -07001181 vPath.setIsVolatile(true);
1182
1183 SkPaint dashAndStroke;
1184 dashAndStroke.setPathEffect(make_dash());
1185 dashAndStroke.setStrokeWidth(2.f);
1186 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001187 TestCase volatileCase(reporter, vPath, dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001188 // We expect a shape made from a volatile path to have a key iff the shape is recognized
bsalomonaa840642016-09-23 12:09:16 -07001189 // as a specialized geometry.
1190 if (geo.isNonPath(dashAndStroke)) {
bsalomon4eeccc92016-04-27 13:30:25 -07001191 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1192 // In this case all the keys should be identical to the non-volatile case.
bsalomona395f7c2016-08-24 17:47:40 -07001193 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
bsalomon4eeccc92016-04-27 13:30:25 -07001194 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1195 } else {
1196 // None of the keys should be valid.
1197 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1198 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1199 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1200 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1201 }
1202}
1203
bsalomona395f7c2016-08-24 17:47:40 -07001204void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
bsalomon409ed732016-04-27 12:36:02 -07001205 /**
1206 * This path effect returns an empty path.
1207 */
1208 class EmptyPathEffect : SkPathEffect {
1209 public:
1210 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1211 const SkRect* cullR) const override {
1212 dst->reset();
1213 return true;
1214 }
1215 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1216 dst->setEmpty();
1217 }
1218 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new EmptyPathEffect); }
1219 Factory getFactory() const override { return nullptr; }
1220 void toString(SkString*) const override {}
1221 private:
1222 EmptyPathEffect() {}
1223 };
1224
1225 SkPath emptyPath;
1226 GrShape emptyShape(emptyPath);
1227 Key emptyKey;
1228 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -07001229 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001230
1231 SkPaint pe;
1232 pe.setPathEffect(EmptyPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001233 TestCase geoCase(geo, pe, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001234 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleKey() == emptyKey);
1235 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectKey() == emptyKey);
1236 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001237 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectShape().isEmpty());
1238 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleShape().isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001239
1240 SkPaint peStroke;
1241 peStroke.setPathEffect(EmptyPathEffect::Make());
1242 peStroke.setStrokeWidth(2.f);
1243 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001244 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001245 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1246 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1247 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001248 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1249 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001250}
1251
bsalomona395f7c2016-08-24 17:47:40 -07001252void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
bsalomond6723842016-06-07 12:20:15 -07001253 /**
bsalomon0ae36a22016-07-18 07:31:13 -07001254 * This path effect always fails to apply.
bsalomond6723842016-06-07 12:20:15 -07001255 */
1256 class FailurePathEffect : SkPathEffect {
1257 public:
1258 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1259 const SkRect* cullR) const override {
1260 return false;
1261 }
1262 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1263 *dst = src;
1264 }
1265 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1266 Factory getFactory() const override { return nullptr; }
1267 void toString(SkString*) const override {}
1268 private:
1269 FailurePathEffect() {}
1270 };
1271
1272 SkPaint fill;
1273 TestCase fillCase(geo, fill, reporter);
1274
1275 SkPaint pe;
1276 pe.setPathEffect(FailurePathEffect::Make());
1277 TestCase peCase(geo, pe, reporter);
1278
1279 SkPaint stroke;
1280 stroke.setStrokeWidth(2.f);
1281 stroke.setStyle(SkPaint::kStroke_Style);
1282 TestCase strokeCase(geo, stroke, reporter);
1283
1284 SkPaint peStroke = stroke;
1285 peStroke.setPathEffect(FailurePathEffect::Make());
1286 TestCase peStrokeCase(geo, peStroke, reporter);
1287
1288 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1289 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1290 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its
1291 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from
1292 // closing it but after the effect fails we can (for the fill+pe case). This causes different
1293 // routes through GrShape to have equivalent but different representations of the path (closed
1294 // or not) but that fill the same.
1295 SkPath a;
1296 SkPath b;
1297 fillCase.appliedPathEffectShape().asPath(&a);
1298 peCase.appliedPathEffectShape().asPath(&b);
1299 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1300
1301 fillCase.appliedFullStyleShape().asPath(&a);
1302 peCase.appliedFullStyleShape().asPath(&b);
1303 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1304
1305 strokeCase.appliedPathEffectShape().asPath(&a);
1306 peStrokeCase.appliedPathEffectShape().asPath(&b);
1307 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1308
1309 strokeCase.appliedFullStyleShape().asPath(&a);
1310 peStrokeCase.appliedFullStyleShape().asPath(&b);
1311 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1312}
1313
Mike Klein43344282017-08-16 11:56:22 -04001314DEF_TEST(GrShape_empty_shape, reporter) {
bsalomon409ed732016-04-27 12:36:02 -07001315 SkPath emptyPath;
1316 SkPaint fill;
bsalomona395f7c2016-08-24 17:47:40 -07001317 TestCase fillEmptyCase(reporter, emptyPath, fill);
bsalomon7c73a532016-05-11 15:15:56 -07001318 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1319 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1320 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001321
1322 Key emptyKey(fillEmptyCase.baseKey());
1323 REPORTER_ASSERT(reporter, emptyKey.count());
1324 TestCase::SelfExpectations expectations;
1325 expectations.fStrokeApplies = false;
1326 expectations.fPEHasEffect = false;
1327 // This will test whether applying style preserves emptiness
1328 fillEmptyCase.testExpectations(reporter, expectations);
1329
1330 // Stroking an empty path should have no effect
1331 SkPath emptyPath2;
1332 SkPaint stroke;
1333 stroke.setStrokeWidth(2.f);
1334 stroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001335 TestCase strokeEmptyCase(reporter, emptyPath2, stroke);
bsalomon409ed732016-04-27 12:36:02 -07001336 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1337
1338 // Dashing and stroking an empty path should have no effect
1339 SkPath emptyPath3;
1340 SkPaint dashAndStroke;
1341 dashAndStroke.setPathEffect(make_dash());
1342 dashAndStroke.setStrokeWidth(2.f);
1343 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001344 TestCase dashAndStrokeEmptyCase(reporter, emptyPath3, dashAndStroke);
bsalomon409ed732016-04-27 12:36:02 -07001345 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1346 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001347
1348 // A shape made from an empty rrect should behave the same as an empty path.
1349 SkRRect emptyRRect = SkRRect::MakeRect(SkRect::MakeEmpty());
1350 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
bsalomona395f7c2016-08-24 17:47:40 -07001351 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001352 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1353 TestCase::kAllSame_ComparisonExpecation);
1354
1355 // Same for a rect.
1356 SkRect emptyRect = SkRect::MakeEmpty();
bsalomona395f7c2016-08-24 17:47:40 -07001357 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
bsalomon5e410b42016-04-28 09:30:46 -07001358 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1359 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001360}
1361
bsalomon70493962016-06-10 08:05:14 -07001362// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1363// canonical point in these cases.
1364unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1365 switch (rrect.getType()) {
1366 case SkRRect::kRect_Type:
1367 return (s + 1) & 0b110;
1368 case SkRRect::kOval_Type:
1369 return s & 0b110;
1370 default:
1371 return s;
1372 }
1373}
1374
1375void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
bsalomoncadb5a22016-06-10 18:28:06 -07001376 enum Style {
bsalomon70493962016-06-10 08:05:14 -07001377 kFill,
1378 kStroke,
1379 kHairline,
1380 kStrokeAndFill
1381 };
1382
1383 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1384 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1385 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1386 strokeRecs[kFill].setFillStyle();
1387 strokeRecs[kStroke].setStrokeStyle(2.f);
1388 strokeRecs[kHairline].setHairlineStyle();
1389 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
bsalomon487f8d32016-07-20 07:15:44 -07001390 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1391 // applyStyle() is called.
1392 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
bsalomon70493962016-06-10 08:05:14 -07001393 sk_sp<SkPathEffect> dashEffect = make_dash();
1394
bsalomoncadb5a22016-06-10 18:28:06 -07001395 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1396
1397 auto index = [](bool inverted,
1398 SkPath::Direction dir,
1399 unsigned start,
1400 Style style,
1401 bool dash) -> int {
1402 return inverted * (2 * 8 * kStyleCnt * 2) +
1403 dir * ( 8 * kStyleCnt * 2) +
1404 start * ( kStyleCnt * 2) +
1405 style * ( 2) +
1406 dash;
1407 };
1408 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1);
1409 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1410 SkAutoTArray<GrShape> shapes(cnt);
1411 for (bool inverted : {false, true}) {
1412 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1413 for (unsigned start = 0; start < 8; ++start) {
1414 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1415 for (bool dash : {false, true}) {
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001416 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
bsalomoncadb5a22016-06-10 18:28:06 -07001417 shapes[index(inverted, dir, start, style, dash)] =
1418 GrShape(rrect, dir, start, SkToBool(inverted),
Robert Phillipsf809c1e2017-01-13 11:02:42 -05001419 GrStyle(strokeRecs[style], std::move(pe)));
bsalomon70493962016-06-10 08:05:14 -07001420 }
1421 }
1422 }
1423 }
1424 }
1425
bsalomonfd32df72016-06-14 14:37:21 -07001426 // Get the keys for some example shape instances that we'll use for comparision against the
1427 // rest.
1428 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction;
1429 static constexpr unsigned kExamplesStart = 0;
1430 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1431 false)];
bsalomon70493962016-06-10 08:05:14 -07001432 Key exampleFillCaseKey;
1433 make_key(&exampleFillCaseKey, exampleFillCase);
1434
bsalomonfd32df72016-06-14 14:37:21 -07001435 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart,
1436 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001437 Key exampleStrokeAndFillCaseKey;
1438 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1439
bsalomonfd32df72016-06-14 14:37:21 -07001440 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill,
1441 false)];
bsalomon70493962016-06-10 08:05:14 -07001442 Key exampleInvFillCaseKey;
1443 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1444
bsalomonfd32df72016-06-14 14:37:21 -07001445 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart,
1446 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001447 Key exampleInvStrokeAndFillCaseKey;
1448 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1449
bsalomonfd32df72016-06-14 14:37:21 -07001450 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke,
1451 false)];
bsalomon70493962016-06-10 08:05:14 -07001452 Key exampleStrokeCaseKey;
1453 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1454
bsalomonfd32df72016-06-14 14:37:21 -07001455 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke,
1456 false)];
1457 Key exampleInvStrokeCaseKey;
1458 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1459
1460 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1461 kHairline, false)];
bsalomon70493962016-06-10 08:05:14 -07001462 Key exampleHairlineCaseKey;
1463 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1464
bsalomonfd32df72016-06-14 14:37:21 -07001465 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1466 kHairline, false)];
1467 Key exampleInvHairlineCaseKey;
1468 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1469
bsalomon70493962016-06-10 08:05:14 -07001470 // These are dummy initializations to suppress warnings.
bsalomoncadb5a22016-06-10 18:28:06 -07001471 SkRRect queryRR = SkRRect::MakeEmpty();
1472 SkPath::Direction queryDir = SkPath::kCW_Direction;
1473 unsigned queryStart = ~0U;
1474 bool queryInverted = true;
bsalomon70493962016-06-10 08:05:14 -07001475
bsalomoncadb5a22016-06-10 18:28:06 -07001476 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1477 REPORTER_ASSERT(r, queryRR == rrect);
1478 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1479 REPORTER_ASSERT(r, 0 == queryStart);
1480 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001481
bsalomoncadb5a22016-06-10 18:28:06 -07001482 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1483 &queryInverted));
1484 REPORTER_ASSERT(r, queryRR == rrect);
1485 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1486 REPORTER_ASSERT(r, 0 == queryStart);
1487 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001488
bsalomoncadb5a22016-06-10 18:28:06 -07001489 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1490 &queryInverted));
1491 REPORTER_ASSERT(r, queryRR == rrect);
1492 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1493 REPORTER_ASSERT(r, 0 == queryStart);
1494 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001495
bsalomoncadb5a22016-06-10 18:28:06 -07001496 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1497 &queryInverted));
1498 REPORTER_ASSERT(r, queryRR == rrect);
1499 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1500 REPORTER_ASSERT(r, 0 == queryStart);
1501 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001502
bsalomoncadb5a22016-06-10 18:28:06 -07001503 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1504 &queryInverted));
1505 REPORTER_ASSERT(r, queryRR == rrect);
1506 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1507 REPORTER_ASSERT(r, 0 == queryStart);
1508 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001509
bsalomonfd32df72016-06-14 14:37:21 -07001510 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1511 &queryInverted));
1512 REPORTER_ASSERT(r, queryRR == rrect);
1513 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1514 REPORTER_ASSERT(r, 0 == queryStart);
1515 REPORTER_ASSERT(r, queryInverted);
1516
bsalomoncadb5a22016-06-10 18:28:06 -07001517 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1518 REPORTER_ASSERT(r, queryRR == rrect);
1519 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1520 REPORTER_ASSERT(r, 0 == queryStart);
1521 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001522
bsalomonfd32df72016-06-14 14:37:21 -07001523 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1524 &queryInverted));
1525 REPORTER_ASSERT(r, queryRR == rrect);
1526 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1527 REPORTER_ASSERT(r, 0 == queryStart);
1528 REPORTER_ASSERT(r, queryInverted);
1529
bsalomon70493962016-06-10 08:05:14 -07001530 // Remember that the key reflects the geometry before styling is applied.
1531 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1532 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1533 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1534 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001535 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001536 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001537 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001538 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001539 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1540 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001541
bsalomoncadb5a22016-06-10 18:28:06 -07001542 for (bool inverted : {false, true}) {
1543 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1544 for (unsigned start = 0; start < 8; ++start) {
1545 for (bool dash : {false, true}) {
1546 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001547 Key fillCaseKey;
1548 make_key(&fillCaseKey, fillCase);
1549
bsalomoncadb5a22016-06-10 18:28:06 -07001550 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1551 kStrokeAndFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001552 Key strokeAndFillCaseKey;
1553 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1554
1555 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1556 // ignore dashing.
1557 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1558 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1559 TestCase a(fillCase, r);
1560 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1561 TestCase c(strokeAndFillCase, r);
1562 TestCase d(inverted ? exampleInvStrokeAndFillCase
1563 : exampleStrokeAndFillCase, r);
1564 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1565 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1566
bsalomoncadb5a22016-06-10 18:28:06 -07001567 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)];
1568 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline,
1569 dash)];
bsalomon70493962016-06-10 08:05:14 -07001570
1571 TestCase e(strokeCase, r);
bsalomon70493962016-06-10 08:05:14 -07001572 TestCase g(hairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001573
bsalomonfd32df72016-06-14 14:37:21 -07001574 // Both hairline and stroke shapes must respect the dashing.
bsalomon70493962016-06-10 08:05:14 -07001575 if (dash) {
bsalomonfd32df72016-06-14 14:37:21 -07001576 // Dashing always ignores the inverseness. skbug.com/5421
1577 TestCase f(exampleStrokeCase, r);
1578 TestCase h(exampleHairlineCase, r);
bsalomoncadb5a22016-06-10 18:28:06 -07001579 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
bsalomon70493962016-06-10 08:05:14 -07001580 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1581 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1582
bsalomoncadb5a22016-06-10 18:28:06 -07001583 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1584 &queryInverted));
1585 REPORTER_ASSERT(r, queryRR == rrect);
1586 REPORTER_ASSERT(r, queryDir == dir);
1587 REPORTER_ASSERT(r, queryStart == expectedStart);
1588 REPORTER_ASSERT(r, !queryInverted);
1589 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1590 &queryInverted));
1591 REPORTER_ASSERT(r, queryRR == rrect);
1592 REPORTER_ASSERT(r, queryDir == dir);
1593 REPORTER_ASSERT(r, queryStart == expectedStart);
1594 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001595
1596 // The pre-style case for the dash will match the non-dash example iff the
1597 // dir and start match (dir=cw, start=0).
bsalomoncadb5a22016-06-10 18:28:06 -07001598 if (0 == expectedStart && SkPath::kCW_Direction == dir) {
bsalomon70493962016-06-10 08:05:14 -07001599 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1600 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1601 } else {
1602 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1603 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1604 }
1605 } else {
bsalomonfd32df72016-06-14 14:37:21 -07001606 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1607 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001608 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1609 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1610 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1611 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1612 }
1613 }
1614 }
1615 }
1616 }
1617}
1618
Mike Klein43344282017-08-16 11:56:22 -04001619DEF_TEST(GrShape_lines, r) {
bsalomon0a0f67e2016-06-28 11:56:42 -07001620 static constexpr SkPoint kA { 1, 1};
1621 static constexpr SkPoint kB { 5, -9};
1622 static constexpr SkPoint kC {-3, 17};
1623
1624 SkPath lineAB;
1625 lineAB.moveTo(kA);
1626 lineAB.lineTo(kB);
1627
1628 SkPath lineBA;
1629 lineBA.moveTo(kB);
1630 lineBA.lineTo(kA);
1631
1632 SkPath lineAC;
1633 lineAC.moveTo(kB);
1634 lineAC.lineTo(kC);
1635
1636 SkPath invLineAB = lineAB;
1637 invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType);
1638
1639 SkPaint fill;
1640 SkPaint stroke;
1641 stroke.setStyle(SkPaint::kStroke_Style);
1642 stroke.setStrokeWidth(2.f);
1643 SkPaint hairline;
1644 hairline.setStyle(SkPaint::kStroke_Style);
1645 hairline.setStrokeWidth(0.f);
1646 SkPaint dash = stroke;
1647 dash.setPathEffect(make_dash());
1648
bsalomona395f7c2016-08-24 17:47:40 -07001649 TestCase fillAB(r, lineAB, fill);
1650 TestCase fillEmpty(r, SkPath(), fill);
bsalomon0a0f67e2016-06-28 11:56:42 -07001651 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1652 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1653
bsalomona395f7c2016-08-24 17:47:40 -07001654 TestCase strokeAB(r, lineAB, stroke);
1655 TestCase strokeBA(r, lineBA, stroke);
1656 TestCase strokeAC(r, lineAC, stroke);
bsalomon0a0f67e2016-06-28 11:56:42 -07001657
bsalomona395f7c2016-08-24 17:47:40 -07001658 TestCase hairlineAB(r, lineAB, hairline);
1659 TestCase hairlineBA(r, lineBA, hairline);
1660 TestCase hairlineAC(r, lineAC, hairline);
bsalomon0a0f67e2016-06-28 11:56:42 -07001661
bsalomona395f7c2016-08-24 17:47:40 -07001662 TestCase dashAB(r, lineAB, dash);
1663 TestCase dashBA(r, lineBA, dash);
1664 TestCase dashAC(r, lineAC, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001665
1666 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1667
1668 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1669 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1670
1671 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1672 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1673
1674 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1675 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1676
1677 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1678
1679 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1680 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1681 bool canonicalizeAsAB;
1682 SkPoint canonicalPts[2] {kA, kB};
1683 // Init these to suppress warnings.
1684 bool inverted = true;
1685 SkPoint pts[2] {{0, 0}, {0, 0}};
1686 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1687 if (pts[0] == kA && pts[1] == kB) {
1688 canonicalizeAsAB = true;
1689 } else if (pts[1] == kA && pts[0] == kB) {
1690 canonicalizeAsAB = false;
1691 SkTSwap(canonicalPts[0], canonicalPts[1]);
1692 } else {
1693 ERRORF(r, "Should return pts (a,b) or (b, a)");
1694 return;
1695 };
1696
1697 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1698 TestCase::kSameUpToPE_ComparisonExpecation);
1699 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1700 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1701 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1702 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1703 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1704 pts[0] == kA && pts[1] == kB);
1705 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1706 pts[0] == kB && pts[1] == kA);
1707
1708
bsalomona395f7c2016-08-24 17:47:40 -07001709 TestCase strokeInvAB(r, invLineAB, stroke);
1710 TestCase hairlineInvAB(r, invLineAB, hairline);
1711 TestCase dashInvAB(r, invLineAB, dash);
bsalomon0a0f67e2016-06-28 11:56:42 -07001712 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1713 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1714 // Dashing ignores inverse.
1715 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1716
1717 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1718 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1719 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1720 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1721 // Dashing ignores inverse.
1722 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1723 pts[0] == kA && pts[1] == kB);
1724
1725}
1726
Mike Klein43344282017-08-16 11:56:22 -04001727DEF_TEST(GrShape_stroked_lines, r) {
bsalomon0ae36a22016-07-18 07:31:13 -07001728 // Paints to try
1729 SkPaint buttCap;
1730 buttCap.setStyle(SkPaint::kStroke_Style);
1731 buttCap.setStrokeWidth(4);
1732 buttCap.setStrokeCap(SkPaint::kButt_Cap);
1733
1734 SkPaint squareCap = buttCap;
1735 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
1736
1737 SkPaint roundCap = buttCap;
1738 roundCap.setStrokeCap(SkPaint::kRound_Cap);
1739
1740 // vertical
1741 SkPath linePath;
1742 linePath.moveTo(4, 4);
1743 linePath.lineTo(4, 5);
1744
1745 SkPaint fill;
1746
Mike Klein43344282017-08-16 11:56:22 -04001747 make_TestCase(r, linePath, buttCap)->compare(
1748 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
1749 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001750
Mike Klein43344282017-08-16 11:56:22 -04001751 make_TestCase(r, linePath, squareCap)->compare(
1752 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
1753 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001754
Mike Klein43344282017-08-16 11:56:22 -04001755 make_TestCase(r, linePath, roundCap)->compare(r,
bsalomona395f7c2016-08-24 17:47:40 -07001756 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
bsalomon0ae36a22016-07-18 07:31:13 -07001757 TestCase::kAllSame_ComparisonExpecation);
1758
1759 // horizontal
1760 linePath.reset();
1761 linePath.moveTo(4, 4);
1762 linePath.lineTo(5, 4);
1763
Mike Klein43344282017-08-16 11:56:22 -04001764 make_TestCase(r, linePath, buttCap)->compare(
1765 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
1766 TestCase::kAllSame_ComparisonExpecation);
1767 make_TestCase(r, linePath, squareCap)->compare(
1768 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
1769 TestCase::kAllSame_ComparisonExpecation);
1770 make_TestCase(r, linePath, roundCap)->compare(
1771 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
1772 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001773
1774 // point
1775 linePath.reset();
1776 linePath.moveTo(4, 4);
1777 linePath.lineTo(4, 4);
1778
Mike Klein43344282017-08-16 11:56:22 -04001779 make_TestCase(r, linePath, buttCap)->compare(
1780 r, TestCase(r, SkRect::MakeEmpty(), fill),
1781 TestCase::kAllSame_ComparisonExpecation);
1782 make_TestCase(r, linePath, squareCap)->compare(
1783 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
1784 TestCase::kAllSame_ComparisonExpecation);
1785 make_TestCase(r, linePath, roundCap)->compare(
1786 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
1787 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -07001788}
1789
Mike Klein43344282017-08-16 11:56:22 -04001790DEF_TEST(GrShape_short_path_keys, r) {
bsalomon67fa4e32016-09-21 08:26:57 -07001791 SkPaint paints[4];
1792 paints[1].setStyle(SkPaint::kStroke_Style);
1793 paints[1].setStrokeWidth(5.f);
1794 paints[2].setStyle(SkPaint::kStroke_Style);
1795 paints[2].setStrokeWidth(0.f);
1796 paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
1797 paints[3].setStrokeWidth(5.f);
1798
bsalomonaa840642016-09-23 12:09:16 -07001799 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
bsalomon67fa4e32016-09-21 08:26:57 -07001800 TestCase::ComparisonExpecation expectation) {
bsalomonaa840642016-09-23 12:09:16 -07001801 SkPath volatileA = pathA;
1802 SkPath volatileB = pathB;
1803 volatileA.setIsVolatile(true);
1804 volatileB.setIsVolatile(true);
bsalomon67fa4e32016-09-21 08:26:57 -07001805 for (const SkPaint& paint : paints) {
bsalomonaa840642016-09-23 12:09:16 -07001806 REPORTER_ASSERT(r, !GrShape(volatileA, paint).hasUnstyledKey());
1807 REPORTER_ASSERT(r, !GrShape(volatileB, paint).hasUnstyledKey());
bsalomon67fa4e32016-09-21 08:26:57 -07001808 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
bsalomonaa840642016-09-23 12:09:16 -07001809 TestCase caseA(PathGeo(pathA, invert), paint, r);
1810 TestCase caseB(PathGeo(pathB, invert), paint, r);
1811 caseA.compare(r, caseB, expectation);
bsalomon67fa4e32016-09-21 08:26:57 -07001812 }
1813 }
1814 };
1815
1816 SkPath pathA;
1817 SkPath pathB;
1818
1819 // Two identical paths
1820 pathA.lineTo(10.f, 10.f);
1821 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
1822
1823 pathB.lineTo(10.f, 10.f);
1824 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07001825 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07001826
1827 // Give path b a different point
1828 pathB.reset();
1829 pathB.lineTo(10.f, 10.f);
1830 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
bsalomonaa840642016-09-23 12:09:16 -07001831 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07001832
1833 // Give path b a different conic weight
1834 pathB.reset();
1835 pathB.lineTo(10.f, 10.f);
1836 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
bsalomonaa840642016-09-23 12:09:16 -07001837 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07001838
1839 // Give path b an extra lineTo verb
1840 pathB.reset();
1841 pathB.lineTo(10.f, 10.f);
1842 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
1843 pathB.lineTo(50.f, 50.f);
bsalomonaa840642016-09-23 12:09:16 -07001844 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07001845
1846 // Give path b a close
1847 pathB.reset();
1848 pathB.lineTo(10.f, 10.f);
1849 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
1850 pathB.close();
bsalomonaa840642016-09-23 12:09:16 -07001851 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
bsalomon67fa4e32016-09-21 08:26:57 -07001852}
1853
bsalomon47cc7692016-04-26 12:56:00 -07001854DEF_TEST(GrShape, reporter) {
bsalomona395f7c2016-08-24 17:47:40 -07001855 SkTArray<std::unique_ptr<Geo>> geos;
1856 SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
1857
bsalomonee295642016-06-06 14:01:25 -07001858 for (auto r : { SkRect::MakeWH(10, 20),
1859 SkRect::MakeWH(-10, -20),
1860 SkRect::MakeWH(-10, 20),
1861 SkRect::MakeWH(10, -20)}) {
bsalomona395f7c2016-08-24 17:47:40 -07001862 geos.emplace_back(new RectGeo(r));
1863 SkPath rectPath;
1864 rectPath.addRect(r);
1865 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
1866 PathGeo::Invert::kNo));
1867 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
1868 PathGeo::Invert::kYes));
1869 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
1870 PathGeo::Invert::kNo));
bsalomonee295642016-06-06 14:01:25 -07001871 }
bsalomon47cc7692016-04-26 12:56:00 -07001872 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -07001873 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
1874 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomona395f7c2016-08-24 17:47:40 -07001875 geos.emplace_back(new RRectGeo(rr));
bsalomon70493962016-06-10 08:05:14 -07001876 test_rrect(reporter, rr);
bsalomona395f7c2016-08-24 17:47:40 -07001877 SkPath rectPath;
1878 rectPath.addRRect(rr);
1879 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
1880 PathGeo::Invert::kNo));
1881 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
1882 PathGeo::Invert::kYes));
1883 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
1884 RRectPathGeo::RRectForStroke::kYes,
1885 PathGeo::Invert::kNo));
bsalomon72dc51c2016-04-27 06:46:23 -07001886 }
1887
Mike Klein43344282017-08-16 11:56:22 -04001888 {
1889 SkPath openRectPath;
1890 openRectPath.moveTo(0, 0);
1891 openRectPath.lineTo(10, 0);
1892 openRectPath.lineTo(10, 10);
1893 openRectPath.lineTo(0, 10);
1894 geos.emplace_back(new RRectPathGeo(
1895 openRectPath, SkRect::MakeWH(10, 10),
1896 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
1897 geos.emplace_back(new RRectPathGeo(
1898 openRectPath, SkRect::MakeWH(10, 10),
1899 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
1900 rrectPathGeos.emplace_back(new RRectPathGeo(
1901 openRectPath, SkRect::MakeWH(10, 10),
1902 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
1903 }
bsalomon72dc51c2016-04-27 06:46:23 -07001904
Mike Klein43344282017-08-16 11:56:22 -04001905 {
1906 SkPath quadPath;
1907 quadPath.quadTo(10, 10, 5, 8);
1908 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
1909 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
1910 }
bsalomon398e3f42016-06-13 10:22:48 -07001911
Mike Klein43344282017-08-16 11:56:22 -04001912 {
1913 SkPath linePath;
1914 linePath.lineTo(10, 10);
1915 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
1916 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
1917 }
bsalomon72dc51c2016-04-27 06:46:23 -07001918
bsalomon0ae36a22016-07-18 07:31:13 -07001919 // Horizontal and vertical paths become rrects when stroked.
Mike Klein43344282017-08-16 11:56:22 -04001920 {
1921 SkPath vLinePath;
1922 vLinePath.lineTo(0, 10);
1923 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
1924 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
1925 }
bsalomon0ae36a22016-07-18 07:31:13 -07001926
Mike Klein43344282017-08-16 11:56:22 -04001927 {
1928 SkPath hLinePath;
1929 hLinePath.lineTo(10, 0);
1930 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
1931 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
1932 }
bsalomon0ae36a22016-07-18 07:31:13 -07001933
bsalomona395f7c2016-08-24 17:47:40 -07001934 for (int i = 0; i < geos.count(); ++i) {
1935 test_basic(reporter, *geos[i]);
1936 test_scale(reporter, *geos[i]);
1937 test_dash_fill(reporter, *geos[i]);
1938 test_null_dash(reporter, *geos[i]);
1939 // Test modifying various stroke params.
1940 test_stroke_param<SkScalar>(
1941 reporter, *geos[i],
bsalomon70493962016-06-10 08:05:14 -07001942 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
1943 SkIntToScalar(2), SkIntToScalar(4));
bsalomona395f7c2016-08-24 17:47:40 -07001944 test_stroke_join(reporter, *geos[i]);
1945 test_stroke_cap(reporter, *geos[i]);
1946 test_miter_limit(reporter, *geos[i]);
1947 test_path_effect_makes_rrect(reporter, *geos[i]);
1948 test_unknown_path_effect(reporter, *geos[i]);
1949 test_path_effect_makes_empty_shape(reporter, *geos[i]);
1950 test_path_effect_fails(reporter, *geos[i]);
1951 test_make_hairline_path_effect(reporter, *geos[i]);
1952 test_volatile_path(reporter, *geos[i]);
bsalomon70493962016-06-10 08:05:14 -07001953 }
bsalomonfd32df72016-06-14 14:37:21 -07001954
bsalomona395f7c2016-08-24 17:47:40 -07001955 for (int i = 0; i < rrectPathGeos.count(); ++i) {
1956 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
bsalomon72dc51c2016-04-27 06:46:23 -07001957 SkPaint fillPaint;
bsalomona395f7c2016-08-24 17:47:40 -07001958 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
bsalomon72dc51c2016-04-27 06:46:23 -07001959 SkRRect rrect;
bsalomona395f7c2016-08-24 17:47:40 -07001960 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
bsalomon70493962016-06-10 08:05:14 -07001961 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
1962 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07001963 if (rrgeo.isNonPath(fillPaint)) {
1964 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
1965 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
1966 TestCase fillRRectCase(reporter, rrect, fillPaint);
bsalomon70493962016-06-10 08:05:14 -07001967 fillPathCase2.compare(reporter, fillRRectCase,
1968 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07001969 }
bsalomon72dc51c2016-04-27 06:46:23 -07001970 SkPaint strokePaint;
1971 strokePaint.setStrokeWidth(3.f);
1972 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomona395f7c2016-08-24 17:47:40 -07001973 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
1974 if (rrgeo.isNonPath(strokePaint)) {
bsalomon0ae36a22016-07-18 07:31:13 -07001975 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
1976 nullptr));
bsalomona395f7c2016-08-24 17:47:40 -07001977 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
1978 TestCase strokeRRectCase(reporter, rrect, strokePaint);
bsalomon72dc51c2016-04-27 06:46:23 -07001979 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07001980 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07001981 }
bsalomon47cc7692016-04-26 12:56:00 -07001982 }
bsalomon409ed732016-04-27 12:36:02 -07001983
bsalomon4eeccc92016-04-27 13:30:25 -07001984 // Test a volatile empty path.
bsalomona395f7c2016-08-24 17:47:40 -07001985 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
bsalomon47cc7692016-04-26 12:56:00 -07001986}
1987
1988#endif