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