blob: 00fa5426d15a026f459b629453b48338502f1ca2 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
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
reed@android.com8a1c16f2008-12-17 15:59:43 +00008
Mike Kleinc0bd9f92019-04-23 12:05:21 -05009#include "include/core/SkPathMeasure.h"
10#include "include/core/SkStrokeRec.h"
11#include "include/effects/Sk1DPathEffect.h"
12#include "src/core/SkReadBuffer.h"
13#include "src/core/SkWriteBuffer.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000014
Mike Reed22234652018-02-27 09:48:39 -050015// Since we are stepping by a float, the do/while loop might go on forever (or nearly so).
16// Put in a governor to limit crash values from looping too long (and allocating too much ram).
17#define MAX_REASONABLE_ITERATIONS 100000
18
Mike Reedec87dc12021-05-20 15:16:34 -040019class Sk1DPathEffect : public SkPathEffect {
20public:
21protected:
22 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override {
23 SkPathMeasure meas(src, false);
24 do {
25 int governor = MAX_REASONABLE_ITERATIONS;
26 SkScalar length = meas.getLength();
27 SkScalar distance = this->begin(length);
28 while (distance < length && --governor >= 0) {
29 SkScalar delta = this->next(dst, distance, meas);
30 if (delta <= 0) {
31 break;
32 }
33 distance += delta;
reed@google.comd7a6fb92011-08-12 14:04:15 +000034 }
Mike Reedec87dc12021-05-20 15:16:34 -040035 } while (meas.nextContour());
36 return true;
37 }
38
39 /** Called at the start of each contour, returns the initial offset
40 into that contour.
41 */
42 virtual SkScalar begin(SkScalar contourLength) const = 0;
43 /** Called with the current distance along the path, with the current matrix
44 for the point/tangent at the specified distance.
45 Return the distance to travel for the next call. If return <= 0, then that
46 contour is done.
47 */
48 virtual SkScalar next(SkPath* dst, SkScalar dist, SkPathMeasure&) const = 0;
49
50private:
51 // For simplicity, assume fast bounds cannot be computed
52 bool computeFastBounds(SkRect*) const override { return false; }
53
54 using INHERITED = SkPathEffect;
55};
reed@android.com8a1c16f2008-12-17 15:59:43 +000056
reed@google.comd7a6fb92011-08-12 14:04:15 +000057///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000058
Mike Reedec87dc12021-05-20 15:16:34 -040059class SkPath1DPathEffectImpl : public Sk1DPathEffect {
60public:
61 SkPath1DPathEffectImpl(const SkPath& path, SkScalar advance, SkScalar phase,
62 SkPath1DPathEffect::Style style) : fPath(path) {
63 SkASSERT(advance > 0 && !path.isEmpty());
Mike Reedfc015d22018-02-24 09:51:47 -050064
Mike Reedec87dc12021-05-20 15:16:34 -040065 // Make the path thread-safe.
66 fPath.updateBoundsCache();
67 (void)fPath.getGenerationID();
Ben Wagner20054dc2019-04-10 11:16:33 -040068
Mike Reedec87dc12021-05-20 15:16:34 -040069 // cleanup their phase parameter, inverting it so that it becomes an
70 // offset along the path (to match the interpretation in PostScript)
71 if (phase < 0) {
72 phase = -phase;
73 if (phase > advance) {
74 phase = SkScalarMod(phase, advance);
75 }
76 } else {
77 if (phase > advance) {
78 phase = SkScalarMod(phase, advance);
79 }
80 phase = advance - phase;
reedca726ab2016-02-22 12:50:25 -080081 }
Mike Reedec87dc12021-05-20 15:16:34 -040082 // now catch the edge case where phase == advance (within epsilon)
83 if (phase >= advance) {
84 phase = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +000085 }
Mike Reedec87dc12021-05-20 15:16:34 -040086 SkASSERT(phase >= 0);
reedca726ab2016-02-22 12:50:25 -080087
Mike Reedec87dc12021-05-20 15:16:34 -040088 fAdvance = advance;
89 fInitialOffset = phase;
90 fStyle = style;
reedca726ab2016-02-22 12:50:25 -080091 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000092
Mike Reedec87dc12021-05-20 15:16:34 -040093 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
94 const SkRect* cullRect) const override {
95 rec->setFillStyle();
96 return this->INHERITED::onFilterPath(dst, src, rec, cullRect);
97 }
98
99 SkScalar begin(SkScalar contourLength) const override {
100 return fInitialOffset;
101 }
102
103 SkScalar next(SkPath*, SkScalar, SkPathMeasure&) const override;
104
105 static sk_sp<SkFlattenable> CreateProc(SkReadBuffer& buffer) {
106 SkScalar advance = buffer.readScalar();
107 SkPath path;
108 buffer.readPath(&path);
109 SkScalar phase = buffer.readScalar();
110 SkPath1DPathEffect::Style style = buffer.read32LE(SkPath1DPathEffect::kLastEnum_Style);
111 return buffer.isValid() ? SkPath1DPathEffect::Make(path, advance, phase, style) : nullptr;
112 }
113
114 void flatten(SkWriteBuffer& buffer) const override {
115 buffer.writeScalar(fAdvance);
116 buffer.writePath(fPath);
117 buffer.writeScalar(fInitialOffset);
118 buffer.writeUInt(fStyle);
119 }
120
121 Factory getFactory() const override { return CreateProc; }
122 const char* getTypeName() const override { return "SkPath1DPathEffect"; }
123
124private:
125 SkPath fPath; // copied from constructor
126 SkScalar fAdvance; // copied from constructor
127 SkScalar fInitialOffset; // computed from phase
128 SkPath1DPathEffect::Style fStyle; // copied from constructor
129
130 using INHERITED = Sk1DPathEffect;
131};
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132
reed@google.comf3edf9f2012-04-12 19:44:38 +0000133static bool morphpoints(SkPoint dst[], const SkPoint src[], int count,
reed@google.comd7a6fb92011-08-12 14:04:15 +0000134 SkPathMeasure& meas, SkScalar dist) {
135 for (int i = 0; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000136 SkPoint pos;
137 SkVector tangent;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000138
reed@android.com8a1c16f2008-12-17 15:59:43 +0000139 SkScalar sx = src[i].fX;
140 SkScalar sy = src[i].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000141
reed@google.comf3edf9f2012-04-12 19:44:38 +0000142 if (!meas.getPosTan(dist + sx, &pos, &tangent)) {
143 return false;
144 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000145
reed@android.com8a1c16f2008-12-17 15:59:43 +0000146 SkMatrix matrix;
147 SkPoint pt;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000148
reed@android.com8a1c16f2008-12-17 15:59:43 +0000149 pt.set(sx, sy);
150 matrix.setSinCos(tangent.fY, tangent.fX, 0, 0);
151 matrix.preTranslate(-sx, 0);
152 matrix.postTranslate(pos.fX, pos.fY);
153 matrix.mapPoints(&dst[i], &pt, 1);
154 }
reed@google.comf3edf9f2012-04-12 19:44:38 +0000155 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156}
157
158/* TODO
159
160Need differentially more subdivisions when the follow-path is curvy. Not sure how to
161determine that, but we need it. I guess a cheap answer is let the caller tell us,
162but that seems like a cop-out. Another answer is to get Rob Johnson to figure it out.
163*/
reed@google.comd7a6fb92011-08-12 14:04:15 +0000164static void morphpath(SkPath* dst, const SkPath& src, SkPathMeasure& meas,
165 SkScalar dist) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166 SkPath::Iter iter(src, false);
167 SkPoint srcP[4], dstP[3];
168 SkPath::Verb verb;
reed@google.comd7a6fb92011-08-12 14:04:15 +0000169
170 while ((verb = iter.next(srcP)) != SkPath::kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171 switch (verb) {
172 case SkPath::kMove_Verb:
reed@google.comf3edf9f2012-04-12 19:44:38 +0000173 if (morphpoints(dstP, srcP, 1, meas, dist)) {
174 dst->moveTo(dstP[0]);
175 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176 break;
177 case SkPath::kLine_Verb:
178 srcP[2] = srcP[1];
179 srcP[1].set(SkScalarAve(srcP[0].fX, srcP[2].fX),
180 SkScalarAve(srcP[0].fY, srcP[2].fY));
John Stiles30212b72020-06-11 17:55:07 -0400181 [[fallthrough]];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182 case SkPath::kQuad_Verb:
reed@google.comf3edf9f2012-04-12 19:44:38 +0000183 if (morphpoints(dstP, &srcP[1], 2, meas, dist)) {
184 dst->quadTo(dstP[0], dstP[1]);
185 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000186 break;
Mike Reedb50e3852018-01-29 15:27:33 -0500187 case SkPath::kConic_Verb:
188 if (morphpoints(dstP, &srcP[1], 2, meas, dist)) {
189 dst->conicTo(dstP[0], dstP[1], iter.conicWeight());
190 }
191 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 case SkPath::kCubic_Verb:
reed@google.comf3edf9f2012-04-12 19:44:38 +0000193 if (morphpoints(dstP, &srcP[1], 3, meas, dist)) {
194 dst->cubicTo(dstP[0], dstP[1], dstP[2]);
195 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000196 break;
197 case SkPath::kClose_Verb:
198 dst->close();
199 break;
200 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000201 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 break;
203 }
204 }
205}
206
Mike Reedec87dc12021-05-20 15:16:34 -0400207SkScalar SkPath1DPathEffectImpl::next(SkPath* dst, SkScalar distance,
208 SkPathMeasure& meas) const {
Kevin Lubick493f89e2020-09-14 08:37:35 -0400209#if defined(SK_BUILD_FOR_FUZZER)
Cary Clark472ab812018-06-19 10:47:15 -0400210 if (dst->countPoints() > 100000) {
211 return fAdvance;
212 }
213#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214 switch (fStyle) {
Mike Reedec87dc12021-05-20 15:16:34 -0400215 case SkPath1DPathEffect::kTranslate_Style: {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216 SkPoint pos;
halcanary96fcdcc2015-08-27 07:41:13 -0700217 if (meas.getPosTan(distance, &pos, nullptr)) {
reed@google.comf3edf9f2012-04-12 19:44:38 +0000218 dst->addPath(fPath, pos.fX, pos.fY);
219 }
reed@google.comd7a6fb92011-08-12 14:04:15 +0000220 } break;
Mike Reedec87dc12021-05-20 15:16:34 -0400221 case SkPath1DPathEffect::kRotate_Style: {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000222 SkMatrix matrix;
reed@google.comf3edf9f2012-04-12 19:44:38 +0000223 if (meas.getMatrix(distance, &matrix)) {
224 dst->addPath(fPath, matrix);
225 }
reed@google.comd7a6fb92011-08-12 14:04:15 +0000226 } break;
Mike Reedec87dc12021-05-20 15:16:34 -0400227 case SkPath1DPathEffect::kMorph_Style:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000228 morphpath(dst, fPath, meas, distance);
229 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000230 }
231 return fAdvance;
232}
robertphillips42dbfa82015-01-26 06:08:52 -0800233
reedca726ab2016-02-22 12:50:25 -0800234///////////////////////////////////////////////////////////////////////////////////////////////////
235
reeda4393342016-03-18 11:22:57 -0700236sk_sp<SkPathEffect> SkPath1DPathEffect::Make(const SkPath& path, SkScalar advance, SkScalar phase,
237 Style style) {
Mike Reedfc015d22018-02-24 09:51:47 -0500238 if (advance <= 0 || !SkScalarIsFinite(advance) || !SkScalarIsFinite(phase) || path.isEmpty()) {
reedca726ab2016-02-22 12:50:25 -0800239 return nullptr;
240 }
Mike Reedec87dc12021-05-20 15:16:34 -0400241 return sk_sp<SkPathEffect>(new SkPath1DPathEffectImpl(path, advance, phase, style));
242}
243
244void SkPath1DPathEffect::RegisterFlattenables() {
245 SK_REGISTER_FLATTENABLE(SkPath1DPathEffectImpl);
reedca726ab2016-02-22 12:50:25 -0800246}