blob: 9246c73f40f3419141bea2a97dbca5a1b3f8aba1 [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
bsalomon70493962016-06-10 08:05:14 -070085 TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
86 : fBase(shape) {
87 this->init(r, scale);
88 }
89
bsalomon47cc7692016-04-26 12:56:00 -070090 struct SelfExpectations {
91 bool fPEHasEffect;
92 bool fPEHasValidKey;
93 bool fStrokeApplies;
94 };
95
96 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
97
98 enum ComparisonExpecation {
99 kAllDifferent_ComparisonExpecation,
100 kSameUpToPE_ComparisonExpecation,
101 kSameUpToStroke_ComparisonExpecation,
102 kAllSame_ComparisonExpecation,
103 };
104
105 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
106
bsalomon72dc51c2016-04-27 06:46:23 -0700107 const GrShape& baseShape() const { return fBase; }
108 const GrShape& appliedPathEffectShape() const { return fAppliedPE; }
109 const GrShape& appliedFullStyleShape() const { return fAppliedFull; }
110
111 // The returned array's count will be 0 if the key shape has no key.
112 const Key& baseKey() const { return fBaseKey; }
113 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
114 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
bsalomon409ed732016-04-27 12:36:02 -0700115 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
bsalomon72dc51c2016-04-27 06:46:23 -0700116
bsalomon47cc7692016-04-26 12:56:00 -0700117private:
bsalomon9fb42032016-05-13 09:23:38 -0700118 static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
119 SkPath path;
120 shape.asPath(&path);
121 // If the bounds are empty, the path ought to be as well.
bsalomon0ae36a22016-07-18 07:31:13 -0700122 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
bsalomon9fb42032016-05-13 09:23:38 -0700123 REPORTER_ASSERT(r, path.isEmpty());
124 return;
125 }
126 if (path.isEmpty()) {
127 return;
128 }
bsalomon70493962016-06-10 08:05:14 -0700129 // The bounds API explicitly calls out that it does not consider inverseness.
130 SkPath p = path;
131 p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType()));
132 REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds));
bsalomon9fb42032016-05-13 09:23:38 -0700133 }
134
bsalomon97fd2d42016-05-09 13:02:01 -0700135 void init(skiatest::Reporter* r, SkScalar scale) {
136 fAppliedPE = fBase.applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
137 fAppliedPEThenStroke = fAppliedPE.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec,
138 scale);
139 fAppliedFull = fBase.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
bsalomon47cc7692016-04-26 12:56:00 -0700140
bsalomon72dc51c2016-04-27 06:46:23 -0700141 make_key(&fBaseKey, fBase);
142 make_key(&fAppliedPEKey, fAppliedPE);
143 make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke);
144 make_key(&fAppliedFullKey, fAppliedFull);
bsalomonfb083272016-05-04 08:27:41 -0700145
146 // Applying the path effect and then the stroke should always be the same as applying
147 // both in one go.
148 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
149 SkPath a, b;
150 fAppliedPEThenStroke.asPath(&a);
151 fAppliedFull.asPath(&b);
bsalomonee295642016-06-06 14:01:25 -0700152 // If the output of the path effect is a rrect then it is possible for a and b to be
153 // different paths that fill identically. The reason is that fAppliedFull will do this:
154 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
155 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
156 // now that there is no longer a path effect, the direction and starting index get
157 // canonicalized before the stroke.
bsalomon70493962016-06-10 08:05:14 -0700158 if (fAppliedPE.asRRect(nullptr, nullptr, nullptr, nullptr)) {
bsalomonee295642016-06-06 14:01:25 -0700159 REPORTER_ASSERT(r, paths_fill_same(a, b));
160 } else {
161 REPORTER_ASSERT(r, a == b);
162 }
bsalomon7c73a532016-05-11 15:15:56 -0700163 REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty());
164
165 SkPath path;
166 fBase.asPath(&path);
167 REPORTER_ASSERT(r, path.isEmpty() == fBase.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700168 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700169 fAppliedPE.asPath(&path);
170 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700171 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE.segmentMask());
bsalomon7c73a532016-05-11 15:15:56 -0700172 fAppliedFull.asPath(&path);
173 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull.isEmpty());
bsalomon06115ee2016-06-07 06:28:51 -0700174 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull.segmentMask());
bsalomonfb083272016-05-04 08:27:41 -0700175
bsalomon9fb42032016-05-13 09:23:38 -0700176 CheckBounds(r, fBase, fBase.bounds());
177 CheckBounds(r, fAppliedPE, fAppliedPE.bounds());
178 CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds());
179 CheckBounds(r, fAppliedFull, fAppliedFull.bounds());
bsalomon0a0f67e2016-06-28 11:56:42 -0700180 SkRect styledBounds = fBase.styledBounds();
bsalomon9fb42032016-05-13 09:23:38 -0700181 CheckBounds(r, fAppliedFull, styledBounds);
bsalomon0a0f67e2016-06-28 11:56:42 -0700182 styledBounds = fAppliedPE.styledBounds();
bsalomon9fb42032016-05-13 09:23:38 -0700183 CheckBounds(r, fAppliedFull, styledBounds);
184
bsalomonfb083272016-05-04 08:27:41 -0700185 // Check that the same path is produced when style is applied by GrShape and GrStyle.
186 SkPath preStyle;
187 SkPath postPathEffect;
188 SkPath postAllStyle;
189
190 fBase.asPath(&preStyle);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700191 SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
bsalomon97fd2d42016-05-09 13:02:01 -0700192 if (fBase.style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
193 scale)) {
bsalomon1a0b9ed2016-05-06 11:07:03 -0700194 // run postPathEffect through GrShape to get any geometry reductions that would have
195 // occurred to fAppliedPE.
196 GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
197
bsalomonfb083272016-05-04 08:27:41 -0700198 SkPath testPath;
199 fAppliedPE.asPath(&testPath);
200 REPORTER_ASSERT(r, testPath == postPathEffect);
bsalomon1a0b9ed2016-05-06 11:07:03 -0700201 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE.style().strokeRec()));
bsalomonfb083272016-05-04 08:27:41 -0700202 }
203 SkStrokeRec::InitStyle fillOrHairline;
bsalomon97fd2d42016-05-09 13:02:01 -0700204 if (fBase.style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
bsalomonfb083272016-05-04 08:27:41 -0700205 SkPath testPath;
206 fAppliedFull.asPath(&testPath);
bsalomon1b28c1a2016-06-20 12:28:17 -0700207 if (fBase.style().hasPathEffect()) {
208 // Because GrShape always does two-stage application when there is a path effect
209 // there may be a reduction/canonicalization step between the path effect and
210 // strokerec not reflected in postAllStyle since it applied both the path effect
211 // and strokerec without analyzing the intermediate path.
212 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
213 } else {
214 // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape
215 // would apply.
216 GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
217 REPORTER_ASSERT(r, testPath == postAllStyle);
218 }
219
bsalomonfb083272016-05-04 08:27:41 -0700220 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
221 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleFill());
222 } else {
223 REPORTER_ASSERT(r, fAppliedFull.style().isSimpleHairline());
224 }
225 }
bsalomon47cc7692016-04-26 12:56:00 -0700226 }
227
228 GrShape fBase;
229 GrShape fAppliedPE;
230 GrShape fAppliedPEThenStroke;
231 GrShape fAppliedFull;
232
233 Key fBaseKey;
234 Key fAppliedPEKey;
235 Key fAppliedPEThenStrokeKey;
236 Key fAppliedFullKey;
bsalomon47cc7692016-04-26 12:56:00 -0700237};
238
239void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
bsalomon47cc7692016-04-26 12:56:00 -0700240 // The base's key should always be valid (unless the path is volatile)
bsalomon72dc51c2016-04-27 06:46:23 -0700241 REPORTER_ASSERT(reporter, fBaseKey.count());
bsalomon47cc7692016-04-26 12:56:00 -0700242 if (expectations.fPEHasEffect) {
243 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700244 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700245 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700246 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700247 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
248 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
bsalomon72dc51c2016-04-27 06:46:23 -0700249 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
bsalomon47cc7692016-04-26 12:56:00 -0700250 }
251 } else {
252 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
bsalomonfb083272016-05-04 08:27:41 -0700253 SkPath a, b;
bsalomon72dc51c2016-04-27 06:46:23 -0700254 fBase.asPath(&a);
255 fAppliedPE.asPath(&b);
256 REPORTER_ASSERT(reporter, a == b);
bsalomon47cc7692016-04-26 12:56:00 -0700257 if (expectations.fStrokeApplies) {
258 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
259 } else {
260 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
261 }
262 }
263}
264
bsalomonac5fcea2016-06-23 12:23:07 -0700265static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
266 SkPath path;
267 shape.asPath(&path);
268 if (shape.style().hasNonDashPathEffect()) {
269 return false;
270 }
271 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
272 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
273 strokeRecStyle == SkStrokeRec::kHairline_Style ||
274 (shape.style().isSimpleFill() && path.isConvex());
275}
276
277static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
278 const Key& keyA, const Key& keyB) {
bsalomonee295642016-06-06 14:01:25 -0700279 // GrShape only respects the input winding direction and start point for rrect shapes
280 // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
281 // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
282 // key will differ. GrShape will have canonicalized the direction and start point for the shape
283 // without the path effect. If *both* have path effects then they should have both preserved
284 // the direction and starting point.
285
286 // The asRRect() output params are all initialized just to silence compiler warnings about
287 // uninitialized variables.
288 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
289 SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
290 unsigned startA = ~0U, startB = ~0U;
bsalomon70493962016-06-10 08:05:14 -0700291 bool invertedA = true, invertedB = true;
bsalomonee295642016-06-06 14:01:25 -0700292
bsalomon70493962016-06-10 08:05:14 -0700293 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
294 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
bsalomonee295642016-06-06 14:01:25 -0700295 bool aHasPE = a.style().hasPathEffect();
296 bool bHasPE = b.style().hasPathEffect();
297 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
bsalomon425c27f2016-06-23 13:18:45 -0700298 // GrShape will close paths with simple fill style.
299 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
bsalomonee295642016-06-06 14:01:25 -0700300 SkPath pathA, pathB;
301 a.asPath(&pathA);
302 b.asPath(&pathB);
bsalomon70493962016-06-10 08:05:14 -0700303
bsalomonfd32df72016-06-14 14:37:21 -0700304 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
305 // non-inverse fill type (or vice versa).
bsalomon70493962016-06-10 08:05:14 -0700306 bool ignoreInversenessDifference = false;
307 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
308 const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
309 const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
bsalomonfd32df72016-06-14 14:37:21 -0700310 bool canDropInverse1 = s1->style().isDashed();
311 bool canDropInverse2 = s2->style().isDashed();
312 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
bsalomon70493962016-06-10 08:05:14 -0700313 }
bsalomona4817af2016-06-23 11:48:26 -0700314 bool ignoreWindingVsEvenOdd = false;
315 if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
316 SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
bsalomonac5fcea2016-06-23 12:23:07 -0700317 bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
318 bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
bsalomona4817af2016-06-23 11:48:26 -0700319 if (aCanChange != bCanChange) {
320 ignoreWindingVsEvenOdd = true;
321 }
322 }
bsalomonee295642016-06-06 14:01:25 -0700323 if (allowSameRRectButDiffStartAndDir) {
324 REPORTER_ASSERT(r, rrectA == rrectB);
325 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
bsalomon70493962016-06-10 08:05:14 -0700326 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
bsalomonee295642016-06-06 14:01:25 -0700327 } else {
bsalomon70493962016-06-10 08:05:14 -0700328 SkPath pA = pathA;
329 SkPath pB = pathB;
bsalomon425c27f2016-06-23 13:18:45 -0700330 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
331 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
bsalomon70493962016-06-10 08:05:14 -0700332 if (ignoreInversenessDifference) {
333 pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
334 pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
bsalomona4817af2016-06-23 11:48:26 -0700335 }
336 if (ignoreWindingVsEvenOdd) {
337 pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
338 : SkPath::kEvenOdd_FillType);
339 pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
340 : SkPath::kEvenOdd_FillType);
341 }
342 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
bsalomon70493962016-06-10 08:05:14 -0700343 REPORTER_ASSERT(r, keyA == keyB);
bsalomona4817af2016-06-23 11:48:26 -0700344 } else {
345 REPORTER_ASSERT(r, keyA != keyB);
bsalomon70493962016-06-10 08:05:14 -0700346 }
bsalomon425c27f2016-06-23 13:18:45 -0700347 if (allowedClosednessDiff) {
bsalomon93f66bc2016-06-21 08:35:49 -0700348 // GrShape will close paths with simple fill style. Make the non-filled path closed
349 // so that the comparision will succeed. Make sure both are closed before comparing.
350 pA.close();
351 pB.close();
352 }
bsalomon70493962016-06-10 08:05:14 -0700353 REPORTER_ASSERT(r, pA == pB);
bsalomonee295642016-06-06 14:01:25 -0700354 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
355 if (aIsRRect) {
356 REPORTER_ASSERT(r, rrectA == rrectB);
357 REPORTER_ASSERT(r, dirA == dirB);
358 REPORTER_ASSERT(r, startA == startB);
bsalomon70493962016-06-10 08:05:14 -0700359 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
bsalomonee295642016-06-06 14:01:25 -0700360 }
361 }
362 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
bsalomon425c27f2016-06-23 13:18:45 -0700363 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
364 // closedness can affect convexity.
365 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
366 if (a.knownToBeConvex()) {
367 REPORTER_ASSERT(r, pathA.isConvex());
368 }
369 if (b.knownToBeConvex()) {
370 REPORTER_ASSERT(r, pathB.isConvex());
371 }
bsalomonee295642016-06-06 14:01:25 -0700372 REPORTER_ASSERT(r, a.bounds() == b.bounds());
bsalomon06115ee2016-06-07 06:28:51 -0700373 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
bsalomon0a0f67e2016-06-28 11:56:42 -0700374 // Init these to suppress warnings.
375 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
376 bool invertedLine[2] {true, true};
377 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
bsalomon425c27f2016-06-23 13:18:45 -0700378 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
379 // doesn't (since the PE can set any fill type on its output path).
380 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
381 // then they may disagree about inverseness.
382 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
383 a.style().isDashed() == b.style().isDashed()) {
384 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
385 b.mayBeInverseFilledAfterStyling());
386 }
bsalomon0a0f67e2016-06-28 11:56:42 -0700387 if (a.asLine(nullptr, nullptr)) {
bsalomon398e3f42016-06-13 10:22:48 -0700388 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
bsalomon0a0f67e2016-06-28 11:56:42 -0700389 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
390 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
391 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
bsalomon398e3f42016-06-13 10:22:48 -0700392 }
bsalomon425c27f2016-06-23 13:18:45 -0700393 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
bsalomonee295642016-06-06 14:01:25 -0700394}
395
396void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
bsalomon47cc7692016-04-26 12:56:00 -0700397 ComparisonExpecation expectation) const {
bsalomon72dc51c2016-04-27 06:46:23 -0700398 SkPath a, b;
bsalomon47cc7692016-04-26 12:56:00 -0700399 switch (expectation) {
400 case kAllDifferent_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700401 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
402 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
403 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700404 break;
405 case kSameUpToPE_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700406 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
407 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
408 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700409 break;
410 case kSameUpToStroke_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700411 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
412 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
413 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700414 break;
415 case kAllSame_ComparisonExpecation:
bsalomonee295642016-06-06 14:01:25 -0700416 check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
417 check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
418 check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey,
419 that.fAppliedFullKey);
bsalomon47cc7692016-04-26 12:56:00 -0700420 break;
421 }
422}
423} // namespace
424
425static sk_sp<SkPathEffect> make_dash() {
426 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
427 static const SkScalar kPhase = 0.75;
428 return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
429}
430
431static sk_sp<SkPathEffect> make_null_dash() {
432 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
433 return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
434}
435
bsalomon487f8d32016-07-20 07:15:44 -0700436//////////////////////////////////////////////////////////////////////////////
437// These functions allow tests to check for special cases where style gets
438// applied by GrShape in its constructor (without calling GrShape::applyStyle).
439// These unfortunately rely on knowing details of GrShape's implementation.
440// These predicates are factored out here to avoid littering the rest of the
441// test code with GrShape implementation details.
442
443static bool path_is_axis_aligned_line(const SkPath& path) {
444 SkPoint pts[2];
445 if (!path.isLine(pts)) {
446 return false;
447 }
448 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
449}
450
451static bool path_is_unclosed_rect(const SkPath& path) {
452 bool closed;
453 return path.isRect(nullptr, &closed, nullptr) && !closed;
454}
455
456// Will a GrShape constructed from a geometry perform a geometric transformation if the style is
457// simple fill that would not otherwise be applied.
458template <typename GEO> static bool fill_changes_geom(const GEO& geo) { return false; }
459template <> bool fill_changes_geom<SkPath>(const SkPath& path) {
460 // unclosed rects get closed. Lines get turned into empty geometry
461 return path_is_unclosed_rect(path) || (path.isLine(nullptr) && !path.isInverseFillType());
462}
463
464// Will a GrShape constructed from the geometry with a stroke style (without path effect) perform a
465// geometric transformation that applies the the stroke immediately without storing a stroke style.
466template <typename GEO> static bool stroke_is_converted_to_fill(const GEO& geo) { return false; }
467template <> bool stroke_is_converted_to_fill(const SkPath& path) {
468 // converted to a rrect.
469 return path_is_axis_aligned_line(path);
470}
471
472// Will a GrShape constructed from the geometry with a stroke-and-fill style (without path effect)
473// perform a geometric transformation that applies the the stroke immediately without storing a
474// stroke-and-fill style.
475template <typename GEO> static bool stroke_and_fill_is_converted_to_fill(const GEO& geo, const SkPaint& paint);
476template <> bool stroke_and_fill_is_converted_to_fill(const SkRect& rect, const SkPaint& paint) {
477 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
478 // Converted to an outset rectangle.
479 return paint.getStrokeJoin() == SkPaint::kMiter_Join &&
480 paint.getStrokeMiter() >= SK_ScalarSqrt2;
481}
482template <> bool stroke_and_fill_is_converted_to_fill(const SkPath& path, const SkPaint& paint) {
483 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
484 if (path_is_axis_aligned_line(path)) {
485 // The fill is ignored (zero area) and the stroke is converted to a rrect.
486 return true;
487 }
488 SkRect rect;
489 unsigned start;
490 SkPath::Direction dir;
491 if (SkPathPriv::IsSimpleClosedRect(path, &rect, &dir, &start)) {
492 return stroke_and_fill_is_converted_to_fill<SkRect>(rect, paint);
493 }
494 return false;
495}
496template <> bool stroke_and_fill_is_converted_to_fill(const SkRRect& rr, const SkPaint& paint) {
497 SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
498 if (rr.isRect()) {
499 return stroke_and_fill_is_converted_to_fill<SkRect>(rr.rect(), paint);
500 }
501 return false;
502}
503
504//////////////////////////////////////////////////////////////////////////////
505
bsalomon72dc51c2016-04-27 06:46:23 -0700506template<typename GEO>
507static void test_basic(skiatest::Reporter* reporter, const GEO& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700508 sk_sp<SkPathEffect> dashPE = make_dash();
509
510 TestCase::SelfExpectations expectations;
511 SkPaint fill;
512
bsalomonfb083272016-05-04 08:27:41 -0700513 TestCase fillCase(geo, fill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700514 expectations.fPEHasEffect = false;
515 expectations.fPEHasValidKey = false;
516 expectations.fStrokeApplies = false;
517 fillCase.testExpectations(reporter, expectations);
518 // Test that another GrShape instance built from the same primitive is the same.
bsalomonfb083272016-05-04 08:27:41 -0700519 TestCase(geo, fill, reporter).compare(reporter, fillCase,
520 TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700521
522 SkPaint stroke2RoundBevel;
523 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
524 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
525 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
526 stroke2RoundBevel.setStrokeWidth(2.f);
bsalomonfb083272016-05-04 08:27:41 -0700527 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700528 expectations.fPEHasValidKey = true;
529 expectations.fPEHasEffect = false;
bsalomon487f8d32016-07-20 07:15:44 -0700530 expectations.fStrokeApplies = !stroke_is_converted_to_fill(geo);
bsalomon47cc7692016-04-26 12:56:00 -0700531 stroke2RoundBevelCase.testExpectations(reporter, expectations);
bsalomonfb083272016-05-04 08:27:41 -0700532 TestCase(geo, stroke2RoundBevel, reporter).compare(reporter, stroke2RoundBevelCase,
533 TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700534
535 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
536 stroke2RoundBevelDash.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700537 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700538 expectations.fPEHasValidKey = true;
539 expectations.fPEHasEffect = true;
540 expectations.fStrokeApplies = true;
541 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
bsalomonfb083272016-05-04 08:27:41 -0700542 TestCase(geo, stroke2RoundBevelDash, reporter).compare(reporter, stroke2RoundBevelDashCase,
543 TestCase::kAllSame_ComparisonExpecation);
bsalomon47cc7692016-04-26 12:56:00 -0700544
bsalomon487f8d32016-07-20 07:15:44 -0700545 if (fill_changes_geom(geo) || stroke_is_converted_to_fill(geo)) {
546 fillCase.compare(reporter, stroke2RoundBevelCase,
547 TestCase::kAllDifferent_ComparisonExpecation);
548 fillCase.compare(reporter, stroke2RoundBevelDashCase,
549 TestCase::kAllDifferent_ComparisonExpecation);
550 } else {
551 fillCase.compare(reporter, stroke2RoundBevelCase,
552 TestCase::kSameUpToStroke_ComparisonExpecation);
553 fillCase.compare(reporter, stroke2RoundBevelDashCase,
554 TestCase::kSameUpToPE_ComparisonExpecation);
555 }
556 if (stroke_is_converted_to_fill(geo)) {
557 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
558 TestCase::kAllDifferent_ComparisonExpecation);
559 } else {
560 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
561 TestCase::kSameUpToPE_ComparisonExpecation);
562 }
bsalomon72dc51c2016-04-27 06:46:23 -0700563
bsalomonf0cf3552016-05-05 08:28:30 -0700564 // Stroke and fill cases
565 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
566 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
567 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
568 expectations.fPEHasValidKey = true;
569 expectations.fPEHasEffect = false;
bsalomon487f8d32016-07-20 07:15:44 -0700570 expectations.fStrokeApplies = !stroke_is_converted_to_fill(geo);
bsalomonf0cf3552016-05-05 08:28:30 -0700571 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
572 TestCase(geo, stroke2RoundBevelAndFill, reporter).compare(reporter,
573 stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
574
575 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
576 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
577 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
578 expectations.fPEHasValidKey = true;
bsalomona0587862016-06-09 06:03:38 -0700579 expectations.fPEHasEffect = false;
bsalomon487f8d32016-07-20 07:15:44 -0700580 expectations.fStrokeApplies = !stroke_is_converted_to_fill(geo);
bsalomonf0cf3552016-05-05 08:28:30 -0700581 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
582 TestCase(geo, stroke2RoundBevelAndFillDash, reporter).compare(
583 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
bsalomona0587862016-06-09 06:03:38 -0700584 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
585 TestCase::kAllSame_ComparisonExpecation);
bsalomonf0cf3552016-05-05 08:28:30 -0700586
bsalomon72dc51c2016-04-27 06:46:23 -0700587 SkPaint hairline;
588 hairline.setStyle(SkPaint::kStroke_Style);
589 hairline.setStrokeWidth(0.f);
bsalomonfb083272016-05-04 08:27:41 -0700590 TestCase hairlineCase(geo, hairline, reporter);
bsalomon487f8d32016-07-20 07:15:44 -0700591 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
592 // in the line and unclosed rect cases).
593 if (fill_changes_geom(geo)) {
594 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
595 } else {
596 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
597 }
bsalomon9ad5d7c2016-05-04 08:44:15 -0700598 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
599 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
600 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
bsalomon47cc7692016-04-26 12:56:00 -0700601
bsalomon0ae36a22016-07-18 07:31:13 -0700602}
603
bsalomon97fd2d42016-05-09 13:02:01 -0700604template<typename GEO>
605static void test_scale(skiatest::Reporter* reporter, const GEO& geo) {
606 sk_sp<SkPathEffect> dashPE = make_dash();
607
608 static const SkScalar kS1 = 1.f;
609 static const SkScalar kS2 = 2.f;
610
611 SkPaint fill;
612 TestCase fillCase1(geo, fill, reporter, kS1);
613 TestCase fillCase2(geo, fill, reporter, kS2);
614 // Scale doesn't affect fills.
615 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
616
617 SkPaint hairline;
618 hairline.setStyle(SkPaint::kStroke_Style);
619 hairline.setStrokeWidth(0.f);
620 TestCase hairlineCase1(geo, hairline, reporter, kS1);
621 TestCase hairlineCase2(geo, hairline, reporter, kS2);
622 // Scale doesn't affect hairlines.
623 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
624
625 SkPaint stroke;
626 stroke.setStyle(SkPaint::kStroke_Style);
627 stroke.setStrokeWidth(2.f);
628 TestCase strokeCase1(geo, stroke, reporter, kS1);
629 TestCase strokeCase2(geo, stroke, reporter, kS2);
bsalomon0ae36a22016-07-18 07:31:13 -0700630 // Scale affects the stroke
bsalomon487f8d32016-07-20 07:15:44 -0700631 if (stroke_is_converted_to_fill(geo)) {
632 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
bsalomon0ae36a22016-07-18 07:31:13 -0700633 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
634 } else {
635 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
636 }
bsalomon97fd2d42016-05-09 13:02:01 -0700637
638 SkPaint strokeDash = stroke;
639 strokeDash.setPathEffect(make_dash());
640 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
641 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
642 // Scale affects the dash and the stroke.
bsalomon487f8d32016-07-20 07:15:44 -0700643 strokeDashCase1.compare(reporter, strokeDashCase2,
644 TestCase::kSameUpToPE_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700645
646 // Stroke and fill cases
647 SkPaint strokeAndFill = stroke;
648 strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
649 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
650 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
bsalomona0587862016-06-09 06:03:38 -0700651 SkPaint strokeAndFillDash = strokeDash;
652 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
653 // Dash is ignored for stroke and fill
654 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
655 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
bsalomon487f8d32016-07-20 07:15:44 -0700656 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
657 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
658 // geometries should agree.
659 if (stroke_and_fill_is_converted_to_fill(geo, strokeAndFillDash)) {
660 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
bsalomon97fd2d42016-05-09 13:02:01 -0700661 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
662 TestCase::kAllSame_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700663 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
664 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700665 } else {
666 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
667 TestCase::kSameUpToStroke_ComparisonExpecation);
668 }
bsalomona0587862016-06-09 06:03:38 -0700669 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
670 TestCase::kAllSame_ComparisonExpecation);
671 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
672 TestCase::kAllSame_ComparisonExpecation);
bsalomon97fd2d42016-05-09 13:02:01 -0700673}
674
bsalomon72dc51c2016-04-27 06:46:23 -0700675template <typename GEO, typename T>
bsalomon06077562016-05-04 13:50:29 -0700676static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo,
677 std::function<void(SkPaint*, T)> setter, T a, T b,
678 bool paramAffectsStroke,
679 bool paramAffectsDashAndStroke) {
680 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
681 // that it can override the stroke width.
bsalomon47cc7692016-04-26 12:56:00 -0700682 SkPaint strokeA;
683 strokeA.setStyle(SkPaint::kStroke_Style);
684 strokeA.setStrokeWidth(2.f);
685 setter(&strokeA, a);
686 SkPaint strokeB;
687 strokeB.setStyle(SkPaint::kStroke_Style);
688 strokeB.setStrokeWidth(2.f);
689 setter(&strokeB, b);
690
bsalomonfb083272016-05-04 08:27:41 -0700691 TestCase strokeACase(geo, strokeA, reporter);
692 TestCase strokeBCase(geo, strokeB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700693 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700694 // If stroking is immediately incorporated into a geometric transformation then the base
695 // shapes will differ.
bsalomon487f8d32016-07-20 07:15:44 -0700696 if (stroke_is_converted_to_fill(geo)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700697 strokeACase.compare(reporter, strokeBCase,
698 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700699 } else {
700 strokeACase.compare(reporter, strokeBCase,
701 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700702 }
bsalomon06077562016-05-04 13:50:29 -0700703 } else {
704 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
705 }
bsalomon47cc7692016-04-26 12:56:00 -0700706
bsalomonf0cf3552016-05-05 08:28:30 -0700707 SkPaint strokeAndFillA = strokeA;
708 SkPaint strokeAndFillB = strokeB;
709 strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
710 strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
711 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
712 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
713 if (paramAffectsStroke) {
bsalomon0ae36a22016-07-18 07:31:13 -0700714 // If stroking is immediately incorporated into a geometric transformation then the base
715 // shapes will differ.
bsalomon487f8d32016-07-20 07:15:44 -0700716 if (stroke_and_fill_is_converted_to_fill(geo, strokeAndFillA) ||
717 stroke_and_fill_is_converted_to_fill(geo, strokeAndFillB)) {
bsalomon0ae36a22016-07-18 07:31:13 -0700718 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700719 TestCase::kAllDifferent_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700720 } else {
721 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
bsalomon487f8d32016-07-20 07:15:44 -0700722 TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon0ae36a22016-07-18 07:31:13 -0700723 }
bsalomonf0cf3552016-05-05 08:28:30 -0700724 } else {
725 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
726 TestCase::kAllSame_ComparisonExpecation);
727 }
728
bsalomon47cc7692016-04-26 12:56:00 -0700729 // Make sure stroking params don't affect fill style.
730 SkPaint fillA = strokeA, fillB = strokeB;
731 fillA.setStyle(SkPaint::kFill_Style);
732 fillB.setStyle(SkPaint::kFill_Style);
bsalomonfb083272016-05-04 08:27:41 -0700733 TestCase fillACase(geo, fillA, reporter);
734 TestCase fillBCase(geo, fillB, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700735 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
736
737 // Make sure just applying the dash but not stroke gives the same key for both stroking
738 // variations.
739 SkPaint dashA = strokeA, dashB = strokeB;
740 dashA.setPathEffect(make_dash());
741 dashB.setPathEffect(make_dash());
bsalomonfb083272016-05-04 08:27:41 -0700742 TestCase dashACase(geo, dashA, reporter);
743 TestCase dashBCase(geo, dashB, reporter);
bsalomon06077562016-05-04 13:50:29 -0700744 if (paramAffectsDashAndStroke) {
bsalomon487f8d32016-07-20 07:15:44 -0700745 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
bsalomon06077562016-05-04 13:50:29 -0700746 } else {
747 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
748 }
bsalomon47cc7692016-04-26 12:56:00 -0700749}
750
bsalomon06077562016-05-04 13:50:29 -0700751template <typename GEO, typename T>
752static void test_stroke_param(skiatest::Reporter* reporter, const GEO& geo,
753 std::function<void(SkPaint*, T)> setter, T a, T b) {
754 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
755};
756
757template <typename GEO>
758static void test_stroke_cap(skiatest::Reporter* reporter, const GEO& geo) {
759 GrShape shape(geo, GrStyle(SkStrokeRec::kHairline_InitStyle));
760 // The cap should only affect shapes that may be open.
761 bool affectsStroke = !shape.knownToBeClosed();
762 // Dashing adds ends that need caps.
763 bool affectsDashAndStroke = true;
764 test_stroke_param_impl<GEO, SkPaint::Cap>(
765 reporter,
766 geo,
767 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
768 SkPaint::kButt_Cap, SkPaint::kRound_Cap,
769 affectsStroke,
770 affectsDashAndStroke);
771};
772
bsalomon0ae36a22016-07-18 07:31:13 -0700773static bool shape_known_not_to_have_joins(const GrShape& shape) {
774 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
775}
776
777template <typename GEO>
778static void test_stroke_join(skiatest::Reporter* reporter, const GEO& geo) {
779 GrShape shape(geo, GrStyle(SkStrokeRec::kHairline_InitStyle));
780 // GrShape recognizes certain types don't have joins and will prevent the join type from
781 // affecting the style key.
782 // Dashing doesn't add additional joins. However, GrShape currently loses track of this
783 // after applying the dash.
784 bool affectsStroke = !shape_known_not_to_have_joins(shape);
785 test_stroke_param_impl<GEO, SkPaint::Join>(
786 reporter,
787 geo,
788 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
789 SkPaint::kRound_Join, SkPaint::kBevel_Join,
790 affectsStroke, true);
791};
792
bsalomon72dc51c2016-04-27 06:46:23 -0700793template <typename GEO>
794static void test_miter_limit(skiatest::Reporter* reporter, const GEO& geo) {
bsalomon06077562016-05-04 13:50:29 -0700795 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
796 p->setStrokeJoin(SkPaint::kMiter_Join);
797 p->setStrokeMiter(miter);
798 };
bsalomon47cc7692016-04-26 12:56:00 -0700799
bsalomon06077562016-05-04 13:50:29 -0700800 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
801 p->setStrokeJoin(SkPaint::kRound_Join);
802 p->setStrokeMiter(miter);
803 };
bsalomon47cc7692016-04-26 12:56:00 -0700804
bsalomon0ae36a22016-07-18 07:31:13 -0700805 GrShape shape(geo, GrStyle(SkStrokeRec::kHairline_InitStyle));
806 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
807
bsalomon06077562016-05-04 13:50:29 -0700808 // The miter limit should affect stroked and dashed-stroked cases when the join type is
809 // miter.
810 test_stroke_param_impl<GEO, SkScalar>(
811 reporter,
812 geo,
813 setMiterJoinAndLimit,
814 0.5f, 0.75f,
bsalomon0ae36a22016-07-18 07:31:13 -0700815 mayHaveJoins,
bsalomon06077562016-05-04 13:50:29 -0700816 true);
bsalomon47cc7692016-04-26 12:56:00 -0700817
bsalomon06077562016-05-04 13:50:29 -0700818 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
819 // not miter.
820 test_stroke_param_impl<GEO, SkScalar>(
821 reporter,
822 geo,
823 setOtherJoinAndLimit,
824 0.5f, 0.75f,
825 false,
826 false);
bsalomon47cc7692016-04-26 12:56:00 -0700827}
828
bsalomon72dc51c2016-04-27 06:46:23 -0700829template<typename GEO>
830static void test_dash_fill(skiatest::Reporter* reporter, const GEO& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700831 // A dash with no stroke should have no effect
832 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
833 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
834 SkPaint dashFill;
835 dashFill.setPathEffect((*md)());
bsalomonfb083272016-05-04 08:27:41 -0700836 TestCase dashFillCase(geo, dashFill, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700837
bsalomonfb083272016-05-04 08:27:41 -0700838 TestCase fillCase(geo, SkPaint(), reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700839 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
840 }
841}
842
bsalomon72dc51c2016-04-27 06:46:23 -0700843template<typename GEO>
844void test_null_dash(skiatest::Reporter* reporter, const GEO& geo) {
bsalomon47cc7692016-04-26 12:56:00 -0700845 SkPaint fill;
846 SkPaint stroke;
847 stroke.setStyle(SkPaint::kStroke_Style);
848 stroke.setStrokeWidth(1.f);
849 SkPaint dash;
850 dash.setStyle(SkPaint::kStroke_Style);
851 dash.setStrokeWidth(1.f);
852 dash.setPathEffect(make_dash());
853 SkPaint nullDash;
854 nullDash.setStyle(SkPaint::kStroke_Style);
855 nullDash.setStrokeWidth(1.f);
856 nullDash.setPathEffect(make_null_dash());
857
bsalomonfb083272016-05-04 08:27:41 -0700858 TestCase fillCase(geo, fill, reporter);
859 TestCase strokeCase(geo, stroke, reporter);
860 TestCase dashCase(geo, dash, reporter);
861 TestCase nullDashCase(geo, nullDash, reporter);
bsalomon47cc7692016-04-26 12:56:00 -0700862
bsalomon487f8d32016-07-20 07:15:44 -0700863 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
bsalomon47cc7692016-04-26 12:56:00 -0700864 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
bsalomon487f8d32016-07-20 07:15:44 -0700865 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
866 // on construction in order to determine how to compare the fill and stroke.
867 if (fill_changes_geom(geo) || stroke_is_converted_to_fill(geo)) {
868 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
869 } else {
870 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
871 }
872 // In the null dash case we may immediately convert to a fill, but not for the normal dash case.
873 if (stroke_is_converted_to_fill(geo)) {
874 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
875 } else {
876 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
877 }
bsalomon47cc7692016-04-26 12:56:00 -0700878}
879
bsalomon72dc51c2016-04-27 06:46:23 -0700880template <typename GEO>
881void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) {
882 /**
883 * This path effect takes any input path and turns it into a rrect. It passes through stroke
884 * info.
885 */
886 class RRectPathEffect : SkPathEffect {
887 public:
888 static const SkRRect& RRect() {
889 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
890 return kRRect;
891 }
892
893 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
894 const SkRect* cullR) const override {
895 dst->reset();
896 dst->addRRect(RRect());
897 return true;
898 }
899 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
900 *dst = RRect().getBounds();
901 }
902 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
903 Factory getFactory() const override { return nullptr; }
904 void toString(SkString*) const override {}
905 private:
906 RRectPathEffect() {}
907 };
908
909 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -0700910 TestCase fillGeoCase(geo, fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700911
912 SkPaint pe;
913 pe.setPathEffect(RRectPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -0700914 TestCase geoPECase(geo, pe, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700915
916 SkPaint peStroke;
917 peStroke.setPathEffect(RRectPathEffect::Make());
918 peStroke.setStrokeWidth(2.f);
919 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -0700920 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700921
bsalomon487f8d32016-07-20 07:15:44 -0700922 // Check whether constructing the filled case would cause the base shape to have a different
923 // geometry (because of a geometric transformation upon initial GrShape construction).
924 if (fill_changes_geom(geo)) {
925 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
926 fillGeoCase.compare(reporter, geoPEStrokeCase,
927 TestCase::kAllDifferent_ComparisonExpecation);
928 } else {
929 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
930 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
931 }
bsalomon72dc51c2016-04-27 06:46:23 -0700932 geoPECase.compare(reporter, geoPEStrokeCase,
933 TestCase::kSameUpToStroke_ComparisonExpecation);
934
bsalomonfb083272016-05-04 08:27:41 -0700935 TestCase rrectFillCase(RRectPathEffect::RRect(), fill, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700936 SkPaint stroke = peStroke;
937 stroke.setPathEffect(nullptr);
bsalomonfb083272016-05-04 08:27:41 -0700938 TestCase rrectStrokeCase(RRectPathEffect::RRect(), stroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700939
940 SkRRect rrect;
941 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
942 // geoPECase, so the full style should be the same as just the PE.
bsalomon70493962016-06-10 08:05:14 -0700943 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
944 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -0700945 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
946 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
947
bsalomon70493962016-06-10 08:05:14 -0700948 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
949 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -0700950 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
951 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
952
953 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
bsalomon70493962016-06-10 08:05:14 -0700954 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
955 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -0700956 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
957 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
958
bsalomon70493962016-06-10 08:05:14 -0700959 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
960 nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -0700961 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
962 rrectStrokeCase.appliedFullStyleKey());
963}
964
965template <typename GEO>
966void test_unknown_path_effect(skiatest::Reporter* reporter, const GEO& geo) {
967 /**
968 * This path effect just adds two lineTos to the input path.
969 */
970 class AddLineTosPathEffect : SkPathEffect {
971 public:
972 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
973 const SkRect* cullR) const override {
974 *dst = src;
975 dst->lineTo(0, 0);
976 dst->lineTo(10, 10);
977 return true;
978 }
979 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
980 *dst = src;
981 dst->growToInclude(0, 0);
982 dst->growToInclude(10, 10);
983 }
984 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
985 Factory getFactory() const override { return nullptr; }
986 void toString(SkString*) const override {}
987 private:
988 AddLineTosPathEffect() {}
989 };
990
bsalomon9ad5d7c2016-05-04 08:44:15 -0700991 // This path effect should make the keys invalid when it is applied. We only produce a path
bsalomon72dc51c2016-04-27 06:46:23 -0700992 // effect key for dash path effects. So the only way another arbitrary path effect can produce
993 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
994 SkPaint peStroke;
995 peStroke.setPathEffect(AddLineTosPathEffect::Make());
996 peStroke.setStrokeWidth(2.f);
997 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -0700998 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -0700999 TestCase::SelfExpectations expectations;
1000 expectations.fPEHasEffect = true;
1001 expectations.fPEHasValidKey = false;
1002 expectations.fStrokeApplies = true;
1003 geoPEStrokeCase.testExpectations(reporter, expectations);
1004}
1005
bsalomon9ad5d7c2016-05-04 08:44:15 -07001006template <typename GEO>
1007void test_make_hairline_path_effect(skiatest::Reporter* reporter, const GEO& geo, bool isNonPath) {
1008 /**
1009 * This path effect just changes the stroke rec to hairline.
1010 */
1011 class MakeHairlinePathEffect : SkPathEffect {
1012 public:
1013 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1014 const SkRect* cullR) const override {
1015 *dst = src;
1016 strokeRec->setHairlineStyle();
1017 return true;
1018 }
bsalomon70493962016-06-10 08:05:14 -07001019 void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; }
bsalomon9ad5d7c2016-05-04 08:44:15 -07001020 static sk_sp<SkPathEffect> Make() {
1021 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1022 }
1023 Factory getFactory() const override { return nullptr; }
1024 void toString(SkString*) const override {}
1025 private:
1026 MakeHairlinePathEffect() {}
1027 };
1028
1029 SkPaint fill;
1030 SkPaint pe;
1031 pe.setPathEffect(MakeHairlinePathEffect::Make());
1032
1033 TestCase peCase(geo, pe, reporter);
1034
bsalomonee295642016-06-06 14:01:25 -07001035 SkPath a, b, c;
bsalomon9ad5d7c2016-05-04 08:44:15 -07001036 peCase.baseShape().asPath(&a);
1037 peCase.appliedPathEffectShape().asPath(&b);
bsalomonee295642016-06-06 14:01:25 -07001038 peCase.appliedFullStyleShape().asPath(&c);
bsalomon9ad5d7c2016-05-04 08:44:15 -07001039 if (isNonPath) {
bsalomonee295642016-06-06 14:01:25 -07001040 // RRect types can have a change in start index or direction after the PE is applied. This
1041 // is because once the PE is applied, GrShape may canonicalize the dir and index since it
1042 // is not germane to the styling any longer.
1043 // Instead we just check that the paths would fill the same both before and after styling.
1044 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1045 REPORTER_ASSERT(reporter, paths_fill_same(a, c));
bsalomon9ad5d7c2016-05-04 08:44:15 -07001046 } else {
bsalomona4817af2016-06-23 11:48:26 -07001047 // The base shape cannot perform canonicalization on the path's fill type because of an
1048 // unknown path effect. However, after the path effect is applied the resulting hairline
1049 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1050 // don't distinguish between even/odd and non-zero winding.
1051 a.setFillType(b.getFillType());
bsalomonee295642016-06-06 14:01:25 -07001052 REPORTER_ASSERT(reporter, a == b);
1053 REPORTER_ASSERT(reporter, a == c);
bsalomon9ad5d7c2016-05-04 08:44:15 -07001054 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1055 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
1056 }
bsalomonee295642016-06-06 14:01:25 -07001057 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1058 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
bsalomon9ad5d7c2016-05-04 08:44:15 -07001059}
1060
bsalomon4eeccc92016-04-27 13:30:25 -07001061/**
1062 * isNonPath indicates whether the initial shape made from the path is expected to be recognized
1063 * as a simpler shape type (e.g. rrect)
1064 */
1065void test_volatile_path(skiatest::Reporter* reporter, const SkPath& path,
1066 bool isNonPath) {
1067 SkPath vPath(path);
1068 vPath.setIsVolatile(true);
1069
1070 SkPaint dashAndStroke;
1071 dashAndStroke.setPathEffect(make_dash());
1072 dashAndStroke.setStrokeWidth(2.f);
1073 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001074 TestCase volatileCase(vPath, dashAndStroke, reporter);
bsalomon4eeccc92016-04-27 13:30:25 -07001075 // We expect a shape made from a volatile path to have a key iff the shape is recognized
1076 // as a specialized geometry.
1077 if (isNonPath) {
1078 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count()));
1079 // In this case all the keys should be identical to the non-volatile case.
bsalomonfb083272016-05-04 08:27:41 -07001080 TestCase nonVolatileCase(path, dashAndStroke, reporter);
bsalomon4eeccc92016-04-27 13:30:25 -07001081 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1082 } else {
1083 // None of the keys should be valid.
1084 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
1085 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
1086 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
1087 REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
1088 }
1089}
1090
bsalomon409ed732016-04-27 12:36:02 -07001091template <typename GEO>
1092void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const GEO& geo) {
1093 /**
1094 * This path effect returns an empty path.
1095 */
1096 class EmptyPathEffect : SkPathEffect {
1097 public:
1098 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1099 const SkRect* cullR) const override {
1100 dst->reset();
1101 return true;
1102 }
1103 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1104 dst->setEmpty();
1105 }
1106 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new EmptyPathEffect); }
1107 Factory getFactory() const override { return nullptr; }
1108 void toString(SkString*) const override {}
1109 private:
1110 EmptyPathEffect() {}
1111 };
1112
1113 SkPath emptyPath;
1114 GrShape emptyShape(emptyPath);
1115 Key emptyKey;
1116 make_key(&emptyKey, emptyShape);
bsalomon7c73a532016-05-11 15:15:56 -07001117 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001118
1119 SkPaint pe;
1120 pe.setPathEffect(EmptyPathEffect::Make());
bsalomonfb083272016-05-04 08:27:41 -07001121 TestCase geoCase(geo, pe, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001122 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleKey() == emptyKey);
1123 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectKey() == emptyKey);
1124 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001125 REPORTER_ASSERT(reporter, geoCase.appliedPathEffectShape().isEmpty());
1126 REPORTER_ASSERT(reporter, geoCase.appliedFullStyleShape().isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001127
1128 SkPaint peStroke;
1129 peStroke.setPathEffect(EmptyPathEffect::Make());
1130 peStroke.setStrokeWidth(2.f);
1131 peStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001132 TestCase geoPEStrokeCase(geo, peStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001133 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1134 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1135 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
bsalomon7c73a532016-05-11 15:15:56 -07001136 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1137 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001138}
1139
bsalomond6723842016-06-07 12:20:15 -07001140template <typename GEO>
1141void test_path_effect_fails(skiatest::Reporter* reporter, const GEO& geo) {
1142 /**
bsalomon0ae36a22016-07-18 07:31:13 -07001143 * This path effect always fails to apply.
bsalomond6723842016-06-07 12:20:15 -07001144 */
1145 class FailurePathEffect : SkPathEffect {
1146 public:
1147 bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1148 const SkRect* cullR) const override {
1149 return false;
1150 }
1151 void computeFastBounds(SkRect* dst, const SkRect& src) const override {
1152 *dst = src;
1153 }
1154 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1155 Factory getFactory() const override { return nullptr; }
1156 void toString(SkString*) const override {}
1157 private:
1158 FailurePathEffect() {}
1159 };
1160
1161 SkPaint fill;
1162 TestCase fillCase(geo, fill, reporter);
1163
1164 SkPaint pe;
1165 pe.setPathEffect(FailurePathEffect::Make());
1166 TestCase peCase(geo, pe, reporter);
1167
1168 SkPaint stroke;
1169 stroke.setStrokeWidth(2.f);
1170 stroke.setStyle(SkPaint::kStroke_Style);
1171 TestCase strokeCase(geo, stroke, reporter);
1172
1173 SkPaint peStroke = stroke;
1174 peStroke.setPathEffect(FailurePathEffect::Make());
1175 TestCase peStrokeCase(geo, peStroke, reporter);
1176
1177 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1178 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1179 // path effect, but then when the path effect fails we can key it. 2) GrShape will change its
1180 // mind about whether a unclosed rect is actually rect. The path effect initially bars us from
1181 // closing it but after the effect fails we can (for the fill+pe case). This causes different
1182 // routes through GrShape to have equivalent but different representations of the path (closed
1183 // or not) but that fill the same.
1184 SkPath a;
1185 SkPath b;
1186 fillCase.appliedPathEffectShape().asPath(&a);
1187 peCase.appliedPathEffectShape().asPath(&b);
1188 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1189
1190 fillCase.appliedFullStyleShape().asPath(&a);
1191 peCase.appliedFullStyleShape().asPath(&b);
1192 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1193
1194 strokeCase.appliedPathEffectShape().asPath(&a);
1195 peStrokeCase.appliedPathEffectShape().asPath(&b);
1196 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1197
1198 strokeCase.appliedFullStyleShape().asPath(&a);
1199 peStrokeCase.appliedFullStyleShape().asPath(&b);
1200 REPORTER_ASSERT(reporter, paths_fill_same(a, b));
1201}
1202
bsalomon409ed732016-04-27 12:36:02 -07001203void test_empty_shape(skiatest::Reporter* reporter) {
1204 SkPath emptyPath;
1205 SkPaint fill;
bsalomonfb083272016-05-04 08:27:41 -07001206 TestCase fillEmptyCase(emptyPath, fill, reporter);
bsalomon7c73a532016-05-11 15:15:56 -07001207 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1208 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1209 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
bsalomon409ed732016-04-27 12:36:02 -07001210
1211 Key emptyKey(fillEmptyCase.baseKey());
1212 REPORTER_ASSERT(reporter, emptyKey.count());
1213 TestCase::SelfExpectations expectations;
1214 expectations.fStrokeApplies = false;
1215 expectations.fPEHasEffect = false;
1216 // This will test whether applying style preserves emptiness
1217 fillEmptyCase.testExpectations(reporter, expectations);
1218
1219 // Stroking an empty path should have no effect
1220 SkPath emptyPath2;
1221 SkPaint stroke;
1222 stroke.setStrokeWidth(2.f);
1223 stroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001224 TestCase strokeEmptyCase(emptyPath2, stroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001225 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1226
1227 // Dashing and stroking an empty path should have no effect
1228 SkPath emptyPath3;
1229 SkPaint dashAndStroke;
1230 dashAndStroke.setPathEffect(make_dash());
1231 dashAndStroke.setStrokeWidth(2.f);
1232 dashAndStroke.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001233 TestCase dashAndStrokeEmptyCase(emptyPath3, dashAndStroke, reporter);
bsalomon409ed732016-04-27 12:36:02 -07001234 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1235 TestCase::kAllSame_ComparisonExpecation);
bsalomon5e410b42016-04-28 09:30:46 -07001236
1237 // A shape made from an empty rrect should behave the same as an empty path.
1238 SkRRect emptyRRect = SkRRect::MakeRect(SkRect::MakeEmpty());
1239 REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
bsalomonfb083272016-05-04 08:27:41 -07001240 TestCase dashAndStrokeEmptyRRectCase(emptyRRect, dashAndStroke, reporter);
bsalomon5e410b42016-04-28 09:30:46 -07001241 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1242 TestCase::kAllSame_ComparisonExpecation);
1243
1244 // Same for a rect.
1245 SkRect emptyRect = SkRect::MakeEmpty();
bsalomonfb083272016-05-04 08:27:41 -07001246 TestCase dashAndStrokeEmptyRectCase(emptyRect, dashAndStroke, reporter);
bsalomon5e410b42016-04-28 09:30:46 -07001247 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1248 TestCase::kAllSame_ComparisonExpecation);
bsalomon409ed732016-04-27 12:36:02 -07001249}
1250
bsalomon70493962016-06-10 08:05:14 -07001251// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1252// canonical point in these cases.
1253unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1254 switch (rrect.getType()) {
1255 case SkRRect::kRect_Type:
1256 return (s + 1) & 0b110;
1257 case SkRRect::kOval_Type:
1258 return s & 0b110;
1259 default:
1260 return s;
1261 }
1262}
1263
1264void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
bsalomoncadb5a22016-06-10 18:28:06 -07001265 enum Style {
bsalomon70493962016-06-10 08:05:14 -07001266 kFill,
1267 kStroke,
1268 kHairline,
1269 kStrokeAndFill
1270 };
1271
1272 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1273 SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle,
1274 SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle};
1275 strokeRecs[kFill].setFillStyle();
1276 strokeRecs[kStroke].setStrokeStyle(2.f);
1277 strokeRecs[kHairline].setHairlineStyle();
1278 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
bsalomon487f8d32016-07-20 07:15:44 -07001279 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1280 // applyStyle() is called.
1281 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
bsalomon70493962016-06-10 08:05:14 -07001282 sk_sp<SkPathEffect> dashEffect = make_dash();
1283
bsalomoncadb5a22016-06-10 18:28:06 -07001284 static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
1285
1286 auto index = [](bool inverted,
1287 SkPath::Direction dir,
1288 unsigned start,
1289 Style style,
1290 bool dash) -> int {
1291 return inverted * (2 * 8 * kStyleCnt * 2) +
1292 dir * ( 8 * kStyleCnt * 2) +
1293 start * ( kStyleCnt * 2) +
1294 style * ( 2) +
1295 dash;
1296 };
1297 static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1);
1298 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1299 SkAutoTArray<GrShape> shapes(cnt);
1300 for (bool inverted : {false, true}) {
1301 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1302 for (unsigned start = 0; start < 8; ++start) {
1303 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1304 for (bool dash : {false, true}) {
bsalomon70493962016-06-10 08:05:14 -07001305 SkPathEffect* pe = dash ? dashEffect.get() : nullptr;
bsalomoncadb5a22016-06-10 18:28:06 -07001306 shapes[index(inverted, dir, start, style, dash)] =
1307 GrShape(rrect, dir, start, SkToBool(inverted),
bsalomon70493962016-06-10 08:05:14 -07001308 GrStyle(strokeRecs[style], pe));
1309 }
1310 }
1311 }
1312 }
1313 }
1314
bsalomonfd32df72016-06-14 14:37:21 -07001315 // Get the keys for some example shape instances that we'll use for comparision against the
1316 // rest.
1317 static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction;
1318 static constexpr unsigned kExamplesStart = 0;
1319 const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1320 false)];
bsalomon70493962016-06-10 08:05:14 -07001321 Key exampleFillCaseKey;
1322 make_key(&exampleFillCaseKey, exampleFillCase);
1323
bsalomonfd32df72016-06-14 14:37:21 -07001324 const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart,
1325 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001326 Key exampleStrokeAndFillCaseKey;
1327 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1328
bsalomonfd32df72016-06-14 14:37:21 -07001329 const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill,
1330 false)];
bsalomon70493962016-06-10 08:05:14 -07001331 Key exampleInvFillCaseKey;
1332 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1333
bsalomonfd32df72016-06-14 14:37:21 -07001334 const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart,
1335 kStrokeAndFill, false)];
bsalomon70493962016-06-10 08:05:14 -07001336 Key exampleInvStrokeAndFillCaseKey;
1337 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1338
bsalomonfd32df72016-06-14 14:37:21 -07001339 const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke,
1340 false)];
bsalomon70493962016-06-10 08:05:14 -07001341 Key exampleStrokeCaseKey;
1342 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1343
bsalomonfd32df72016-06-14 14:37:21 -07001344 const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke,
1345 false)];
1346 Key exampleInvStrokeCaseKey;
1347 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1348
1349 const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1350 kHairline, false)];
bsalomon70493962016-06-10 08:05:14 -07001351 Key exampleHairlineCaseKey;
1352 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1353
bsalomonfd32df72016-06-14 14:37:21 -07001354 const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1355 kHairline, false)];
1356 Key exampleInvHairlineCaseKey;
1357 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1358
bsalomon70493962016-06-10 08:05:14 -07001359 // These are dummy initializations to suppress warnings.
bsalomoncadb5a22016-06-10 18:28:06 -07001360 SkRRect queryRR = SkRRect::MakeEmpty();
1361 SkPath::Direction queryDir = SkPath::kCW_Direction;
1362 unsigned queryStart = ~0U;
1363 bool queryInverted = true;
bsalomon70493962016-06-10 08:05:14 -07001364
bsalomoncadb5a22016-06-10 18:28:06 -07001365 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1366 REPORTER_ASSERT(r, queryRR == rrect);
1367 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1368 REPORTER_ASSERT(r, 0 == queryStart);
1369 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001370
bsalomoncadb5a22016-06-10 18:28:06 -07001371 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1372 &queryInverted));
1373 REPORTER_ASSERT(r, queryRR == rrect);
1374 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1375 REPORTER_ASSERT(r, 0 == queryStart);
1376 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001377
bsalomoncadb5a22016-06-10 18:28:06 -07001378 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1379 &queryInverted));
1380 REPORTER_ASSERT(r, queryRR == rrect);
1381 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1382 REPORTER_ASSERT(r, 0 == queryStart);
1383 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001384
bsalomoncadb5a22016-06-10 18:28:06 -07001385 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1386 &queryInverted));
1387 REPORTER_ASSERT(r, queryRR == rrect);
1388 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1389 REPORTER_ASSERT(r, 0 == queryStart);
1390 REPORTER_ASSERT(r, queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001391
bsalomoncadb5a22016-06-10 18:28:06 -07001392 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1393 &queryInverted));
1394 REPORTER_ASSERT(r, queryRR == rrect);
1395 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1396 REPORTER_ASSERT(r, 0 == queryStart);
1397 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001398
bsalomonfd32df72016-06-14 14:37:21 -07001399 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1400 &queryInverted));
1401 REPORTER_ASSERT(r, queryRR == rrect);
1402 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1403 REPORTER_ASSERT(r, 0 == queryStart);
1404 REPORTER_ASSERT(r, queryInverted);
1405
bsalomoncadb5a22016-06-10 18:28:06 -07001406 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1407 REPORTER_ASSERT(r, queryRR == rrect);
1408 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1409 REPORTER_ASSERT(r, 0 == queryStart);
1410 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001411
bsalomonfd32df72016-06-14 14:37:21 -07001412 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1413 &queryInverted));
1414 REPORTER_ASSERT(r, queryRR == rrect);
1415 REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
1416 REPORTER_ASSERT(r, 0 == queryStart);
1417 REPORTER_ASSERT(r, queryInverted);
1418
bsalomon70493962016-06-10 08:05:14 -07001419 // Remember that the key reflects the geometry before styling is applied.
1420 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1421 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1422 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1423 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001424 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001425 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001426 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001427 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
bsalomonfd32df72016-06-14 14:37:21 -07001428 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1429 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
bsalomon70493962016-06-10 08:05:14 -07001430
bsalomoncadb5a22016-06-10 18:28:06 -07001431 for (bool inverted : {false, true}) {
1432 for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
1433 for (unsigned start = 0; start < 8; ++start) {
1434 for (bool dash : {false, true}) {
1435 const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001436 Key fillCaseKey;
1437 make_key(&fillCaseKey, fillCase);
1438
bsalomoncadb5a22016-06-10 18:28:06 -07001439 const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1440 kStrokeAndFill, dash)];
bsalomon70493962016-06-10 08:05:14 -07001441 Key strokeAndFillCaseKey;
1442 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1443
1444 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1445 // ignore dashing.
1446 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1447 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1448 TestCase a(fillCase, r);
1449 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1450 TestCase c(strokeAndFillCase, r);
1451 TestCase d(inverted ? exampleInvStrokeAndFillCase
1452 : exampleStrokeAndFillCase, r);
1453 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1454 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1455
bsalomoncadb5a22016-06-10 18:28:06 -07001456 const GrShape& strokeCase = shapes[index(inverted, dir, start, kStroke, dash)];
1457 const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline,
1458 dash)];
bsalomon70493962016-06-10 08:05:14 -07001459
1460 TestCase e(strokeCase, r);
bsalomon70493962016-06-10 08:05:14 -07001461 TestCase g(hairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001462
bsalomonfd32df72016-06-14 14:37:21 -07001463 // Both hairline and stroke shapes must respect the dashing.
bsalomon70493962016-06-10 08:05:14 -07001464 if (dash) {
bsalomonfd32df72016-06-14 14:37:21 -07001465 // Dashing always ignores the inverseness. skbug.com/5421
1466 TestCase f(exampleStrokeCase, r);
1467 TestCase h(exampleHairlineCase, r);
bsalomoncadb5a22016-06-10 18:28:06 -07001468 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
bsalomon70493962016-06-10 08:05:14 -07001469 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1470 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1471
bsalomoncadb5a22016-06-10 18:28:06 -07001472 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1473 &queryInverted));
1474 REPORTER_ASSERT(r, queryRR == rrect);
1475 REPORTER_ASSERT(r, queryDir == dir);
1476 REPORTER_ASSERT(r, queryStart == expectedStart);
1477 REPORTER_ASSERT(r, !queryInverted);
1478 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1479 &queryInverted));
1480 REPORTER_ASSERT(r, queryRR == rrect);
1481 REPORTER_ASSERT(r, queryDir == dir);
1482 REPORTER_ASSERT(r, queryStart == expectedStart);
1483 REPORTER_ASSERT(r, !queryInverted);
bsalomon70493962016-06-10 08:05:14 -07001484
1485 // The pre-style case for the dash will match the non-dash example iff the
1486 // dir and start match (dir=cw, start=0).
bsalomoncadb5a22016-06-10 18:28:06 -07001487 if (0 == expectedStart && SkPath::kCW_Direction == dir) {
bsalomon70493962016-06-10 08:05:14 -07001488 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1489 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1490 } else {
1491 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1492 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1493 }
1494 } else {
bsalomonfd32df72016-06-14 14:37:21 -07001495 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1496 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
bsalomon70493962016-06-10 08:05:14 -07001497 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1498 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1499 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1500 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1501 }
1502 }
1503 }
1504 }
1505 }
1506}
1507
bsalomon0a0f67e2016-06-28 11:56:42 -07001508void test_lines(skiatest::Reporter* r) {
1509 static constexpr SkPoint kA { 1, 1};
1510 static constexpr SkPoint kB { 5, -9};
1511 static constexpr SkPoint kC {-3, 17};
1512
1513 SkPath lineAB;
1514 lineAB.moveTo(kA);
1515 lineAB.lineTo(kB);
1516
1517 SkPath lineBA;
1518 lineBA.moveTo(kB);
1519 lineBA.lineTo(kA);
1520
1521 SkPath lineAC;
1522 lineAC.moveTo(kB);
1523 lineAC.lineTo(kC);
1524
1525 SkPath invLineAB = lineAB;
1526 invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType);
1527
1528 SkPaint fill;
1529 SkPaint stroke;
1530 stroke.setStyle(SkPaint::kStroke_Style);
1531 stroke.setStrokeWidth(2.f);
1532 SkPaint hairline;
1533 hairline.setStyle(SkPaint::kStroke_Style);
1534 hairline.setStrokeWidth(0.f);
1535 SkPaint dash = stroke;
1536 dash.setPathEffect(make_dash());
1537
1538 TestCase fillAB(lineAB, fill, r);
1539 TestCase fillEmpty(SkPath(), fill, r);
1540 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1541 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1542
1543 TestCase strokeAB(lineAB, stroke, r);
1544 TestCase strokeBA(lineBA, stroke, r);
1545 TestCase strokeAC(lineAC, stroke, r);
1546
1547 TestCase hairlineAB(lineAB, hairline, r);
1548 TestCase hairlineBA(lineBA, hairline, r);
1549 TestCase hairlineAC(lineAC, hairline, r);
1550
1551 TestCase dashAB(lineAB, dash, r);
1552 TestCase dashBA(lineBA, dash, r);
1553 TestCase dashAC(lineAC, dash, r);
1554
1555 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1556
1557 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1558 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1559
1560 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1561 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1562
1563 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1564 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1565
1566 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1567
1568 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1569 // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1570 bool canonicalizeAsAB;
1571 SkPoint canonicalPts[2] {kA, kB};
1572 // Init these to suppress warnings.
1573 bool inverted = true;
1574 SkPoint pts[2] {{0, 0}, {0, 0}};
1575 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1576 if (pts[0] == kA && pts[1] == kB) {
1577 canonicalizeAsAB = true;
1578 } else if (pts[1] == kA && pts[0] == kB) {
1579 canonicalizeAsAB = false;
1580 SkTSwap(canonicalPts[0], canonicalPts[1]);
1581 } else {
1582 ERRORF(r, "Should return pts (a,b) or (b, a)");
1583 return;
1584 };
1585
1586 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1587 TestCase::kSameUpToPE_ComparisonExpecation);
1588 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
1589 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1590 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
1591 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1592 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
1593 pts[0] == kA && pts[1] == kB);
1594 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
1595 pts[0] == kB && pts[1] == kA);
1596
1597
1598 TestCase strokeInvAB(invLineAB, stroke, r);
1599 TestCase hairlineInvAB(invLineAB, hairline, r);
1600 TestCase dashInvAB(invLineAB, dash, r);
1601 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
1602 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
1603 // Dashing ignores inverse.
1604 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
1605
1606 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1607 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1608 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
1609 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
1610 // Dashing ignores inverse.
1611 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
1612 pts[0] == kA && pts[1] == kB);
1613
1614}
1615
bsalomon0ae36a22016-07-18 07:31:13 -07001616static void test_stroked_lines(skiatest::Reporter* r) {
1617 // Paints to try
1618 SkPaint buttCap;
1619 buttCap.setStyle(SkPaint::kStroke_Style);
1620 buttCap.setStrokeWidth(4);
1621 buttCap.setStrokeCap(SkPaint::kButt_Cap);
1622
1623 SkPaint squareCap = buttCap;
1624 squareCap.setStrokeCap(SkPaint::kSquare_Cap);
1625
1626 SkPaint roundCap = buttCap;
1627 roundCap.setStrokeCap(SkPaint::kRound_Cap);
1628
1629 // vertical
1630 SkPath linePath;
1631 linePath.moveTo(4, 4);
1632 linePath.lineTo(4, 5);
1633
1634 SkPaint fill;
1635
1636 TestCase(linePath, buttCap, r).compare(r, TestCase(SkRect::MakeLTRB(2, 4, 6, 5), fill, r),
1637 TestCase::kAllSame_ComparisonExpecation);
1638
1639 TestCase(linePath, squareCap, r).compare(r, TestCase(SkRect::MakeLTRB(2, 2, 6, 7), fill, r),
1640 TestCase::kAllSame_ComparisonExpecation);
1641
1642 TestCase(linePath, roundCap, r).compare(r,
1643 TestCase(SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill, r),
1644 TestCase::kAllSame_ComparisonExpecation);
1645
1646 // horizontal
1647 linePath.reset();
1648 linePath.moveTo(4, 4);
1649 linePath.lineTo(5, 4);
1650
1651 TestCase(linePath, buttCap, r).compare(r, TestCase(SkRect::MakeLTRB(4, 2, 5, 6), fill, r),
1652 TestCase::kAllSame_ComparisonExpecation);
1653 TestCase(linePath, squareCap, r).compare(r, TestCase(SkRect::MakeLTRB(2, 2, 7, 6), fill, r),
1654 TestCase::kAllSame_ComparisonExpecation);
1655 TestCase(linePath, roundCap, r).compare(r,
1656 TestCase(SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill, r),
1657 TestCase::kAllSame_ComparisonExpecation);
1658
1659 // point
1660 linePath.reset();
1661 linePath.moveTo(4, 4);
1662 linePath.lineTo(4, 4);
1663
1664 TestCase(linePath, buttCap, r).compare(r, TestCase(SkRect::MakeEmpty(), fill, r),
1665 TestCase::kAllSame_ComparisonExpecation);
1666 TestCase(linePath, squareCap, r).compare(r, TestCase(SkRect::MakeLTRB(2, 2, 6, 6), fill, r),
1667 TestCase::kAllSame_ComparisonExpecation);
1668 TestCase(linePath, roundCap, r).compare(r,
1669 TestCase(SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill, r),
1670 TestCase::kAllSame_ComparisonExpecation);
1671}
1672
bsalomon47cc7692016-04-26 12:56:00 -07001673DEF_TEST(GrShape, reporter) {
bsalomonee295642016-06-06 14:01:25 -07001674 for (auto r : { SkRect::MakeWH(10, 20),
1675 SkRect::MakeWH(-10, -20),
1676 SkRect::MakeWH(-10, 20),
1677 SkRect::MakeWH(10, -20)}) {
1678 test_basic(reporter, r);
1679 test_scale(reporter, r);
1680 test_dash_fill(reporter, r);
1681 test_null_dash(reporter, r);
1682 // Test modifying various stroke params.
1683 test_stroke_param<SkRect, SkScalar>(
1684 reporter, r,
1685 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
1686 SkIntToScalar(2), SkIntToScalar(4));
bsalomon0ae36a22016-07-18 07:31:13 -07001687 test_stroke_join(reporter, r);
bsalomonee295642016-06-06 14:01:25 -07001688 test_stroke_cap(reporter, r);
1689 test_miter_limit(reporter, r);
1690 test_path_effect_makes_rrect(reporter, r);
1691 test_unknown_path_effect(reporter, r);
1692 test_path_effect_makes_empty_shape(reporter, r);
bsalomond6723842016-06-07 12:20:15 -07001693 test_path_effect_fails(reporter, r);
bsalomonee295642016-06-06 14:01:25 -07001694 test_make_hairline_path_effect(reporter, r, true);
bsalomon398e3f42016-06-13 10:22:48 -07001695 GrShape shape(r);
bsalomon0a0f67e2016-06-28 11:56:42 -07001696 REPORTER_ASSERT(reporter, !shape.asLine(nullptr, nullptr));
bsalomonee295642016-06-06 14:01:25 -07001697 }
bsalomon47cc7692016-04-26 12:56:00 -07001698
1699 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
bsalomonee295642016-06-06 14:01:25 -07001700 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
1701 SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
bsalomon47cc7692016-04-26 12:56:00 -07001702 test_basic(reporter, rr);
bsalomon70493962016-06-10 08:05:14 -07001703 test_rrect(reporter, rr);
bsalomon97fd2d42016-05-09 13:02:01 -07001704 test_scale(reporter, rr);
bsalomon47cc7692016-04-26 12:56:00 -07001705 test_dash_fill(reporter, rr);
1706 test_null_dash(reporter, rr);
1707 // Test modifying various stroke params.
bsalomon72dc51c2016-04-27 06:46:23 -07001708 test_stroke_param<SkRRect, SkScalar>(
bsalomon47cc7692016-04-26 12:56:00 -07001709 reporter, rr,
1710 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
1711 SkIntToScalar(2), SkIntToScalar(4));
bsalomon0ae36a22016-07-18 07:31:13 -07001712 test_stroke_join(reporter, rr);
bsalomon06077562016-05-04 13:50:29 -07001713 test_stroke_cap(reporter, rr);
bsalomon47cc7692016-04-26 12:56:00 -07001714 test_miter_limit(reporter, rr);
bsalomon72dc51c2016-04-27 06:46:23 -07001715 test_path_effect_makes_rrect(reporter, rr);
1716 test_unknown_path_effect(reporter, rr);
bsalomon409ed732016-04-27 12:36:02 -07001717 test_path_effect_makes_empty_shape(reporter, rr);
bsalomond6723842016-06-07 12:20:15 -07001718 test_path_effect_fails(reporter, rr);
bsalomon9ad5d7c2016-05-04 08:44:15 -07001719 test_make_hairline_path_effect(reporter, rr, true);
bsalomon398e3f42016-06-13 10:22:48 -07001720 GrShape shape(rr);
bsalomon0a0f67e2016-06-28 11:56:42 -07001721 REPORTER_ASSERT(reporter, !shape.asLine(nullptr, nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001722 }
1723
1724 struct TestPath {
bsalomon93f66bc2016-06-21 08:35:49 -07001725 TestPath(const SkPath& path, bool isRRectFill, bool isRRectStroke, bool isLine,
1726 const SkRRect& rrect)
bsalomon72dc51c2016-04-27 06:46:23 -07001727 : fPath(path)
1728 , fIsRRectForFill(isRRectFill)
1729 , fIsRRectForStroke(isRRectStroke)
bsalomon398e3f42016-06-13 10:22:48 -07001730 , fIsLine(isLine)
bsalomon72dc51c2016-04-27 06:46:23 -07001731 , fRRect(rrect) {}
1732 SkPath fPath;
1733 bool fIsRRectForFill;
1734 bool fIsRRectForStroke;
bsalomon398e3f42016-06-13 10:22:48 -07001735 bool fIsLine;
bsalomon72dc51c2016-04-27 06:46:23 -07001736 SkRRect fRRect;
1737 };
1738 SkTArray<TestPath> paths;
1739
1740 SkPath circlePath;
1741 circlePath.addCircle(10, 10, 10);
bsalomon398e3f42016-06-13 10:22:48 -07001742 paths.emplace_back(circlePath, true, true, false, SkRRect::MakeOval(SkRect::MakeWH(20,20)));
bsalomon72dc51c2016-04-27 06:46:23 -07001743
1744 SkPath rectPath;
1745 rectPath.addRect(SkRect::MakeWH(10, 10));
bsalomon398e3f42016-06-13 10:22:48 -07001746 paths.emplace_back(rectPath, true, true, false, SkRRect::MakeRect(SkRect::MakeWH(10, 10)));
bsalomon72dc51c2016-04-27 06:46:23 -07001747
1748 SkPath openRectPath;
1749 openRectPath.moveTo(0, 0);
1750 openRectPath.lineTo(10, 0);
1751 openRectPath.lineTo(10, 10);
1752 openRectPath.lineTo(0, 10);
bsalomon398e3f42016-06-13 10:22:48 -07001753 paths.emplace_back(openRectPath, true, false, false, SkRRect::MakeRect(SkRect::MakeWH(10, 10)));
bsalomon72dc51c2016-04-27 06:46:23 -07001754
1755 SkPath quadPath;
1756 quadPath.quadTo(10, 10, 5, 8);
bsalomon398e3f42016-06-13 10:22:48 -07001757 paths.emplace_back(quadPath, false, false, false, SkRRect());
1758
1759 SkPath linePath;
1760 linePath.lineTo(10, 10);
1761 paths.emplace_back(linePath, false, false, true, SkRRect());
bsalomon72dc51c2016-04-27 06:46:23 -07001762
bsalomon0ae36a22016-07-18 07:31:13 -07001763 // Horizontal and vertical paths become rrects when stroked.
1764 SkPath vLinePath;
1765 vLinePath.lineTo(0, 10);
1766 paths.emplace_back(vLinePath, false, false, true, SkRRect());
1767
1768 SkPath hLinePath;
1769 hLinePath.lineTo(10, 0);
1770 paths.emplace_back(hLinePath, false, false, true, SkRRect());
1771
bsalomon72dc51c2016-04-27 06:46:23 -07001772 for (auto testPath : paths) {
bsalomon70493962016-06-10 08:05:14 -07001773 for (bool inverseFill : {false, true}) {
1774 if (inverseFill) {
1775 if (testPath.fPath.getFillType() == SkPath::kEvenOdd_FillType) {
1776 testPath.fPath.setFillType(SkPath::kInverseEvenOdd_FillType);
1777 } else {
1778 SkASSERT(testPath.fPath.getFillType() == SkPath::kWinding_FillType);
1779 testPath.fPath.setFillType(SkPath::kInverseWinding_FillType);
1780 }
1781 }
1782 const SkPath& path = testPath.fPath;
bsalomon487f8d32016-07-20 07:15:44 -07001783 test_basic(reporter, path);
1784 test_null_dash(reporter, path);
1785 test_path_effect_makes_rrect(reporter, path);
bsalomon70493962016-06-10 08:05:14 -07001786 test_scale(reporter, path);
1787 // This test uses a stroking paint, hence use of fIsRRectForStroke
bsalomon0a0f67e2016-06-28 11:56:42 -07001788 test_volatile_path(reporter, path, testPath.fIsRRectForStroke || testPath.fIsLine);
bsalomon70493962016-06-10 08:05:14 -07001789 test_dash_fill(reporter, path);
1790 // Test modifying various stroke params.
1791 test_stroke_param<SkPath, SkScalar>(
1792 reporter, path,
1793 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
1794 SkIntToScalar(2), SkIntToScalar(4));
bsalomon0ae36a22016-07-18 07:31:13 -07001795 test_stroke_join(reporter, path);
bsalomon70493962016-06-10 08:05:14 -07001796 test_stroke_cap(reporter, path);
1797 test_miter_limit(reporter, path);
1798 test_unknown_path_effect(reporter, path);
1799 test_path_effect_makes_empty_shape(reporter, path);
1800 test_path_effect_fails(reporter, path);
bsalomon0a0f67e2016-06-28 11:56:42 -07001801 test_make_hairline_path_effect(reporter, path, testPath.fIsRRectForStroke ||
1802 testPath.fIsLine);
bsalomon72dc51c2016-04-27 06:46:23 -07001803 }
bsalomon70493962016-06-10 08:05:14 -07001804 }
bsalomonfd32df72016-06-14 14:37:21 -07001805
bsalomon70493962016-06-10 08:05:14 -07001806 for (auto testPath : paths) {
1807 const SkPath& path = testPath.fPath;
bsalomon72dc51c2016-04-27 06:46:23 -07001808
1809 SkPaint fillPaint;
bsalomonfb083272016-05-04 08:27:41 -07001810 TestCase fillPathCase(path, fillPaint, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001811 SkRRect rrect;
1812 REPORTER_ASSERT(reporter, testPath.fIsRRectForFill ==
bsalomon70493962016-06-10 08:05:14 -07001813 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
1814 nullptr));
bsalomon72dc51c2016-04-27 06:46:23 -07001815 if (testPath.fIsRRectForFill) {
bsalomon70493962016-06-10 08:05:14 -07001816 TestCase fillPathCase2(testPath.fPath, fillPaint, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001817 REPORTER_ASSERT(reporter, rrect == testPath.fRRect);
bsalomonfb083272016-05-04 08:27:41 -07001818 TestCase fillRRectCase(rrect, fillPaint, reporter);
bsalomon70493962016-06-10 08:05:14 -07001819 fillPathCase2.compare(reporter, fillRRectCase,
1820 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07001821 }
bsalomon72dc51c2016-04-27 06:46:23 -07001822 SkPaint strokePaint;
1823 strokePaint.setStrokeWidth(3.f);
1824 strokePaint.setStyle(SkPaint::kStroke_Style);
bsalomonfb083272016-05-04 08:27:41 -07001825 TestCase strokePathCase(path, strokePaint, reporter);
bsalomon0ae36a22016-07-18 07:31:13 -07001826 if (testPath.fIsRRectForStroke) {
1827 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
1828 nullptr));
1829 }
1830
bsalomon72dc51c2016-04-27 06:46:23 -07001831 if (testPath.fIsRRectForStroke) {
1832 REPORTER_ASSERT(reporter, rrect == testPath.fRRect);
bsalomonfb083272016-05-04 08:27:41 -07001833 TestCase strokeRRectCase(rrect, strokePaint, reporter);
bsalomon72dc51c2016-04-27 06:46:23 -07001834 strokePathCase.compare(reporter, strokeRRectCase,
bsalomonee295642016-06-06 14:01:25 -07001835 TestCase::kAllSame_ComparisonExpecation);
bsalomon72dc51c2016-04-27 06:46:23 -07001836 }
bsalomon47cc7692016-04-26 12:56:00 -07001837 }
bsalomon409ed732016-04-27 12:36:02 -07001838
bsalomon4eeccc92016-04-27 13:30:25 -07001839 // Test a volatile empty path.
1840 test_volatile_path(reporter, SkPath(), true);
1841
bsalomon409ed732016-04-27 12:36:02 -07001842 test_empty_shape(reporter);
bsalomon0a0f67e2016-06-28 11:56:42 -07001843
1844 test_lines(reporter);
bsalomon0ae36a22016-07-18 07:31:13 -07001845
1846 test_stroked_lines(reporter);
bsalomon47cc7692016-04-26 12:56:00 -07001847}
1848
1849#endif