1 /*
2 * Copyright 2006 The Android Open Source Project
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 "include/effects/Sk1DPathEffect.h"
9
10 #include "include/core/SkFlattenable.h"
11 #include "include/core/SkMatrix.h"
12 #include "include/core/SkPath.h"
13 #include "include/core/SkPathEffect.h"
14 #include "include/core/SkPathMeasure.h"
15 #include "include/core/SkPoint.h"
16 #include "include/core/SkRefCnt.h"
17 #include "include/core/SkScalar.h"
18 #include "include/core/SkStrokeRec.h"
19 #include "include/core/SkTypes.h"
20 #include "include/private/base/SkFloatingPoint.h"
21 #include "src/core/SkPathEffectBase.h"
22 #include "src/core/SkReadBuffer.h"
23 #include "src/core/SkWriteBuffer.h"
24
25 struct SkRect;
26
27 // Since we are stepping by a float, the do/while loop might go on forever (or nearly so).
28 // Put in a governor to limit crash values from looping too long (and allocating too much ram).
29 #define MAX_REASONABLE_ITERATIONS 100000
30
31 class Sk1DPathEffect : public SkPathEffectBase {
32 public:
33 protected:
onFilterPath(SkPath * dst,const SkPath & src,SkStrokeRec *,const SkRect *,const SkMatrix &) const34 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*,
35 const SkMatrix&) const override {
36 SkPathMeasure meas(src, false);
37 do {
38 int governor = MAX_REASONABLE_ITERATIONS;
39 SkScalar length = meas.getLength();
40 SkScalar distance = this->begin(length);
41 while (distance < length && --governor >= 0) {
42 SkScalar delta = this->next(dst, distance, meas);
43 if (delta <= 0) {
44 break;
45 }
46 distance += delta;
47 }
48 if (governor < 0) {
49 return false;
50 }
51 } while (meas.nextContour());
52 return true;
53 }
54
55 /** Called at the start of each contour, returns the initial offset
56 into that contour.
57 */
58 virtual SkScalar begin(SkScalar contourLength) const = 0;
59 /** Called with the current distance along the path, with the current matrix
60 for the point/tangent at the specified distance.
61 Return the distance to travel for the next call. If return <= 0, then that
62 contour is done.
63 */
64 virtual SkScalar next(SkPath* dst, SkScalar dist, SkPathMeasure&) const = 0;
65
66 private:
67 // For simplicity, assume fast bounds cannot be computed
computeFastBounds(SkRect *) const68 bool computeFastBounds(SkRect*) const override { return false; }
69 };
70
71 ///////////////////////////////////////////////////////////////////////////////
72
73 class SkPath1DPathEffectImpl : public Sk1DPathEffect {
74 public:
SkPath1DPathEffectImpl(const SkPath & path,SkScalar advance,SkScalar phase,SkPath1DPathEffect::Style style)75 SkPath1DPathEffectImpl(const SkPath& path, SkScalar advance, SkScalar phase,
76 SkPath1DPathEffect::Style style) : fPath(path) {
77 SkASSERT(advance > 0 && !path.isEmpty());
78
79 // Make the path thread-safe.
80 fPath.updateBoundsCache();
81 (void)fPath.getGenerationID();
82
83 // cleanup their phase parameter, inverting it so that it becomes an
84 // offset along the path (to match the interpretation in PostScript)
85 if (phase < 0) {
86 phase = -phase;
87 if (phase > advance) {
88 phase = SkScalarMod(phase, advance);
89 }
90 } else {
91 if (phase > advance) {
92 phase = SkScalarMod(phase, advance);
93 }
94 phase = advance - phase;
95 }
96 // now catch the edge case where phase == advance (within epsilon)
97 if (phase >= advance) {
98 phase = 0;
99 }
100 SkASSERT(phase >= 0);
101
102 fAdvance = advance;
103 fInitialOffset = phase;
104 fStyle = style;
105 }
106
onFilterPath(SkPath * dst,const SkPath & src,SkStrokeRec * rec,const SkRect * cullRect,const SkMatrix & ctm) const107 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
108 const SkRect* cullRect, const SkMatrix& ctm) const override {
109 rec->setFillStyle();
110 return this->INHERITED::onFilterPath(dst, src, rec, cullRect, ctm);
111 }
112
begin(SkScalar contourLength) const113 SkScalar begin(SkScalar contourLength) const override {
114 return fInitialOffset;
115 }
116
117 SkScalar next(SkPath*, SkScalar, SkPathMeasure&) const override;
118
CreateProc(SkReadBuffer & buffer)119 static sk_sp<SkFlattenable> CreateProc(SkReadBuffer& buffer) {
120 SkScalar advance = buffer.readScalar();
121 SkPath path;
122 buffer.readPath(&path);
123 SkScalar phase = buffer.readScalar();
124 SkPath1DPathEffect::Style style = buffer.read32LE(SkPath1DPathEffect::kLastEnum_Style);
125 return buffer.isValid() ? SkPath1DPathEffect::Make(path, advance, phase, style) : nullptr;
126 }
127
flatten(SkWriteBuffer & buffer) const128 void flatten(SkWriteBuffer& buffer) const override {
129 buffer.writeScalar(fAdvance);
130 buffer.writePath(fPath);
131 buffer.writeScalar(fInitialOffset);
132 buffer.writeUInt(fStyle);
133 }
134
getFactory() const135 Factory getFactory() const override { return CreateProc; }
getTypeName() const136 const char* getTypeName() const override { return "SkPath1DPathEffect"; }
137
138 private:
139 SkPath fPath; // copied from constructor
140 SkScalar fAdvance; // copied from constructor
141 SkScalar fInitialOffset; // computed from phase
142 SkPath1DPathEffect::Style fStyle; // copied from constructor
143
144 using INHERITED = Sk1DPathEffect;
145 };
146
morphpoints(SkPoint dst[],const SkPoint src[],int count,SkPathMeasure & meas,SkScalar dist)147 static bool morphpoints(SkPoint dst[], const SkPoint src[], int count,
148 SkPathMeasure& meas, SkScalar dist) {
149 for (int i = 0; i < count; i++) {
150 SkPoint pos;
151 SkVector tangent;
152
153 SkScalar sx = src[i].fX;
154 SkScalar sy = src[i].fY;
155
156 if (!meas.getPosTan(dist + sx, &pos, &tangent)) {
157 return false;
158 }
159
160 SkMatrix matrix;
161 SkPoint pt;
162
163 pt.set(sx, sy);
164 matrix.setSinCos(tangent.fY, tangent.fX, 0, 0);
165 matrix.preTranslate(-sx, 0);
166 matrix.postTranslate(pos.fX, pos.fY);
167 matrix.mapPoints(&dst[i], &pt, 1);
168 }
169 return true;
170 }
171
172 /* TODO
173
174 Need differentially more subdivisions when the follow-path is curvy. Not sure how to
175 determine that, but we need it. I guess a cheap answer is let the caller tell us,
176 but that seems like a cop-out. Another answer is to get Rob Johnson to figure it out.
177 */
morphpath(SkPath * dst,const SkPath & src,SkPathMeasure & meas,SkScalar dist)178 static void morphpath(SkPath* dst, const SkPath& src, SkPathMeasure& meas,
179 SkScalar dist) {
180 SkPath::Iter iter(src, false);
181 SkPoint srcP[4], dstP[3];
182 SkPath::Verb verb;
183
184 while ((verb = iter.next(srcP)) != SkPath::kDone_Verb) {
185 switch (verb) {
186 case SkPath::kMove_Verb:
187 if (morphpoints(dstP, srcP, 1, meas, dist)) {
188 dst->moveTo(dstP[0]);
189 }
190 break;
191 case SkPath::kLine_Verb:
192 srcP[2] = srcP[1];
193 srcP[1].set(SkScalarAve(srcP[0].fX, srcP[2].fX),
194 SkScalarAve(srcP[0].fY, srcP[2].fY));
195 [[fallthrough]];
196 case SkPath::kQuad_Verb:
197 if (morphpoints(dstP, &srcP[1], 2, meas, dist)) {
198 dst->quadTo(dstP[0], dstP[1]);
199 }
200 break;
201 case SkPath::kConic_Verb:
202 if (morphpoints(dstP, &srcP[1], 2, meas, dist)) {
203 dst->conicTo(dstP[0], dstP[1], iter.conicWeight());
204 }
205 break;
206 case SkPath::kCubic_Verb:
207 if (morphpoints(dstP, &srcP[1], 3, meas, dist)) {
208 dst->cubicTo(dstP[0], dstP[1], dstP[2]);
209 }
210 break;
211 case SkPath::kClose_Verb:
212 dst->close();
213 break;
214 default:
215 SkDEBUGFAIL("unknown verb");
216 break;
217 }
218 }
219 }
220
next(SkPath * dst,SkScalar distance,SkPathMeasure & meas) const221 SkScalar SkPath1DPathEffectImpl::next(SkPath* dst, SkScalar distance,
222 SkPathMeasure& meas) const {
223 #if defined(SK_BUILD_FOR_FUZZER)
224 if (dst->countPoints() > 100000) {
225 return fAdvance;
226 }
227 #endif
228 switch (fStyle) {
229 case SkPath1DPathEffect::kTranslate_Style: {
230 SkPoint pos;
231 if (meas.getPosTan(distance, &pos, nullptr)) {
232 dst->addPath(fPath, pos.fX, pos.fY);
233 }
234 } break;
235 case SkPath1DPathEffect::kRotate_Style: {
236 SkMatrix matrix;
237 if (meas.getMatrix(distance, &matrix)) {
238 dst->addPath(fPath, matrix);
239 }
240 } break;
241 case SkPath1DPathEffect::kMorph_Style:
242 morphpath(dst, fPath, meas, distance);
243 break;
244 }
245 return fAdvance;
246 }
247
248 ///////////////////////////////////////////////////////////////////////////////////////////////////
249
Make(const SkPath & path,SkScalar advance,SkScalar phase,Style style)250 sk_sp<SkPathEffect> SkPath1DPathEffect::Make(const SkPath& path, SkScalar advance, SkScalar phase,
251 Style style) {
252 if (advance <= 0 || !SkIsFinite(advance, phase) || path.isEmpty()) {
253 return nullptr;
254 }
255 return sk_sp<SkPathEffect>(new SkPath1DPathEffectImpl(path, advance, phase, style));
256 }
257
RegisterFlattenables()258 void SkPath1DPathEffect::RegisterFlattenables() {
259 SK_REGISTER_FLATTENABLE(SkPath1DPathEffectImpl);
260 }
261