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