Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
GrStyledShapeTest.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
14#include "include/core/SkPath.h"
20#include "include/core/SkRect.h"
32#include "src/core/SkPathPriv.h"
33#include "src/core/SkRectPriv.h"
37#include "tests/Test.h"
38
39#include <cstdint>
40#include <cstring>
41#include <functional>
42#include <initializer_list>
43#include <memory>
44#include <string>
45#include <utility>
46
47using namespace skia_private;
48
50 if (const auto* lp = this->originalPathForListeners()) {
51 return lp->getGenerationID();
52 }
53 return SkPath().getGenerationID();
54}
55
57 return fShape.isPath();
58}
59
61 return fShape.isPath() && !fShape.path().isVolatile();
62}
63
65
66static bool make_key(Key* key, const GrStyledShape& shape) {
67 int size = shape.unstyledKeySize();
68 if (size <= 0) {
69 key->reset(0);
70 return false;
71 }
72 SkASSERT(size);
73 key->reset(size);
74 shape.writeUnstyledKey(key->begin());
75 return true;
76}
77
78static bool paths_fill_same(const SkPath& a, const SkPath& b) {
79 SkPath pathXor;
80 Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
81 return pathXor.isEmpty();
82}
83
84static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
85 // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
86 // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
87 // rendering within the bounds (with a tolerance). Then we render the path and check that
88 // everything got clipped out.
89 static constexpr int kRes = 2000;
90 // This tolerance is in units of 1/kRes fractions of the bounds width/height.
91 static constexpr int kTol = 2;
92 static_assert(kRes % 4 == 0);
95 surface->getCanvas()->clear(0x0);
96 SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
97 SkMatrix matrix = SkMatrix::RectToRect(bounds, clip);
98 clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
99 surface->getCanvas()->clipRect(clip, SkClipOp::kDifference);
100 surface->getCanvas()->concat(matrix);
101 SkPaint whitePaint;
102 whitePaint.setColor(SK_ColorWHITE);
103 surface->getCanvas()->drawPath(path, whitePaint);
104 SkPixmap pixmap;
105 surface->getCanvas()->peekPixels(&pixmap);
106#if defined(SK_BUILD_FOR_WIN)
107 // The static constexpr version in #else causes cl.exe to crash.
108 const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
109#else
110 static constexpr uint8_t kZeros[kRes] = {0};
111#endif
112 for (int y = 0; y < kRes; ++y) {
113 const uint8_t* row = pixmap.addr8(0, y);
114 if (0 != memcmp(kZeros, row, kRes)) {
115 return false;
116 }
117 }
118#ifdef SK_BUILD_FOR_WIN
119 free(const_cast<uint8_t*>(kZeros));
120#endif
121 return true;
122}
123
125 SkPath path;
126 shape.asPath(&path);
127 if (shape.style().hasNonDashPathEffect()) {
128 return false;
129 }
130 const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
131 return strokeRecStyle == SkStrokeRec::kStroke_Style ||
132 strokeRecStyle == SkStrokeRec::kHairline_Style ||
133 (shape.style().isSimpleFill() && path.isConvex());
134}
135
137 const Key& keyA, const Key& keyB) {
138 // GrStyledShape only respects the input winding direction and start point for rrect shapes
139 // when there is a path effect. Thus, if there are two GrStyledShapes representing the same
140 // rrect but one has a path effect in its style and the other doesn't then asPath() and the
141 // unstyled key will differ. GrStyledShape will have canonicalized the direction and start point
142 // for the shape without the path effect. If *both* have path effects then they should have both
143 // preserved the direction and starting point.
144
145 // The asRRect() output params are all initialized just to silence compiler warnings about
146 // uninitialized variables.
147 SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
149 unsigned startA = ~0U, startB = ~0U;
150 bool invertedA = true, invertedB = true;
151
152 bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA);
153 bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB);
154 bool aHasPE = a.style().hasPathEffect();
155 bool bHasPE = b.style().hasPathEffect();
156 bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
157 // GrStyledShape will close paths with simple fill style.
158 bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
159 SkPath pathA, pathB;
160 a.asPath(&pathA);
161 b.asPath(&pathB);
162
163 // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
164 // non-inverse fill type (or vice versa).
165 bool ignoreInversenessDifference = false;
166 if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
167 const GrStyledShape* s1 = pathA.isInverseFillType() ? &a : &b;
168 const GrStyledShape* s2 = pathA.isInverseFillType() ? &b : &a;
169 bool canDropInverse1 = s1->style().isDashed();
170 bool canDropInverse2 = s2->style().isDashed();
171 ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
172 }
173 bool ignoreWindingVsEvenOdd = false;
178 if (aCanChange != bCanChange) {
179 ignoreWindingVsEvenOdd = true;
180 }
181 }
182 if (allowSameRRectButDiffStartAndDir) {
183 REPORTER_ASSERT(r, rrectA == rrectB);
184 REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
185 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
186 } else {
187 SkPath pA = pathA;
188 SkPath pB = pathB;
189 REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
190 REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
191 if (ignoreInversenessDifference) {
194 }
195 if (ignoreWindingVsEvenOdd) {
200 }
201 if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
202 REPORTER_ASSERT(r, keyA == keyB);
203 } else {
204 REPORTER_ASSERT(r, keyA != keyB);
205 }
206 if (allowedClosednessDiff) {
207 // GrStyledShape will close paths with simple fill style. Make the non-filled path
208 // closed so that the comparision will succeed. Make sure both are closed before
209 // comparing.
210 pA.close();
211 pB.close();
212 }
213 REPORTER_ASSERT(r, pA == pB);
214 REPORTER_ASSERT(r, aIsRRect == bIsRRect);
215 if (aIsRRect) {
216 REPORTER_ASSERT(r, rrectA == rrectB);
217 REPORTER_ASSERT(r, dirA == dirB);
218 REPORTER_ASSERT(r, startA == startB);
219 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
220 }
221 }
222 REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
223 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
224 // closedness can affect convexity.
225 REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
226 if (a.knownToBeConvex()) {
227 REPORTER_ASSERT(r, pathA.isConvex());
228 }
229 if (b.knownToBeConvex()) {
230 REPORTER_ASSERT(r, pathB.isConvex());
231 }
232 REPORTER_ASSERT(r, a.bounds() == b.bounds());
233 REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
234 // Init these to suppress warnings.
235 SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
236 bool invertedLine[2] {true, true};
237 REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
238 // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
239 // doesn't (since the PE can set any fill type on its output path).
240 // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
241 // then they may disagree about inverseness.
242 if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
243 a.style().isDashed() == b.style().isDashed()) {
244 REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
245 b.mayBeInverseFilledAfterStyling());
246 }
247 if (a.asLine(nullptr, nullptr)) {
248 REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
249 REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
250 REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
251 REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
252 }
253 REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
254}
255
257 const GrStyledShape& pe, const GrStyledShape& peStroke,
258 const GrStyledShape& full) {
259 bool baseIsNonVolatilePath = base.testingOnly_isNonVolatilePath();
260 bool peIsPath = pe.testingOnly_isPath();
261 bool peStrokeIsPath = peStroke.testingOnly_isPath();
262 bool fullIsPath = full.testingOnly_isPath();
263
264 REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath);
265
266 uint32_t baseID = base.testingOnly_getOriginalGenerationID();
267 uint32_t peID = pe.testingOnly_getOriginalGenerationID();
268 uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID();
269 uint32_t fullID = full.testingOnly_getOriginalGenerationID();
270
271 // All empty paths have the same gen ID
272 uint32_t emptyID = SkPath().getGenerationID();
273
274 // If we started with a real path, then our genID should match that path's gen ID (and not be
275 // empty). If we started with a simple shape or a volatile path, our original path should have
276 // been reset.
277 REPORTER_ASSERT(r, baseIsNonVolatilePath == (baseID != emptyID));
278
279 // For the derived shapes, if they're simple types, their original paths should have been reset
280 REPORTER_ASSERT(r, peIsPath || (peID == emptyID));
281 REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID));
282 REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID));
283
284 if (!peIsPath) {
285 // If the path effect produces a simple shape, then there are no unbroken chains to test
286 return;
287 }
288
289 // From here on, we know that the path effect produced a shape that was a "real" path
290
291 if (baseIsNonVolatilePath) {
292 REPORTER_ASSERT(r, baseID == peID);
293 }
294
295 if (peStrokeIsPath) {
296 REPORTER_ASSERT(r, peID == peStrokeID);
297 REPORTER_ASSERT(r, peStrokeID == fullID);
298 }
299
300 if (baseIsNonVolatilePath && peStrokeIsPath) {
301 REPORTER_ASSERT(r, baseID == peStrokeID);
302 REPORTER_ASSERT(r, baseID == fullID);
303 }
304}
305
306void test_inversions(skiatest::Reporter* r, const GrStyledShape& shape, const Key& shapeKey) {
309 Key preserveKey;
310 make_key(&preserveKey, preserve);
311
313 Key flipKey;
314 make_key(&flipKey, flip);
315
318 Key invertedKey;
319 make_key(&invertedKey, inverted);
320
323 Key noninvertedKey;
324 make_key(&noninvertedKey, noninverted);
325
326 if (invertedKey.size() || noninvertedKey.size()) {
327 REPORTER_ASSERT(r, invertedKey != noninvertedKey);
328 }
329 if (shape.style().isSimpleFill()) {
330 check_equivalence(r, shape, preserve, shapeKey, preserveKey);
331 }
332 if (shape.inverseFilled()) {
333 check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
334 check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
335 } else {
336 check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
337 check_equivalence(r, flip, inverted, flipKey, invertedKey);
338 }
339
341 Key doubleFlipKey;
342 make_key(&doubleFlipKey, doubleFlip);
343 // It can be the case that the double flip has no key but preserve does. This happens when the
344 // original shape has an inherited style key. That gets dropped on the first inversion flip.
345 if (preserveKey.size() && !doubleFlipKey.size()) {
346 preserveKey.clear();
347 }
348 check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
349}
350
351namespace {
352/**
353 * Geo is a factory for creating a GrStyledShape from another representation. It also answers some
354 * questions about expected behavior for GrStyledShape given the inputs.
355 */
356class Geo {
357public:
358 virtual ~Geo() {}
359 virtual GrStyledShape makeShape(const SkPaint&) const = 0;
360 virtual SkPath path() const = 0;
361 // These functions allow tests to check for special cases where style gets
362 // applied by GrStyledShape in its constructor (without calling GrStyledShape::applyStyle).
363 // These unfortunately rely on knowing details of GrStyledShape's implementation.
364 // These predicates are factored out here to avoid littering the rest of the
365 // test code with GrStyledShape implementation details.
366 virtual bool fillChangesGeom() const { return false; }
367 virtual bool strokeIsConvertedToFill() const { return false; }
368 virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; }
369 // Is this something we expect GrStyledShape to recognize as something simpler than a path.
370 virtual bool isNonPath(const SkPaint& paint) const { return true; }
371};
372
373class RectGeo : public Geo {
374public:
375 RectGeo(const SkRect& rect) : fRect(rect) {}
376
377 SkPath path() const override {
378 SkPath path;
379 path.addRect(fRect);
380 return path;
381 }
382
383 GrStyledShape makeShape(const SkPaint& paint) const override {
384 return GrStyledShape(fRect, paint);
385 }
386
387 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
389 // Converted to an outset rectangle or round rect
390 return (paint.getStrokeJoin() == SkPaint::kMiter_Join &&
391 paint.getStrokeMiter() >= SK_ScalarSqrt2) ||
392 paint.getStrokeJoin() == SkPaint::kRound_Join;
393 }
394
395private:
397};
398
399class RRectGeo : public Geo {
400public:
401 RRectGeo(const SkRRect& rrect) : fRRect(rrect) {}
402
403 GrStyledShape makeShape(const SkPaint& paint) const override {
404 return GrStyledShape(fRRect, paint);
405 }
406
407 SkPath path() const override {
408 SkPath path;
409 path.addRRect(fRRect);
410 return path;
411 }
412
413 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
415 if (fRRect.isRect()) {
416 return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint);
417 }
418 return false;
419 }
420
421private:
423};
424
425class ArcGeo : public Geo {
426public:
427 ArcGeo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter)
428 : fOval(oval)
429 , fStartAngle(startAngle)
430 , fSweepAngle(sweepAngle)
431 , fUseCenter(useCenter) {}
432
433 SkPath path() const override {
434 SkPath path;
435 SkPathPriv::CreateDrawArcPath(&path, fOval, fStartAngle, fSweepAngle, fUseCenter, false);
436 return path;
437 }
438
439 GrStyledShape makeShape(const SkPaint& paint) const override {
440 return GrStyledShape::MakeArc(fOval, fStartAngle, fSweepAngle, fUseCenter, GrStyle(paint));
441 }
442
443 // GrStyledShape specializes when created from arc params but it doesn't recognize arcs from
444 // SkPath.
445 bool isNonPath(const SkPaint& paint) const override { return false; }
446
447private:
448 SkRect fOval;
449 SkScalar fStartAngle;
450 SkScalar fSweepAngle;
451 bool fUseCenter;
452};
453
454class PathGeo : public Geo {
455public:
456 enum class Invert { kNo, kYes };
457
458 PathGeo(const SkPath& path, Invert invert) : fPath(path) {
459 SkASSERT(!path.isInverseFillType());
460 if (Invert::kYes == invert) {
463 } else {
466 }
467 }
468 }
469
470 GrStyledShape makeShape(const SkPaint& paint) const override {
471 return GrStyledShape(fPath, paint);
472 }
473
474 SkPath path() const override { return fPath; }
475
476 bool fillChangesGeom() const override {
477 // unclosed rects get closed. Lines get turned into empty geometry
478 return this->isUnclosedRect() || fPath.isLine(nullptr);
479 }
480
481 bool strokeIsConvertedToFill() const override {
482 return this->isAxisAlignedLine();
483 }
484
485 bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
487 if (this->isAxisAlignedLine()) {
488 // The fill is ignored (zero area) and the stroke is converted to a rrect.
489 return true;
490 }
491 SkRect rect;
492 unsigned start;
494 if (SkPathPriv::IsSimpleRect(fPath, false, &rect, &dir, &start)) {
495 return RectGeo(rect).strokeAndFillIsConvertedToFill(paint);
496 }
497 return false;
498 }
499
500 bool isNonPath(const SkPaint& paint) const override {
501 return fPath.isLine(nullptr) || fPath.isEmpty();
502 }
503
504private:
505 bool isAxisAlignedLine() const {
506 SkPoint pts[2];
507 if (!fPath.isLine(pts)) {
508 return false;
509 }
510 return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY;
511 }
512
513 bool isUnclosedRect() const {
514 bool closed;
515 return fPath.isRect(nullptr, &closed, nullptr) && !closed;
516 }
517
519};
520
521class RRectPathGeo : public PathGeo {
522public:
523 enum class RRectForStroke { kNo, kYes };
524
525 RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke,
526 Invert invert)
527 : PathGeo(path, invert)
528 , fRRect(equivalentRRect)
529 , fRRectForStroke(rrectForStroke) {}
530
531 RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke,
532 Invert invert)
533 : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {}
534
535 bool isNonPath(const SkPaint& paint) const override {
536 if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) {
537 return true;
538 }
539 return false;
540 }
541
542 const SkRRect& rrect() const { return fRRect; }
543
544private:
546 RRectForStroke fRRectForStroke;
547};
548
549class TestCase {
550public:
551 TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
553 : fBase(new GrStyledShape(geo.makeShape(paint))) {
554 this->init(r, scale);
555 }
556
557 template <typename... ShapeArgs>
558 TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs)
559 : fBase(new GrStyledShape(shapeArgs...)) {
560 this->init(r, SK_Scalar1);
561 }
562
564 : fBase(new GrStyledShape(shape)) {
565 this->init(r, scale);
566 }
567
568 struct SelfExpectations {
569 bool fPEHasEffect;
570 bool fPEHasValidKey;
571 bool fStrokeApplies;
572 };
573
574 void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
575
576 enum ComparisonExpecation {
577 kAllDifferent_ComparisonExpecation,
578 kSameUpToPE_ComparisonExpecation,
579 kSameUpToStroke_ComparisonExpecation,
580 kAllSame_ComparisonExpecation,
581 };
582
583 void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
584
585 const GrStyledShape& baseShape() const { return *fBase; }
586 const GrStyledShape& appliedPathEffectShape() const { return *fAppliedPE; }
587 const GrStyledShape& appliedFullStyleShape() const { return *fAppliedFull; }
588
589 // The returned array's count will be 0 if the key shape has no key.
590 const Key& baseKey() const { return fBaseKey; }
591 const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
592 const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
593 const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
594
595private:
596 static void CheckBounds(skiatest::Reporter* r, const GrStyledShape& shape,
597 const SkRect& bounds) {
598 SkPath path;
599 shape.asPath(&path);
600 // If the bounds are empty, the path ought to be as well.
601 if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
602 REPORTER_ASSERT(r, path.isEmpty());
603 return;
604 }
605 if (path.isEmpty()) {
606 return;
607 }
608 // The bounds API explicitly calls out that it does not consider inverseness.
609 SkPath p = path;
610 p.setFillType(SkPathFillType_ConvertToNonInverse(path.getFillType()));
612 }
613
615 fAppliedPE = std::make_unique<GrStyledShape>();
616 fAppliedPEThenStroke = std::make_unique<GrStyledShape>();
617 fAppliedFull = std::make_unique<GrStyledShape>();
618
619 *fAppliedPE = fBase->applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
620 *fAppliedPEThenStroke =
621 fAppliedPE->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
622 *fAppliedFull = fBase->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
623
624 make_key(&fBaseKey, *fBase);
625 make_key(&fAppliedPEKey, *fAppliedPE);
626 make_key(&fAppliedPEThenStrokeKey, *fAppliedPEThenStroke);
627 make_key(&fAppliedFullKey, *fAppliedFull);
628
629 // All shapes should report the same "original" path, so that path renderers can get to it
630 // if necessary.
631 check_original_path_ids(r, *fBase, *fAppliedPE, *fAppliedPEThenStroke, *fAppliedFull);
632
633 // Applying the path effect and then the stroke should always be the same as applying
634 // both in one go.
635 REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
636 SkPath a, b;
637 fAppliedPEThenStroke->asPath(&a);
638 fAppliedFull->asPath(&b);
639 // If the output of the path effect is a rrect then it is possible for a and b to be
640 // different paths that fill identically. The reason is that fAppliedFull will do this:
641 // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
642 // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
643 // now that there is no longer a path effect, the direction and starting index get
644 // canonicalized before the stroke.
645 if (fAppliedPE->asRRect(nullptr, nullptr, nullptr, nullptr)) {
647 } else {
648 REPORTER_ASSERT(r, a == b);
649 }
650 REPORTER_ASSERT(r, fAppliedFull->isEmpty() == fAppliedPEThenStroke->isEmpty());
651
652 SkPath path;
653 fBase->asPath(&path);
654 REPORTER_ASSERT(r, path.isEmpty() == fBase->isEmpty());
655 REPORTER_ASSERT(r, path.getSegmentMasks() == fBase->segmentMask());
656 fAppliedPE->asPath(&path);
657 REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE->isEmpty());
658 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE->segmentMask());
659 fAppliedFull->asPath(&path);
660 REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull->isEmpty());
661 REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull->segmentMask());
662
663 CheckBounds(r, *fBase, fBase->bounds());
664 CheckBounds(r, *fAppliedPE, fAppliedPE->bounds());
665 CheckBounds(r, *fAppliedPEThenStroke, fAppliedPEThenStroke->bounds());
666 CheckBounds(r, *fAppliedFull, fAppliedFull->bounds());
667 SkRect styledBounds = fBase->styledBounds();
668 CheckBounds(r, *fAppliedFull, styledBounds);
669 styledBounds = fAppliedPE->styledBounds();
670 CheckBounds(r, *fAppliedFull, styledBounds);
671
672 // Check that the same path is produced when style is applied by GrStyledShape and GrStyle.
673 SkPath preStyle;
674 SkPath postPathEffect;
675 SkPath postAllStyle;
676
677 fBase->asPath(&preStyle);
679 if (fBase->style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
680 scale)) {
681 // run postPathEffect through GrStyledShape to get any geometry reductions that would
682 // have occurred to fAppliedPE.
683 GrStyledShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr))
684 .asPath(&postPathEffect);
685
686 SkPath testPath;
687 fAppliedPE->asPath(&testPath);
688 REPORTER_ASSERT(r, testPath == postPathEffect);
689 REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE->style().strokeRec()));
690 }
691 SkStrokeRec::InitStyle fillOrHairline;
692 if (fBase->style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
693 SkPath testPath;
694 fAppliedFull->asPath(&testPath);
695 if (fBase->style().hasPathEffect()) {
696 // Because GrStyledShape always does two-stage application when there is a path
697 // effect there may be a reduction/canonicalization step between the path effect and
698 // strokerec not reflected in postAllStyle since it applied both the path effect
699 // and strokerec without analyzing the intermediate path.
700 REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
701 } else {
702 // Make sure that postAllStyle sees any reductions/canonicalizations that
703 // GrStyledShape would apply.
704 GrStyledShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
705 REPORTER_ASSERT(r, testPath == postAllStyle);
706 }
707
708 if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
709 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleFill());
710 } else {
711 REPORTER_ASSERT(r, fAppliedFull->style().isSimpleHairline());
712 }
713 }
714 test_inversions(r, *fBase, fBaseKey);
715 test_inversions(r, *fAppliedPE, fAppliedPEKey);
716 test_inversions(r, *fAppliedFull, fAppliedFullKey);
717 }
718
719 std::unique_ptr<GrStyledShape> fBase;
720 std::unique_ptr<GrStyledShape> fAppliedPE;
721 std::unique_ptr<GrStyledShape> fAppliedPEThenStroke;
722 std::unique_ptr<GrStyledShape> fAppliedFull;
723
724 Key fBaseKey;
725 Key fAppliedPEKey;
726 Key fAppliedPEThenStrokeKey;
727 Key fAppliedFullKey;
728};
729
730void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
731 // The base's key should always be valid (unless the path is volatile)
732 REPORTER_ASSERT(reporter, fBaseKey.size());
733 if (expectations.fPEHasEffect) {
734 REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
735 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.size()));
736 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
737 REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.size()));
738 if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
739 REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
740 REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.size()));
741 }
742 } else {
743 REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
744 SkPath a, b;
745 fBase->asPath(&a);
746 fAppliedPE->asPath(&b);
748 if (expectations.fStrokeApplies) {
749 REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
750 } else {
751 REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
752 }
753 }
754}
755
756void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
757 ComparisonExpecation expectation) const {
758 SkPath a, b;
759 switch (expectation) {
760 case kAllDifferent_ComparisonExpecation:
761 REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
762 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
763 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
764 break;
765 case kSameUpToPE_ComparisonExpecation:
766 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
767 REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
768 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
769 break;
770 case kSameUpToStroke_ComparisonExpecation:
771 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
772 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
773 REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
774 break;
775 case kAllSame_ComparisonExpecation:
776 check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
777 check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
778 check_equivalence(r, *fAppliedFull, *that.fAppliedFull, fAppliedFullKey,
779 that.fAppliedFullKey);
780 break;
781 }
782}
783} // namespace
784
786 static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
787 static const SkScalar kPhase = 0.75;
788 return SkDashPathEffect::Make(kIntervals, std::size(kIntervals), kPhase);
789}
790
792 static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
793 return SkDashPathEffect::Make(kNullIntervals, std::size(kNullIntervals), 0.f);
794}
795
796// We make enough TestCases, and they're large enough, that on Google3 builds we exceed
797// the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
798template <typename... Args>
799static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
800 return std::make_unique<TestCase>( std::forward<Args>(args)... );
801}
802
803static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
805
806 TestCase::SelfExpectations expectations;
807 SkPaint fill;
808
809 TestCase fillCase(geo, fill, reporter);
810 expectations.fPEHasEffect = false;
811 expectations.fPEHasValidKey = false;
812 expectations.fStrokeApplies = false;
813 fillCase.testExpectations(reporter, expectations);
814 // Test that another GrStyledShape instance built from the same primitive is the same.
815 make_TestCase(geo, fill, reporter)
816 ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
817
818 SkPaint stroke2RoundBevel;
819 stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
820 stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
821 stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
822 stroke2RoundBevel.setStrokeWidth(2.f);
823 TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
824 expectations.fPEHasValidKey = true;
825 expectations.fPEHasEffect = false;
826 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
827 stroke2RoundBevelCase.testExpectations(reporter, expectations);
828 make_TestCase(geo, stroke2RoundBevel, reporter)
829 ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
830
831 SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
832 stroke2RoundBevelDash.setPathEffect(make_dash());
833 TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
834 expectations.fPEHasValidKey = true;
835 expectations.fPEHasEffect = true;
836 expectations.fStrokeApplies = true;
837 stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
838 make_TestCase(geo, stroke2RoundBevelDash, reporter)
839 ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
840
841 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
842 fillCase.compare(reporter, stroke2RoundBevelCase,
843 TestCase::kAllDifferent_ComparisonExpecation);
844 fillCase.compare(reporter, stroke2RoundBevelDashCase,
845 TestCase::kAllDifferent_ComparisonExpecation);
846 } else {
847 fillCase.compare(reporter, stroke2RoundBevelCase,
848 TestCase::kSameUpToStroke_ComparisonExpecation);
849 fillCase.compare(reporter, stroke2RoundBevelDashCase,
850 TestCase::kSameUpToPE_ComparisonExpecation);
851 }
852 if (geo.strokeIsConvertedToFill()) {
853 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
854 TestCase::kAllDifferent_ComparisonExpecation);
855 } else {
856 stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
857 TestCase::kSameUpToPE_ComparisonExpecation);
858 }
859
860 // Stroke and fill cases
861 SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
862 stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
863 TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
864 expectations.fPEHasValidKey = true;
865 expectations.fPEHasEffect = false;
866 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
867 stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
868 make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
869 reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
870
871 SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
872 stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
873 TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
874 expectations.fPEHasValidKey = true;
875 expectations.fPEHasEffect = false;
876 expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
877 stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
878 make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
879 reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
880 stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
881 TestCase::kAllSame_ComparisonExpecation);
882
883 SkPaint hairline;
885 hairline.setStrokeWidth(0.f);
886 TestCase hairlineCase(geo, hairline, reporter);
887 // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except
888 // in the line and unclosed rect cases).
889 if (geo.fillChangesGeom()) {
890 hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
891 } else {
892 hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
893 }
894 REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
895 REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
896 REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
897
898}
899
900static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
902
903 static const SkScalar kS1 = 1.f;
904 static const SkScalar kS2 = 2.f;
905
906 SkPaint fill;
907 TestCase fillCase1(geo, fill, reporter, kS1);
908 TestCase fillCase2(geo, fill, reporter, kS2);
909 // Scale doesn't affect fills.
910 fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
911
912 SkPaint hairline;
914 hairline.setStrokeWidth(0.f);
915 TestCase hairlineCase1(geo, hairline, reporter, kS1);
916 TestCase hairlineCase2(geo, hairline, reporter, kS2);
917 // Scale doesn't affect hairlines.
918 hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
919
920 SkPaint stroke;
922 stroke.setStrokeWidth(2.f);
923 TestCase strokeCase1(geo, stroke, reporter, kS1);
924 TestCase strokeCase2(geo, stroke, reporter, kS2);
925 // Scale affects the stroke
926 if (geo.strokeIsConvertedToFill()) {
927 REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
928 strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
929 } else {
930 strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
931 }
932
933 SkPaint strokeDash = stroke;
934 strokeDash.setPathEffect(make_dash());
935 TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
936 TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
937 // Scale affects the dash and the stroke.
938 strokeDashCase1.compare(reporter, strokeDashCase2,
939 TestCase::kSameUpToPE_ComparisonExpecation);
940
941 // Stroke and fill cases
942 SkPaint strokeAndFill = stroke;
944 TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
945 TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
946 SkPaint strokeAndFillDash = strokeDash;
947 strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
948 // Dash is ignored for stroke and fill
949 TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
950 TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
951 // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g.
952 // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the
953 // geometries should agree.
954 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
955 REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
956 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
957 TestCase::kAllSame_ComparisonExpecation);
958 strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
959 TestCase::kAllSame_ComparisonExpecation);
960 } else {
961 strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
962 TestCase::kSameUpToStroke_ComparisonExpecation);
963 }
964 strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
965 TestCase::kAllSame_ComparisonExpecation);
966 strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
967 TestCase::kAllSame_ComparisonExpecation);
968}
969
970template <typename T>
972 std::function<void(SkPaint*, T)> setter, T a, T b,
973 bool paramAffectsStroke,
974 bool paramAffectsDashAndStroke) {
975 // Set the stroke width so that we don't get hairline. However, call the setter afterward so
976 // that it can override the stroke width.
977 SkPaint strokeA;
979 strokeA.setStrokeWidth(2.f);
980 setter(&strokeA, a);
981 SkPaint strokeB;
983 strokeB.setStrokeWidth(2.f);
984 setter(&strokeB, b);
985
986 TestCase strokeACase(geo, strokeA, reporter);
987 TestCase strokeBCase(geo, strokeB, reporter);
988 if (paramAffectsStroke) {
989 // If stroking is immediately incorporated into a geometric transformation then the base
990 // shapes will differ.
991 if (geo.strokeIsConvertedToFill()) {
992 strokeACase.compare(reporter, strokeBCase,
993 TestCase::kAllDifferent_ComparisonExpecation);
994 } else {
995 strokeACase.compare(reporter, strokeBCase,
996 TestCase::kSameUpToStroke_ComparisonExpecation);
997 }
998 } else {
999 strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
1000 }
1001
1002 SkPaint strokeAndFillA = strokeA;
1003 SkPaint strokeAndFillB = strokeB;
1006 TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
1007 TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
1008 if (paramAffectsStroke) {
1009 // If stroking is immediately incorporated into a geometric transformation then the base
1010 // shapes will differ.
1011 if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
1012 geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
1013 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
1014 TestCase::kAllDifferent_ComparisonExpecation);
1015 } else {
1016 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
1017 TestCase::kSameUpToStroke_ComparisonExpecation);
1018 }
1019 } else {
1020 strokeAndFillACase.compare(reporter, strokeAndFillBCase,
1021 TestCase::kAllSame_ComparisonExpecation);
1022 }
1023
1024 // Make sure stroking params don't affect fill style.
1025 SkPaint fillA = strokeA, fillB = strokeB;
1027 fillB.setStyle(SkPaint::kFill_Style);
1028 TestCase fillACase(geo, fillA, reporter);
1029 TestCase fillBCase(geo, fillB, reporter);
1030 fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
1031
1032 // Make sure just applying the dash but not stroke gives the same key for both stroking
1033 // variations.
1034 SkPaint dashA = strokeA, dashB = strokeB;
1035 dashA.setPathEffect(make_dash());
1036 dashB.setPathEffect(make_dash());
1037 TestCase dashACase(geo, dashA, reporter);
1038 TestCase dashBCase(geo, dashB, reporter);
1039 if (paramAffectsDashAndStroke) {
1040 dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1041 } else {
1042 dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
1043 }
1044}
1045
1046template <typename T>
1047static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
1048 std::function<void(SkPaint*, T)> setter, T a, T b) {
1049 test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
1050}
1051
1052static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) {
1053 SkPaint hairline;
1054 hairline.setStrokeWidth(0);
1056 GrStyledShape shape = geo.makeShape(hairline);
1057 // The cap should only affect shapes that may be open.
1058 bool affectsStroke = !shape.knownToBeClosed();
1059 // Dashing adds ends that need caps.
1060 bool affectsDashAndStroke = true;
1061 test_stroke_param_impl<SkPaint::Cap>(
1062 reporter,
1063 geo,
1064 [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
1066 affectsStroke,
1067 affectsDashAndStroke);
1068}
1069
1071 return shape.asLine(nullptr, nullptr) || shape.isEmpty();
1072}
1073
1074static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) {
1075 SkPaint hairline;
1076 hairline.setStrokeWidth(0);
1078 GrStyledShape shape = geo.makeShape(hairline);
1079 // GrStyledShape recognizes certain types don't have joins and will prevent the join type from
1080 // affecting the style key.
1081 // Dashing doesn't add additional joins. However, GrStyledShape currently loses track of this
1082 // after applying the dash.
1083 bool affectsStroke = !shape_known_not_to_have_joins(shape);
1084 test_stroke_param_impl<SkPaint::Join>(
1085 reporter,
1086 geo,
1087 [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
1089 affectsStroke, true);
1090}
1091
1092static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) {
1093 auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1094 p->setStrokeJoin(SkPaint::kMiter_Join);
1095 p->setStrokeMiter(miter);
1096 };
1097
1098 auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
1099 p->setStrokeJoin(SkPaint::kRound_Join);
1100 p->setStrokeMiter(miter);
1101 };
1102
1103 SkPaint hairline;
1104 hairline.setStrokeWidth(0);
1106 GrStyledShape shape = geo.makeShape(hairline);
1107 bool mayHaveJoins = !shape_known_not_to_have_joins(shape);
1108
1109 // The miter limit should affect stroked and dashed-stroked cases when the join type is
1110 // miter.
1111 test_stroke_param_impl<SkScalar>(
1112 reporter,
1113 geo,
1114 setMiterJoinAndLimit,
1115 0.5f, 0.75f,
1116 mayHaveJoins,
1117 true);
1118
1119 // The miter limit should not affect stroked and dashed-stroked cases when the join type is
1120 // not miter.
1121 test_stroke_param_impl<SkScalar>(
1122 reporter,
1123 geo,
1124 setOtherJoinAndLimit,
1125 0.5f, 0.75f,
1126 false,
1127 false);
1128}
1129
1130static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
1131 // A dash with no stroke should have no effect
1132 using DashFactoryFn = sk_sp<SkPathEffect>(*)();
1133 for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
1134 SkPaint dashFill;
1135 dashFill.setPathEffect((*md)());
1136 TestCase dashFillCase(geo, dashFill, reporter);
1137
1138 TestCase fillCase(geo, SkPaint(), reporter);
1139 dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
1140 }
1141}
1142
1144 SkPaint fill;
1145 SkPaint stroke;
1147 stroke.setStrokeWidth(1.f);
1148 SkPaint dash;
1150 dash.setStrokeWidth(1.f);
1151 dash.setPathEffect(make_dash());
1152 SkPaint nullDash;
1154 nullDash.setStrokeWidth(1.f);
1155 nullDash.setPathEffect(make_null_dash());
1156
1157 TestCase fillCase(geo, fill, reporter);
1158 TestCase strokeCase(geo, stroke, reporter);
1159 TestCase dashCase(geo, dash, reporter);
1160 TestCase nullDashCase(geo, nullDash, reporter);
1161
1162 // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
1163 nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
1164 // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation
1165 // on construction in order to determine how to compare the fill and stroke.
1166 if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
1167 nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
1168 } else {
1169 nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
1170 }
1171 // In the null dash case we may immediately convert to a fill, but not for the normal dash case.
1172 if (geo.strokeIsConvertedToFill()) {
1173 nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
1174 } else {
1175 nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
1176 }
1177}
1178
1180 /**
1181 * This path effect takes any input path and turns it into a rrect. It passes through stroke
1182 * info.
1183 */
1184 class RRectPathEffect : SkPathEffectBase {
1185 public:
1186 static const SkRRect& RRect() {
1187 static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
1188 return kRRect;
1189 }
1190
1191 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
1192 Factory getFactory() const override { return nullptr; }
1193 const char* getTypeName() const override { return nullptr; }
1194
1195 protected:
1196 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1197 const SkRect* cullR, const SkMatrix&) const override {
1198 dst->reset();
1199 dst->addRRect(RRect());
1200 return true;
1201 }
1202
1203 bool computeFastBounds(SkRect* bounds) const override {
1204 if (bounds) {
1205 *bounds = RRect().getBounds();
1206 }
1207 return true;
1208 }
1209
1210 private:
1211 RRectPathEffect() {}
1212 };
1213
1214 SkPaint fill;
1215 TestCase fillGeoCase(geo, fill, reporter);
1216
1217 SkPaint pe;
1218 pe.setPathEffect(RRectPathEffect::Make());
1219 TestCase geoPECase(geo, pe, reporter);
1220
1221 SkPaint peStroke;
1222 peStroke.setPathEffect(RRectPathEffect::Make());
1223 peStroke.setStrokeWidth(2.f);
1225 TestCase geoPEStrokeCase(geo, peStroke, reporter);
1226
1227 // Check whether constructing the filled case would cause the base shape to have a different
1228 // geometry (because of a geometric transformation upon initial GrStyledShape construction).
1229 if (geo.fillChangesGeom()) {
1230 fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
1231 fillGeoCase.compare(reporter, geoPEStrokeCase,
1232 TestCase::kAllDifferent_ComparisonExpecation);
1233 } else {
1234 fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
1235 fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
1236 }
1237 geoPECase.compare(reporter, geoPEStrokeCase,
1238 TestCase::kSameUpToStroke_ComparisonExpecation);
1239
1240 TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
1241 SkPaint stroke = peStroke;
1242 stroke.setPathEffect(nullptr);
1243 TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
1244
1245 SkRRect rrect;
1246 // Applying the path effect should make a SkRRect shape. There is no further stroking in the
1247 // geoPECase, so the full style should be the same as just the PE.
1248 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr,
1249 nullptr));
1250 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1251 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
1252
1253 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr,
1254 nullptr));
1255 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1256 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
1257
1258 // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
1259 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr,
1260 nullptr, nullptr));
1261 REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
1262 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
1263
1264 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
1265 nullptr, nullptr));
1266 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
1267 rrectStrokeCase.appliedFullStyleKey());
1268}
1269
1271 /**
1272 * This path effect just adds two lineTos to the input path.
1273 */
1274 class AddLineTosPathEffect : SkPathEffectBase {
1275 public:
1276 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
1277 Factory getFactory() const override { return nullptr; }
1278 const char* getTypeName() const override { return nullptr; }
1279
1280 protected:
1281 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1282 const SkRect* cullR, const SkMatrix&) const override {
1283 *dst = src;
1284 // To avoid triggering data-based keying of paths with few verbs we add many segments.
1285 for (int i = 0; i < 100; ++i) {
1286 dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
1287 }
1288 return true;
1289 }
1290 bool computeFastBounds(SkRect* bounds) const override {
1291 if (bounds) {
1292 SkRectPriv::GrowToInclude(bounds, {0, 0});
1293 SkRectPriv::GrowToInclude(bounds, {100, 100});
1294 }
1295 return true;
1296 }
1297 private:
1298 AddLineTosPathEffect() {}
1299 };
1300
1301 // This path effect should make the keys invalid when it is applied. We only produce a path
1302 // effect key for dash path effects. So the only way another arbitrary path effect can produce
1303 // a styled result with a key is to produce a non-path shape that has a purely geometric key.
1304 SkPaint peStroke;
1305 peStroke.setPathEffect(AddLineTosPathEffect::Make());
1306 peStroke.setStrokeWidth(2.f);
1308 TestCase geoPEStrokeCase(geo, peStroke, reporter);
1309 TestCase::SelfExpectations expectations;
1310 expectations.fPEHasEffect = true;
1311 expectations.fPEHasValidKey = false;
1312 expectations.fStrokeApplies = true;
1313 geoPEStrokeCase.testExpectations(reporter, expectations);
1314}
1315
1317 /**
1318 * This path effect just changes the stroke rec to hairline.
1319 */
1320 class MakeHairlinePathEffect : SkPathEffectBase {
1321 public:
1322 static sk_sp<SkPathEffect> Make() {
1323 return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
1324 }
1325 Factory getFactory() const override { return nullptr; }
1326 const char* getTypeName() const override { return nullptr; }
1327
1328 protected:
1329 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
1330 const SkRect* cullR, const SkMatrix&) const override {
1331 *dst = src;
1332 strokeRec->setHairlineStyle();
1333 return true;
1334 }
1335 private:
1336 bool computeFastBounds(SkRect* bounds) const override { return true; }
1337
1338 MakeHairlinePathEffect() {}
1339 };
1340
1341 SkPaint fill;
1342 SkPaint pe;
1343 pe.setPathEffect(MakeHairlinePathEffect::Make());
1344
1345 TestCase peCase(geo, pe, reporter);
1346
1347 SkPath a, b, c;
1348 peCase.baseShape().asPath(&a);
1349 peCase.appliedPathEffectShape().asPath(&b);
1350 peCase.appliedFullStyleShape().asPath(&c);
1351 if (geo.isNonPath(pe)) {
1352 // RRect types can have a change in start index or direction after the PE is applied. This
1353 // is because once the PE is applied, GrStyledShape may canonicalize the dir and index since
1354 // it is not germane to the styling any longer.
1355 // Instead we just check that the paths would fill the same both before and after styling.
1358 } else {
1359 // The base shape cannot perform canonicalization on the path's fill type because of an
1360 // unknown path effect. However, after the path effect is applied the resulting hairline
1361 // shape will canonicalize the path fill type since hairlines (and stroking in general)
1362 // don't distinguish between even/odd and non-zero winding.
1363 a.setFillType(b.getFillType());
1365 REPORTER_ASSERT(reporter, a == c);
1366 // If the resulting path is small enough then it will have a key.
1369 REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
1370 REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
1371 }
1372 REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
1373 REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
1374}
1375
1377 SkPath vPath = geo.path();
1378 vPath.setIsVolatile(true);
1379
1380 SkPaint dashAndStroke;
1381 dashAndStroke.setPathEffect(make_dash());
1382 dashAndStroke.setStrokeWidth(2.f);
1383 dashAndStroke.setStyle(SkPaint::kStroke_Style);
1384 TestCase volatileCase(reporter, vPath, dashAndStroke);
1385 // We expect a shape made from a volatile path to have a key iff the shape is recognized
1386 // as a specialized geometry.
1387 if (geo.isNonPath(dashAndStroke)) {
1388 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().size()));
1389 // In this case all the keys should be identical to the non-volatile case.
1390 TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke);
1391 volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
1392 } else {
1393 // None of the keys should be valid.
1394 REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().empty()));
1395 REPORTER_ASSERT(reporter, SkToBool(volatileCase.appliedPathEffectKey().empty()));
1396 REPORTER_ASSERT(reporter, SkToBool(volatileCase.appliedFullStyleKey().empty()));
1397 REPORTER_ASSERT(reporter, SkToBool(volatileCase.appliedPathEffectThenStrokeKey().empty()));
1398 }
1399}
1400
1402 /**
1403 * This path effect returns an empty path (possibly inverted)
1404 */
1405 class EmptyPathEffect : SkPathEffectBase {
1406 public:
1407 static sk_sp<SkPathEffect> Make(bool invert) {
1408 return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
1409 }
1410 Factory getFactory() const override { return nullptr; }
1411 const char* getTypeName() const override { return nullptr; }
1412 protected:
1413 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1414 const SkRect* cullR, const SkMatrix&) const override {
1415 dst->reset();
1416 if (fInvert) {
1417 dst->toggleInverseFillType();
1418 }
1419 return true;
1420 }
1421 bool computeFastBounds(SkRect* bounds) const override {
1422 if (bounds) {
1423 *bounds = { 0, 0, 0, 0 };
1424 }
1425 return true;
1426 }
1427 private:
1428 bool fInvert;
1429 EmptyPathEffect(bool invert) : fInvert(invert) {}
1430 };
1431
1432 SkPath emptyPath;
1433 GrStyledShape emptyShape(emptyPath);
1434 Key emptyKey;
1435 make_key(&emptyKey, emptyShape);
1436 REPORTER_ASSERT(reporter, emptyShape.isEmpty());
1437
1438 emptyPath.toggleInverseFillType();
1439 GrStyledShape invertedEmptyShape(emptyPath);
1440 Key invertedEmptyKey;
1441 make_key(&invertedEmptyKey, invertedEmptyShape);
1442 REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
1443
1444 REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
1445
1446 SkPaint pe;
1447 pe.setPathEffect(EmptyPathEffect::Make(false));
1448 TestCase geoPECase(geo, pe, reporter);
1449 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
1450 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
1451 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
1452 REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
1453 REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
1454 REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
1455 REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
1456
1457 SkPaint peStroke;
1458 peStroke.setPathEffect(EmptyPathEffect::Make(false));
1459 peStroke.setStrokeWidth(2.f);
1461 TestCase geoPEStrokeCase(geo, peStroke, reporter);
1462 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
1463 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
1464 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
1465 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
1466 REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
1467 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
1468 REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
1469 pe.setPathEffect(EmptyPathEffect::Make(true));
1470
1471 TestCase geoPEInvertCase(geo, pe, reporter);
1472 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
1473 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
1474 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1475 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
1476 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
1477 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
1478 REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
1479
1480 peStroke.setPathEffect(EmptyPathEffect::Make(true));
1481 TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
1482 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
1483 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
1485 geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
1486 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
1487 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
1488 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
1489 REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
1490}
1491
1493 /**
1494 * This path effect always fails to apply.
1495 */
1496 class FailurePathEffect : SkPathEffectBase {
1497 public:
1498 static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
1499 Factory getFactory() const override { return nullptr; }
1500 const char* getTypeName() const override { return nullptr; }
1501 protected:
1502 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
1503 const SkRect* cullR, const SkMatrix&) const override {
1504 return false;
1505 }
1506 private:
1507 bool computeFastBounds(SkRect* bounds) const override { return false; }
1508
1509 FailurePathEffect() {}
1510 };
1511
1512 SkPaint fill;
1513 TestCase fillCase(geo, fill, reporter);
1514
1515 SkPaint pe;
1516 pe.setPathEffect(FailurePathEffect::Make());
1517 TestCase peCase(geo, pe, reporter);
1518
1519 SkPaint stroke;
1520 stroke.setStrokeWidth(2.f);
1522 TestCase strokeCase(geo, stroke, reporter);
1523
1524 SkPaint peStroke = stroke;
1525 peStroke.setPathEffect(FailurePathEffect::Make());
1526 TestCase peStrokeCase(geo, peStroke, reporter);
1527
1528 // In general the path effect failure can cause some of the TestCase::compare() tests to fail
1529 // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
1530 // path effect, but then when the path effect fails we can key it. 2) GrStyledShape will change
1531 // its mind about whether a unclosed rect is actually rect. The path effect initially bars us
1532 // from closing it but after the effect fails we can (for the fill+pe case). This causes
1533 // different routes through GrStyledShape to have equivalent but different representations of
1534 // the path (closed or not) but that fill the same.
1535 SkPath a;
1536 SkPath b;
1537 fillCase.appliedPathEffectShape().asPath(&a);
1538 peCase.appliedPathEffectShape().asPath(&b);
1540
1541 fillCase.appliedFullStyleShape().asPath(&a);
1542 peCase.appliedFullStyleShape().asPath(&b);
1544
1545 strokeCase.appliedPathEffectShape().asPath(&a);
1546 peStrokeCase.appliedPathEffectShape().asPath(&b);
1548
1549 strokeCase.appliedFullStyleShape().asPath(&a);
1550 peStrokeCase.appliedFullStyleShape().asPath(&b);
1552}
1553
1554DEF_TEST(GrStyledShape_empty_shape, reporter) {
1555 SkPath emptyPath;
1556 SkPath invertedEmptyPath;
1557 invertedEmptyPath.toggleInverseFillType();
1558 SkPaint fill;
1559 TestCase fillEmptyCase(reporter, emptyPath, fill);
1560 REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
1561 REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
1562 REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
1563 REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
1564 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
1565 REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
1566 TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
1567 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
1568 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
1569 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
1570 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
1571 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
1572 REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
1573
1574 const Key& emptyKey = fillEmptyCase.baseKey();
1575 REPORTER_ASSERT(reporter, emptyKey.size());
1576 const Key& inverseEmptyKey = fillInvertedEmptyCase.baseKey();
1577 REPORTER_ASSERT(reporter, inverseEmptyKey.size());
1578 TestCase::SelfExpectations expectations;
1579 expectations.fStrokeApplies = false;
1580 expectations.fPEHasEffect = false;
1581 // This will test whether applying style preserves emptiness
1582 fillEmptyCase.testExpectations(reporter, expectations);
1583 fillInvertedEmptyCase.testExpectations(reporter, expectations);
1584
1585 // Stroking an empty path should have no effect
1586 SkPaint stroke;
1587 stroke.setStrokeWidth(2.f);
1591 TestCase strokeEmptyCase(reporter, emptyPath, stroke);
1592 strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1593 TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
1594 strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
1595 TestCase::kAllSame_ComparisonExpecation);
1596
1597 // Dashing and stroking an empty path should have no effect
1598 SkPaint dashAndStroke;
1599 dashAndStroke.setPathEffect(make_dash());
1600 dashAndStroke.setStrokeWidth(2.f);
1601 dashAndStroke.setStyle(SkPaint::kStroke_Style);
1602 TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
1603 dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
1604 TestCase::kAllSame_ComparisonExpecation);
1605 TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
1606 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1607 dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
1608 TestCase::kAllSame_ComparisonExpecation);
1609
1610 // A shape made from an empty rrect should behave the same as an empty path when filled and
1611 // when stroked. The shape is closed so it does not produce caps when stroked. When dashed there
1612 // is no path to dash along, making it equivalent as well.
1613 SkRRect emptyRRect = SkRRect::MakeEmpty();
1615
1616 TestCase fillEmptyRRectCase(reporter, emptyRRect, fill);
1617 fillEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1618
1619 TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke);
1620 strokeEmptyRRectCase.compare(reporter, strokeEmptyCase,
1621 TestCase::kAllSame_ComparisonExpecation);
1622
1623 TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
1624 dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
1625 TestCase::kAllSame_ComparisonExpecation);
1626
1627 static constexpr SkPathDirection kDir = SkPathDirection::kCCW;
1628 static constexpr int kStart = 0;
1629
1630 TestCase fillInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, GrStyle(fill));
1631 fillInvertedEmptyRRectCase.compare(reporter, fillInvertedEmptyCase,
1632 TestCase::kAllSame_ComparisonExpecation);
1633
1634 TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true,
1635 GrStyle(stroke));
1636 strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase,
1637 TestCase::kAllSame_ComparisonExpecation);
1638
1639 TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
1640 GrStyle(dashAndStroke));
1641 dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
1642 TestCase::kAllSame_ComparisonExpecation);
1643
1644 // Same for a rect.
1645 SkRect emptyRect = SkRect::MakeEmpty();
1646 TestCase fillEmptyRectCase(reporter, emptyRect, fill);
1647 fillEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
1648
1649 TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
1650 dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
1651 TestCase::kAllSame_ComparisonExpecation);
1652
1653 TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
1654 kStart, true, GrStyle(dashAndStroke));
1655 // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
1656 dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
1657 TestCase::kAllSame_ComparisonExpecation);
1658}
1659
1660// rect and oval types have rrect start indices that collapse to the same point. Here we select the
1661// canonical point in these cases.
1662unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) {
1663 switch (rrect.getType()) {
1665 return (s + 1) & 0b110;
1667 return s & 0b110;
1668 default:
1669 return s;
1670 }
1671}
1672
1673void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
1674 enum Style {
1675 kFill,
1676 kStroke,
1677 kHairline,
1678 kStrokeAndFill
1679 };
1680
1681 // SkStrokeRec has no default cons., so init with kFill before calling the setters below.
1684 strokeRecs[kFill].setFillStyle();
1685 strokeRecs[kStroke].setStrokeStyle(2.f);
1686 strokeRecs[kHairline].setHairlineStyle();
1687 strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true);
1688 // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
1689 // applyStyle() is called.
1690 strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
1691 sk_sp<SkPathEffect> dashEffect = make_dash();
1692
1693 static constexpr size_t kStyleCnt = std::size(strokeRecs);
1694
1695 auto index = [](bool inverted,
1696 SkPathDirection dir,
1697 unsigned start,
1698 Style style,
1699 bool dash) -> int {
1700 return inverted * (2 * 8 * kStyleCnt * 2) +
1701 (int)dir * ( 8 * kStyleCnt * 2) +
1702 start * ( kStyleCnt * 2) +
1703 style * ( 2) +
1704 dash;
1705 };
1706 static const SkPathDirection kSecondDirection = static_cast<SkPathDirection>(1);
1707 const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
1708 AutoTArray<GrStyledShape> shapes(cnt);
1709 for (bool inverted : {false, true}) {
1711 for (unsigned start = 0; start < 8; ++start) {
1712 for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
1713 for (bool dash : {false, true}) {
1714 sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
1715 shapes[index(inverted, dir, start, style, dash)] =
1716 GrStyledShape(rrect, dir, start, SkToBool(inverted),
1717 GrStyle(strokeRecs[style], std::move(pe)));
1718 }
1719 }
1720 }
1721 }
1722 }
1723
1724 // Get the keys for some example shape instances that we'll use for comparision against the
1725 // rest.
1726 static constexpr SkPathDirection kExamplesDir = SkPathDirection::kCW;
1727 static constexpr unsigned kExamplesStart = 0;
1728 const GrStyledShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
1729 false)];
1730 Key exampleFillCaseKey;
1731 make_key(&exampleFillCaseKey, exampleFillCase);
1732
1733 const GrStyledShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir,
1734 kExamplesStart, kStrokeAndFill, false)];
1735 Key exampleStrokeAndFillCaseKey;
1736 make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
1737
1738 const GrStyledShape& exampleInvFillCase = shapes[index(true, kExamplesDir,
1739 kExamplesStart, kFill, false)];
1740 Key exampleInvFillCaseKey;
1741 make_key(&exampleInvFillCaseKey, exampleInvFillCase);
1742
1743 const GrStyledShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir,
1744 kExamplesStart, kStrokeAndFill,
1745 false)];
1746 Key exampleInvStrokeAndFillCaseKey;
1747 make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
1748
1749 const GrStyledShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart,
1750 kStroke, false)];
1751 Key exampleStrokeCaseKey;
1752 make_key(&exampleStrokeCaseKey, exampleStrokeCase);
1753
1754 const GrStyledShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart,
1755 kStroke, false)];
1756 Key exampleInvStrokeCaseKey;
1757 make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
1758
1759 const GrStyledShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
1760 kHairline, false)];
1761 Key exampleHairlineCaseKey;
1762 make_key(&exampleHairlineCaseKey, exampleHairlineCase);
1763
1764 const GrStyledShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
1765 kHairline, false)];
1766 Key exampleInvHairlineCaseKey;
1767 make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
1768
1769 // These initializations suppress warnings.
1770 SkRRect queryRR = SkRRect::MakeEmpty();
1772 unsigned queryStart = ~0U;
1773 bool queryInverted = true;
1774
1775 REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1776 REPORTER_ASSERT(r, queryRR == rrect);
1777 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
1778 REPORTER_ASSERT(r, 0 == queryStart);
1779 REPORTER_ASSERT(r, !queryInverted);
1780
1781 REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1782 &queryInverted));
1783 REPORTER_ASSERT(r, queryRR == rrect);
1784 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
1785 REPORTER_ASSERT(r, 0 == queryStart);
1786 REPORTER_ASSERT(r, queryInverted);
1787
1788 REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1789 &queryInverted));
1790 REPORTER_ASSERT(r, queryRR == rrect);
1791 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
1792 REPORTER_ASSERT(r, 0 == queryStart);
1793 REPORTER_ASSERT(r, !queryInverted);
1794
1795 REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
1796 &queryInverted));
1797 REPORTER_ASSERT(r, queryRR == rrect);
1798 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
1799 REPORTER_ASSERT(r, 0 == queryStart);
1800 REPORTER_ASSERT(r, queryInverted);
1801
1802 REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1803 &queryInverted));
1804 REPORTER_ASSERT(r, queryRR == rrect);
1805 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
1806 REPORTER_ASSERT(r, 0 == queryStart);
1807 REPORTER_ASSERT(r, !queryInverted);
1808
1809 REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1810 &queryInverted));
1811 REPORTER_ASSERT(r, queryRR == rrect);
1812 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
1813 REPORTER_ASSERT(r, 0 == queryStart);
1814 REPORTER_ASSERT(r, queryInverted);
1815
1816 REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
1817 REPORTER_ASSERT(r, queryRR == rrect);
1818 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
1819 REPORTER_ASSERT(r, 0 == queryStart);
1820 REPORTER_ASSERT(r, !queryInverted);
1821
1822 REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1823 &queryInverted));
1824 REPORTER_ASSERT(r, queryRR == rrect);
1825 REPORTER_ASSERT(r, SkPathDirection::kCW == queryDir);
1826 REPORTER_ASSERT(r, 0 == queryStart);
1827 REPORTER_ASSERT(r, queryInverted);
1828
1829 // Remember that the key reflects the geometry before styling is applied.
1830 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey);
1831 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey);
1832 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey);
1833 REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey);
1834 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeCaseKey);
1835 REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
1836 REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
1837 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
1838 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
1839 REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
1840
1841 for (bool inverted : {false, true}) {
1843 for (unsigned start = 0; start < 8; ++start) {
1844 for (bool dash : {false, true}) {
1845 const GrStyledShape& fillCase = shapes[index(inverted, dir, start, kFill,
1846 dash)];
1847 Key fillCaseKey;
1848 make_key(&fillCaseKey, fillCase);
1849
1850 const GrStyledShape& strokeAndFillCase = shapes[index(inverted, dir, start,
1851 kStrokeAndFill, dash)];
1852 Key strokeAndFillCaseKey;
1853 make_key(&strokeAndFillCaseKey, strokeAndFillCase);
1854
1855 // Both fill and stroke-and-fill shapes must respect the inverseness and both
1856 // ignore dashing.
1857 REPORTER_ASSERT(r, !fillCase.style().pathEffect());
1858 REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect());
1859 TestCase a(fillCase, r);
1860 TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r);
1861 TestCase c(strokeAndFillCase, r);
1862 TestCase d(inverted ? exampleInvStrokeAndFillCase
1863 : exampleStrokeAndFillCase, r);
1864 a.compare(r, b, TestCase::kAllSame_ComparisonExpecation);
1865 c.compare(r, d, TestCase::kAllSame_ComparisonExpecation);
1866
1867 const GrStyledShape& strokeCase = shapes[index(inverted, dir, start, kStroke,
1868 dash)];
1869 const GrStyledShape& hairlineCase = shapes[index(inverted, dir, start,
1870 kHairline, dash)];
1871
1872 TestCase e(strokeCase, r);
1873 TestCase g(hairlineCase, r);
1874
1875 // Both hairline and stroke shapes must respect the dashing.
1876 if (dash) {
1877 // Dashing always ignores the inverseness. skbug.com/5421
1878 TestCase f(exampleStrokeCase, r);
1879 TestCase h(exampleHairlineCase, r);
1880 unsigned expectedStart = canonicalize_rrect_start(start, rrect);
1881 REPORTER_ASSERT(r, strokeCase.style().pathEffect());
1882 REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
1883
1884 REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
1885 &queryInverted));
1886 REPORTER_ASSERT(r, queryRR == rrect);
1887 REPORTER_ASSERT(r, queryDir == dir);
1888 REPORTER_ASSERT(r, queryStart == expectedStart);
1889 REPORTER_ASSERT(r, !queryInverted);
1890 REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
1891 &queryInverted));
1892 REPORTER_ASSERT(r, queryRR == rrect);
1893 REPORTER_ASSERT(r, queryDir == dir);
1894 REPORTER_ASSERT(r, queryStart == expectedStart);
1895 REPORTER_ASSERT(r, !queryInverted);
1896
1897 // The pre-style case for the dash will match the non-dash example iff the
1898 // dir and start match (dir=cw, start=0).
1899 if (0 == expectedStart && SkPathDirection::kCW == dir) {
1900 e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation);
1901 g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation);
1902 } else {
1903 e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation);
1904 g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation);
1905 }
1906 } else {
1907 TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
1908 TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
1909 REPORTER_ASSERT(r, !strokeCase.style().pathEffect());
1910 REPORTER_ASSERT(r, !hairlineCase.style().pathEffect());
1911 e.compare(r, f, TestCase::kAllSame_ComparisonExpecation);
1912 g.compare(r, h, TestCase::kAllSame_ComparisonExpecation);
1913 }
1914 }
1915 }
1916 }
1917 }
1918}
1919
1920DEF_TEST(GrStyledShape_lines, r) {
1921 static constexpr SkPoint kA { 1, 1};
1922 static constexpr SkPoint kB { 5, -9};
1923 static constexpr SkPoint kC {-3, 17};
1924
1925 SkPath lineAB = SkPath::Line(kA, kB);
1926 SkPath lineBA = SkPath::Line(kB, kA);
1927 SkPath lineAC = SkPath::Line(kB, kC);
1928 SkPath invLineAB = lineAB;
1929
1931
1932 SkPaint fill;
1933 SkPaint stroke;
1935 stroke.setStrokeWidth(2.f);
1936 SkPaint hairline;
1938 hairline.setStrokeWidth(0.f);
1939 SkPaint dash = stroke;
1940 dash.setPathEffect(make_dash());
1941
1942 TestCase fillAB(r, lineAB, fill);
1943 TestCase fillEmpty(r, SkPath(), fill);
1944 fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
1945 REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
1946
1947 SkPath path;
1948 path.toggleInverseFillType();
1949 TestCase fillEmptyInverted(r, path, fill);
1950 TestCase fillABInverted(r, invLineAB, fill);
1951 fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
1952 REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
1953
1954 TestCase strokeAB(r, lineAB, stroke);
1955 TestCase strokeBA(r, lineBA, stroke);
1956 TestCase strokeAC(r, lineAC, stroke);
1957
1958 TestCase hairlineAB(r, lineAB, hairline);
1959 TestCase hairlineBA(r, lineBA, hairline);
1960 TestCase hairlineAC(r, lineAC, hairline);
1961
1962 TestCase dashAB(r, lineAB, dash);
1963 TestCase dashBA(r, lineBA, dash);
1964 TestCase dashAC(r, lineAC, dash);
1965
1966 strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
1967
1968 strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
1969 strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
1970
1971 hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
1972 hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
1973
1974 dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
1975 dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
1976
1977 strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
1978
1979 // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
1980 // GrStyledShape canonicalizes line endpoints (when it can, i.e. when not dashed).
1981 bool canonicalizeAsAB;
1982 SkPoint canonicalPts[2] {kA, kB};
1983 // Init these to suppress warnings.
1984 bool inverted = true;
1985 SkPoint pts[2] {{0, 0}, {0, 0}};
1986 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
1987 if (pts[0] == kA && pts[1] == kB) {
1988 canonicalizeAsAB = true;
1989 } else if (pts[1] == kA && pts[0] == kB) {
1990 canonicalizeAsAB = false;
1991 using std::swap;
1992 swap(canonicalPts[0], canonicalPts[1]);
1993 } else {
1994 ERRORF(r, "Should return pts (a,b) or (b, a)");
1995 return;
1996 }
1997
1998 strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
1999 TestCase::kSameUpToPE_ComparisonExpecation);
2000 REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
2001 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
2002 REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
2003 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
2004 REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
2005 pts[0] == kA && pts[1] == kB);
2006 REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
2007 pts[0] == kB && pts[1] == kA);
2008
2009
2010 TestCase strokeInvAB(r, invLineAB, stroke);
2011 TestCase hairlineInvAB(r, invLineAB, hairline);
2012 TestCase dashInvAB(r, invLineAB, dash);
2013 strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
2014 hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
2015 // Dashing ignores inverse.
2016 dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
2017
2018 REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
2019 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
2020 REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
2021 pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
2022 // Dashing ignores inverse.
2023 REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
2024 pts[0] == kA && pts[1] == kB);
2025
2026}
2027
2028DEF_TEST(GrStyledShape_stroked_lines, r) {
2029 static constexpr SkScalar kIntervals1[] = {1.f, 0.f};
2030 auto dash1 = SkDashPathEffect::Make(kIntervals1, std::size(kIntervals1), 0.f);
2032 static constexpr SkScalar kIntervals2[] = {10.f, 0.f, 5.f, 0.f};
2033 auto dash2 = SkDashPathEffect::Make(kIntervals2, std::size(kIntervals2), 10.f);
2035
2036 sk_sp<SkPathEffect> pathEffects[] = {nullptr, std::move(dash1), std::move(dash2)};
2037
2038 for (const auto& pe : pathEffects) {
2039 // Paints to try
2040 SkPaint buttCap;
2042 buttCap.setStrokeWidth(4);
2044 buttCap.setPathEffect(pe);
2045
2046 SkPaint squareCap = buttCap;
2048 squareCap.setPathEffect(pe);
2049
2050 SkPaint roundCap = buttCap;
2052 roundCap.setPathEffect(pe);
2053
2054 // vertical
2055 SkPath linePath;
2056 linePath.moveTo(4, 4);
2057 linePath.lineTo(4, 5);
2058
2059 SkPaint fill;
2060
2061 make_TestCase(r, linePath, buttCap)->compare(
2062 r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
2063 TestCase::kAllSame_ComparisonExpecation);
2064
2065 make_TestCase(r, linePath, squareCap)->compare(
2066 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
2067 TestCase::kAllSame_ComparisonExpecation);
2068
2069 make_TestCase(r, linePath, roundCap)->compare(r,
2070 TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
2071 TestCase::kAllSame_ComparisonExpecation);
2072
2073 // horizontal
2074 linePath.reset();
2075 linePath.moveTo(4, 4);
2076 linePath.lineTo(5, 4);
2077
2078 make_TestCase(r, linePath, buttCap)->compare(
2079 r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
2080 TestCase::kAllSame_ComparisonExpecation);
2081 make_TestCase(r, linePath, squareCap)->compare(
2082 r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
2083 TestCase::kAllSame_ComparisonExpecation);
2084 make_TestCase(r, linePath, roundCap)->compare(
2085 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
2086 TestCase::kAllSame_ComparisonExpecation);
2087
2088 // point
2089 linePath.reset();
2090 linePath.moveTo(4, 4);
2091 linePath.lineTo(4, 4);
2092
2093 make_TestCase(r, linePath, buttCap)->compare(
2094 r, TestCase(r, SkRect::MakeEmpty(), fill),
2095 TestCase::kAllSame_ComparisonExpecation);
2096 make_TestCase(r, linePath, squareCap)->compare(
2097 r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
2098 TestCase::kAllSame_ComparisonExpecation);
2099 make_TestCase(r, linePath, roundCap)->compare(
2100 r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill),
2101 TestCase::kAllSame_ComparisonExpecation);
2102 }
2103}
2104
2105DEF_TEST(GrStyledShape_short_path_keys, r) {
2106 SkPaint paints[4];
2108 paints[1].setStrokeWidth(5.f);
2110 paints[2].setStrokeWidth(0.f);
2112 paints[3].setStrokeWidth(5.f);
2113
2114 auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
2115 TestCase::ComparisonExpecation expectation) {
2116 SkPath volatileA = pathA;
2117 SkPath volatileB = pathB;
2118 volatileA.setIsVolatile(true);
2119 volatileB.setIsVolatile(true);
2120 for (const SkPaint& paint : paints) {
2121 REPORTER_ASSERT(r, !GrStyledShape(volatileA, paint).hasUnstyledKey());
2122 REPORTER_ASSERT(r, !GrStyledShape(volatileB, paint).hasUnstyledKey());
2123 for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
2124 TestCase caseA(PathGeo(pathA, invert), paint, r);
2125 TestCase caseB(PathGeo(pathB, invert), paint, r);
2126 caseA.compare(r, caseB, expectation);
2127 }
2128 }
2129 };
2130
2131 SkPath pathA;
2132 SkPath pathB;
2133
2134 // Two identical paths
2135 pathA.lineTo(10.f, 10.f);
2136 pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2137
2138 pathB.lineTo(10.f, 10.f);
2139 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2140 compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
2141
2142 // Give path b a different point
2143 pathB.reset();
2144 pathB.lineTo(10.f, 10.f);
2145 pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
2146 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
2147
2148 // Give path b a different conic weight
2149 pathB.reset();
2150 pathB.lineTo(10.f, 10.f);
2151 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2152 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
2153
2154 // Give path b an extra lineTo verb
2155 pathB.reset();
2156 pathB.lineTo(10.f, 10.f);
2157 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
2158 pathB.lineTo(50.f, 50.f);
2159 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
2160
2161 // Give path b a close
2162 pathB.reset();
2163 pathB.lineTo(10.f, 10.f);
2164 pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
2165 pathB.close();
2166 compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
2167}
2168
2172
2173 for (auto r : { SkRect::MakeWH(10, 20),
2174 SkRect::MakeWH(-10, -20),
2175 SkRect::MakeWH(-10, 20),
2176 SkRect::MakeWH(10, -20)}) {
2177 geos.emplace_back(new RectGeo(r));
2178 SkPath rectPath;
2179 rectPath.addRect(r);
2180 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2181 PathGeo::Invert::kNo));
2182 geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2183 PathGeo::Invert::kYes));
2184 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes,
2185 PathGeo::Invert::kNo));
2186 }
2187 for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
2188 SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
2190 geos.emplace_back(new RRectGeo(rr));
2191 test_rrect(reporter, rr);
2192 SkPath rectPath;
2193 rectPath.addRRect(rr);
2194 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2195 PathGeo::Invert::kNo));
2196 geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes,
2197 PathGeo::Invert::kYes));
2198 rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr,
2199 RRectPathGeo::RRectForStroke::kYes,
2200 PathGeo::Invert::kNo));
2201 }
2202
2203 // Arcs
2204 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, false));
2205 geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, true));
2206
2207 {
2208 SkPath openRectPath;
2209 openRectPath.moveTo(0, 0);
2210 openRectPath.lineTo(10, 0);
2211 openRectPath.lineTo(10, 10);
2212 openRectPath.lineTo(0, 10);
2213 geos.emplace_back(new RRectPathGeo(
2214 openRectPath, SkRect::MakeWH(10, 10),
2215 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2216 geos.emplace_back(new RRectPathGeo(
2217 openRectPath, SkRect::MakeWH(10, 10),
2218 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes));
2219 rrectPathGeos.emplace_back(new RRectPathGeo(
2220 openRectPath, SkRect::MakeWH(10, 10),
2221 RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo));
2222 }
2223
2224 {
2225 SkPath quadPath;
2226 quadPath.quadTo(10, 10, 5, 8);
2227 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
2228 geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
2229 }
2230
2231 {
2232 SkPath linePath;
2233 linePath.lineTo(10, 10);
2234 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo));
2235 geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes));
2236 }
2237
2238 // Horizontal and vertical paths become rrects when stroked.
2239 {
2240 SkPath vLinePath;
2241 vLinePath.lineTo(0, 10);
2242 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
2243 geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
2244 }
2245
2246 {
2247 SkPath hLinePath;
2248 hLinePath.lineTo(10, 0);
2249 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
2250 geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
2251 }
2252
2253 for (int i = 0; i < geos.size(); ++i) {
2254 test_basic(reporter, *geos[i]);
2255 test_scale(reporter, *geos[i]);
2256 test_dash_fill(reporter, *geos[i]);
2257 test_null_dash(reporter, *geos[i]);
2258 // Test modifying various stroke params.
2259 test_stroke_param<SkScalar>(
2260 reporter, *geos[i],
2261 [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
2263 test_stroke_join(reporter, *geos[i]);
2264 test_stroke_cap(reporter, *geos[i]);
2265 test_miter_limit(reporter, *geos[i]);
2271 test_volatile_path(reporter, *geos[i]);
2272 }
2273
2274 for (int i = 0; i < rrectPathGeos.size(); ++i) {
2275 const RRectPathGeo& rrgeo = *rrectPathGeos[i];
2276 SkPaint fillPaint;
2277 TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
2278 SkRRect rrect;
2279 REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
2280 fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2281 nullptr));
2282 if (rrgeo.isNonPath(fillPaint)) {
2283 TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint);
2284 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2285 TestCase fillRRectCase(reporter, rrect, fillPaint);
2286 fillPathCase2.compare(reporter, fillRRectCase,
2287 TestCase::kAllSame_ComparisonExpecation);
2288 }
2289 SkPaint strokePaint;
2290 strokePaint.setStrokeWidth(3.f);
2291 strokePaint.setStyle(SkPaint::kStroke_Style);
2292 TestCase strokePathCase(reporter, rrgeo.path(), strokePaint);
2293 if (rrgeo.isNonPath(strokePaint)) {
2294 REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
2295 nullptr));
2296 REPORTER_ASSERT(reporter, rrect == rrgeo.rrect());
2297 TestCase strokeRRectCase(reporter, rrect, strokePaint);
2298 strokePathCase.compare(reporter, strokeRRectCase,
2299 TestCase::kAllSame_ComparisonExpecation);
2300 }
2301 }
2302
2303 // Test a volatile empty path.
2304 test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
2305}
2306
2307DEF_TEST(GrStyledShape_arcs, reporter) {
2309 roundStroke.setStrokeStyle(2.f);
2311
2312 SkStrokeRec squareStroke(roundStroke);
2314
2315 SkStrokeRec roundStrokeAndFill(roundStroke);
2316 roundStrokeAndFill.setStrokeStyle(2.f, true);
2317
2318 static constexpr SkScalar kIntervals[] = {1, 2};
2319 auto dash = SkDashPathEffect::Make(kIntervals, std::size(kIntervals), 1.5f);
2320
2321 TArray<GrStyle> styles;
2324 styles.push_back(GrStyle(roundStroke, nullptr));
2325 styles.push_back(GrStyle(squareStroke, nullptr));
2326 styles.push_back(GrStyle(roundStrokeAndFill, nullptr));
2327 styles.push_back(GrStyle(roundStroke, dash));
2328
2329 for (const auto& style : styles) {
2330 // An empty rect never draws anything according to SkCanvas::drawArc() docs.
2331 TestCase emptyArc(GrStyledShape::MakeArc(SkRect::MakeEmpty(), 0, 90.f, false, style),
2332 reporter);
2333 TestCase emptyPath(reporter, SkPath(), style);
2334 emptyArc.compare(reporter, emptyPath, TestCase::kAllSame_ComparisonExpecation);
2335
2336 static constexpr SkRect kOval1{0, 0, 50, 50};
2337 static constexpr SkRect kOval2{50, 0, 100, 50};
2338 // Test that swapping starting and ending angle doesn't change the shape unless the arc
2339 // has a path effect. Also test that different ovals produce different shapes.
2340 TestCase arc1CW(GrStyledShape::MakeArc(kOval1, 0, 90.f, false, style), reporter);
2341 TestCase arc1CCW(GrStyledShape::MakeArc(kOval1, 90.f, -90.f, false, style), reporter);
2342
2343 TestCase arc1CWWithCenter(GrStyledShape::MakeArc(kOval1, 0, 90.f, true, style), reporter);
2344 TestCase arc1CCWWithCenter(GrStyledShape::MakeArc(kOval1, 90.f, -90.f, true, style),
2345 reporter);
2346
2347 TestCase arc2CW(GrStyledShape::MakeArc(kOval2, 0, 90.f, false, style), reporter);
2348 TestCase arc2CWWithCenter(GrStyledShape::MakeArc(kOval2, 0, 90.f, true, style), reporter);
2349
2350 auto reversedExepectations = style.hasPathEffect()
2351 ? TestCase::kAllDifferent_ComparisonExpecation
2352 : TestCase::kAllSame_ComparisonExpecation;
2353 arc1CW.compare(reporter, arc1CCW, reversedExepectations);
2354 arc1CWWithCenter.compare(reporter, arc1CCWWithCenter, reversedExepectations);
2355 arc1CW.compare(reporter, arc2CW, TestCase::kAllDifferent_ComparisonExpecation);
2356 arc1CW.compare(reporter, arc1CWWithCenter, TestCase::kAllDifferent_ComparisonExpecation);
2357 arc1CWWithCenter.compare(reporter, arc2CWWithCenter,
2358 TestCase::kAllDifferent_ComparisonExpecation);
2359
2360 // Test that two arcs that start at the same angle but specified differently are equivalent.
2361 TestCase arc3A(GrStyledShape::MakeArc(kOval1, 224.f, 73.f, false, style), reporter);
2362 TestCase arc3B(GrStyledShape::MakeArc(kOval1, 224.f - 360.f, 73.f, false, style), reporter);
2363 arc3A.compare(reporter, arc3B, TestCase::kAllDifferent_ComparisonExpecation);
2364
2365 // Test that an arc that traverses the entire oval (and then some) is equivalent to the
2366 // oval itself unless there is a path effect.
2367 TestCase ovalArc(GrStyledShape::MakeArc(kOval1, 150.f, -790.f, false, style), reporter);
2369 auto ovalExpectations = style.hasPathEffect() ? TestCase::kAllDifferent_ComparisonExpecation
2370 : TestCase::kAllSame_ComparisonExpecation;
2371 if (style.strokeRec().getWidth() >= 0 && style.strokeRec().getCap() != SkPaint::kButt_Cap) {
2372 ovalExpectations = TestCase::kAllDifferent_ComparisonExpecation;
2373 }
2374 ovalArc.compare(reporter, oval, ovalExpectations);
2375
2376 // If the the arc starts/ends at the center then it is then equivalent to the oval only for
2377 // simple fills.
2378 TestCase ovalArcWithCenter(GrStyledShape::MakeArc(kOval1, 304.f, 1225.f, true, style),
2379 reporter);
2380 ovalExpectations = style.isSimpleFill() ? TestCase::kAllSame_ComparisonExpecation
2381 : TestCase::kAllDifferent_ComparisonExpecation;
2382 ovalArcWithCenter.compare(reporter, oval, ovalExpectations);
2383 }
2384}
2385
2386DEF_TEST(GrShapeInversion, r) {
2387 SkPath path;
2388 SkScalar radii[] = {10.f, 10.f, 10.f, 10.f,
2389 10.f, 10.f, 10.f, 10.f};
2390 path.addRoundRect(SkRect::MakeWH(50, 50), radii);
2391 path.toggleInverseFillType();
2392
2393 GrShape inverseRRect(path);
2394 GrShape rrect(inverseRRect);
2395 rrect.setInverted(false);
2396
2397 REPORTER_ASSERT(r, inverseRRect.inverted() && inverseRRect.isPath());
2398 REPORTER_ASSERT(r, !rrect.inverted() && rrect.isPath());
2399
2400 // Invertedness should be preserved after simplification
2401 inverseRRect.simplify();
2402 rrect.simplify();
2403
2404 REPORTER_ASSERT(r, inverseRRect.inverted() && inverseRRect.isRRect());
2405 REPORTER_ASSERT(r, !rrect.inverted() && rrect.isRRect());
2406
2407 // Invertedness should be reset when calling reset().
2408 inverseRRect.reset();
2409 REPORTER_ASSERT(r, !inverseRRect.inverted() && inverseRRect.isEmpty());
2410 inverseRRect.setPath(path);
2411 inverseRRect.reset();
2412 REPORTER_ASSERT(r, !inverseRRect.inverted() && inverseRRect.isEmpty());
2413}
SkPath fPath
static bool compare(const SkBitmap &ref, const SkIRect &iref, const SkBitmap &test, const SkIRect &itest)
Definition BlurTest.cpp:100
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
SkRRect fRRect
SkRect fRect
reporter
static bool paths_fill_same(const SkPath &a, const SkPath &b)
static void test_stroke_cap(skiatest::Reporter *reporter, const Geo &geo)
static void test_stroke_join(skiatest::Reporter *reporter, const Geo &geo)
void test_path_effect_makes_empty_shape(skiatest::Reporter *reporter, const Geo &geo)
unsigned canonicalize_rrect_start(int s, const SkRRect &rrect)
static void test_scale(skiatest::Reporter *reporter, const Geo &geo)
void test_make_hairline_path_effect(skiatest::Reporter *reporter, const Geo &geo)
void test_path_effect_makes_rrect(skiatest::Reporter *reporter, const Geo &geo)
static void test_basic(skiatest::Reporter *reporter, const Geo &geo)
void test_rrect(skiatest::Reporter *r, const SkRRect &rrect)
static void test_stroke_param(skiatest::Reporter *reporter, const Geo &geo, std::function< void(SkPaint *, T)> setter, T a, T b)
void test_inversions(skiatest::Reporter *r, const GrStyledShape &shape, const Key &shapeKey)
static void test_stroke_param_impl(skiatest::Reporter *reporter, const Geo &geo, std::function< void(SkPaint *, T)> setter, T a, T b, bool paramAffectsStroke, bool paramAffectsDashAndStroke)
static sk_sp< SkPathEffect > make_null_dash()
static void check_original_path_ids(skiatest::Reporter *r, const GrStyledShape &base, const GrStyledShape &pe, const GrStyledShape &peStroke, const GrStyledShape &full)
static bool shape_known_not_to_have_joins(const GrStyledShape &shape)
static std::unique_ptr< TestCase > make_TestCase(Args &&... args)
static void check_equivalence(skiatest::Reporter *r, const GrStyledShape &a, const GrStyledShape &b, const Key &keyA, const Key &keyB)
static bool make_key(Key *key, const GrStyledShape &shape)
void test_volatile_path(skiatest::Reporter *reporter, const Geo &geo)
void test_unknown_path_effect(skiatest::Reporter *reporter, const Geo &geo)
static bool can_interchange_winding_and_even_odd_fill(const GrStyledShape &shape)
static void test_dash_fill(skiatest::Reporter *reporter, const Geo &geo)
static bool test_bounds_by_rasterizing(const SkPath &path, const SkRect &bounds)
void test_null_dash(skiatest::Reporter *reporter, const Geo &geo)
static sk_sp< SkPathEffect > make_dash()
static void test_miter_limit(skiatest::Reporter *reporter, const Geo &geo)
void test_path_effect_fails(skiatest::Reporter *reporter, const Geo &geo)
@ kC
#define SkASSERT(cond)
Definition SkAssert.h:116
constexpr SkColor SK_ColorWHITE
Definition SkColor.h:122
static std::unique_ptr< SkEncoder > Make(SkWStream *dst, const SkPixmap *src, const SkYUVAPixmaps *srcYUVA, const SkColorSpace *srcYUVAColorSpace, const SkJpegEncoder::Options &options)
@ kYes
Do pre-clip the geometry before applying the (perspective) matrix.
@ kNo
Don't pre-clip the geometry before applying the (perspective) matrix.
static SkPDFGradientShader::Key make_key(const SkShader *shader, const SkMatrix &canvasTransform, const SkIRect &bbox)
@ kXOR_SkPathOp
exclusive-or the two paths
Definition SkPathOps.h:26
SkPathDirection
Definition SkPathTypes.h:34
static SkPathFillType SkPathFillType_ConvertToNonInverse(SkPathFillType ft)
Definition SkPathTypes.h:30
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition SkPath.cpp:3824
@ kRRect
#define SK_Scalar1
Definition SkScalar.h:18
#define SkIntToScalar(x)
Definition SkScalar.h:57
#define SK_ScalarSqrt2
Definition SkScalar.h:20
static constexpr bool SkToBool(const T &x)
Definition SkTo.h:35
#define DEF_TEST(name, reporter)
Definition Test.h:312
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
#define ERRORF(r,...)
Definition Test.h:293
bool isPath() const
Definition GrShape.h:88
void reset()
Definition GrShape.h:188
bool isRRect() const
Definition GrShape.h:87
SkPath & path()
Definition GrShape.h:140
void setPath(const SkPath &path)
Definition GrShape.h:175
bool inverted() const
Definition GrShape.h:99
bool isEmpty() const
Definition GrShape.h:84
bool simplify(unsigned flags=kAll_Flags)
Definition GrShape.cpp:242
bool isDashed() const
Definition GrStyle.h:126
static const GrStyle & SimpleHairline()
Definition GrStyle.h:39
SkPathEffect * pathEffect() const
Definition GrStyle.h:119
static const GrStyle & SimpleFill()
Definition GrStyle.h:30
bool hasNonDashPathEffect() const
Definition GrStyle.h:124
bool isSimpleFill() const
Definition GrStyle.h:114
const SkStrokeRec & strokeRec() const
Definition GrStyle.h:140
bool knownToBeClosed() const
void asPath(SkPath *out) const
void writeUnstyledKey(uint32_t *key) const
bool isEmpty() const
int unstyledKeySize() const
bool asLine(SkPoint pts[2], bool *inverted) const
bool inverseFilled() const
const GrStyle & style() const
static GrStyledShape MakeArc(const SkRect &oval, SkScalar startAngleDegrees, SkScalar sweepAngleDegrees, bool useCenter, const GrStyle &style, DoSimplify=DoSimplify::kYes)
bool testingOnly_isPath() const
bool testingOnly_isNonVolatilePath() const
uint32_t testingOnly_getOriginalGenerationID() const
static GrStyledShape MakeFilled(const GrStyledShape &original, FillInversion=FillInversion::kPreserve)
bool asRRect(SkRRect *rrect, SkPathDirection *dir, unsigned *start, bool *inverted) const
static sk_sp< SkPathEffect > Make(const SkScalar intervals[], int count, SkScalar phase)
virtual const char * getTypeName() const =0
virtual Factory getFactory() const =0
static SkMatrix RectToRect(const SkRect &src, const SkRect &dst, ScaleToFit mode=kFill_ScaleToFit)
Definition SkMatrix.h:157
@ kRound_Cap
adds circle
Definition SkPaint.h:335
@ kButt_Cap
no stroke extension
Definition SkPaint.h:334
@ kSquare_Cap
adds square
Definition SkPaint.h:336
void setStyle(Style style)
Definition SkPaint.cpp:105
void setColor(SkColor color)
Definition SkPaint.cpp:119
void setStrokeCap(Cap cap)
Definition SkPaint.cpp:179
@ kStroke_Style
set to stroke geometry
Definition SkPaint.h:194
@ kFill_Style
set to fill geometry
Definition SkPaint.h:193
@ kStrokeAndFill_Style
sets to stroke and fill geometry
Definition SkPaint.h:195
void setStrokeJoin(Join join)
Definition SkPaint.cpp:189
void setPathEffect(sk_sp< SkPathEffect > pathEffect)
@ kRound_Join
adds circle
Definition SkPaint.h:360
@ kMiter_Join
extends to miter limit
Definition SkPaint.h:359
@ kBevel_Join
connects outside edges
Definition SkPaint.h:361
void setStrokeWidth(SkScalar width)
Definition SkPaint.cpp:159
virtual bool computeFastBounds(SkRect *bounds) const =0
virtual bool onFilterPath(SkPath *, const SkPath &, SkStrokeRec *, const SkRect *, const SkMatrix &) const =0
static bool IsSimpleRect(const SkPath &path, bool isSimpleFill, SkRect *rect, SkPathDirection *direction, unsigned *start)
Definition SkPath.cpp:3180
static void CreateDrawArcPath(SkPath *path, const SkRect &oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect)
Definition SkPath.cpp:3290
bool isEmpty() const
Definition SkPath.cpp:406
bool isInverseFillType() const
Definition SkPath.h:244
uint32_t getGenerationID() const
Definition SkPath.cpp:356
SkPath & moveTo(SkScalar x, SkScalar y)
Definition SkPath.cpp:678
bool isLine(SkPoint line[2]) const
Definition SkPath.cpp:388
SkPathFillType getFillType() const
Definition SkPath.h:230
void setFillType(SkPathFillType ft)
Definition SkPath.h:235
SkPath & lineTo(SkScalar x, SkScalar y)
Definition SkPath.cpp:718
SkPath & addRRect(const SkRRect &rrect, SkPathDirection dir=SkPathDirection::kCW)
Definition SkPath.cpp:990
void toggleInverseFillType()
Definition SkPath.h:249
bool isVolatile() const
Definition SkPath.h:350
SkPath & setIsVolatile(bool isVolatile)
Definition SkPath.h:370
SkPath & reset()
Definition SkPath.cpp:360
SkPath & quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2)
Definition SkPath.cpp:736
bool isConvex() const
Definition SkPath.cpp:416
SkPath & conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar w)
Definition SkPath.cpp:756
SkPath & close()
Definition SkPath.cpp:813
bool isRect(SkRect *rect, bool *isClosed=nullptr, SkPathDirection *direction=nullptr) const
Definition SkPath.cpp:506
static SkPath Line(const SkPoint a, const SkPoint b)
Definition SkPath.h:106
SkPath & addRect(const SkRect &rect, SkPathDirection dir, unsigned start)
Definition SkPath.cpp:854
const uint8_t * addr8() const
Definition SkPixmap.h:326
Type getType() const
Definition SkRRect.h:76
const SkRect & rect() const
Definition SkRRect.h:264
@ kOval_Type
non-zero width and height filled with radii
Definition SkRRect.h:69
@ kEmpty_Type
zero width or height
Definition SkRRect.h:67
@ kRect_Type
non-zero width and height, and zeroed radii
Definition SkRRect.h:68
static SkRRect MakeOval(const SkRect &oval)
Definition SkRRect.h:162
static SkRRect MakeRect(const SkRect &r)
Definition SkRRect.h:149
static SkRRect MakeRectXY(const SkRect &rect, SkScalar xRad, SkScalar yRad)
Definition SkRRect.h:180
bool isRect() const
Definition SkRRect.h:84
static SkRRect MakeEmpty()
Definition SkRRect.h:142
static void GrowToInclude(SkRect *r, const SkPoint &pt)
Definition SkRectPriv.h:47
Style getStyle() const
void setHairlineStyle()
void setStrokeStyle(SkScalar width, bool strokeAndFill=false)
void setFillStyle()
void setStrokeParams(SkPaint::Cap cap, SkPaint::Join join, SkScalar miterLimit)
Definition SkStrokeRec.h:65
int size() const
Definition SkTArray.h:416
T & emplace_back(Args &&... args)
Definition SkTArray.h:243
const Paint & paint
int dash2[]
int dash1[]
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition main.cc:19
VkSurfaceKHR surface
Definition main.cc:49
float SkScalar
Definition extension.cpp:12
static bool b
struct MyStruct s
struct MyStruct a[10]
gboolean invert
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
double y
Optional< SkRect > bounds
Definition SkRecords.h:189
SkRRect rrect
Definition SkRecords.h:232
SkRect oval
Definition SkRecords.h:249
sk_sp< SkBlender > blender SkRect rect
Definition SkRecords.h:350
SkScalar startAngle
Definition SkRecords.h:250
SkScalar sweepAngle
Definition SkRecords.h:251
SK_API sk_sp< SkSurface > Raster(const SkImageInfo &imageInfo, size_t rowBytes, const SkSurfaceProps *surfaceProps)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switches.h:57
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets dir
Definition switches.h:145
Definition full.py:1
static void swap(TArray< T, M > &a, TArray< T, M > &b)
Definition SkTArray.h:737
init(device_serial, adb_binary)
Definition _adb_path.py:12
SkScalar w
SkScalar h
const Scalar scale
static SkImageInfo MakeA8(int width, int height)
float fX
x-axis value
float fY
y-axis value
static constexpr SkRect MakeEmpty()
Definition SkRect.h:595
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition SkRect.h:659
static constexpr SkRect MakeWH(float w, float h)
Definition SkRect.h:609
static constexpr SkRect MakeLTRB(float l, float t, float r, float b)
Definition SkRect.h:646