1 | /* |
2 | * Copyright 2020 Google Inc. |
3 | * |
4 | * Use of this source code is governed by a BSD-style license that can be |
5 | * found in the LICENSE file. |
6 | */ |
7 | |
8 | #include "modules/sksg/include/SkSGGeometryEffect.h" |
9 | |
10 | #include "include/core/SkCanvas.h" |
11 | #include "include/core/SkStrokeRec.h" |
12 | #include "include/effects/SkCornerPathEffect.h" |
13 | #include "include/effects/SkDashPathEffect.h" |
14 | #include "include/effects/SkTrimPathEffect.h" |
15 | #include "include/pathops/SkPathOps.h" |
16 | #include "modules/sksg/src/SkSGTransformPriv.h" |
17 | |
18 | #include <cmath> |
19 | |
20 | namespace sksg { |
21 | |
22 | GeometryEffect::GeometryEffect(sk_sp<GeometryNode> child) |
23 | : fChild(std::move(child)) { |
24 | SkASSERT(fChild); |
25 | |
26 | this->observeInval(fChild); |
27 | } |
28 | |
29 | GeometryEffect::~GeometryEffect() { |
30 | this->unobserveInval(fChild); |
31 | } |
32 | |
33 | void GeometryEffect::onClip(SkCanvas* canvas, bool antiAlias) const { |
34 | canvas->clipPath(fPath, SkClipOp::kIntersect, antiAlias); |
35 | } |
36 | |
37 | void GeometryEffect::onDraw(SkCanvas* canvas, const SkPaint& paint) const { |
38 | canvas->drawPath(fPath, paint); |
39 | } |
40 | |
41 | bool GeometryEffect::onContains(const SkPoint& p) const { |
42 | return fPath.contains(p.x(), p.y()); |
43 | } |
44 | |
45 | SkPath GeometryEffect::onAsPath() const { |
46 | return fPath; |
47 | } |
48 | |
49 | SkRect GeometryEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) { |
50 | SkASSERT(this->hasInval()); |
51 | |
52 | fChild->revalidate(ic, ctm); |
53 | |
54 | fPath = this->onRevalidateEffect(fChild); |
55 | fPath.shrinkToFit(); |
56 | |
57 | return fPath.computeTightBounds(); |
58 | } |
59 | |
60 | SkPath TrimEffect::onRevalidateEffect(const sk_sp<GeometryNode>& child) { |
61 | SkPath path = child->asPath(); |
62 | |
63 | if (const auto trim = SkTrimPathEffect::Make(fStart, fStop, fMode)) { |
64 | SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle); |
65 | SkAssertResult(trim->filterPath(&path, path, &rec, nullptr)); |
66 | } |
67 | |
68 | return path; |
69 | } |
70 | |
71 | GeometryTransform::GeometryTransform(sk_sp<GeometryNode> child, sk_sp<Transform> transform) |
72 | : INHERITED(std::move(child)) |
73 | , fTransform(std::move(transform)) { |
74 | SkASSERT(fTransform); |
75 | this->observeInval(fTransform); |
76 | } |
77 | |
78 | GeometryTransform::~GeometryTransform() { |
79 | this->unobserveInval(fTransform); |
80 | } |
81 | |
82 | SkPath GeometryTransform::onRevalidateEffect(const sk_sp<GeometryNode>& child) { |
83 | fTransform->revalidate(nullptr, SkMatrix::I()); |
84 | const auto m = TransformPriv::As<SkMatrix>(fTransform); |
85 | |
86 | SkPath path = child->asPath(); |
87 | path.transform(m); |
88 | |
89 | return path; |
90 | } |
91 | |
92 | namespace { |
93 | |
94 | sk_sp<SkPathEffect> make_dash(const std::vector<float> intervals, float phase) { |
95 | if (intervals.empty()) { |
96 | return nullptr; |
97 | } |
98 | |
99 | const auto* intervals_ptr = intervals.data(); |
100 | auto intervals_count = intervals.size(); |
101 | |
102 | SkSTArray<32, float, true> storage; |
103 | if (intervals_count & 1) { |
104 | intervals_count *= 2; |
105 | storage.resize(intervals_count); |
106 | intervals_ptr = storage.data(); |
107 | |
108 | std::copy(intervals.begin(), intervals.end(), storage.begin()); |
109 | std::copy(intervals.begin(), intervals.end(), storage.begin() + intervals.size()); |
110 | } |
111 | |
112 | return SkDashPathEffect::Make(intervals_ptr, SkToInt(intervals_count), phase); |
113 | } |
114 | |
115 | } // namespace |
116 | |
117 | SkPath DashEffect::onRevalidateEffect(const sk_sp<GeometryNode>& child) { |
118 | SkPath path = child->asPath(); |
119 | |
120 | if (const auto dash_patheffect = make_dash(fIntervals, fPhase)) { |
121 | SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle); |
122 | dash_patheffect->filterPath(&path, path, &rec, nullptr); |
123 | } |
124 | |
125 | return path; |
126 | } |
127 | |
128 | SkPath RoundEffect::onRevalidateEffect(const sk_sp<GeometryNode>& child) { |
129 | SkPath path = child->asPath(); |
130 | |
131 | if (const auto round = SkCornerPathEffect::Make(fRadius)) { |
132 | SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle); |
133 | SkAssertResult(round->filterPath(&path, path, &rec, nullptr)); |
134 | } |
135 | |
136 | return path; |
137 | } |
138 | |
139 | SkPath OffsetEffect::onRevalidateEffect(const sk_sp<GeometryNode>& child) { |
140 | SkPath path = child->asPath(); |
141 | |
142 | if (!SkScalarNearlyZero(fOffset)) { |
143 | SkPaint paint; |
144 | paint.setStyle(SkPaint::kStroke_Style); |
145 | paint.setStrokeWidth(std::abs(fOffset) * 2); |
146 | paint.setStrokeMiter(fMiterLimit); |
147 | paint.setStrokeJoin(fJoin); |
148 | |
149 | SkPath fill_path; |
150 | paint.getFillPath(path, &fill_path, nullptr); |
151 | |
152 | if (fOffset > 0) { |
153 | Op(path, fill_path, kUnion_SkPathOp, &path); |
154 | } else { |
155 | Op(path, fill_path, kDifference_SkPathOp, &path); |
156 | } |
157 | |
158 | // TODO: this seems to break path combining (winding mismatch?) |
159 | // Simplify(path, &path); |
160 | } |
161 | |
162 | return path; |
163 | } |
164 | |
165 | } // namespace sksg |
166 | |