blob: f4d3d31b4229b9da6eadb557bff82ddb93e193bb [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"
bsalomon47cc7692016-04-26 12:56:00 -070018
bsalomon72dc51c2016-04-27 06:46:23 -070019using Key = SkTArray<uint32_t>;
20
21static bool make_key(Key* key, const GrShape& shape) {
22 int size = shape.unstyledKeySize();
23 if (size <= 0) {
24 key->reset(0);
25 return false;
26 }
27 SkASSERT(size);
28 key->reset(size);
29 shape.writeUnstyledKey(key->begin());
30 return true;
31}
32
bsalomonee295642016-06-06 14:01:25 -070033static bool paths_fill_same(const SkPath& a, const SkPath& b) {
34 SkPath pathXor;
35 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
36 return pathXor.isEmpty();
37}
38
bsalomon9fb42032016-05-13 09:23:38 -070039static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
40 static constexpr int kRes = 2000;
41 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
42 static constexpr int kTol = 0;
43 GR_STATIC_ASSERT(kRes % 4 == 0);
44 SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
45 sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
46 surface->getCanvas()->clear(0x0);
47 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
48 SkMatrix matrix;
49 matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit);
50 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
51 surface->getCanvas()->clipRect(clip, SkRegion::kDifference_Op);
52 surface->getCanvas()->concat(matrix);
53 SkPaint whitePaint;
54 whitePaint.setColor(SK_ColorWHITE);
55 surface->getCanvas()->drawPath(path, whitePaint);
56 SkPixmap pixmap;
57 surface->getCanvas()->peekPixels(&pixmap);
58#if defined(SK_BUILD_FOR_WIN)
59 // The static constexpr version in #else causes cl.exe to crash.
60 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
61#else
62 static constexpr uint8_t kZeros[kRes] = {0};
63#endif
64 for (int y = 0; y < kRes/4; ++y) {
65 const uint8_t* row = pixmap.addr8(0, y);
66 if (0 != memcmp(kZeros, row, kRes)) {
67 return false;
68 }
69 }
70#ifdef SK_BUILD_FOR_WIN
71 free(const_cast<uint8_t*>(kZeros));
72#endif
73 return true;
74}
bsalomon72dc51c2016-04-27 06:46:23 -070075
bsalomon9fb42032016-05-13 09:23:38 -070076namespace {
bsalomon47cc7692016-04-26 12:56:00 -070077class TestCase {
78public:
bsalomon72dc51c2016-04-27 06:46:23 -070079 template <typename GEO>
bsalomon97fd2d42016-05-09 13:02:01 -070080 TestCase(const GEO& geo, const SkPaint& paint, skiatest::Reporter* r,
81 SkScalar scale = SK_Scalar1) : fBase(geo, paint) {
82 this->init(r, scale);
bsalomon47cc7692016-04-26 12:56:00 -070083 }
84
85 struct SelfExpectations {
86 bool fPEHasEffect;
87 bool fPEHasValidKey;
88 bool fStrokeApplies;
89 };
90
91 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
92
93 enum ComparisonExpecation {
94 kAllDifferent_ComparisonExpecation,
95 kSameUpToPE_ComparisonExpecation,
96 kSameUpToStroke_ComparisonExpecation,
97 kAllSame_ComparisonExpecation,
98 };
99
100 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
101
bsalomon72dc51c2016-04-27 06:46:23 -0700102 const GrShape& baseShape() const { return fBase; }
103 const GrShape& appliedPathEffectShape() const { return fAppliedPE; }
104 const GrShape& appliedFullStyleShape() const { return fAppliedFull; }
105
106 // The returned array's count will be 0 if the key shape has no key.
107 const Key& baseKey() const { return fBaseKey; }
108 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
109 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700110 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700111
bsalomon47cc7692016-04-26 12:56:00 -0700112private:
bsalomon9fb42032016-05-13 09:23:38 -0700113 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
114 SkPath path;
115 shape.asPath(&path);
116 // If the bounds are empty, the path ought to be as well.
117 if (bounds.isEmpty()) {
118 REPORTER_ASSERT(r, path.isEmpty());
119 return;
120 }
121 if (path.isEmpty()) {
122 return;
123 }
124 REPORTER_ASSERT(r, test_bounds_by_rasterizing(path, bounds));
125 }
126
bsalomon97fd2d42016-05-09 13:02:01 -0700127 void init(skiatest::Reporter* r, SkScalar scale) {
128 fAppliedPE = fBase.applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
129 fAppliedPEThenStroke = fAppliedPE.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec,
130 scale);
131 fAppliedFull = fBase.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700132
bsalomon72dc51c2016-04-27 06:46:23 -0700133 make_key(&fBaseKey, fBase);
134 make_key(&fAppliedPEKey, fAppliedPE);
135 make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke);
136 make_key(&fAppliedFullKey, fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700137
138 // Applying the path effect and then the stroke should always be the same as applying
139 // both in one go.
140 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
141 SkPath a, b;
142 fAppliedPEThenStroke.asPath(&a);
143 fAppliedFull.asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700144 // If the output of the path effect is a rrect then it is possible for a and b to be
145 // different paths that fill identically. The reason is that fAppliedFull will do this:
146 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
147 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
148 // now that there is no longer a path effect, the direction and starting index get
149 // canonicalized before the stroke.
150 if (fAppliedPE.asRRect(nullptr, nullptr, nullptr)) {
151 REPORTER_ASSERT(r, paths_fill_same(a, b));
152 } else {
153 REPORTER_ASSERT(r, a == b);
154 }
bsalomon7c73a532016-05-11 15:15:56 -0700155 REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty());
156
157 SkPath path;
158 fBase.asPath(&path);
159 REPORTER_ASSERT(r, path.isEmpty() == fBase.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700160 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700161 fAppliedPE.asPath(&path);
162 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700163 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700164 fAppliedFull.asPath(&path);
165 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700166 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull.segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700167
bsalomon9fb42032016-05-13 09:23:38 -0700168 CheckBounds(r, fBase, fBase.bounds());
169 CheckBounds(r, fAppliedPE, fAppliedPE.bounds());
170 CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds());
171 CheckBounds(r, fAppliedFull, fAppliedFull.bounds());
172 SkRect styledBounds;
173 fBase.styledBounds(&styledBounds);
174 CheckBounds(r, fAppliedFull, styledBounds);
175 fAppliedPE.styledBounds(&styledBounds);
176 CheckBounds(r, fAppliedFull, styledBounds);
177
bsalomonfb083272016-05-04 08:27:41 -0700178 // Check that the same path is produced when style is applied by GrShape and GrStyle.
179 SkPath preStyle;
180 SkPath postPathEffect;
181 SkPath postAllStyle;
182
183 fBase.asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700184 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
bsalomon97fd2d42016-05-09 13:02:01 -0700185 if (fBase.style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
186 scale)) {
bsalomon1a0b9ed2016-05-06 11:07:03 -0700187 // run postPathEffect through GrShape to get any geometry reductions that would have
188 // occurred to fAppliedPE.
189 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
190
bsalomonfb083272016-05-04 08:27:41 -0700191 SkPath testPath;
192 fAppliedPE.asPath(&testPath);
193 REPORTER_ASSERT(r, testPath == postPathEffect);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700194 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE.style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700195 }
196 SkStrokeRec::InitStyle fillOrHairline;
bsalomon97fd2d42016-05-09 13:02:01 -0700197 if (fBase.style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomon1a0b9ed2016-05-06 11:07:03 -0700198 // run postPathEffect through GrShape to get any reductions that would have occurred
199 // to fAppliedFull.
200 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
201
bsalomonfb083272016-05-04 08:27:41 -0700202 SkPath testPath;
203 fAppliedFull.asPath(&testPath);
204 REPORTER_ASSERT(r, testPath == postAllStyle);
205 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
206 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleFill());
207 } else {
208 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline());
209 }
210 }
bsalomon47cc7692016-04-26 12:56:00 -0700211 }
212
213 GrShape fBase;
214 GrShape fAppliedPE;
215 GrShape fAppliedPEThenStroke;
216 GrShape fAppliedFull;
217
218 Key fBaseKey;
219 Key fAppliedPEKey;
220 Key fAppliedPEThenStrokeKey;
221 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700222};
223
224void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700225 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700226 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700227 if (expectations.fPEHasEffect) {
228 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700229 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700230 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700231 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700232 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
233 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700234 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700235 }
236 } else {
237 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700238 SkPath a, b;
bsalomon72dc51c2016-04-27 06:46:23 -0700239 fBase.asPath(&a);
240 fAppliedPE.asPath(&b);
241 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700242 if (expectations.fStrokeApplies) {
243 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
244 } else {
245 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
246 }
247 }
248}
249
bsalomonee295642016-06-06 14:01:25 -0700250void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
251 const Key& keyA, const Key& keyB) {
252 // GrShape only respects the input winding direction and start point for rrect shapes
253 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
254 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
255 // key will differ. GrShape will have canonicalized the direction and start point for the shape
256 // without the path effect. If *both* have path effects then they should have both preserved
257 // the direction and starting point.
258
259 // The asRRect() output params are all initialized just to silence compiler warnings about
260 // uninitialized variables.
261 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
262 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
263 unsigned startA = ~0U, startB = ~0U;
264
265 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA);
266 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB);
267 bool aHasPE = a.style().hasPathEffect();
268 bool bHasPE = b.style().hasPathEffect();
269 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
270 SkPath pathA, pathB;
271 a.asPath(&pathA);
272 b.asPath(&pathB);
273 if (allowSameRRectButDiffStartAndDir) {
274 REPORTER_ASSERT(r, rrectA == rrectB);
275 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
276 } else {
277 REPORTER_ASSERT(r, pathA == pathB);
278 REPORTER_ASSERT(r, keyA == keyB);
279 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
280 if (aIsRRect) {
281 REPORTER_ASSERT(r, rrectA == rrectB);
282 REPORTER_ASSERT(r, dirA == dirB);
283 REPORTER_ASSERT(r, startA == startB);
284 }
285 }
286 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
287 REPORTER_ASSERT(r, a.knownToBeClosed() == b.knownToBeClosed());
288 REPORTER_ASSERT(r, a.bounds() == b.bounds());
bsalomon06115ee2016-06-07 06:28:51 -0700289 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
bsalomonee295642016-06-06 14:01:25 -0700290}
291
292void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700293 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700294 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700295 switch (expectation) {
296 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700297 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
298 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
299 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700300 break;
301 case kSameUpToPE_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700302 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
303 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
304 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700305 break;
306 case kSameUpToStroke_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700307 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
308 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
309 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700310 break;
311 case kAllSame_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700312 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
313 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
314 check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey,
315 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700316 break;
317 }
318}
319} // namespace
320
321static sk_sp<SkPathEffect> make_dash() {
322 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
323 static const SkScalar kPhase = 0.75;
324 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
325}
326
327static sk_sp<SkPathEffect> make_null_dash() {
328 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
329 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
330}
331
bsalomon72dc51c2016-04-27 06:46:23 -0700332template<typename GEO>
333static void test_basic(skiatest::Reporter* reporter, const GEO& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700334 sk_sp<SkPathEffect> dashPE = make_dash();
335
336 TestCase::SelfExpectations expectations;
337 SkPaint fill;
338
bsalomonfb083272016-05-04 08:27:41 -0700339 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700340 expectations.fPEHasEffect = false;
341 expectations.fPEHasValidKey = false;
342 expectations.fStrokeApplies = false;
343 fillCase.testExpectations(reporter, expectations);
344 // Test that another GrShape instance built from the same primitive is the same.
bsalomonfb083272016-05-04 08:27:41 -0700345 TestCase(geo, fill, reporter).compare(reporter, fillCase,
346 TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700347
348 SkPaint stroke2RoundBevel;
349 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
350 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
351 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
352 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700353 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700354 expectations.fPEHasValidKey = true;
355 expectations.fPEHasEffect = false;
356 expectations.fStrokeApplies = true;
357 stroke2RoundBevelCase.testExpectations(reporter, expectations);
bsalomonfb083272016-05-04 08:27:41 -0700358 TestCase(geo, stroke2RoundBevel, reporter).compare(reporter, stroke2RoundBevelCase,
359 TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700360
361 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
362 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700363 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700364 expectations.fPEHasValidKey = true;
365 expectations.fPEHasEffect = true;
366 expectations.fStrokeApplies = true;
367 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
bsalomonfb083272016-05-04 08:27:41 -0700368 TestCase(geo, stroke2RoundBevelDash, reporter).compare(reporter, stroke2RoundBevelDashCase,
369 TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700370
371 fillCase.compare(reporter, stroke2RoundBevelCase,
372 TestCase::kSameUpToStroke_ComparisonExpecation);
373 fillCase.compare(reporter, stroke2RoundBevelDashCase,
374 TestCase::kSameUpToPE_ComparisonExpecation);
375 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
376 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -0700377
bsalomonf0cf3552016-05-05 08:28:30 -0700378 // Stroke and fill cases
379 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
380 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
381 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
382 expectations.fPEHasValidKey = true;
383 expectations.fPEHasEffect = false;
384 expectations.fStrokeApplies = true;
385 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
386 TestCase(geo, stroke2RoundBevelAndFill, reporter).compare(reporter,
387 stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
388
389 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
390 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
391 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
392 expectations.fPEHasValidKey = true;
393 expectations.fPEHasEffect = true;
394 expectations.fStrokeApplies = true;
395 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
396 TestCase(geo, stroke2RoundBevelAndFillDash, reporter).compare(
397 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
398
399 stroke2RoundBevelAndFillCase.compare(reporter, stroke2RoundBevelCase,
400 TestCase::kSameUpToStroke_ComparisonExpecation);
401 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelDashCase,
402 TestCase::kSameUpToStroke_ComparisonExpecation);
403 stroke2RoundBevelAndFillCase.compare(reporter, stroke2RoundBevelAndFillDashCase,
404 TestCase::kSameUpToPE_ComparisonExpecation);
405
bsalomon72dc51c2016-04-27 06:46:23 -0700406 SkPaint hairline;
407 hairline.setStyle(SkPaint::kStroke_Style);
408 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700409 TestCase hairlineCase(geo, hairline, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700410 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill.
411 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon9ad5d7c2016-05-04 08:44:15 -0700412 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
413 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
414 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700415}
416
bsalomon97fd2d42016-05-09 13:02:01 -0700417template<typename GEO>
418static void test_scale(skiatest::Reporter* reporter, const GEO& geo) {
419 sk_sp<SkPathEffect> dashPE = make_dash();
420
421 static const SkScalar kS1 = 1.f;
422 static const SkScalar kS2 = 2.f;
423
424 SkPaint fill;
425 TestCase fillCase1(geo, fill, reporter, kS1);
426 TestCase fillCase2(geo, fill, reporter, kS2);
427 // Scale doesn't affect fills.
428 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
429
430 SkPaint hairline;
431 hairline.setStyle(SkPaint::kStroke_Style);
432 hairline.setStrokeWidth(0.f);
433 TestCase hairlineCase1(geo, hairline, reporter, kS1);
434 TestCase hairlineCase2(geo, hairline, reporter, kS2);
435 // Scale doesn't affect hairlines.
436 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
437
438 SkPaint stroke;
439 stroke.setStyle(SkPaint::kStroke_Style);
440 stroke.setStrokeWidth(2.f);
441 TestCase strokeCase1(geo, stroke, reporter, kS1);
442 TestCase strokeCase2(geo, stroke, reporter, kS2);
443 // Scale affects the stroke.
444 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
445
446 SkPaint strokeDash = stroke;
447 strokeDash.setPathEffect(make_dash());
448 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
449 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
450 // Scale affects the dash and the stroke.
451 strokeDashCase1.compare(reporter, strokeDashCase2, TestCase::kSameUpToPE_ComparisonExpecation);
452
453 // Stroke and fill cases
454 SkPaint strokeAndFill = stroke;
455 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
456 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
457 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
458 // Scale affects the stroke. Though, this can wind up creating a rect when the input is a rect.
459 // In that case we wind up with a pure geometry key and the geometries are the same.
460 SkRRect rrect;
bsalomonee295642016-06-06 14:01:25 -0700461 if (strokeAndFillCase1.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)) {
bsalomon97fd2d42016-05-09 13:02:01 -0700462 // We currently only expect to get here in the rect->rect case.
463 REPORTER_ASSERT(reporter, rrect.isRect());
bsalomonee295642016-06-06 14:01:25 -0700464 REPORTER_ASSERT(reporter,
465 strokeAndFillCase1.baseShape().asRRect(&rrect, nullptr, nullptr) &&
466 rrect.isRect());
bsalomon97fd2d42016-05-09 13:02:01 -0700467 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
468 TestCase::kAllSame_ComparisonExpecation);
469 } else {
470 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
471 TestCase::kSameUpToStroke_ComparisonExpecation);
472 }
473
474 SkPaint strokeAndFillDash = strokeDash;
475 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
476 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
477 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
478 // Scale affects the path effect and stroke.
479 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
480 TestCase::kSameUpToPE_ComparisonExpecation);
481}
482
bsalomon72dc51c2016-04-27 06:46:23 -0700483template <typename GEO, typename T>
bsalomon06077562016-05-04 13:50:29 -0700484static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo,
485 std::function<void(SkPaint*, T)> setter, T a, T b,
486 bool paramAffectsStroke,
487 bool paramAffectsDashAndStroke) {
488 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
489 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700490 SkPaint strokeA;
491 strokeA.setStyle(SkPaint::kStroke_Style);
492 strokeA.setStrokeWidth(2.f);
493 setter(&strokeA, a);
494 SkPaint strokeB;
495 strokeB.setStyle(SkPaint::kStroke_Style);
496 strokeB.setStrokeWidth(2.f);
497 setter(&strokeB, b);
498
bsalomonfb083272016-05-04 08:27:41 -0700499 TestCase strokeACase(geo, strokeA, reporter);
500 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700501 if (paramAffectsStroke) {
502 strokeACase.compare(reporter, strokeBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
503 } else {
504 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
505 }
bsalomon47cc7692016-04-26 12:56:00 -0700506
bsalomonf0cf3552016-05-05 08:28:30 -0700507 SkPaint strokeAndFillA = strokeA;
508 SkPaint strokeAndFillB = strokeB;
509 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
510 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
511 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
512 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
513 if (paramAffectsStroke) {
514 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
515 TestCase::kSameUpToStroke_ComparisonExpecation);
516 } else {
517 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
518 TestCase::kAllSame_ComparisonExpecation);
519 }
520
bsalomon47cc7692016-04-26 12:56:00 -0700521 // Make sure stroking params don't affect fill style.
522 SkPaint fillA = strokeA, fillB = strokeB;
523 fillA.setStyle(SkPaint::kFill_Style);
524 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -0700525 TestCase fillACase(geo, fillA, reporter);
526 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700527 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
528
529 // Make sure just applying the dash but not stroke gives the same key for both stroking
530 // variations.
531 SkPaint dashA = strokeA, dashB = strokeB;
532 dashA.setPathEffect(make_dash());
533 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700534 TestCase dashACase(geo, dashA, reporter);
535 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700536 if (paramAffectsDashAndStroke) {
537 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
538 } else {
539 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
540 }
bsalomonf0cf3552016-05-05 08:28:30 -0700541
542 SkPaint dashStrokeAndFillA = dashA, dashStrokeAndFillB = dashB;
543 dashStrokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
544 dashStrokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
545 TestCase dashStrokeAndFillACase(geo, dashStrokeAndFillA, reporter);
546 TestCase dashStrokeAndFillBCase(geo, dashStrokeAndFillB, reporter);
547 if (paramAffectsDashAndStroke) {
548 dashStrokeAndFillACase.compare(reporter, dashStrokeAndFillBCase,
549 TestCase::kSameUpToStroke_ComparisonExpecation);
550 } else {
551 dashStrokeAndFillACase.compare(reporter, dashStrokeAndFillBCase,
552 TestCase::kAllSame_ComparisonExpecation);
553 }
bsalomon47cc7692016-04-26 12:56:00 -0700554}
555
bsalomon06077562016-05-04 13:50:29 -0700556template <typename GEO, typename T>
557static void test_stroke_param(skiatest::Reporter* reporter, const GEO& geo,
558 std::function<void(SkPaint*, T)> setter, T a, T b) {
559 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
560};
561
562template <typename GEO>
563static void test_stroke_cap(skiatest::Reporter* reporter, const GEO& geo) {
564 GrShape shape(geo, GrStyle(SkStrokeRec::kHairline_InitStyle));
565 // The cap should only affect shapes that may be open.
566 bool affectsStroke = !shape.knownToBeClosed();
567 // Dashing adds ends that need caps.
568 bool affectsDashAndStroke = true;
569 test_stroke_param_impl<GEO, SkPaint::Cap>(
570 reporter,
571 geo,
572 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
573 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
574 affectsStroke,
575 affectsDashAndStroke);
576};
577
bsalomon72dc51c2016-04-27 06:46:23 -0700578template <typename GEO>
579static void test_miter_limit(skiatest::Reporter* reporter, const GEO& geo) {
bsalomon06077562016-05-04 13:50:29 -0700580 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
581 p->setStrokeJoin(SkPaint::kMiter_Join);
582 p->setStrokeMiter(miter);
583 };
bsalomon47cc7692016-04-26 12:56:00 -0700584
bsalomon06077562016-05-04 13:50:29 -0700585 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
586 p->setStrokeJoin(SkPaint::kRound_Join);
587 p->setStrokeMiter(miter);
588 };
bsalomon47cc7692016-04-26 12:56:00 -0700589
bsalomon06077562016-05-04 13:50:29 -0700590 // The miter limit should affect stroked and dashed-stroked cases when the join type is
591 // miter.
592 test_stroke_param_impl<GEO, SkScalar>(
593 reporter,
594 geo,
595 setMiterJoinAndLimit,
596 0.5f, 0.75f,
597 true,
598 true);
bsalomon47cc7692016-04-26 12:56:00 -0700599
bsalomon06077562016-05-04 13:50:29 -0700600 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
601 // not miter.
602 test_stroke_param_impl<GEO, SkScalar>(
603 reporter,
604 geo,
605 setOtherJoinAndLimit,
606 0.5f, 0.75f,
607 false,
608 false);
bsalomon47cc7692016-04-26 12:56:00 -0700609}
610
bsalomon72dc51c2016-04-27 06:46:23 -0700611template<typename GEO>
612static void test_dash_fill(skiatest::Reporter* reporter, const GEO& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700613 // A dash with no stroke should have no effect
614 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
615 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
616 SkPaint dashFill;
617 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -0700618 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700619
bsalomonfb083272016-05-04 08:27:41 -0700620 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700621 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
622 }
623}
624
bsalomon72dc51c2016-04-27 06:46:23 -0700625template<typename GEO>
626void test_null_dash(skiatest::Reporter* reporter, const GEO& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700627 SkPaint fill;
628 SkPaint stroke;
629 stroke.setStyle(SkPaint::kStroke_Style);
630 stroke.setStrokeWidth(1.f);
631 SkPaint dash;
632 dash.setStyle(SkPaint::kStroke_Style);
633 dash.setStrokeWidth(1.f);
634 dash.setPathEffect(make_dash());
635 SkPaint nullDash;
636 nullDash.setStyle(SkPaint::kStroke_Style);
637 nullDash.setStrokeWidth(1.f);
638 nullDash.setPathEffect(make_null_dash());
639
bsalomonfb083272016-05-04 08:27:41 -0700640 TestCase fillCase(geo, fill, reporter);
641 TestCase strokeCase(geo, stroke, reporter);
642 TestCase dashCase(geo, dash, reporter);
643 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700644
645 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
646 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
647 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
648}
649
bsalomon72dc51c2016-04-27 06:46:23 -0700650template <typename GEO>
651void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) {
652 /**
653 * This path effect takes any input path and turns it into a rrect. It passes through stroke
654 * info.
655 */
656 class RRectPathEffect : SkPathEffect {
657 public:
658 static const SkRRect& RRect() {
659 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
660 return kRRect;
661 }
662
663 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
664 const SkRect* cullR) const override {
665 dst->reset();
666 dst->addRRect(RRect());
667 return true;
668 }
669 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
670 *dst = RRect().getBounds();
671 }
672 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
673 Factory getFactory() const override { return nullptr; }
674 void toString(SkString*) const override {}
675 private:
676 RRectPathEffect() {}
677 };
678
679 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -0700680 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700681
682 SkPaint pe;
683 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -0700684 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700685
686 SkPaint peStroke;
687 peStroke.setPathEffect(RRectPathEffect::Make());
688 peStroke.setStrokeWidth(2.f);
689 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -0700690 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700691
692 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
693 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
694 geoPECase.compare(reporter, geoPEStrokeCase,
695 TestCase::kSameUpToStroke_ComparisonExpecation);
696
bsalomonfb083272016-05-04 08:27:41 -0700697 TestCase rrectFillCase(RRectPathEffect::RRect(), fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700698 SkPaint stroke = peStroke;
699 stroke.setPathEffect(nullptr);
bsalomonfb083272016-05-04 08:27:41 -0700700 TestCase rrectStrokeCase(RRectPathEffect::RRect(), stroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700701
702 SkRRect rrect;
703 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
704 // geoPECase, so the full style should be the same as just the PE.
bsalomonee295642016-06-06 14:01:25 -0700705 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -0700706 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
707 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
708
bsalomonee295642016-06-06 14:01:25 -0700709 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -0700710 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
711 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
712
713 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomonee295642016-06-06 14:01:25 -0700714 REPORTER_ASSERT(reporter,
715 geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -0700716 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
717 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
718
bsalomonee295642016-06-06 14:01:25 -0700719 REPORTER_ASSERT(reporter,
720 !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -0700721 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
722 rrectStrokeCase.appliedFullStyleKey());
723}
724
725template <typename GEO>
726void test_unknown_path_effect(skiatest::Reporter* reporter, const GEO& geo) {
727 /**
728 * This path effect just adds two lineTos to the input path.
729 */
730 class AddLineTosPathEffect : SkPathEffect {
731 public:
732 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
733 const SkRect* cullR) const override {
734 *dst = src;
735 dst->lineTo(0, 0);
736 dst->lineTo(10, 10);
737 return true;
738 }
739 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
740 *dst = src;
741 dst->growToInclude(0, 0);
742 dst->growToInclude(10, 10);
743 }
744 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
745 Factory getFactory() const override { return nullptr; }
746 void toString(SkString*) const override {}
747 private:
748 AddLineTosPathEffect() {}
749 };
750
bsalomon9ad5d7c2016-05-04 08:44:15 -0700751 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -0700752 // effect key for dash path effects. So the only way another arbitrary path effect can produce
753 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
754 SkPaint peStroke;
755 peStroke.setPathEffect(AddLineTosPathEffect::Make());
756 peStroke.setStrokeWidth(2.f);
757 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -0700758 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700759 TestCase::SelfExpectations expectations;
760 expectations.fPEHasEffect = true;
761 expectations.fPEHasValidKey = false;
762 expectations.fStrokeApplies = true;
763 geoPEStrokeCase.testExpectations(reporter, expectations);
764}
765
bsalomon9ad5d7c2016-05-04 08:44:15 -0700766template <typename GEO>
767void test_make_hairline_path_effect(skiatest::Reporter* reporter, const GEO& geo, bool isNonPath) {
768 /**
769 * This path effect just changes the stroke rec to hairline.
770 */
771 class MakeHairlinePathEffect : SkPathEffect {
772 public:
773 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
774 const SkRect* cullR) const override {
775 *dst = src;
776 strokeRec->setHairlineStyle();
777 return true;
778 }
779 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; }
780 static sk_sp<SkPathEffect> Make() {
781 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
782 }
783 Factory getFactory() const override { return nullptr; }
784 void toString(SkString*) const override {}
785 private:
786 MakeHairlinePathEffect() {}
787 };
788
789 SkPaint fill;
790 SkPaint pe;
791 pe.setPathEffect(MakeHairlinePathEffect::Make());
792
793 TestCase peCase(geo, pe, reporter);
794
bsalomonee295642016-06-06 14:01:25 -0700795 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -0700796 peCase.baseShape().asPath(&a);
797 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700798 peCase.appliedFullStyleShape().asPath(&c);
bsalomon9ad5d7c2016-05-04 08:44:15 -0700799 if (isNonPath) {
bsalomonee295642016-06-06 14:01:25 -0700800 // RRect types can have a change in start index or direction after the PE is applied. This
801 // is because once the PE is applied, GrShape may canonicalize the dir and index since it
802 // is not germane to the styling any longer.
803 // Instead we just check that the paths would fill the same both before and after styling.
804 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
805 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -0700806 } else {
bsalomonee295642016-06-06 14:01:25 -0700807 REPORTER_ASSERT(reporter, a == b);
808 REPORTER_ASSERT(reporter, a == c);
bsalomon9ad5d7c2016-05-04 08:44:15 -0700809 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
810 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
811 }
bsalomonee295642016-06-06 14:01:25 -0700812 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
813 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -0700814}
815
bsalomon4eeccc92016-04-27 13:30:25 -0700816/**
817 * isNonPath indicates whether the initial shape made from the path is expected to be recognized
818 * as a simpler shape type (e.g. rrect)
819 */
820void test_volatile_path(skiatest::Reporter* reporter, const SkPath& path,
821 bool isNonPath) {
822 SkPath vPath(path);
823 vPath.setIsVolatile(true);
824
825 SkPaint dashAndStroke;
826 dashAndStroke.setPathEffect(make_dash());
827 dashAndStroke.setStrokeWidth(2.f);
828 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -0700829 TestCase volatileCase(vPath, dashAndStroke, reporter);
bsalomon4eeccc92016-04-27 13:30:25 -0700830 // We expect a shape made from a volatile path to have a key iff the shape is recognized
831 // as a specialized geometry.
832 if (isNonPath) {
833 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
834 // In this case all the keys should be identical to the non-volatile case.
bsalomonfb083272016-05-04 08:27:41 -0700835 TestCase nonVolatileCase(path, dashAndStroke, reporter);
bsalomon4eeccc92016-04-27 13:30:25 -0700836 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
837 } else {
838 // None of the keys should be valid.
839 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
840 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
841 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
842 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
843 }
844}
845
bsalomon409ed732016-04-27 12:36:02 -0700846template <typename GEO>
847void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const GEO& geo) {
848 /**
849 * This path effect returns an empty path.
850 */
851 class EmptyPathEffect : SkPathEffect {
852 public:
853 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
854 const SkRect* cullR) const override {
855 dst->reset();
856 return true;
857 }
858 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
859 dst->setEmpty();
860 }
861 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new EmptyPathEffect); }
862 Factory getFactory() const override { return nullptr; }
863 void toString(SkString*) const override {}
864 private:
865 EmptyPathEffect() {}
866 };
867
868 SkPath emptyPath;
869 GrShape emptyShape(emptyPath);
870 Key emptyKey;
871 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -0700872 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -0700873
874 SkPaint pe;
875 pe.setPathEffect(EmptyPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -0700876 TestCase geoCase(geo, pe, reporter);
bsalomon409ed732016-04-27 12:36:02 -0700877 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleKey() == emptyKey);
878 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectKey() == emptyKey);
879 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -0700880 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectShape().isEmpty());
881 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleShape().isEmpty());
bsalomon409ed732016-04-27 12:36:02 -0700882
883 SkPaint peStroke;
884 peStroke.setPathEffect(EmptyPathEffect::Make());
885 peStroke.setStrokeWidth(2.f);
886 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -0700887 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -0700888 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
889 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
890 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -0700891 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
892 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
bsalomon409ed732016-04-27 12:36:02 -0700893}
894
895void test_empty_shape(skiatest::Reporter* reporter) {
896 SkPath emptyPath;
897 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -0700898 TestCase fillEmptyCase(emptyPath, fill, reporter);
bsalomon7c73a532016-05-11 15:15:56 -0700899 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
900 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
901 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
bsalomon409ed732016-04-27 12:36:02 -0700902
903 Key emptyKey(fillEmptyCase.baseKey());
904 REPORTER_ASSERT(reporter, emptyKey.count());
905 TestCase::SelfExpectations expectations;
906 expectations.fStrokeApplies = false;
907 expectations.fPEHasEffect = false;
908 // This will test whether applying style preserves emptiness
909 fillEmptyCase.testExpectations(reporter, expectations);
910
911 // Stroking an empty path should have no effect
912 SkPath emptyPath2;
913 SkPaint stroke;
914 stroke.setStrokeWidth(2.f);
915 stroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -0700916 TestCase strokeEmptyCase(emptyPath2, stroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -0700917 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
918
919 // Dashing and stroking an empty path should have no effect
920 SkPath emptyPath3;
921 SkPaint dashAndStroke;
922 dashAndStroke.setPathEffect(make_dash());
923 dashAndStroke.setStrokeWidth(2.f);
924 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -0700925 TestCase dashAndStrokeEmptyCase(emptyPath3, dashAndStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -0700926 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
927 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -0700928
929 // A shape made from an empty rrect should behave the same as an empty path.
930 SkRRect emptyRRect = SkRRect::MakeRect(SkRect::MakeEmpty());
931 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
bsalomonfb083272016-05-04 08:27:41 -0700932 TestCase dashAndStrokeEmptyRRectCase(emptyRRect, dashAndStroke, reporter);
bsalomon5e410b42016-04-28 09:30:46 -0700933 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
934 TestCase::kAllSame_ComparisonExpecation);
935
936 // Same for a rect.
937 SkRect emptyRect = SkRect::MakeEmpty();
bsalomonfb083272016-05-04 08:27:41 -0700938 TestCase dashAndStrokeEmptyRectCase(emptyRect, dashAndStroke, reporter);
bsalomon5e410b42016-04-28 09:30:46 -0700939 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
940 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -0700941}
942
bsalomon47cc7692016-04-26 12:56:00 -0700943DEF_TEST(GrShape, reporter) {
bsalomonee295642016-06-06 14:01:25 -0700944 for (auto r : { SkRect::MakeWH(10, 20),
945 SkRect::MakeWH(-10, -20),
946 SkRect::MakeWH(-10, 20),
947 SkRect::MakeWH(10, -20)}) {
948 test_basic(reporter, r);
949 test_scale(reporter, r);
950 test_dash_fill(reporter, r);
951 test_null_dash(reporter, r);
952 // Test modifying various stroke params.
953 test_stroke_param<SkRect, SkScalar>(
954 reporter, r,
955 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
956 SkIntToScalar(2), SkIntToScalar(4));
957 test_stroke_param<SkRect, SkPaint::Join>(
958 reporter, r,
959 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
960 SkPaint::kMiter_Join, SkPaint::kRound_Join);
961 test_stroke_cap(reporter, r);
962 test_miter_limit(reporter, r);
963 test_path_effect_makes_rrect(reporter, r);
964 test_unknown_path_effect(reporter, r);
965 test_path_effect_makes_empty_shape(reporter, r);
966 test_make_hairline_path_effect(reporter, r, true);
967 }
bsalomon47cc7692016-04-26 12:56:00 -0700968
969 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -0700970 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
971 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomon47cc7692016-04-26 12:56:00 -0700972 test_basic(reporter, rr);
bsalomon97fd2d42016-05-09 13:02:01 -0700973 test_scale(reporter, rr);
bsalomon47cc7692016-04-26 12:56:00 -0700974 test_dash_fill(reporter, rr);
975 test_null_dash(reporter, rr);
976 // Test modifying various stroke params.
bsalomon72dc51c2016-04-27 06:46:23 -0700977 test_stroke_param<SkRRect, SkScalar>(
bsalomon47cc7692016-04-26 12:56:00 -0700978 reporter, rr,
979 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
980 SkIntToScalar(2), SkIntToScalar(4));
bsalomon72dc51c2016-04-27 06:46:23 -0700981 test_stroke_param<SkRRect, SkPaint::Join>(
bsalomon47cc7692016-04-26 12:56:00 -0700982 reporter, rr,
983 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
984 SkPaint::kMiter_Join, SkPaint::kRound_Join);
bsalomon06077562016-05-04 13:50:29 -0700985 test_stroke_cap(reporter, rr);
bsalomon47cc7692016-04-26 12:56:00 -0700986 test_miter_limit(reporter, rr);
bsalomon72dc51c2016-04-27 06:46:23 -0700987 test_path_effect_makes_rrect(reporter, rr);
988 test_unknown_path_effect(reporter, rr);
bsalomon409ed732016-04-27 12:36:02 -0700989 test_path_effect_makes_empty_shape(reporter, rr);
bsalomon9ad5d7c2016-05-04 08:44:15 -0700990 test_make_hairline_path_effect(reporter, rr, true);
bsalomon72dc51c2016-04-27 06:46:23 -0700991 }
992
993 struct TestPath {
994 TestPath(const SkPath& path, bool isRRectFill, bool isRRectStroke ,const SkRRect& rrect)
995 : fPath(path)
996 , fIsRRectForFill(isRRectFill)
997 , fIsRRectForStroke(isRRectStroke)
998 , fRRect(rrect) {}
999 SkPath fPath;
1000 bool fIsRRectForFill;
1001 bool fIsRRectForStroke;
1002 SkRRect fRRect;
1003 };
1004 SkTArray<TestPath> paths;
1005
1006 SkPath circlePath;
1007 circlePath.addCircle(10, 10, 10);
1008 paths.emplace_back(circlePath, true, true, SkRRect::MakeOval(SkRect::MakeWH(20,20)));
1009
1010 SkPath rectPath;
1011 rectPath.addRect(SkRect::MakeWH(10, 10));
1012 paths.emplace_back(rectPath, true, true, SkRRect::MakeRect(SkRect::MakeWH(10, 10)));
1013
1014 SkPath openRectPath;
1015 openRectPath.moveTo(0, 0);
1016 openRectPath.lineTo(10, 0);
1017 openRectPath.lineTo(10, 10);
1018 openRectPath.lineTo(0, 10);
1019 paths.emplace_back(openRectPath, true, false, SkRRect::MakeRect(SkRect::MakeWH(10, 10)));
1020
1021 SkPath quadPath;
1022 quadPath.quadTo(10, 10, 5, 8);
1023 paths.emplace_back(quadPath, false, false, SkRRect());
1024
1025 for (auto testPath : paths) {
1026 const SkPath& path = testPath.fPath;
1027 // These tests all assume that the original GrShape for fill and stroke will be the same.
1028 // However, that is not the case in special cases (e.g. a unclosed rect becomes a RRect
1029 // GrShape with a fill style but becomes a Path GrShape when stroked).
1030 if (testPath.fIsRRectForFill == testPath.fIsRRectForStroke) {
1031 test_basic(reporter, path);
1032 test_null_dash(reporter, path);
1033 test_path_effect_makes_rrect(reporter, path);
1034 }
bsalomon97fd2d42016-05-09 13:02:01 -07001035 test_scale(reporter, path);
bsalomon4eeccc92016-04-27 13:30:25 -07001036 // This test uses a stroking paint, hence use of fIsRRectForStroke
1037 test_volatile_path(reporter, path, testPath.fIsRRectForStroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001038 test_dash_fill(reporter, path);
1039 // Test modifying various stroke params.
1040 test_stroke_param<SkPath, SkScalar>(
1041 reporter, path,
1042 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
1043 SkIntToScalar(2), SkIntToScalar(4));
bsalomon72dc51c2016-04-27 06:46:23 -07001044 test_stroke_param<SkPath, SkPaint::Join>(
1045 reporter, path,
1046 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
1047 SkPaint::kMiter_Join, SkPaint::kRound_Join);
bsalomon06077562016-05-04 13:50:29 -07001048 test_stroke_cap(reporter, path);
bsalomon72dc51c2016-04-27 06:46:23 -07001049 test_miter_limit(reporter, path);
1050 test_unknown_path_effect(reporter, path);
bsalomon409ed732016-04-27 12:36:02 -07001051 test_path_effect_makes_empty_shape(reporter, path);
bsalomon9ad5d7c2016-05-04 08:44:15 -07001052 test_make_hairline_path_effect(reporter, path, testPath.fIsRRectForStroke);
bsalomon72dc51c2016-04-27 06:46:23 -07001053
1054 SkPaint fillPaint;
bsalomonfb083272016-05-04 08:27:41 -07001055 TestCase fillPathCase(path, fillPaint, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001056 SkRRect rrect;
1057 REPORTER_ASSERT(reporter, testPath.fIsRRectForFill ==
bsalomonee295642016-06-06 14:01:25 -07001058 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001059 if (testPath.fIsRRectForFill) {
bsalomonee295642016-06-06 14:01:25 -07001060 TestCase fillPathCase2(path, fillPaint, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001061 REPORTER_ASSERT(reporter, rrect == testPath.fRRect);
bsalomonfb083272016-05-04 08:27:41 -07001062 TestCase fillRRectCase(rrect, fillPaint, reporter);
bsalomonee295642016-06-06 14:01:25 -07001063 fillPathCase2.compare(reporter, fillRRectCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07001064 }
1065
1066 SkPaint strokePaint;
1067 strokePaint.setStrokeWidth(3.f);
1068 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001069 TestCase strokePathCase(path, strokePaint, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001070 REPORTER_ASSERT(reporter, testPath.fIsRRectForStroke ==
bsalomonee295642016-06-06 14:01:25 -07001071 strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001072 if (testPath.fIsRRectForStroke) {
1073 REPORTER_ASSERT(reporter, rrect == testPath.fRRect);
bsalomonfb083272016-05-04 08:27:41 -07001074 TestCase strokeRRectCase(rrect, strokePaint, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001075 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07001076 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07001077 }
bsalomon47cc7692016-04-26 12:56:00 -07001078 }
bsalomon409ed732016-04-27 12:36:02 -07001079
bsalomon4eeccc92016-04-27 13:30:25 -07001080 // Test a volatile empty path.
1081 test_volatile_path(reporter, SkPath(), true);
1082
bsalomon409ed732016-04-27 12:36:02 -07001083 test_empty_shape(reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001084}
1085
1086#endif