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
20namespace sksg {
21
22GeometryEffect::GeometryEffect(sk_sp<GeometryNode> child)
23 : fChild(std::move(child)) {
24 SkASSERT(fChild);
25
26 this->observeInval(fChild);
27}
28
29GeometryEffect::~GeometryEffect() {
30 this->unobserveInval(fChild);
31}
32
33void GeometryEffect::onClip(SkCanvas* canvas, bool antiAlias) const {
34 canvas->clipPath(fPath, SkClipOp::kIntersect, antiAlias);
35}
36
37void GeometryEffect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
38 canvas->drawPath(fPath, paint);
39}
40
41bool GeometryEffect::onContains(const SkPoint& p) const {
42 return fPath.contains(p.x(), p.y());
43}
44
45SkPath GeometryEffect::onAsPath() const {
46 return fPath;
47}
48
49SkRect 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
60SkPath 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
71GeometryTransform::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
78GeometryTransform::~GeometryTransform() {
79 this->unobserveInval(fTransform);
80}
81
82SkPath 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
92namespace {
93
94sk_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
117SkPath 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
128SkPath 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
139SkPath 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