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