1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2020 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker #include "imgui.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBitmap.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPath.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPathMeasure.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPathUtils.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/utils/SkParsePath.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkGeometry.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "tools/viewer/ClickHandlerSlide.h"
17*c8dee2aaSAndroid Build Coastguard Worker
18*c8dee2aaSAndroid Build Coastguard Worker #include <stack>
19*c8dee2aaSAndroid Build Coastguard Worker #include <vector>
20*c8dee2aaSAndroid Build Coastguard Worker
21*c8dee2aaSAndroid Build Coastguard Worker namespace {
22*c8dee2aaSAndroid Build Coastguard Worker
23*c8dee2aaSAndroid Build Coastguard Worker //////////////////////////////////////////////////////////////////////////////
24*c8dee2aaSAndroid Build Coastguard Worker
rotate90(const SkPoint & p)25*c8dee2aaSAndroid Build Coastguard Worker constexpr inline SkPoint rotate90(const SkPoint& p) { return {p.fY, -p.fX}; }
rotate180(const SkPoint & p)26*c8dee2aaSAndroid Build Coastguard Worker inline SkPoint rotate180(const SkPoint& p) { return p * -1; }
isClockwise(const SkPoint & a,const SkPoint & b)27*c8dee2aaSAndroid Build Coastguard Worker inline bool isClockwise(const SkPoint& a, const SkPoint& b) { return a.cross(b) > 0; }
28*c8dee2aaSAndroid Build Coastguard Worker
checkSetLength(SkPoint p,float len,const char * file,int line)29*c8dee2aaSAndroid Build Coastguard Worker static SkPoint checkSetLength(SkPoint p, float len, const char* file, int line) {
30*c8dee2aaSAndroid Build Coastguard Worker if (!p.setLength(len)) {
31*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("%s:%d: Failed to set point length\n", file, line);
32*c8dee2aaSAndroid Build Coastguard Worker }
33*c8dee2aaSAndroid Build Coastguard Worker return p;
34*c8dee2aaSAndroid Build Coastguard Worker }
35*c8dee2aaSAndroid Build Coastguard Worker
36*c8dee2aaSAndroid Build Coastguard Worker /** Version of setLength that prints debug msg on failure to help catch edge cases */
37*c8dee2aaSAndroid Build Coastguard Worker #define setLength(p, len) checkSetLength(p, len, __FILE__, __LINE__)
38*c8dee2aaSAndroid Build Coastguard Worker
choose(uint64_t n,uint64_t k)39*c8dee2aaSAndroid Build Coastguard Worker constexpr uint64_t choose(uint64_t n, uint64_t k) {
40*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(n >= k);
41*c8dee2aaSAndroid Build Coastguard Worker uint64_t result = 1;
42*c8dee2aaSAndroid Build Coastguard Worker for (uint64_t i = 1; i <= k; i++) {
43*c8dee2aaSAndroid Build Coastguard Worker result *= (n + 1 - i);
44*c8dee2aaSAndroid Build Coastguard Worker result /= i;
45*c8dee2aaSAndroid Build Coastguard Worker }
46*c8dee2aaSAndroid Build Coastguard Worker return result;
47*c8dee2aaSAndroid Build Coastguard Worker }
48*c8dee2aaSAndroid Build Coastguard Worker
49*c8dee2aaSAndroid Build Coastguard Worker //////////////////////////////////////////////////////////////////////////////
50*c8dee2aaSAndroid Build Coastguard Worker
51*c8dee2aaSAndroid Build Coastguard Worker /**
52*c8dee2aaSAndroid Build Coastguard Worker * A scalar (float-valued weights) Bezier curve of arbitrary degree.
53*c8dee2aaSAndroid Build Coastguard Worker */
54*c8dee2aaSAndroid Build Coastguard Worker class ScalarBezCurve {
55*c8dee2aaSAndroid Build Coastguard Worker public:
56*c8dee2aaSAndroid Build Coastguard Worker inline static constexpr int kDegreeInvalid = -1;
57*c8dee2aaSAndroid Build Coastguard Worker
58*c8dee2aaSAndroid Build Coastguard Worker /** Creates an empty curve with invalid degree. */
ScalarBezCurve()59*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve() : fDegree(kDegreeInvalid) {}
60*c8dee2aaSAndroid Build Coastguard Worker
61*c8dee2aaSAndroid Build Coastguard Worker /** Creates a curve of the specified degree with weights initialized to 0. */
ScalarBezCurve(int degree)62*c8dee2aaSAndroid Build Coastguard Worker explicit ScalarBezCurve(int degree) : fDegree(degree) {
63*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(degree >= 0);
64*c8dee2aaSAndroid Build Coastguard Worker fWeights.resize(degree + 1, {0});
65*c8dee2aaSAndroid Build Coastguard Worker }
66*c8dee2aaSAndroid Build Coastguard Worker
67*c8dee2aaSAndroid Build Coastguard Worker /** Creates a curve of specified degree with the given weights. */
ScalarBezCurve(int degree,const std::vector<float> & weights)68*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve(int degree, const std::vector<float>& weights) : ScalarBezCurve(degree) {
69*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(degree >= 0);
70*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(weights.size() == (size_t)degree + 1);
71*c8dee2aaSAndroid Build Coastguard Worker fWeights.insert(fWeights.begin(), weights.begin(), weights.end());
72*c8dee2aaSAndroid Build Coastguard Worker }
73*c8dee2aaSAndroid Build Coastguard Worker
74*c8dee2aaSAndroid Build Coastguard Worker /** Returns the extreme-valued weight */
extremumWeight() const75*c8dee2aaSAndroid Build Coastguard Worker float extremumWeight() const {
76*c8dee2aaSAndroid Build Coastguard Worker float f = 0;
77*c8dee2aaSAndroid Build Coastguard Worker int sign = 1;
78*c8dee2aaSAndroid Build Coastguard Worker for (float w : fWeights) {
79*c8dee2aaSAndroid Build Coastguard Worker if (std::abs(w) > f) {
80*c8dee2aaSAndroid Build Coastguard Worker f = std::abs(w);
81*c8dee2aaSAndroid Build Coastguard Worker sign = w >= 0 ? 1 : -1;
82*c8dee2aaSAndroid Build Coastguard Worker }
83*c8dee2aaSAndroid Build Coastguard Worker }
84*c8dee2aaSAndroid Build Coastguard Worker return sign * f;
85*c8dee2aaSAndroid Build Coastguard Worker }
86*c8dee2aaSAndroid Build Coastguard Worker
87*c8dee2aaSAndroid Build Coastguard Worker /** Evaluates the curve at t */
eval(float t) const88*c8dee2aaSAndroid Build Coastguard Worker float eval(float t) const { return Eval(*this, t); }
89*c8dee2aaSAndroid Build Coastguard Worker
90*c8dee2aaSAndroid Build Coastguard Worker /** Evaluates the curve at t */
Eval(const ScalarBezCurve & curve,float t)91*c8dee2aaSAndroid Build Coastguard Worker static float Eval(const ScalarBezCurve& curve, float t) {
92*c8dee2aaSAndroid Build Coastguard Worker // Set up starting point of recursion (k=0)
93*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve result = curve;
94*c8dee2aaSAndroid Build Coastguard Worker
95*c8dee2aaSAndroid Build Coastguard Worker for (int k = 1; k <= curve.fDegree; k++) {
96*c8dee2aaSAndroid Build Coastguard Worker // k is level of recursion, k-1 has previous level's values.
97*c8dee2aaSAndroid Build Coastguard Worker for (int i = curve.fDegree; i >= k; i--) {
98*c8dee2aaSAndroid Build Coastguard Worker result.fWeights[i] = result.fWeights[i - 1] * (1 - t) + result.fWeights[i] * t;
99*c8dee2aaSAndroid Build Coastguard Worker }
100*c8dee2aaSAndroid Build Coastguard Worker }
101*c8dee2aaSAndroid Build Coastguard Worker
102*c8dee2aaSAndroid Build Coastguard Worker return result.fWeights[curve.fDegree];
103*c8dee2aaSAndroid Build Coastguard Worker }
104*c8dee2aaSAndroid Build Coastguard Worker
105*c8dee2aaSAndroid Build Coastguard Worker /** Splits this curve at t into two halves (of the same degree) */
split(float t,ScalarBezCurve * left,ScalarBezCurve * right) const106*c8dee2aaSAndroid Build Coastguard Worker void split(float t, ScalarBezCurve* left, ScalarBezCurve* right) const {
107*c8dee2aaSAndroid Build Coastguard Worker Split(*this, t, left, right);
108*c8dee2aaSAndroid Build Coastguard Worker }
109*c8dee2aaSAndroid Build Coastguard Worker
110*c8dee2aaSAndroid Build Coastguard Worker /** Splits this curve into the subinterval [tmin,tmax]. */
split(float tmin,float tmax,ScalarBezCurve * result) const111*c8dee2aaSAndroid Build Coastguard Worker void split(float tmin, float tmax, ScalarBezCurve* result) const {
112*c8dee2aaSAndroid Build Coastguard Worker // TODO: I believe there's a more efficient algorithm for this
113*c8dee2aaSAndroid Build Coastguard Worker const float tRel = tmin / tmax;
114*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve ll, rl, rr;
115*c8dee2aaSAndroid Build Coastguard Worker this->split(tmax, &rl, &rr);
116*c8dee2aaSAndroid Build Coastguard Worker rl.split(tRel, &ll, result);
117*c8dee2aaSAndroid Build Coastguard Worker }
118*c8dee2aaSAndroid Build Coastguard Worker
119*c8dee2aaSAndroid Build Coastguard Worker /** Splits the curve at t into two halves (of the same degree) */
Split(const ScalarBezCurve & curve,float t,ScalarBezCurve * left,ScalarBezCurve * right)120*c8dee2aaSAndroid Build Coastguard Worker static void Split(const ScalarBezCurve& curve,
121*c8dee2aaSAndroid Build Coastguard Worker float t,
122*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve* left,
123*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve* right) {
124*c8dee2aaSAndroid Build Coastguard Worker // Set up starting point of recursion (k=0)
125*c8dee2aaSAndroid Build Coastguard Worker const int degree = curve.fDegree;
126*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve result = curve;
127*c8dee2aaSAndroid Build Coastguard Worker *left = ScalarBezCurve(degree);
128*c8dee2aaSAndroid Build Coastguard Worker *right = ScalarBezCurve(degree);
129*c8dee2aaSAndroid Build Coastguard Worker left->fWeights[0] = curve.fWeights[0];
130*c8dee2aaSAndroid Build Coastguard Worker right->fWeights[degree] = curve.fWeights[degree];
131*c8dee2aaSAndroid Build Coastguard Worker
132*c8dee2aaSAndroid Build Coastguard Worker for (int k = 1; k <= degree; k++) {
133*c8dee2aaSAndroid Build Coastguard Worker // k is level of recursion, k-1 has previous level's values.
134*c8dee2aaSAndroid Build Coastguard Worker for (int i = degree; i >= k; i--) {
135*c8dee2aaSAndroid Build Coastguard Worker result.fWeights[i] = result.fWeights[i - 1] * (1 - t) + result.fWeights[i] * t;
136*c8dee2aaSAndroid Build Coastguard Worker }
137*c8dee2aaSAndroid Build Coastguard Worker
138*c8dee2aaSAndroid Build Coastguard Worker left->fWeights[k] = result.fWeights[k];
139*c8dee2aaSAndroid Build Coastguard Worker right->fWeights[degree - k] = result.fWeights[degree];
140*c8dee2aaSAndroid Build Coastguard Worker }
141*c8dee2aaSAndroid Build Coastguard Worker }
142*c8dee2aaSAndroid Build Coastguard Worker
143*c8dee2aaSAndroid Build Coastguard Worker /**
144*c8dee2aaSAndroid Build Coastguard Worker * Increases the degree of the curve to the given degree. Has no effect if the
145*c8dee2aaSAndroid Build Coastguard Worker * degree is already equal to the given degree.
146*c8dee2aaSAndroid Build Coastguard Worker *
147*c8dee2aaSAndroid Build Coastguard Worker * This process is always exact (NB the reverse, degree reduction, is not exact).
148*c8dee2aaSAndroid Build Coastguard Worker */
elevateDegree(int newDegree)149*c8dee2aaSAndroid Build Coastguard Worker void elevateDegree(int newDegree) {
150*c8dee2aaSAndroid Build Coastguard Worker if (newDegree == fDegree) {
151*c8dee2aaSAndroid Build Coastguard Worker return;
152*c8dee2aaSAndroid Build Coastguard Worker }
153*c8dee2aaSAndroid Build Coastguard Worker
154*c8dee2aaSAndroid Build Coastguard Worker fWeights = ElevateDegree(*this, newDegree).fWeights;
155*c8dee2aaSAndroid Build Coastguard Worker fDegree = newDegree;
156*c8dee2aaSAndroid Build Coastguard Worker }
157*c8dee2aaSAndroid Build Coastguard Worker
158*c8dee2aaSAndroid Build Coastguard Worker /**
159*c8dee2aaSAndroid Build Coastguard Worker * Increases the degree of the curve to the given degree. Has no effect if the
160*c8dee2aaSAndroid Build Coastguard Worker * degree is already equal to the given degree.
161*c8dee2aaSAndroid Build Coastguard Worker *
162*c8dee2aaSAndroid Build Coastguard Worker * This process is always exact (NB the reverse, degree reduction, is not exact).
163*c8dee2aaSAndroid Build Coastguard Worker */
ElevateDegree(const ScalarBezCurve & curve,int newDegree)164*c8dee2aaSAndroid Build Coastguard Worker static ScalarBezCurve ElevateDegree(const ScalarBezCurve& curve, int newDegree) {
165*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(newDegree >= curve.degree());
166*c8dee2aaSAndroid Build Coastguard Worker if (newDegree == curve.degree()) {
167*c8dee2aaSAndroid Build Coastguard Worker return curve;
168*c8dee2aaSAndroid Build Coastguard Worker }
169*c8dee2aaSAndroid Build Coastguard Worker
170*c8dee2aaSAndroid Build Coastguard Worker // From Farouki, Rajan, "Algorithms for polynomials in Bernstein form" 1988.
171*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve elevated(newDegree);
172*c8dee2aaSAndroid Build Coastguard Worker const int r = newDegree - curve.fDegree;
173*c8dee2aaSAndroid Build Coastguard Worker const int n = curve.fDegree;
174*c8dee2aaSAndroid Build Coastguard Worker
175*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i <= n + r; i++) {
176*c8dee2aaSAndroid Build Coastguard Worker elevated.fWeights[i] = 0;
177*c8dee2aaSAndroid Build Coastguard Worker for (int j = std::max(0, i - r); j <= std::min(n, i); j++) {
178*c8dee2aaSAndroid Build Coastguard Worker const float f =
179*c8dee2aaSAndroid Build Coastguard Worker (choose(n, j) * choose(r, i - j)) / static_cast<float>(choose(n + r, i));
180*c8dee2aaSAndroid Build Coastguard Worker elevated.fWeights[i] += curve.fWeights[j] * f;
181*c8dee2aaSAndroid Build Coastguard Worker }
182*c8dee2aaSAndroid Build Coastguard Worker }
183*c8dee2aaSAndroid Build Coastguard Worker
184*c8dee2aaSAndroid Build Coastguard Worker return elevated;
185*c8dee2aaSAndroid Build Coastguard Worker }
186*c8dee2aaSAndroid Build Coastguard Worker
187*c8dee2aaSAndroid Build Coastguard Worker /**
188*c8dee2aaSAndroid Build Coastguard Worker * Returns the zero-set of this curve, which is a list of t values where the curve crosses 0.
189*c8dee2aaSAndroid Build Coastguard Worker */
zeroSet() const190*c8dee2aaSAndroid Build Coastguard Worker std::vector<float> zeroSet() const { return ZeroSet(*this); }
191*c8dee2aaSAndroid Build Coastguard Worker
192*c8dee2aaSAndroid Build Coastguard Worker /**
193*c8dee2aaSAndroid Build Coastguard Worker * Returns the zero-set of the curve, which is a list of t values where the curve crosses 0.
194*c8dee2aaSAndroid Build Coastguard Worker */
ZeroSet(const ScalarBezCurve & curve)195*c8dee2aaSAndroid Build Coastguard Worker static std::vector<float> ZeroSet(const ScalarBezCurve& curve) {
196*c8dee2aaSAndroid Build Coastguard Worker constexpr float kTol = 0.001f;
197*c8dee2aaSAndroid Build Coastguard Worker std::vector<float> result;
198*c8dee2aaSAndroid Build Coastguard Worker ZeroSetRec(curve, 0, 1, kTol, &result);
199*c8dee2aaSAndroid Build Coastguard Worker return result;
200*c8dee2aaSAndroid Build Coastguard Worker }
201*c8dee2aaSAndroid Build Coastguard Worker
202*c8dee2aaSAndroid Build Coastguard Worker /** Multiplies the curve's weights by a constant value */
Mul(const ScalarBezCurve & curve,float f)203*c8dee2aaSAndroid Build Coastguard Worker static ScalarBezCurve Mul(const ScalarBezCurve& curve, float f) {
204*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve result = curve;
205*c8dee2aaSAndroid Build Coastguard Worker for (int k = 0; k <= curve.fDegree; k++) {
206*c8dee2aaSAndroid Build Coastguard Worker result.fWeights[k] *= f;
207*c8dee2aaSAndroid Build Coastguard Worker }
208*c8dee2aaSAndroid Build Coastguard Worker return result;
209*c8dee2aaSAndroid Build Coastguard Worker }
210*c8dee2aaSAndroid Build Coastguard Worker
211*c8dee2aaSAndroid Build Coastguard Worker /**
212*c8dee2aaSAndroid Build Coastguard Worker * Multiplies the two curves and returns the result.
213*c8dee2aaSAndroid Build Coastguard Worker *
214*c8dee2aaSAndroid Build Coastguard Worker * Degree of resulting curve is the sum of the degrees of the input curves.
215*c8dee2aaSAndroid Build Coastguard Worker */
Mul(const ScalarBezCurve & a,const ScalarBezCurve & b)216*c8dee2aaSAndroid Build Coastguard Worker static ScalarBezCurve Mul(const ScalarBezCurve& a, const ScalarBezCurve& b) {
217*c8dee2aaSAndroid Build Coastguard Worker // From G. Elber, "Free form surface analysis using a hybrid of symbolic and numeric
218*c8dee2aaSAndroid Build Coastguard Worker // computation". PhD thesis, 1992. p.11.
219*c8dee2aaSAndroid Build Coastguard Worker const int n = a.degree(), m = b.degree();
220*c8dee2aaSAndroid Build Coastguard Worker const int newDegree = n + m;
221*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve result(newDegree);
222*c8dee2aaSAndroid Build Coastguard Worker
223*c8dee2aaSAndroid Build Coastguard Worker for (int k = 0; k <= newDegree; k++) {
224*c8dee2aaSAndroid Build Coastguard Worker result.fWeights[k] = 0;
225*c8dee2aaSAndroid Build Coastguard Worker for (int i = std::max(0, k - n); i <= std::min(k, m); i++) {
226*c8dee2aaSAndroid Build Coastguard Worker const float f =
227*c8dee2aaSAndroid Build Coastguard Worker (choose(m, i) * choose(n, k - i)) / static_cast<float>(choose(m + n, k));
228*c8dee2aaSAndroid Build Coastguard Worker result.fWeights[k] += a.fWeights[i] * b.fWeights[k - i] * f;
229*c8dee2aaSAndroid Build Coastguard Worker }
230*c8dee2aaSAndroid Build Coastguard Worker }
231*c8dee2aaSAndroid Build Coastguard Worker
232*c8dee2aaSAndroid Build Coastguard Worker return result;
233*c8dee2aaSAndroid Build Coastguard Worker }
234*c8dee2aaSAndroid Build Coastguard Worker
235*c8dee2aaSAndroid Build Coastguard Worker /** Returns a^2 + b^2. This is a specialized method because the loops are easily fused. */
AddSquares(const ScalarBezCurve & a,const ScalarBezCurve & b)236*c8dee2aaSAndroid Build Coastguard Worker static ScalarBezCurve AddSquares(const ScalarBezCurve& a, const ScalarBezCurve& b) {
237*c8dee2aaSAndroid Build Coastguard Worker const int n = a.degree(), m = b.degree();
238*c8dee2aaSAndroid Build Coastguard Worker const int newDegree = n + m;
239*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve result(newDegree);
240*c8dee2aaSAndroid Build Coastguard Worker
241*c8dee2aaSAndroid Build Coastguard Worker for (int k = 0; k <= newDegree; k++) {
242*c8dee2aaSAndroid Build Coastguard Worker float aSq = 0, bSq = 0;
243*c8dee2aaSAndroid Build Coastguard Worker for (int i = std::max(0, k - n); i <= std::min(k, m); i++) {
244*c8dee2aaSAndroid Build Coastguard Worker const float f =
245*c8dee2aaSAndroid Build Coastguard Worker (choose(m, i) * choose(n, k - i)) / static_cast<float>(choose(m + n, k));
246*c8dee2aaSAndroid Build Coastguard Worker aSq += a.fWeights[i] * a.fWeights[k - i] * f;
247*c8dee2aaSAndroid Build Coastguard Worker bSq += b.fWeights[i] * b.fWeights[k - i] * f;
248*c8dee2aaSAndroid Build Coastguard Worker }
249*c8dee2aaSAndroid Build Coastguard Worker result.fWeights[k] = aSq + bSq;
250*c8dee2aaSAndroid Build Coastguard Worker }
251*c8dee2aaSAndroid Build Coastguard Worker
252*c8dee2aaSAndroid Build Coastguard Worker return result;
253*c8dee2aaSAndroid Build Coastguard Worker }
254*c8dee2aaSAndroid Build Coastguard Worker
255*c8dee2aaSAndroid Build Coastguard Worker /** Returns a - b. */
Sub(const ScalarBezCurve & a,const ScalarBezCurve & b)256*c8dee2aaSAndroid Build Coastguard Worker static ScalarBezCurve Sub(const ScalarBezCurve& a, const ScalarBezCurve& b) {
257*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve result = a;
258*c8dee2aaSAndroid Build Coastguard Worker result.sub(b);
259*c8dee2aaSAndroid Build Coastguard Worker return result;
260*c8dee2aaSAndroid Build Coastguard Worker }
261*c8dee2aaSAndroid Build Coastguard Worker
262*c8dee2aaSAndroid Build Coastguard Worker /** Subtracts the other curve from this curve */
sub(const ScalarBezCurve & other)263*c8dee2aaSAndroid Build Coastguard Worker void sub(const ScalarBezCurve& other) {
264*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(other.fDegree == fDegree);
265*c8dee2aaSAndroid Build Coastguard Worker for (int k = 0; k <= fDegree; k++) {
266*c8dee2aaSAndroid Build Coastguard Worker fWeights[k] -= other.fWeights[k];
267*c8dee2aaSAndroid Build Coastguard Worker }
268*c8dee2aaSAndroid Build Coastguard Worker }
269*c8dee2aaSAndroid Build Coastguard Worker
270*c8dee2aaSAndroid Build Coastguard Worker /** Subtracts a constant from this curve */
sub(float f)271*c8dee2aaSAndroid Build Coastguard Worker void sub(float f) {
272*c8dee2aaSAndroid Build Coastguard Worker for (int k = 0; k <= fDegree; k++) {
273*c8dee2aaSAndroid Build Coastguard Worker fWeights[k] -= f;
274*c8dee2aaSAndroid Build Coastguard Worker }
275*c8dee2aaSAndroid Build Coastguard Worker }
276*c8dee2aaSAndroid Build Coastguard Worker
277*c8dee2aaSAndroid Build Coastguard Worker /** Returns the curve degree */
degree() const278*c8dee2aaSAndroid Build Coastguard Worker int degree() const { return fDegree; }
279*c8dee2aaSAndroid Build Coastguard Worker
280*c8dee2aaSAndroid Build Coastguard Worker /** Returns the curve weights */
weights() const281*c8dee2aaSAndroid Build Coastguard Worker const std::vector<float>& weights() const { return fWeights; }
282*c8dee2aaSAndroid Build Coastguard Worker
operator [](size_t i) const283*c8dee2aaSAndroid Build Coastguard Worker float operator[](size_t i) const { return fWeights[i]; }
operator [](size_t i)284*c8dee2aaSAndroid Build Coastguard Worker float& operator[](size_t i) { return fWeights[i]; }
285*c8dee2aaSAndroid Build Coastguard Worker
286*c8dee2aaSAndroid Build Coastguard Worker private:
287*c8dee2aaSAndroid Build Coastguard Worker /** Recursive helper for ZeroSet */
ZeroSetRec(const ScalarBezCurve & curve,float tmin,float tmax,float tol,std::vector<float> * result)288*c8dee2aaSAndroid Build Coastguard Worker static void ZeroSetRec(const ScalarBezCurve& curve,
289*c8dee2aaSAndroid Build Coastguard Worker float tmin,
290*c8dee2aaSAndroid Build Coastguard Worker float tmax,
291*c8dee2aaSAndroid Build Coastguard Worker float tol,
292*c8dee2aaSAndroid Build Coastguard Worker std::vector<float>* result) {
293*c8dee2aaSAndroid Build Coastguard Worker float lenP = 0;
294*c8dee2aaSAndroid Build Coastguard Worker bool allPos = curve.fWeights[0] >= 0, allNeg = curve.fWeights[0] < 0;
295*c8dee2aaSAndroid Build Coastguard Worker for (int i = 1; i <= curve.fDegree; i++) {
296*c8dee2aaSAndroid Build Coastguard Worker lenP += std::abs(curve.fWeights[i] - curve.fWeights[i - 1]);
297*c8dee2aaSAndroid Build Coastguard Worker allPos &= curve.fWeights[i] >= 0;
298*c8dee2aaSAndroid Build Coastguard Worker allNeg &= curve.fWeights[i] < 0;
299*c8dee2aaSAndroid Build Coastguard Worker }
300*c8dee2aaSAndroid Build Coastguard Worker if (lenP <= tol) {
301*c8dee2aaSAndroid Build Coastguard Worker result->push_back((tmin + tmax) * 0.5);
302*c8dee2aaSAndroid Build Coastguard Worker return;
303*c8dee2aaSAndroid Build Coastguard Worker } else if (allPos || allNeg) {
304*c8dee2aaSAndroid Build Coastguard Worker // No zero crossings possible if the coefficients don't change sign (convex hull
305*c8dee2aaSAndroid Build Coastguard Worker // property)
306*c8dee2aaSAndroid Build Coastguard Worker return;
307*c8dee2aaSAndroid Build Coastguard Worker } else if (SkScalarNearlyZero(tmax - tmin)) {
308*c8dee2aaSAndroid Build Coastguard Worker return;
309*c8dee2aaSAndroid Build Coastguard Worker } else {
310*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve left(curve.fDegree), right(curve.fDegree);
311*c8dee2aaSAndroid Build Coastguard Worker Split(curve, 0.5f, &left, &right);
312*c8dee2aaSAndroid Build Coastguard Worker
313*c8dee2aaSAndroid Build Coastguard Worker const float tmid = (tmin + tmax) * 0.5;
314*c8dee2aaSAndroid Build Coastguard Worker ZeroSetRec(left, tmin, tmid, tol, result);
315*c8dee2aaSAndroid Build Coastguard Worker ZeroSetRec(right, tmid, tmax, tol, result);
316*c8dee2aaSAndroid Build Coastguard Worker }
317*c8dee2aaSAndroid Build Coastguard Worker }
318*c8dee2aaSAndroid Build Coastguard Worker
319*c8dee2aaSAndroid Build Coastguard Worker int fDegree;
320*c8dee2aaSAndroid Build Coastguard Worker std::vector<float> fWeights;
321*c8dee2aaSAndroid Build Coastguard Worker };
322*c8dee2aaSAndroid Build Coastguard Worker
323*c8dee2aaSAndroid Build Coastguard Worker //////////////////////////////////////////////////////////////////////////////
324*c8dee2aaSAndroid Build Coastguard Worker
325*c8dee2aaSAndroid Build Coastguard Worker /** Helper class that measures per-verb path lengths. */
326*c8dee2aaSAndroid Build Coastguard Worker class PathVerbMeasure {
327*c8dee2aaSAndroid Build Coastguard Worker public:
PathVerbMeasure(const SkPath & path)328*c8dee2aaSAndroid Build Coastguard Worker explicit PathVerbMeasure(const SkPath& path) : fPath(path), fIter(path, false) { nextVerb(); }
329*c8dee2aaSAndroid Build Coastguard Worker
330*c8dee2aaSAndroid Build Coastguard Worker SkScalar totalLength() const;
331*c8dee2aaSAndroid Build Coastguard Worker
currentVerbLength()332*c8dee2aaSAndroid Build Coastguard Worker SkScalar currentVerbLength() { return fMeas.getLength(); }
333*c8dee2aaSAndroid Build Coastguard Worker
334*c8dee2aaSAndroid Build Coastguard Worker void nextVerb();
335*c8dee2aaSAndroid Build Coastguard Worker
336*c8dee2aaSAndroid Build Coastguard Worker private:
337*c8dee2aaSAndroid Build Coastguard Worker const SkPath& fPath;
338*c8dee2aaSAndroid Build Coastguard Worker SkPoint fFirstPointInContour;
339*c8dee2aaSAndroid Build Coastguard Worker SkPoint fPreviousPoint;
340*c8dee2aaSAndroid Build Coastguard Worker SkPath fCurrVerb;
341*c8dee2aaSAndroid Build Coastguard Worker SkPath::Iter fIter;
342*c8dee2aaSAndroid Build Coastguard Worker SkPathMeasure fMeas;
343*c8dee2aaSAndroid Build Coastguard Worker };
344*c8dee2aaSAndroid Build Coastguard Worker
totalLength() const345*c8dee2aaSAndroid Build Coastguard Worker SkScalar PathVerbMeasure::totalLength() const {
346*c8dee2aaSAndroid Build Coastguard Worker SkPathMeasure meas(fPath, false);
347*c8dee2aaSAndroid Build Coastguard Worker return meas.getLength();
348*c8dee2aaSAndroid Build Coastguard Worker }
349*c8dee2aaSAndroid Build Coastguard Worker
nextVerb()350*c8dee2aaSAndroid Build Coastguard Worker void PathVerbMeasure::nextVerb() {
351*c8dee2aaSAndroid Build Coastguard Worker SkPoint pts[4];
352*c8dee2aaSAndroid Build Coastguard Worker SkPath::Verb verb = fIter.next(pts);
353*c8dee2aaSAndroid Build Coastguard Worker
354*c8dee2aaSAndroid Build Coastguard Worker while (verb == SkPath::kMove_Verb || verb == SkPath::kClose_Verb) {
355*c8dee2aaSAndroid Build Coastguard Worker if (verb == SkPath::kMove_Verb) {
356*c8dee2aaSAndroid Build Coastguard Worker fFirstPointInContour = pts[0];
357*c8dee2aaSAndroid Build Coastguard Worker fPreviousPoint = fFirstPointInContour;
358*c8dee2aaSAndroid Build Coastguard Worker }
359*c8dee2aaSAndroid Build Coastguard Worker verb = fIter.next(pts);
360*c8dee2aaSAndroid Build Coastguard Worker }
361*c8dee2aaSAndroid Build Coastguard Worker
362*c8dee2aaSAndroid Build Coastguard Worker fCurrVerb.rewind();
363*c8dee2aaSAndroid Build Coastguard Worker fCurrVerb.moveTo(fPreviousPoint);
364*c8dee2aaSAndroid Build Coastguard Worker switch (verb) {
365*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kLine_Verb:
366*c8dee2aaSAndroid Build Coastguard Worker fCurrVerb.lineTo(pts[1]);
367*c8dee2aaSAndroid Build Coastguard Worker break;
368*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kQuad_Verb:
369*c8dee2aaSAndroid Build Coastguard Worker fCurrVerb.quadTo(pts[1], pts[2]);
370*c8dee2aaSAndroid Build Coastguard Worker break;
371*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kCubic_Verb:
372*c8dee2aaSAndroid Build Coastguard Worker fCurrVerb.cubicTo(pts[1], pts[2], pts[3]);
373*c8dee2aaSAndroid Build Coastguard Worker break;
374*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kConic_Verb:
375*c8dee2aaSAndroid Build Coastguard Worker fCurrVerb.conicTo(pts[1], pts[2], fIter.conicWeight());
376*c8dee2aaSAndroid Build Coastguard Worker break;
377*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kDone_Verb:
378*c8dee2aaSAndroid Build Coastguard Worker break;
379*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kClose_Verb:
380*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kMove_Verb:
381*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(false);
382*c8dee2aaSAndroid Build Coastguard Worker break;
383*c8dee2aaSAndroid Build Coastguard Worker }
384*c8dee2aaSAndroid Build Coastguard Worker
385*c8dee2aaSAndroid Build Coastguard Worker fCurrVerb.getLastPt(&fPreviousPoint);
386*c8dee2aaSAndroid Build Coastguard Worker fMeas.setPath(&fCurrVerb, false);
387*c8dee2aaSAndroid Build Coastguard Worker }
388*c8dee2aaSAndroid Build Coastguard Worker
389*c8dee2aaSAndroid Build Coastguard Worker //////////////////////////////////////////////////////////////////////////////
390*c8dee2aaSAndroid Build Coastguard Worker
391*c8dee2aaSAndroid Build Coastguard Worker // Several debug-only visualization helpers
392*c8dee2aaSAndroid Build Coastguard Worker namespace viz {
393*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<ScalarBezCurve> outerErr;
394*c8dee2aaSAndroid Build Coastguard Worker SkPath outerFirstApprox;
395*c8dee2aaSAndroid Build Coastguard Worker } // namespace viz
396*c8dee2aaSAndroid Build Coastguard Worker
397*c8dee2aaSAndroid Build Coastguard Worker /**
398*c8dee2aaSAndroid Build Coastguard Worker * Prototype variable-width path stroker.
399*c8dee2aaSAndroid Build Coastguard Worker *
400*c8dee2aaSAndroid Build Coastguard Worker * Takes as input a path to be stroked, and two distance functions (inside and outside).
401*c8dee2aaSAndroid Build Coastguard Worker * Produces a fill path with the stroked path geometry.
402*c8dee2aaSAndroid Build Coastguard Worker *
403*c8dee2aaSAndroid Build Coastguard Worker * The algorithms in use here are from:
404*c8dee2aaSAndroid Build Coastguard Worker *
405*c8dee2aaSAndroid Build Coastguard Worker * G. Elber, E. Cohen. "Error bounded variable distance offset operator for free form curves and
406*c8dee2aaSAndroid Build Coastguard Worker * surfaces." International Journal of Computational Geometry & Applications 1, no. 01 (1991)
407*c8dee2aaSAndroid Build Coastguard Worker *
408*c8dee2aaSAndroid Build Coastguard Worker * G. Elber. "Free form surface analysis using a hybrid of symbolic and numeric computation."
409*c8dee2aaSAndroid Build Coastguard Worker * PhD diss., Dept. of Computer Science, University of Utah, 1992.
410*c8dee2aaSAndroid Build Coastguard Worker */
411*c8dee2aaSAndroid Build Coastguard Worker class SkVarWidthStroker {
412*c8dee2aaSAndroid Build Coastguard Worker public:
413*c8dee2aaSAndroid Build Coastguard Worker /** Metric to use for interpolation of distance function across path segments. */
414*c8dee2aaSAndroid Build Coastguard Worker enum class LengthMetric {
415*c8dee2aaSAndroid Build Coastguard Worker /** Each path segment gets an equal interval of t in [0,1] */
416*c8dee2aaSAndroid Build Coastguard Worker kNumSegments,
417*c8dee2aaSAndroid Build Coastguard Worker /** Each path segment gets t interval equal to its percent of the total path length */
418*c8dee2aaSAndroid Build Coastguard Worker kPathLength,
419*c8dee2aaSAndroid Build Coastguard Worker };
420*c8dee2aaSAndroid Build Coastguard Worker
421*c8dee2aaSAndroid Build Coastguard Worker /**
422*c8dee2aaSAndroid Build Coastguard Worker * Strokes the path with a fixed-width distance function. This produces a traditional stroked
423*c8dee2aaSAndroid Build Coastguard Worker * path.
424*c8dee2aaSAndroid Build Coastguard Worker */
getFillPath(const SkPath & path,const SkPaint & paint)425*c8dee2aaSAndroid Build Coastguard Worker SkPath getFillPath(const SkPath& path, const SkPaint& paint) {
426*c8dee2aaSAndroid Build Coastguard Worker return getFillPath(path, paint, identityVarWidth(paint.getStrokeWidth()),
427*c8dee2aaSAndroid Build Coastguard Worker identityVarWidth(paint.getStrokeWidth()));
428*c8dee2aaSAndroid Build Coastguard Worker }
429*c8dee2aaSAndroid Build Coastguard Worker
430*c8dee2aaSAndroid Build Coastguard Worker /**
431*c8dee2aaSAndroid Build Coastguard Worker * Strokes the given path using the two given distance functions for inner and outer offsets.
432*c8dee2aaSAndroid Build Coastguard Worker */
433*c8dee2aaSAndroid Build Coastguard Worker SkPath getFillPath(const SkPath& path,
434*c8dee2aaSAndroid Build Coastguard Worker const SkPaint& paint,
435*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& varWidth,
436*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& varWidthInner,
437*c8dee2aaSAndroid Build Coastguard Worker LengthMetric lengthMetric = LengthMetric::kNumSegments);
438*c8dee2aaSAndroid Build Coastguard Worker
439*c8dee2aaSAndroid Build Coastguard Worker private:
440*c8dee2aaSAndroid Build Coastguard Worker /** Helper struct referring to a single segment of an SkPath */
441*c8dee2aaSAndroid Build Coastguard Worker struct PathSegment {
442*c8dee2aaSAndroid Build Coastguard Worker SkPath::Verb fVerb;
443*c8dee2aaSAndroid Build Coastguard Worker std::array<SkPoint, 4> fPoints;
444*c8dee2aaSAndroid Build Coastguard Worker };
445*c8dee2aaSAndroid Build Coastguard Worker
446*c8dee2aaSAndroid Build Coastguard Worker struct OffsetSegments {
447*c8dee2aaSAndroid Build Coastguard Worker std::vector<PathSegment> fInner;
448*c8dee2aaSAndroid Build Coastguard Worker std::vector<PathSegment> fOuter;
449*c8dee2aaSAndroid Build Coastguard Worker };
450*c8dee2aaSAndroid Build Coastguard Worker
451*c8dee2aaSAndroid Build Coastguard Worker /** Initialize stroker state */
452*c8dee2aaSAndroid Build Coastguard Worker void initForPath(const SkPath& path, const SkPaint& paint);
453*c8dee2aaSAndroid Build Coastguard Worker
454*c8dee2aaSAndroid Build Coastguard Worker /** Strokes a path segment */
455*c8dee2aaSAndroid Build Coastguard Worker OffsetSegments strokeSegment(const PathSegment& segment,
456*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& varWidth,
457*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& varWidthInner,
458*c8dee2aaSAndroid Build Coastguard Worker bool needsMove);
459*c8dee2aaSAndroid Build Coastguard Worker
460*c8dee2aaSAndroid Build Coastguard Worker /**
461*c8dee2aaSAndroid Build Coastguard Worker * Strokes the given segment using the given distance function.
462*c8dee2aaSAndroid Build Coastguard Worker *
463*c8dee2aaSAndroid Build Coastguard Worker * Returns a list of quad segments that approximate the offset curve.
464*c8dee2aaSAndroid Build Coastguard Worker * TODO: no reason this needs to return a vector of quads, can just append to the path
465*c8dee2aaSAndroid Build Coastguard Worker */
466*c8dee2aaSAndroid Build Coastguard Worker std::vector<PathSegment> strokeSegment(const PathSegment& seg,
467*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& distanceFunc) const;
468*c8dee2aaSAndroid Build Coastguard Worker
469*c8dee2aaSAndroid Build Coastguard Worker /** Adds an endcap to fOuter */
470*c8dee2aaSAndroid Build Coastguard Worker enum class CapLocation { Start, End };
471*c8dee2aaSAndroid Build Coastguard Worker void endcap(CapLocation loc);
472*c8dee2aaSAndroid Build Coastguard Worker
473*c8dee2aaSAndroid Build Coastguard Worker /** Adds a join between the two segments */
474*c8dee2aaSAndroid Build Coastguard Worker void join(const SkPoint& common,
475*c8dee2aaSAndroid Build Coastguard Worker float innerRadius,
476*c8dee2aaSAndroid Build Coastguard Worker float outerRadius,
477*c8dee2aaSAndroid Build Coastguard Worker const OffsetSegments& prev,
478*c8dee2aaSAndroid Build Coastguard Worker const OffsetSegments& curr);
479*c8dee2aaSAndroid Build Coastguard Worker
480*c8dee2aaSAndroid Build Coastguard Worker /** Appends path in reverse to result */
481*c8dee2aaSAndroid Build Coastguard Worker static void appendPathReversed(const SkPath& path, SkPath* result);
482*c8dee2aaSAndroid Build Coastguard Worker
483*c8dee2aaSAndroid Build Coastguard Worker /** Returns the segment unit normal and unit tangent if not nullptr */
484*c8dee2aaSAndroid Build Coastguard Worker static SkPoint unitNormal(const PathSegment& seg, float t, SkPoint* tangentOut);
485*c8dee2aaSAndroid Build Coastguard Worker
486*c8dee2aaSAndroid Build Coastguard Worker /** Returns the degree of a segment curve */
487*c8dee2aaSAndroid Build Coastguard Worker static int segmentDegree(const PathSegment& seg);
488*c8dee2aaSAndroid Build Coastguard Worker
489*c8dee2aaSAndroid Build Coastguard Worker /** Splits a path segment at t */
490*c8dee2aaSAndroid Build Coastguard Worker static void splitSegment(const PathSegment& seg, float t, PathSegment* segA, PathSegment* segB);
491*c8dee2aaSAndroid Build Coastguard Worker
492*c8dee2aaSAndroid Build Coastguard Worker /**
493*c8dee2aaSAndroid Build Coastguard Worker * Returns a quadratic segment that approximates the given segment using the given distance
494*c8dee2aaSAndroid Build Coastguard Worker * function.
495*c8dee2aaSAndroid Build Coastguard Worker */
496*c8dee2aaSAndroid Build Coastguard Worker static void approximateSegment(const PathSegment& seg,
497*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& distFnc,
498*c8dee2aaSAndroid Build Coastguard Worker PathSegment* approxQuad);
499*c8dee2aaSAndroid Build Coastguard Worker
500*c8dee2aaSAndroid Build Coastguard Worker /** Returns a constant (deg 0) distance function for the given stroke width */
identityVarWidth(float strokeWidth)501*c8dee2aaSAndroid Build Coastguard Worker static ScalarBezCurve identityVarWidth(float strokeWidth) {
502*c8dee2aaSAndroid Build Coastguard Worker return ScalarBezCurve(0, {strokeWidth / 2.0f});
503*c8dee2aaSAndroid Build Coastguard Worker }
504*c8dee2aaSAndroid Build Coastguard Worker
505*c8dee2aaSAndroid Build Coastguard Worker float fRadius;
506*c8dee2aaSAndroid Build Coastguard Worker SkPaint::Cap fCap;
507*c8dee2aaSAndroid Build Coastguard Worker SkPaint::Join fJoin;
508*c8dee2aaSAndroid Build Coastguard Worker SkPath fInner, fOuter;
509*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve fVarWidth, fVarWidthInner;
510*c8dee2aaSAndroid Build Coastguard Worker float fCurrT;
511*c8dee2aaSAndroid Build Coastguard Worker };
512*c8dee2aaSAndroid Build Coastguard Worker
initForPath(const SkPath & path,const SkPaint & paint)513*c8dee2aaSAndroid Build Coastguard Worker void SkVarWidthStroker::initForPath(const SkPath& path, const SkPaint& paint) {
514*c8dee2aaSAndroid Build Coastguard Worker fRadius = paint.getStrokeWidth() / 2;
515*c8dee2aaSAndroid Build Coastguard Worker fCap = paint.getStrokeCap();
516*c8dee2aaSAndroid Build Coastguard Worker fJoin = paint.getStrokeJoin();
517*c8dee2aaSAndroid Build Coastguard Worker fInner.rewind();
518*c8dee2aaSAndroid Build Coastguard Worker fOuter.rewind();
519*c8dee2aaSAndroid Build Coastguard Worker fCurrT = 0;
520*c8dee2aaSAndroid Build Coastguard Worker }
521*c8dee2aaSAndroid Build Coastguard Worker
getFillPath(const SkPath & path,const SkPaint & paint,const ScalarBezCurve & varWidth,const ScalarBezCurve & varWidthInner,LengthMetric lengthMetric)522*c8dee2aaSAndroid Build Coastguard Worker SkPath SkVarWidthStroker::getFillPath(const SkPath& path,
523*c8dee2aaSAndroid Build Coastguard Worker const SkPaint& paint,
524*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& varWidth,
525*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& varWidthInner,
526*c8dee2aaSAndroid Build Coastguard Worker LengthMetric lengthMetric) {
527*c8dee2aaSAndroid Build Coastguard Worker const auto appendStrokes = [this](const OffsetSegments& strokes, bool needsMove) {
528*c8dee2aaSAndroid Build Coastguard Worker if (needsMove) {
529*c8dee2aaSAndroid Build Coastguard Worker fOuter.moveTo(strokes.fOuter.front().fPoints[0]);
530*c8dee2aaSAndroid Build Coastguard Worker fInner.moveTo(strokes.fInner.front().fPoints[0]);
531*c8dee2aaSAndroid Build Coastguard Worker }
532*c8dee2aaSAndroid Build Coastguard Worker
533*c8dee2aaSAndroid Build Coastguard Worker for (const PathSegment& seg : strokes.fOuter) {
534*c8dee2aaSAndroid Build Coastguard Worker fOuter.quadTo(seg.fPoints[1], seg.fPoints[2]);
535*c8dee2aaSAndroid Build Coastguard Worker }
536*c8dee2aaSAndroid Build Coastguard Worker
537*c8dee2aaSAndroid Build Coastguard Worker for (const PathSegment& seg : strokes.fInner) {
538*c8dee2aaSAndroid Build Coastguard Worker fInner.quadTo(seg.fPoints[1], seg.fPoints[2]);
539*c8dee2aaSAndroid Build Coastguard Worker }
540*c8dee2aaSAndroid Build Coastguard Worker };
541*c8dee2aaSAndroid Build Coastguard Worker
542*c8dee2aaSAndroid Build Coastguard Worker initForPath(path, paint);
543*c8dee2aaSAndroid Build Coastguard Worker fVarWidth = varWidth;
544*c8dee2aaSAndroid Build Coastguard Worker fVarWidthInner = varWidthInner;
545*c8dee2aaSAndroid Build Coastguard Worker
546*c8dee2aaSAndroid Build Coastguard Worker // TODO: this assumes one contour:
547*c8dee2aaSAndroid Build Coastguard Worker PathVerbMeasure meas(path);
548*c8dee2aaSAndroid Build Coastguard Worker const float totalPathLength = lengthMetric == LengthMetric::kPathLength
549*c8dee2aaSAndroid Build Coastguard Worker ? meas.totalLength()
550*c8dee2aaSAndroid Build Coastguard Worker : (path.countVerbs() - 1);
551*c8dee2aaSAndroid Build Coastguard Worker
552*c8dee2aaSAndroid Build Coastguard Worker // Trace the inner and outer paths simultaneously. Inner will therefore be
553*c8dee2aaSAndroid Build Coastguard Worker // recorded in reverse from how we trace the outline.
554*c8dee2aaSAndroid Build Coastguard Worker SkPath::Iter it(path, false);
555*c8dee2aaSAndroid Build Coastguard Worker PathSegment segment, prevSegment;
556*c8dee2aaSAndroid Build Coastguard Worker OffsetSegments offsetSegs, prevOffsetSegs;
557*c8dee2aaSAndroid Build Coastguard Worker bool firstSegment = true, prevWasFirst = false;
558*c8dee2aaSAndroid Build Coastguard Worker
559*c8dee2aaSAndroid Build Coastguard Worker float lenTraveled = 0;
560*c8dee2aaSAndroid Build Coastguard Worker while ((segment.fVerb = it.next(&segment.fPoints[0])) != SkPath::kDone_Verb) {
561*c8dee2aaSAndroid Build Coastguard Worker const float verbLength = lengthMetric == LengthMetric::kPathLength
562*c8dee2aaSAndroid Build Coastguard Worker ? (meas.currentVerbLength() / totalPathLength)
563*c8dee2aaSAndroid Build Coastguard Worker : (1.0f / totalPathLength);
564*c8dee2aaSAndroid Build Coastguard Worker const float tmin = lenTraveled;
565*c8dee2aaSAndroid Build Coastguard Worker const float tmax = lenTraveled + verbLength;
566*c8dee2aaSAndroid Build Coastguard Worker
567*c8dee2aaSAndroid Build Coastguard Worker // Subset the distance function for the current interval.
568*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve partVarWidth, partVarWidthInner;
569*c8dee2aaSAndroid Build Coastguard Worker fVarWidth.split(tmin, tmax, &partVarWidth);
570*c8dee2aaSAndroid Build Coastguard Worker fVarWidthInner.split(tmin, tmax, &partVarWidthInner);
571*c8dee2aaSAndroid Build Coastguard Worker partVarWidthInner = ScalarBezCurve::Mul(partVarWidthInner, -1);
572*c8dee2aaSAndroid Build Coastguard Worker
573*c8dee2aaSAndroid Build Coastguard Worker // Stroke the current segment
574*c8dee2aaSAndroid Build Coastguard Worker switch (segment.fVerb) {
575*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kLine_Verb:
576*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kQuad_Verb:
577*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kCubic_Verb:
578*c8dee2aaSAndroid Build Coastguard Worker offsetSegs = strokeSegment(segment, partVarWidth, partVarWidthInner, firstSegment);
579*c8dee2aaSAndroid Build Coastguard Worker break;
580*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kMove_Verb:
581*c8dee2aaSAndroid Build Coastguard Worker // Don't care about multiple contours currently
582*c8dee2aaSAndroid Build Coastguard Worker continue;
583*c8dee2aaSAndroid Build Coastguard Worker default:
584*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Unhandled path verb %d\n", segment.fVerb);
585*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(false);
586*c8dee2aaSAndroid Build Coastguard Worker break;
587*c8dee2aaSAndroid Build Coastguard Worker }
588*c8dee2aaSAndroid Build Coastguard Worker
589*c8dee2aaSAndroid Build Coastguard Worker // Join to the previous segment
590*c8dee2aaSAndroid Build Coastguard Worker if (!firstSegment) {
591*c8dee2aaSAndroid Build Coastguard Worker // Append prev inner and outer strokes
592*c8dee2aaSAndroid Build Coastguard Worker appendStrokes(prevOffsetSegs, prevWasFirst);
593*c8dee2aaSAndroid Build Coastguard Worker
594*c8dee2aaSAndroid Build Coastguard Worker // Append the join
595*c8dee2aaSAndroid Build Coastguard Worker const float innerRadius = varWidthInner.eval(tmin);
596*c8dee2aaSAndroid Build Coastguard Worker const float outerRadius = varWidth.eval(tmin);
597*c8dee2aaSAndroid Build Coastguard Worker join(segment.fPoints[0], innerRadius, outerRadius, prevOffsetSegs, offsetSegs);
598*c8dee2aaSAndroid Build Coastguard Worker }
599*c8dee2aaSAndroid Build Coastguard Worker
600*c8dee2aaSAndroid Build Coastguard Worker std::swap(segment, prevSegment);
601*c8dee2aaSAndroid Build Coastguard Worker std::swap(offsetSegs, prevOffsetSegs);
602*c8dee2aaSAndroid Build Coastguard Worker prevWasFirst = firstSegment;
603*c8dee2aaSAndroid Build Coastguard Worker firstSegment = false;
604*c8dee2aaSAndroid Build Coastguard Worker lenTraveled += verbLength;
605*c8dee2aaSAndroid Build Coastguard Worker meas.nextVerb();
606*c8dee2aaSAndroid Build Coastguard Worker }
607*c8dee2aaSAndroid Build Coastguard Worker
608*c8dee2aaSAndroid Build Coastguard Worker // Finish appending final offset segments
609*c8dee2aaSAndroid Build Coastguard Worker appendStrokes(prevOffsetSegs, prevWasFirst);
610*c8dee2aaSAndroid Build Coastguard Worker
611*c8dee2aaSAndroid Build Coastguard Worker // Open contour => endcap at the end
612*c8dee2aaSAndroid Build Coastguard Worker const bool isClosed = path.isLastContourClosed();
613*c8dee2aaSAndroid Build Coastguard Worker if (isClosed) {
614*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Unhandled closed contour\n");
615*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(false);
616*c8dee2aaSAndroid Build Coastguard Worker } else {
617*c8dee2aaSAndroid Build Coastguard Worker endcap(CapLocation::End);
618*c8dee2aaSAndroid Build Coastguard Worker }
619*c8dee2aaSAndroid Build Coastguard Worker
620*c8dee2aaSAndroid Build Coastguard Worker // Walk inner path in reverse, appending to result
621*c8dee2aaSAndroid Build Coastguard Worker appendPathReversed(fInner, &fOuter);
622*c8dee2aaSAndroid Build Coastguard Worker endcap(CapLocation::Start);
623*c8dee2aaSAndroid Build Coastguard Worker
624*c8dee2aaSAndroid Build Coastguard Worker return fOuter;
625*c8dee2aaSAndroid Build Coastguard Worker }
626*c8dee2aaSAndroid Build Coastguard Worker
strokeSegment(const PathSegment & segment,const ScalarBezCurve & varWidth,const ScalarBezCurve & varWidthInner,bool needsMove)627*c8dee2aaSAndroid Build Coastguard Worker SkVarWidthStroker::OffsetSegments SkVarWidthStroker::strokeSegment(
628*c8dee2aaSAndroid Build Coastguard Worker const PathSegment& segment,
629*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& varWidth,
630*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& varWidthInner,
631*c8dee2aaSAndroid Build Coastguard Worker bool needsMove) {
632*c8dee2aaSAndroid Build Coastguard Worker viz::outerErr.reset(nullptr);
633*c8dee2aaSAndroid Build Coastguard Worker
634*c8dee2aaSAndroid Build Coastguard Worker std::vector<PathSegment> outer = strokeSegment(segment, varWidth);
635*c8dee2aaSAndroid Build Coastguard Worker std::vector<PathSegment> inner = strokeSegment(segment, varWidthInner);
636*c8dee2aaSAndroid Build Coastguard Worker return {inner, outer};
637*c8dee2aaSAndroid Build Coastguard Worker }
638*c8dee2aaSAndroid Build Coastguard Worker
strokeSegment(const PathSegment & seg,const ScalarBezCurve & distanceFunc) const639*c8dee2aaSAndroid Build Coastguard Worker std::vector<SkVarWidthStroker::PathSegment> SkVarWidthStroker::strokeSegment(
640*c8dee2aaSAndroid Build Coastguard Worker const PathSegment& seg, const ScalarBezCurve& distanceFunc) const {
641*c8dee2aaSAndroid Build Coastguard Worker // Work item for the recursive splitting stack.
642*c8dee2aaSAndroid Build Coastguard Worker struct Item {
643*c8dee2aaSAndroid Build Coastguard Worker PathSegment fSeg;
644*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve fDistFnc, fDistFncSqd;
645*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve fSegX, fSegY;
646*c8dee2aaSAndroid Build Coastguard Worker
647*c8dee2aaSAndroid Build Coastguard Worker Item(const PathSegment& seg,
648*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& distFnc,
649*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& distFncSqd)
650*c8dee2aaSAndroid Build Coastguard Worker : fSeg(seg), fDistFnc(distFnc), fDistFncSqd(distFncSqd) {
651*c8dee2aaSAndroid Build Coastguard Worker const int segDegree = segmentDegree(seg);
652*c8dee2aaSAndroid Build Coastguard Worker fSegX = ScalarBezCurve(segDegree);
653*c8dee2aaSAndroid Build Coastguard Worker fSegY = ScalarBezCurve(segDegree);
654*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i <= segDegree; i++) {
655*c8dee2aaSAndroid Build Coastguard Worker fSegX[i] = seg.fPoints[i].fX;
656*c8dee2aaSAndroid Build Coastguard Worker fSegY[i] = seg.fPoints[i].fY;
657*c8dee2aaSAndroid Build Coastguard Worker }
658*c8dee2aaSAndroid Build Coastguard Worker }
659*c8dee2aaSAndroid Build Coastguard Worker };
660*c8dee2aaSAndroid Build Coastguard Worker
661*c8dee2aaSAndroid Build Coastguard Worker // Push the initial segment and distance function
662*c8dee2aaSAndroid Build Coastguard Worker std::stack<Item> stack;
663*c8dee2aaSAndroid Build Coastguard Worker stack.push(Item(seg, distanceFunc, ScalarBezCurve::Mul(distanceFunc, distanceFunc)));
664*c8dee2aaSAndroid Build Coastguard Worker
665*c8dee2aaSAndroid Build Coastguard Worker std::vector<PathSegment> result;
666*c8dee2aaSAndroid Build Coastguard Worker constexpr int kMaxIters = 5000; /** TODO: this is completely arbitrary */
667*c8dee2aaSAndroid Build Coastguard Worker int iter = 0;
668*c8dee2aaSAndroid Build Coastguard Worker while (!stack.empty()) {
669*c8dee2aaSAndroid Build Coastguard Worker if (iter++ >= kMaxIters) break;
670*c8dee2aaSAndroid Build Coastguard Worker const Item item = stack.top();
671*c8dee2aaSAndroid Build Coastguard Worker stack.pop();
672*c8dee2aaSAndroid Build Coastguard Worker
673*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& distFnc = item.fDistFnc;
674*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve distFncSqd = item.fDistFncSqd;
675*c8dee2aaSAndroid Build Coastguard Worker const float kTol = std::abs(0.5f * distFnc.extremumWeight());
676*c8dee2aaSAndroid Build Coastguard Worker
677*c8dee2aaSAndroid Build Coastguard Worker // Compute a quad that approximates stroke outline
678*c8dee2aaSAndroid Build Coastguard Worker PathSegment quadApprox;
679*c8dee2aaSAndroid Build Coastguard Worker approximateSegment(item.fSeg, distFnc, &quadApprox);
680*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve quadApproxX(2), quadApproxY(2);
681*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < 3; i++) {
682*c8dee2aaSAndroid Build Coastguard Worker quadApproxX[i] = quadApprox.fPoints[i].fX;
683*c8dee2aaSAndroid Build Coastguard Worker quadApproxY[i] = quadApprox.fPoints[i].fY;
684*c8dee2aaSAndroid Build Coastguard Worker }
685*c8dee2aaSAndroid Build Coastguard Worker
686*c8dee2aaSAndroid Build Coastguard Worker // Compute control polygon for the delta(t) curve. First must elevate to a common degree.
687*c8dee2aaSAndroid Build Coastguard Worker const int deltaDegree = std::max(quadApproxX.degree(), item.fSegX.degree());
688*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve segX = item.fSegX, segY = item.fSegY;
689*c8dee2aaSAndroid Build Coastguard Worker segX.elevateDegree(deltaDegree);
690*c8dee2aaSAndroid Build Coastguard Worker segY.elevateDegree(deltaDegree);
691*c8dee2aaSAndroid Build Coastguard Worker quadApproxX.elevateDegree(deltaDegree);
692*c8dee2aaSAndroid Build Coastguard Worker quadApproxY.elevateDegree(deltaDegree);
693*c8dee2aaSAndroid Build Coastguard Worker
694*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve deltaX = ScalarBezCurve::Sub(quadApproxX, segX);
695*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve deltaY = ScalarBezCurve::Sub(quadApproxY, segY);
696*c8dee2aaSAndroid Build Coastguard Worker
697*c8dee2aaSAndroid Build Coastguard Worker // Compute psi(t) = delta_x(t)^2 + delta_y(t)^2.
698*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve E = ScalarBezCurve::AddSquares(deltaX, deltaY);
699*c8dee2aaSAndroid Build Coastguard Worker
700*c8dee2aaSAndroid Build Coastguard Worker // Promote E and d(t)^2 to a common degree.
701*c8dee2aaSAndroid Build Coastguard Worker const int commonDeg = std::max(distFncSqd.degree(), E.degree());
702*c8dee2aaSAndroid Build Coastguard Worker distFncSqd.elevateDegree(commonDeg);
703*c8dee2aaSAndroid Build Coastguard Worker E.elevateDegree(commonDeg);
704*c8dee2aaSAndroid Build Coastguard Worker
705*c8dee2aaSAndroid Build Coastguard Worker // Subtract dist squared curve from E, resulting in:
706*c8dee2aaSAndroid Build Coastguard Worker // eps(t) = delta_x(t)^2 + delta_y(t)^2 - d(t)^2
707*c8dee2aaSAndroid Build Coastguard Worker E.sub(distFncSqd);
708*c8dee2aaSAndroid Build Coastguard Worker
709*c8dee2aaSAndroid Build Coastguard Worker // Purely for debugging/testing, save the first approximation and error function:
710*c8dee2aaSAndroid Build Coastguard Worker if (viz::outerErr == nullptr) {
711*c8dee2aaSAndroid Build Coastguard Worker using namespace viz;
712*c8dee2aaSAndroid Build Coastguard Worker outerErr = std::make_unique<ScalarBezCurve>(E);
713*c8dee2aaSAndroid Build Coastguard Worker outerFirstApprox.rewind();
714*c8dee2aaSAndroid Build Coastguard Worker outerFirstApprox.moveTo(quadApprox.fPoints[0]);
715*c8dee2aaSAndroid Build Coastguard Worker outerFirstApprox.quadTo(quadApprox.fPoints[1], quadApprox.fPoints[2]);
716*c8dee2aaSAndroid Build Coastguard Worker }
717*c8dee2aaSAndroid Build Coastguard Worker
718*c8dee2aaSAndroid Build Coastguard Worker // Compute maxErr, which is just the max coefficient of eps (using convex hull property
719*c8dee2aaSAndroid Build Coastguard Worker // of bez curves)
720*c8dee2aaSAndroid Build Coastguard Worker float maxAbsErr = std::abs(E.extremumWeight());
721*c8dee2aaSAndroid Build Coastguard Worker
722*c8dee2aaSAndroid Build Coastguard Worker if (maxAbsErr > kTol) {
723*c8dee2aaSAndroid Build Coastguard Worker PathSegment left, right;
724*c8dee2aaSAndroid Build Coastguard Worker splitSegment(item.fSeg, 0.5f, &left, &right);
725*c8dee2aaSAndroid Build Coastguard Worker
726*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve distFncL, distFncR;
727*c8dee2aaSAndroid Build Coastguard Worker distFnc.split(0.5f, &distFncL, &distFncR);
728*c8dee2aaSAndroid Build Coastguard Worker
729*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve distFncSqdL, distFncSqdR;
730*c8dee2aaSAndroid Build Coastguard Worker distFncSqd.split(0.5f, &distFncSqdL, &distFncSqdR);
731*c8dee2aaSAndroid Build Coastguard Worker
732*c8dee2aaSAndroid Build Coastguard Worker stack.push(Item(right, distFncR, distFncSqdR));
733*c8dee2aaSAndroid Build Coastguard Worker stack.push(Item(left, distFncL, distFncSqdL));
734*c8dee2aaSAndroid Build Coastguard Worker } else {
735*c8dee2aaSAndroid Build Coastguard Worker // Approximation is good enough.
736*c8dee2aaSAndroid Build Coastguard Worker quadApprox.fVerb = SkPath::kQuad_Verb;
737*c8dee2aaSAndroid Build Coastguard Worker result.push_back(quadApprox);
738*c8dee2aaSAndroid Build Coastguard Worker }
739*c8dee2aaSAndroid Build Coastguard Worker }
740*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(!result.empty());
741*c8dee2aaSAndroid Build Coastguard Worker return result;
742*c8dee2aaSAndroid Build Coastguard Worker }
743*c8dee2aaSAndroid Build Coastguard Worker
endcap(CapLocation loc)744*c8dee2aaSAndroid Build Coastguard Worker void SkVarWidthStroker::endcap(CapLocation loc) {
745*c8dee2aaSAndroid Build Coastguard Worker const auto buttCap = [this](CapLocation loc) {
746*c8dee2aaSAndroid Build Coastguard Worker if (loc == CapLocation::Start) {
747*c8dee2aaSAndroid Build Coastguard Worker // Back at the start of the path: just close the stroked outline
748*c8dee2aaSAndroid Build Coastguard Worker fOuter.close();
749*c8dee2aaSAndroid Build Coastguard Worker } else {
750*c8dee2aaSAndroid Build Coastguard Worker // Inner last pt == first pt when appending in reverse
751*c8dee2aaSAndroid Build Coastguard Worker SkPoint innerLastPt;
752*c8dee2aaSAndroid Build Coastguard Worker fInner.getLastPt(&innerLastPt);
753*c8dee2aaSAndroid Build Coastguard Worker fOuter.lineTo(innerLastPt);
754*c8dee2aaSAndroid Build Coastguard Worker }
755*c8dee2aaSAndroid Build Coastguard Worker };
756*c8dee2aaSAndroid Build Coastguard Worker
757*c8dee2aaSAndroid Build Coastguard Worker switch (fCap) {
758*c8dee2aaSAndroid Build Coastguard Worker case SkPaint::kButt_Cap:
759*c8dee2aaSAndroid Build Coastguard Worker buttCap(loc);
760*c8dee2aaSAndroid Build Coastguard Worker break;
761*c8dee2aaSAndroid Build Coastguard Worker default:
762*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Unhandled endcap %d\n", fCap);
763*c8dee2aaSAndroid Build Coastguard Worker buttCap(loc);
764*c8dee2aaSAndroid Build Coastguard Worker break;
765*c8dee2aaSAndroid Build Coastguard Worker }
766*c8dee2aaSAndroid Build Coastguard Worker }
767*c8dee2aaSAndroid Build Coastguard Worker
join(const SkPoint & common,float innerRadius,float outerRadius,const OffsetSegments & prev,const OffsetSegments & curr)768*c8dee2aaSAndroid Build Coastguard Worker void SkVarWidthStroker::join(const SkPoint& common,
769*c8dee2aaSAndroid Build Coastguard Worker float innerRadius,
770*c8dee2aaSAndroid Build Coastguard Worker float outerRadius,
771*c8dee2aaSAndroid Build Coastguard Worker const OffsetSegments& prev,
772*c8dee2aaSAndroid Build Coastguard Worker const OffsetSegments& curr) {
773*c8dee2aaSAndroid Build Coastguard Worker const auto miterJoin = [this](const SkPoint& common,
774*c8dee2aaSAndroid Build Coastguard Worker float leftRadius,
775*c8dee2aaSAndroid Build Coastguard Worker float rightRadius,
776*c8dee2aaSAndroid Build Coastguard Worker const OffsetSegments& prev,
777*c8dee2aaSAndroid Build Coastguard Worker const OffsetSegments& curr) {
778*c8dee2aaSAndroid Build Coastguard Worker // With variable-width stroke you can actually have a situation where both sides
779*c8dee2aaSAndroid Build Coastguard Worker // need an "inner" or an "outer" join. So we call the two sides "left" and
780*c8dee2aaSAndroid Build Coastguard Worker // "right" and they can each independently get an inner or outer join.
781*c8dee2aaSAndroid Build Coastguard Worker const auto makeJoin = [this, &common, &prev, &curr](bool left, float radius) {
782*c8dee2aaSAndroid Build Coastguard Worker SkPath* path = left ? &fOuter : &fInner;
783*c8dee2aaSAndroid Build Coastguard Worker const auto& prevSegs = left ? prev.fOuter : prev.fInner;
784*c8dee2aaSAndroid Build Coastguard Worker const auto& currSegs = left ? curr.fOuter : curr.fInner;
785*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(!prevSegs.empty());
786*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(!currSegs.empty());
787*c8dee2aaSAndroid Build Coastguard Worker const SkPoint afterEndpt = currSegs.front().fPoints[0];
788*c8dee2aaSAndroid Build Coastguard Worker SkPoint before = unitNormal(prevSegs.back(), 1, nullptr);
789*c8dee2aaSAndroid Build Coastguard Worker SkPoint after = unitNormal(currSegs.front(), 0, nullptr);
790*c8dee2aaSAndroid Build Coastguard Worker
791*c8dee2aaSAndroid Build Coastguard Worker // Don't create any join geometry if the normals are nearly identical.
792*c8dee2aaSAndroid Build Coastguard Worker const float cosTheta = before.dot(after);
793*c8dee2aaSAndroid Build Coastguard Worker if (!SkScalarNearlyZero(1 - cosTheta)) {
794*c8dee2aaSAndroid Build Coastguard Worker bool outerJoin;
795*c8dee2aaSAndroid Build Coastguard Worker if (left) {
796*c8dee2aaSAndroid Build Coastguard Worker outerJoin = isClockwise(before, after);
797*c8dee2aaSAndroid Build Coastguard Worker } else {
798*c8dee2aaSAndroid Build Coastguard Worker before = rotate180(before);
799*c8dee2aaSAndroid Build Coastguard Worker after = rotate180(after);
800*c8dee2aaSAndroid Build Coastguard Worker outerJoin = !isClockwise(before, after);
801*c8dee2aaSAndroid Build Coastguard Worker }
802*c8dee2aaSAndroid Build Coastguard Worker
803*c8dee2aaSAndroid Build Coastguard Worker if (outerJoin) {
804*c8dee2aaSAndroid Build Coastguard Worker // Before and after have the same origin and magnitude, so before+after is the
805*c8dee2aaSAndroid Build Coastguard Worker // diagonal of their rhombus. Origin of this vector is the midpoint of the miter
806*c8dee2aaSAndroid Build Coastguard Worker // line.
807*c8dee2aaSAndroid Build Coastguard Worker SkPoint miterVec = before + after;
808*c8dee2aaSAndroid Build Coastguard Worker
809*c8dee2aaSAndroid Build Coastguard Worker // Note the relationship (draw a right triangle with the miter line as its
810*c8dee2aaSAndroid Build Coastguard Worker // hypoteneuse):
811*c8dee2aaSAndroid Build Coastguard Worker // sin(theta/2) = strokeWidth / miterLength
812*c8dee2aaSAndroid Build Coastguard Worker // so miterLength = strokeWidth / sin(theta/2)
813*c8dee2aaSAndroid Build Coastguard Worker // where miterLength is the length of the miter from outer point to inner
814*c8dee2aaSAndroid Build Coastguard Worker // corner. miterVec's origin is the midpoint of the miter line, so we use
815*c8dee2aaSAndroid Build Coastguard Worker // strokeWidth/2. Sqrt is just an application of half-angle identities.
816*c8dee2aaSAndroid Build Coastguard Worker const float sinHalfTheta = sqrtf(0.5 * (1 + cosTheta));
817*c8dee2aaSAndroid Build Coastguard Worker const float halfMiterLength = radius / sinHalfTheta;
818*c8dee2aaSAndroid Build Coastguard Worker // TODO: miter length limit
819*c8dee2aaSAndroid Build Coastguard Worker miterVec = setLength(miterVec, halfMiterLength);
820*c8dee2aaSAndroid Build Coastguard Worker
821*c8dee2aaSAndroid Build Coastguard Worker // Outer join: connect to the miter point, and then to t=0 of next segment.
822*c8dee2aaSAndroid Build Coastguard Worker path->lineTo(common + miterVec);
823*c8dee2aaSAndroid Build Coastguard Worker path->lineTo(afterEndpt);
824*c8dee2aaSAndroid Build Coastguard Worker } else {
825*c8dee2aaSAndroid Build Coastguard Worker // Connect to the miter midpoint (common path endpoint of the two segments),
826*c8dee2aaSAndroid Build Coastguard Worker // and then to t=0 of the next segment. This adds an interior "loop"
827*c8dee2aaSAndroid Build Coastguard Worker // of geometry that handles edge cases where segment lengths are shorter than
828*c8dee2aaSAndroid Build Coastguard Worker // the stroke width.
829*c8dee2aaSAndroid Build Coastguard Worker path->lineTo(common);
830*c8dee2aaSAndroid Build Coastguard Worker path->lineTo(afterEndpt);
831*c8dee2aaSAndroid Build Coastguard Worker }
832*c8dee2aaSAndroid Build Coastguard Worker }
833*c8dee2aaSAndroid Build Coastguard Worker };
834*c8dee2aaSAndroid Build Coastguard Worker
835*c8dee2aaSAndroid Build Coastguard Worker makeJoin(true, leftRadius);
836*c8dee2aaSAndroid Build Coastguard Worker makeJoin(false, rightRadius);
837*c8dee2aaSAndroid Build Coastguard Worker };
838*c8dee2aaSAndroid Build Coastguard Worker
839*c8dee2aaSAndroid Build Coastguard Worker switch (fJoin) {
840*c8dee2aaSAndroid Build Coastguard Worker case SkPaint::kMiter_Join:
841*c8dee2aaSAndroid Build Coastguard Worker miterJoin(common, innerRadius, outerRadius, prev, curr);
842*c8dee2aaSAndroid Build Coastguard Worker break;
843*c8dee2aaSAndroid Build Coastguard Worker default:
844*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Unhandled join %d\n", fJoin);
845*c8dee2aaSAndroid Build Coastguard Worker miterJoin(common, innerRadius, outerRadius, prev, curr);
846*c8dee2aaSAndroid Build Coastguard Worker break;
847*c8dee2aaSAndroid Build Coastguard Worker }
848*c8dee2aaSAndroid Build Coastguard Worker }
849*c8dee2aaSAndroid Build Coastguard Worker
appendPathReversed(const SkPath & path,SkPath * result)850*c8dee2aaSAndroid Build Coastguard Worker void SkVarWidthStroker::appendPathReversed(const SkPath& path, SkPath* result) {
851*c8dee2aaSAndroid Build Coastguard Worker const int numVerbs = path.countVerbs();
852*c8dee2aaSAndroid Build Coastguard Worker const int numPoints = path.countPoints();
853*c8dee2aaSAndroid Build Coastguard Worker std::vector<uint8_t> verbs;
854*c8dee2aaSAndroid Build Coastguard Worker std::vector<SkPoint> points;
855*c8dee2aaSAndroid Build Coastguard Worker verbs.resize(numVerbs);
856*c8dee2aaSAndroid Build Coastguard Worker points.resize(numPoints);
857*c8dee2aaSAndroid Build Coastguard Worker path.getVerbs(verbs.data(), numVerbs);
858*c8dee2aaSAndroid Build Coastguard Worker path.getPoints(points.data(), numPoints);
859*c8dee2aaSAndroid Build Coastguard Worker
860*c8dee2aaSAndroid Build Coastguard Worker for (int i = numVerbs - 1, j = numPoints; i >= 0; i--) {
861*c8dee2aaSAndroid Build Coastguard Worker auto verb = static_cast<SkPath::Verb>(verbs[i]);
862*c8dee2aaSAndroid Build Coastguard Worker switch (verb) {
863*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kLine_Verb: {
864*c8dee2aaSAndroid Build Coastguard Worker j -= 1;
865*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(j >= 1);
866*c8dee2aaSAndroid Build Coastguard Worker result->lineTo(points[j - 1]);
867*c8dee2aaSAndroid Build Coastguard Worker break;
868*c8dee2aaSAndroid Build Coastguard Worker }
869*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kQuad_Verb: {
870*c8dee2aaSAndroid Build Coastguard Worker j -= 1;
871*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(j >= 2);
872*c8dee2aaSAndroid Build Coastguard Worker result->quadTo(points[j - 1], points[j - 2]);
873*c8dee2aaSAndroid Build Coastguard Worker j -= 1;
874*c8dee2aaSAndroid Build Coastguard Worker break;
875*c8dee2aaSAndroid Build Coastguard Worker }
876*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kMove_Verb:
877*c8dee2aaSAndroid Build Coastguard Worker // Ignore
878*c8dee2aaSAndroid Build Coastguard Worker break;
879*c8dee2aaSAndroid Build Coastguard Worker default:
880*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(false);
881*c8dee2aaSAndroid Build Coastguard Worker break;
882*c8dee2aaSAndroid Build Coastguard Worker }
883*c8dee2aaSAndroid Build Coastguard Worker }
884*c8dee2aaSAndroid Build Coastguard Worker }
885*c8dee2aaSAndroid Build Coastguard Worker
segmentDegree(const PathSegment & seg)886*c8dee2aaSAndroid Build Coastguard Worker int SkVarWidthStroker::segmentDegree(const PathSegment& seg) {
887*c8dee2aaSAndroid Build Coastguard Worker static constexpr int lut[] = {
888*c8dee2aaSAndroid Build Coastguard Worker -1, // move,
889*c8dee2aaSAndroid Build Coastguard Worker 1, // line
890*c8dee2aaSAndroid Build Coastguard Worker 2, // quad
891*c8dee2aaSAndroid Build Coastguard Worker -1, // conic
892*c8dee2aaSAndroid Build Coastguard Worker 3, // cubic
893*c8dee2aaSAndroid Build Coastguard Worker -1 // done
894*c8dee2aaSAndroid Build Coastguard Worker };
895*c8dee2aaSAndroid Build Coastguard Worker const int deg = lut[static_cast<uint8_t>(seg.fVerb)];
896*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(deg > 0);
897*c8dee2aaSAndroid Build Coastguard Worker return deg;
898*c8dee2aaSAndroid Build Coastguard Worker }
899*c8dee2aaSAndroid Build Coastguard Worker
splitSegment(const PathSegment & seg,float t,PathSegment * segA,PathSegment * segB)900*c8dee2aaSAndroid Build Coastguard Worker void SkVarWidthStroker::splitSegment(const PathSegment& seg,
901*c8dee2aaSAndroid Build Coastguard Worker float t,
902*c8dee2aaSAndroid Build Coastguard Worker PathSegment* segA,
903*c8dee2aaSAndroid Build Coastguard Worker PathSegment* segB) {
904*c8dee2aaSAndroid Build Coastguard Worker // TODO: although general, this is a pretty slow way to do this
905*c8dee2aaSAndroid Build Coastguard Worker const int degree = segmentDegree(seg);
906*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve x(degree), y(degree);
907*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i <= degree; i++) {
908*c8dee2aaSAndroid Build Coastguard Worker x[i] = seg.fPoints[i].fX;
909*c8dee2aaSAndroid Build Coastguard Worker y[i] = seg.fPoints[i].fY;
910*c8dee2aaSAndroid Build Coastguard Worker }
911*c8dee2aaSAndroid Build Coastguard Worker
912*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve leftX(degree), rightX(degree), leftY(degree), rightY(degree);
913*c8dee2aaSAndroid Build Coastguard Worker x.split(t, &leftX, &rightX);
914*c8dee2aaSAndroid Build Coastguard Worker y.split(t, &leftY, &rightY);
915*c8dee2aaSAndroid Build Coastguard Worker
916*c8dee2aaSAndroid Build Coastguard Worker segA->fVerb = segB->fVerb = seg.fVerb;
917*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i <= degree; i++) {
918*c8dee2aaSAndroid Build Coastguard Worker segA->fPoints[i] = {leftX[i], leftY[i]};
919*c8dee2aaSAndroid Build Coastguard Worker segB->fPoints[i] = {rightX[i], rightY[i]};
920*c8dee2aaSAndroid Build Coastguard Worker }
921*c8dee2aaSAndroid Build Coastguard Worker }
922*c8dee2aaSAndroid Build Coastguard Worker
approximateSegment(const PathSegment & seg,const ScalarBezCurve & distFnc,PathSegment * approxQuad)923*c8dee2aaSAndroid Build Coastguard Worker void SkVarWidthStroker::approximateSegment(const PathSegment& seg,
924*c8dee2aaSAndroid Build Coastguard Worker const ScalarBezCurve& distFnc,
925*c8dee2aaSAndroid Build Coastguard Worker PathSegment* approxQuad) {
926*c8dee2aaSAndroid Build Coastguard Worker // This is a simple control polygon transformation.
927*c8dee2aaSAndroid Build Coastguard Worker // From F. Yzerman. "Precise offsetting of quadratic Bezier curves". 2019.
928*c8dee2aaSAndroid Build Coastguard Worker // TODO: detect and handle more degenerate cases (e.g. linear)
929*c8dee2aaSAndroid Build Coastguard Worker // TODO: Tiller-Hanson works better in many cases but does not generalize well
930*c8dee2aaSAndroid Build Coastguard Worker SkPoint tangentStart, tangentEnd;
931*c8dee2aaSAndroid Build Coastguard Worker SkPoint offsetStart = unitNormal(seg, 0, &tangentStart);
932*c8dee2aaSAndroid Build Coastguard Worker SkPoint offsetEnd = unitNormal(seg, 1, &tangentEnd);
933*c8dee2aaSAndroid Build Coastguard Worker SkPoint offsetMid = offsetStart + offsetEnd;
934*c8dee2aaSAndroid Build Coastguard Worker
935*c8dee2aaSAndroid Build Coastguard Worker const float radiusStart = distFnc.eval(0);
936*c8dee2aaSAndroid Build Coastguard Worker const float radiusMid = distFnc.eval(0.5f);
937*c8dee2aaSAndroid Build Coastguard Worker const float radiusEnd = distFnc.eval(1);
938*c8dee2aaSAndroid Build Coastguard Worker
939*c8dee2aaSAndroid Build Coastguard Worker offsetStart = radiusStart == 0 ? SkPoint::Make(0, 0) : setLength(offsetStart, radiusStart);
940*c8dee2aaSAndroid Build Coastguard Worker offsetMid = radiusMid == 0 ? SkPoint::Make(0, 0) : setLength(offsetMid, radiusMid);
941*c8dee2aaSAndroid Build Coastguard Worker offsetEnd = radiusEnd == 0 ? SkPoint::Make(0, 0) : setLength(offsetEnd, radiusEnd);
942*c8dee2aaSAndroid Build Coastguard Worker
943*c8dee2aaSAndroid Build Coastguard Worker SkPoint start, mid, end;
944*c8dee2aaSAndroid Build Coastguard Worker switch (segmentDegree(seg)) {
945*c8dee2aaSAndroid Build Coastguard Worker case 1:
946*c8dee2aaSAndroid Build Coastguard Worker start = seg.fPoints[0];
947*c8dee2aaSAndroid Build Coastguard Worker end = seg.fPoints[1];
948*c8dee2aaSAndroid Build Coastguard Worker mid = (start + end) * 0.5f;
949*c8dee2aaSAndroid Build Coastguard Worker break;
950*c8dee2aaSAndroid Build Coastguard Worker case 2:
951*c8dee2aaSAndroid Build Coastguard Worker start = seg.fPoints[0];
952*c8dee2aaSAndroid Build Coastguard Worker mid = seg.fPoints[1];
953*c8dee2aaSAndroid Build Coastguard Worker end = seg.fPoints[2];
954*c8dee2aaSAndroid Build Coastguard Worker break;
955*c8dee2aaSAndroid Build Coastguard Worker case 3:
956*c8dee2aaSAndroid Build Coastguard Worker start = seg.fPoints[0];
957*c8dee2aaSAndroid Build Coastguard Worker mid = (seg.fPoints[1] + seg.fPoints[2]) * 0.5f;
958*c8dee2aaSAndroid Build Coastguard Worker end = seg.fPoints[3];
959*c8dee2aaSAndroid Build Coastguard Worker break;
960*c8dee2aaSAndroid Build Coastguard Worker default:
961*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Unhandled degree for segment approximation");
962*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(false);
963*c8dee2aaSAndroid Build Coastguard Worker break;
964*c8dee2aaSAndroid Build Coastguard Worker }
965*c8dee2aaSAndroid Build Coastguard Worker
966*c8dee2aaSAndroid Build Coastguard Worker approxQuad->fPoints[0] = start + offsetStart;
967*c8dee2aaSAndroid Build Coastguard Worker approxQuad->fPoints[1] = mid + offsetMid;
968*c8dee2aaSAndroid Build Coastguard Worker approxQuad->fPoints[2] = end + offsetEnd;
969*c8dee2aaSAndroid Build Coastguard Worker }
970*c8dee2aaSAndroid Build Coastguard Worker
unitNormal(const PathSegment & seg,float t,SkPoint * tangentOut)971*c8dee2aaSAndroid Build Coastguard Worker SkPoint SkVarWidthStroker::unitNormal(const PathSegment& seg, float t, SkPoint* tangentOut) {
972*c8dee2aaSAndroid Build Coastguard Worker switch (seg.fVerb) {
973*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kLine_Verb: {
974*c8dee2aaSAndroid Build Coastguard Worker const SkPoint tangent = setLength(seg.fPoints[1] - seg.fPoints[0], 1);
975*c8dee2aaSAndroid Build Coastguard Worker const SkPoint normal = rotate90(tangent);
976*c8dee2aaSAndroid Build Coastguard Worker if (tangentOut) {
977*c8dee2aaSAndroid Build Coastguard Worker *tangentOut = tangent;
978*c8dee2aaSAndroid Build Coastguard Worker }
979*c8dee2aaSAndroid Build Coastguard Worker return normal;
980*c8dee2aaSAndroid Build Coastguard Worker }
981*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kQuad_Verb: {
982*c8dee2aaSAndroid Build Coastguard Worker SkPoint tangent;
983*c8dee2aaSAndroid Build Coastguard Worker if (t == 0) {
984*c8dee2aaSAndroid Build Coastguard Worker tangent = seg.fPoints[1] - seg.fPoints[0];
985*c8dee2aaSAndroid Build Coastguard Worker } else if (t == 1) {
986*c8dee2aaSAndroid Build Coastguard Worker tangent = seg.fPoints[2] - seg.fPoints[1];
987*c8dee2aaSAndroid Build Coastguard Worker } else {
988*c8dee2aaSAndroid Build Coastguard Worker tangent = ((seg.fPoints[1] - seg.fPoints[0]) * (1 - t) +
989*c8dee2aaSAndroid Build Coastguard Worker (seg.fPoints[2] - seg.fPoints[1]) * t) *
990*c8dee2aaSAndroid Build Coastguard Worker 2;
991*c8dee2aaSAndroid Build Coastguard Worker }
992*c8dee2aaSAndroid Build Coastguard Worker if (!tangent.normalize()) {
993*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Failed to normalize quad tangent\n");
994*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(false);
995*c8dee2aaSAndroid Build Coastguard Worker }
996*c8dee2aaSAndroid Build Coastguard Worker if (tangentOut) {
997*c8dee2aaSAndroid Build Coastguard Worker *tangentOut = tangent;
998*c8dee2aaSAndroid Build Coastguard Worker }
999*c8dee2aaSAndroid Build Coastguard Worker return rotate90(tangent);
1000*c8dee2aaSAndroid Build Coastguard Worker }
1001*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kCubic_Verb: {
1002*c8dee2aaSAndroid Build Coastguard Worker SkPoint tangent;
1003*c8dee2aaSAndroid Build Coastguard Worker SkEvalCubicAt(seg.fPoints.data(), t, nullptr, &tangent, nullptr);
1004*c8dee2aaSAndroid Build Coastguard Worker if (!tangent.normalize()) {
1005*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Failed to normalize cubic tangent\n");
1006*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(false);
1007*c8dee2aaSAndroid Build Coastguard Worker }
1008*c8dee2aaSAndroid Build Coastguard Worker if (tangentOut) {
1009*c8dee2aaSAndroid Build Coastguard Worker *tangentOut = tangent;
1010*c8dee2aaSAndroid Build Coastguard Worker }
1011*c8dee2aaSAndroid Build Coastguard Worker return rotate90(tangent);
1012*c8dee2aaSAndroid Build Coastguard Worker }
1013*c8dee2aaSAndroid Build Coastguard Worker default:
1014*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Unhandled verb for unit normal %d\n", seg.fVerb);
1015*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(false);
1016*c8dee2aaSAndroid Build Coastguard Worker return {};
1017*c8dee2aaSAndroid Build Coastguard Worker }
1018*c8dee2aaSAndroid Build Coastguard Worker }
1019*c8dee2aaSAndroid Build Coastguard Worker
1020*c8dee2aaSAndroid Build Coastguard Worker } // namespace
1021*c8dee2aaSAndroid Build Coastguard Worker
1022*c8dee2aaSAndroid Build Coastguard Worker //////////////////////////////////////////////////////////////////////////////
1023*c8dee2aaSAndroid Build Coastguard Worker
1024*c8dee2aaSAndroid Build Coastguard Worker class VariableWidthStrokerSlide : public ClickHandlerSlide {
1025*c8dee2aaSAndroid Build Coastguard Worker public:
VariableWidthStrokerSlide()1026*c8dee2aaSAndroid Build Coastguard Worker VariableWidthStrokerSlide()
1027*c8dee2aaSAndroid Build Coastguard Worker : fShowHidden(true)
1028*c8dee2aaSAndroid Build Coastguard Worker , fShowSkeleton(true)
1029*c8dee2aaSAndroid Build Coastguard Worker , fShowStrokePoints(false)
1030*c8dee2aaSAndroid Build Coastguard Worker , fShowUI(false)
1031*c8dee2aaSAndroid Build Coastguard Worker , fDifferentInnerFunc(false)
1032*c8dee2aaSAndroid Build Coastguard Worker , fShowErrorCurve(false) {
1033*c8dee2aaSAndroid Build Coastguard Worker resetToDefaults();
1034*c8dee2aaSAndroid Build Coastguard Worker
1035*c8dee2aaSAndroid Build Coastguard Worker fPtsPaint.setAntiAlias(true);
1036*c8dee2aaSAndroid Build Coastguard Worker fPtsPaint.setStrokeWidth(10);
1037*c8dee2aaSAndroid Build Coastguard Worker fPtsPaint.setStrokeCap(SkPaint::kRound_Cap);
1038*c8dee2aaSAndroid Build Coastguard Worker
1039*c8dee2aaSAndroid Build Coastguard Worker fStrokePointsPaint.setAntiAlias(true);
1040*c8dee2aaSAndroid Build Coastguard Worker fStrokePointsPaint.setStrokeWidth(5);
1041*c8dee2aaSAndroid Build Coastguard Worker fStrokePointsPaint.setStrokeCap(SkPaint::kRound_Cap);
1042*c8dee2aaSAndroid Build Coastguard Worker
1043*c8dee2aaSAndroid Build Coastguard Worker fStrokePaint.setAntiAlias(true);
1044*c8dee2aaSAndroid Build Coastguard Worker fStrokePaint.setStyle(SkPaint::kStroke_Style);
1045*c8dee2aaSAndroid Build Coastguard Worker fStrokePaint.setColor(0x80FF0000);
1046*c8dee2aaSAndroid Build Coastguard Worker
1047*c8dee2aaSAndroid Build Coastguard Worker fNewFillPaint.setAntiAlias(true);
1048*c8dee2aaSAndroid Build Coastguard Worker fNewFillPaint.setColor(0x8000FF00);
1049*c8dee2aaSAndroid Build Coastguard Worker
1050*c8dee2aaSAndroid Build Coastguard Worker fHiddenPaint.setAntiAlias(true);
1051*c8dee2aaSAndroid Build Coastguard Worker fHiddenPaint.setStyle(SkPaint::kStroke_Style);
1052*c8dee2aaSAndroid Build Coastguard Worker fHiddenPaint.setColor(0xFF0000FF);
1053*c8dee2aaSAndroid Build Coastguard Worker
1054*c8dee2aaSAndroid Build Coastguard Worker fSkeletonPaint.setAntiAlias(true);
1055*c8dee2aaSAndroid Build Coastguard Worker fSkeletonPaint.setStyle(SkPaint::kStroke_Style);
1056*c8dee2aaSAndroid Build Coastguard Worker fSkeletonPaint.setColor(SK_ColorRED);
1057*c8dee2aaSAndroid Build Coastguard Worker
1058*c8dee2aaSAndroid Build Coastguard Worker fName = "VariableWidthStroker";
1059*c8dee2aaSAndroid Build Coastguard Worker }
1060*c8dee2aaSAndroid Build Coastguard Worker
load(SkScalar w,SkScalar h)1061*c8dee2aaSAndroid Build Coastguard Worker void load(SkScalar w, SkScalar h) override { fWinSize = {w, h}; }
1062*c8dee2aaSAndroid Build Coastguard Worker
resize(SkScalar w,SkScalar h)1063*c8dee2aaSAndroid Build Coastguard Worker void resize(SkScalar w, SkScalar h) override { fWinSize = {w, h}; }
1064*c8dee2aaSAndroid Build Coastguard Worker
onChar(SkUnichar uni)1065*c8dee2aaSAndroid Build Coastguard Worker bool onChar(SkUnichar uni) override {
1066*c8dee2aaSAndroid Build Coastguard Worker switch (uni) {
1067*c8dee2aaSAndroid Build Coastguard Worker case '0':
1068*c8dee2aaSAndroid Build Coastguard Worker this->toggle(fShowUI);
1069*c8dee2aaSAndroid Build Coastguard Worker return true;
1070*c8dee2aaSAndroid Build Coastguard Worker case '1':
1071*c8dee2aaSAndroid Build Coastguard Worker this->toggle(fShowSkeleton);
1072*c8dee2aaSAndroid Build Coastguard Worker return true;
1073*c8dee2aaSAndroid Build Coastguard Worker case '2':
1074*c8dee2aaSAndroid Build Coastguard Worker this->toggle(fShowHidden);
1075*c8dee2aaSAndroid Build Coastguard Worker return true;
1076*c8dee2aaSAndroid Build Coastguard Worker case '3':
1077*c8dee2aaSAndroid Build Coastguard Worker this->toggle(fShowStrokePoints);
1078*c8dee2aaSAndroid Build Coastguard Worker return true;
1079*c8dee2aaSAndroid Build Coastguard Worker case '4':
1080*c8dee2aaSAndroid Build Coastguard Worker this->toggle(fShowErrorCurve);
1081*c8dee2aaSAndroid Build Coastguard Worker return true;
1082*c8dee2aaSAndroid Build Coastguard Worker case '5':
1083*c8dee2aaSAndroid Build Coastguard Worker this->toggle(fLengthMetric);
1084*c8dee2aaSAndroid Build Coastguard Worker return true;
1085*c8dee2aaSAndroid Build Coastguard Worker case 'x':
1086*c8dee2aaSAndroid Build Coastguard Worker resetToDefaults();
1087*c8dee2aaSAndroid Build Coastguard Worker return true;
1088*c8dee2aaSAndroid Build Coastguard Worker case '-':
1089*c8dee2aaSAndroid Build Coastguard Worker fWidth -= 5;
1090*c8dee2aaSAndroid Build Coastguard Worker return true;
1091*c8dee2aaSAndroid Build Coastguard Worker case '=':
1092*c8dee2aaSAndroid Build Coastguard Worker fWidth += 5;
1093*c8dee2aaSAndroid Build Coastguard Worker return true;
1094*c8dee2aaSAndroid Build Coastguard Worker default:
1095*c8dee2aaSAndroid Build Coastguard Worker break;
1096*c8dee2aaSAndroid Build Coastguard Worker }
1097*c8dee2aaSAndroid Build Coastguard Worker return false;
1098*c8dee2aaSAndroid Build Coastguard Worker }
1099*c8dee2aaSAndroid Build Coastguard Worker
draw(SkCanvas * canvas)1100*c8dee2aaSAndroid Build Coastguard Worker void draw(SkCanvas* canvas) override {
1101*c8dee2aaSAndroid Build Coastguard Worker canvas->drawColor(0xFFEEEEEE);
1102*c8dee2aaSAndroid Build Coastguard Worker
1103*c8dee2aaSAndroid Build Coastguard Worker SkPath path;
1104*c8dee2aaSAndroid Build Coastguard Worker this->makePath(&path);
1105*c8dee2aaSAndroid Build Coastguard Worker
1106*c8dee2aaSAndroid Build Coastguard Worker fStrokePaint.setStrokeWidth(fWidth);
1107*c8dee2aaSAndroid Build Coastguard Worker
1108*c8dee2aaSAndroid Build Coastguard Worker // Elber-Cohen stroker result
1109*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve distFnc = makeDistFnc(fDistFncs, fWidth);
1110*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve distFncInner =
1111*c8dee2aaSAndroid Build Coastguard Worker fDifferentInnerFunc ? makeDistFnc(fDistFncsInner, fWidth) : distFnc;
1112*c8dee2aaSAndroid Build Coastguard Worker SkVarWidthStroker stroker;
1113*c8dee2aaSAndroid Build Coastguard Worker SkPath fillPath =
1114*c8dee2aaSAndroid Build Coastguard Worker stroker.getFillPath(path, fStrokePaint, distFnc, distFncInner, fLengthMetric);
1115*c8dee2aaSAndroid Build Coastguard Worker fillPath.setFillType(SkPathFillType::kWinding);
1116*c8dee2aaSAndroid Build Coastguard Worker canvas->drawPath(fillPath, fNewFillPaint);
1117*c8dee2aaSAndroid Build Coastguard Worker
1118*c8dee2aaSAndroid Build Coastguard Worker if (fShowHidden) {
1119*c8dee2aaSAndroid Build Coastguard Worker canvas->drawPath(fillPath, fHiddenPaint);
1120*c8dee2aaSAndroid Build Coastguard Worker }
1121*c8dee2aaSAndroid Build Coastguard Worker
1122*c8dee2aaSAndroid Build Coastguard Worker if (fShowSkeleton) {
1123*c8dee2aaSAndroid Build Coastguard Worker canvas->drawPath(path, fSkeletonPaint);
1124*c8dee2aaSAndroid Build Coastguard Worker canvas->drawPoints(SkCanvas::kPoints_PointMode, fPathPts.size(), fPathPts.data(),
1125*c8dee2aaSAndroid Build Coastguard Worker fPtsPaint);
1126*c8dee2aaSAndroid Build Coastguard Worker }
1127*c8dee2aaSAndroid Build Coastguard Worker
1128*c8dee2aaSAndroid Build Coastguard Worker if (fShowStrokePoints) {
1129*c8dee2aaSAndroid Build Coastguard Worker drawStrokePoints(canvas, fillPath);
1130*c8dee2aaSAndroid Build Coastguard Worker }
1131*c8dee2aaSAndroid Build Coastguard Worker
1132*c8dee2aaSAndroid Build Coastguard Worker if (fShowUI) {
1133*c8dee2aaSAndroid Build Coastguard Worker drawUI();
1134*c8dee2aaSAndroid Build Coastguard Worker }
1135*c8dee2aaSAndroid Build Coastguard Worker
1136*c8dee2aaSAndroid Build Coastguard Worker if (fShowErrorCurve && viz::outerErr != nullptr) {
1137*c8dee2aaSAndroid Build Coastguard Worker SkPaint firstApproxPaint;
1138*c8dee2aaSAndroid Build Coastguard Worker firstApproxPaint.setStrokeWidth(4);
1139*c8dee2aaSAndroid Build Coastguard Worker firstApproxPaint.setStyle(SkPaint::kStroke_Style);
1140*c8dee2aaSAndroid Build Coastguard Worker firstApproxPaint.setColor(SK_ColorRED);
1141*c8dee2aaSAndroid Build Coastguard Worker canvas->drawPath(viz::outerFirstApprox, firstApproxPaint);
1142*c8dee2aaSAndroid Build Coastguard Worker drawErrorCurve(canvas, *viz::outerErr);
1143*c8dee2aaSAndroid Build Coastguard Worker }
1144*c8dee2aaSAndroid Build Coastguard Worker }
1145*c8dee2aaSAndroid Build Coastguard Worker
1146*c8dee2aaSAndroid Build Coastguard Worker protected:
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey modi)1147*c8dee2aaSAndroid Build Coastguard Worker Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
1148*c8dee2aaSAndroid Build Coastguard Worker const SkScalar tol = 4;
1149*c8dee2aaSAndroid Build Coastguard Worker const SkRect r = SkRect::MakeXYWH(x - tol, y - tol, tol * 2, tol * 2);
1150*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < fPathPts.size(); ++i) {
1151*c8dee2aaSAndroid Build Coastguard Worker if (r.intersects(SkRect::MakeXYWH(fPathPts[i].fX, fPathPts[i].fY, 1, 1))) {
1152*c8dee2aaSAndroid Build Coastguard Worker return new Click([this, i](Click* c) {
1153*c8dee2aaSAndroid Build Coastguard Worker fPathPts[i] = c->fCurr;
1154*c8dee2aaSAndroid Build Coastguard Worker return true;
1155*c8dee2aaSAndroid Build Coastguard Worker });
1156*c8dee2aaSAndroid Build Coastguard Worker }
1157*c8dee2aaSAndroid Build Coastguard Worker }
1158*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
1159*c8dee2aaSAndroid Build Coastguard Worker }
1160*c8dee2aaSAndroid Build Coastguard Worker
onClick(ClickHandlerSlide::Click *)1161*c8dee2aaSAndroid Build Coastguard Worker bool onClick(ClickHandlerSlide::Click *) override { return false; }
1162*c8dee2aaSAndroid Build Coastguard Worker
1163*c8dee2aaSAndroid Build Coastguard Worker private:
1164*c8dee2aaSAndroid Build Coastguard Worker /** Selectable menu item for choosing distance functions */
1165*c8dee2aaSAndroid Build Coastguard Worker struct DistFncMenuItem {
1166*c8dee2aaSAndroid Build Coastguard Worker std::string fName;
1167*c8dee2aaSAndroid Build Coastguard Worker int fDegree;
1168*c8dee2aaSAndroid Build Coastguard Worker bool fSelected;
1169*c8dee2aaSAndroid Build Coastguard Worker std::vector<float> fWeights;
1170*c8dee2aaSAndroid Build Coastguard Worker
DistFncMenuItemVariableWidthStrokerSlide::DistFncMenuItem1171*c8dee2aaSAndroid Build Coastguard Worker DistFncMenuItem(const std::string& name, int degree, bool selected) {
1172*c8dee2aaSAndroid Build Coastguard Worker fName = name;
1173*c8dee2aaSAndroid Build Coastguard Worker fDegree = degree;
1174*c8dee2aaSAndroid Build Coastguard Worker fSelected = selected;
1175*c8dee2aaSAndroid Build Coastguard Worker fWeights.resize(degree + 1, 1.0f);
1176*c8dee2aaSAndroid Build Coastguard Worker }
1177*c8dee2aaSAndroid Build Coastguard Worker };
1178*c8dee2aaSAndroid Build Coastguard Worker
toggle(bool & value)1179*c8dee2aaSAndroid Build Coastguard Worker void toggle(bool& value) { value = !value; }
toggle(SkVarWidthStroker::LengthMetric & value)1180*c8dee2aaSAndroid Build Coastguard Worker void toggle(SkVarWidthStroker::LengthMetric& value) {
1181*c8dee2aaSAndroid Build Coastguard Worker value = value == SkVarWidthStroker::LengthMetric::kPathLength
1182*c8dee2aaSAndroid Build Coastguard Worker ? SkVarWidthStroker::LengthMetric::kNumSegments
1183*c8dee2aaSAndroid Build Coastguard Worker : SkVarWidthStroker::LengthMetric::kPathLength;
1184*c8dee2aaSAndroid Build Coastguard Worker }
1185*c8dee2aaSAndroid Build Coastguard Worker
resetToDefaults()1186*c8dee2aaSAndroid Build Coastguard Worker void resetToDefaults() {
1187*c8dee2aaSAndroid Build Coastguard Worker fPathPts[0] = {300, 400};
1188*c8dee2aaSAndroid Build Coastguard Worker fPathPts[1] = {500, 400};
1189*c8dee2aaSAndroid Build Coastguard Worker fPathPts[2] = {700, 400};
1190*c8dee2aaSAndroid Build Coastguard Worker fPathPts[3] = {900, 400};
1191*c8dee2aaSAndroid Build Coastguard Worker fPathPts[4] = {1100, 400};
1192*c8dee2aaSAndroid Build Coastguard Worker
1193*c8dee2aaSAndroid Build Coastguard Worker fWidth = 175;
1194*c8dee2aaSAndroid Build Coastguard Worker
1195*c8dee2aaSAndroid Build Coastguard Worker fLengthMetric = SkVarWidthStroker::LengthMetric::kPathLength;
1196*c8dee2aaSAndroid Build Coastguard Worker fDistFncs = fDefaultsDistFncs;
1197*c8dee2aaSAndroid Build Coastguard Worker fDistFncsInner = fDefaultsDistFncs;
1198*c8dee2aaSAndroid Build Coastguard Worker }
1199*c8dee2aaSAndroid Build Coastguard Worker
makePath(SkPath * path)1200*c8dee2aaSAndroid Build Coastguard Worker void makePath(SkPath* path) {
1201*c8dee2aaSAndroid Build Coastguard Worker path->moveTo(fPathPts[0]);
1202*c8dee2aaSAndroid Build Coastguard Worker path->quadTo(fPathPts[1], fPathPts[2]);
1203*c8dee2aaSAndroid Build Coastguard Worker path->quadTo(fPathPts[3], fPathPts[4]);
1204*c8dee2aaSAndroid Build Coastguard Worker }
1205*c8dee2aaSAndroid Build Coastguard Worker
makeDistFnc(const std::vector<DistFncMenuItem> & fncs,float strokeWidth)1206*c8dee2aaSAndroid Build Coastguard Worker static ScalarBezCurve makeDistFnc(const std::vector<DistFncMenuItem>& fncs, float strokeWidth) {
1207*c8dee2aaSAndroid Build Coastguard Worker const float radius = strokeWidth / 2;
1208*c8dee2aaSAndroid Build Coastguard Worker for (const auto& df : fncs) {
1209*c8dee2aaSAndroid Build Coastguard Worker if (df.fSelected) {
1210*c8dee2aaSAndroid Build Coastguard Worker return ScalarBezCurve::Mul(ScalarBezCurve(df.fDegree, df.fWeights), radius);
1211*c8dee2aaSAndroid Build Coastguard Worker }
1212*c8dee2aaSAndroid Build Coastguard Worker }
1213*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(false);
1214*c8dee2aaSAndroid Build Coastguard Worker return ScalarBezCurve(0, {radius});
1215*c8dee2aaSAndroid Build Coastguard Worker }
1216*c8dee2aaSAndroid Build Coastguard Worker
drawStrokePoints(SkCanvas * canvas,const SkPath & fillPath)1217*c8dee2aaSAndroid Build Coastguard Worker void drawStrokePoints(SkCanvas* canvas, const SkPath& fillPath) {
1218*c8dee2aaSAndroid Build Coastguard Worker SkPath::Iter it(fillPath, false);
1219*c8dee2aaSAndroid Build Coastguard Worker SkPoint points[4];
1220*c8dee2aaSAndroid Build Coastguard Worker SkPath::Verb verb;
1221*c8dee2aaSAndroid Build Coastguard Worker std::vector<SkPoint> pointsVec, ctrlPts;
1222*c8dee2aaSAndroid Build Coastguard Worker while ((verb = it.next(&points[0])) != SkPath::kDone_Verb) {
1223*c8dee2aaSAndroid Build Coastguard Worker switch (verb) {
1224*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kLine_Verb:
1225*c8dee2aaSAndroid Build Coastguard Worker pointsVec.push_back(points[1]);
1226*c8dee2aaSAndroid Build Coastguard Worker break;
1227*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kQuad_Verb:
1228*c8dee2aaSAndroid Build Coastguard Worker ctrlPts.push_back(points[1]);
1229*c8dee2aaSAndroid Build Coastguard Worker pointsVec.push_back(points[2]);
1230*c8dee2aaSAndroid Build Coastguard Worker break;
1231*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kMove_Verb:
1232*c8dee2aaSAndroid Build Coastguard Worker pointsVec.push_back(points[0]);
1233*c8dee2aaSAndroid Build Coastguard Worker break;
1234*c8dee2aaSAndroid Build Coastguard Worker case SkPath::kClose_Verb:
1235*c8dee2aaSAndroid Build Coastguard Worker break;
1236*c8dee2aaSAndroid Build Coastguard Worker default:
1237*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Unhandled path verb %d for stroke points\n", verb);
1238*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(false);
1239*c8dee2aaSAndroid Build Coastguard Worker break;
1240*c8dee2aaSAndroid Build Coastguard Worker }
1241*c8dee2aaSAndroid Build Coastguard Worker }
1242*c8dee2aaSAndroid Build Coastguard Worker
1243*c8dee2aaSAndroid Build Coastguard Worker canvas->drawPoints(SkCanvas::kPoints_PointMode, pointsVec.size(), pointsVec.data(),
1244*c8dee2aaSAndroid Build Coastguard Worker fStrokePointsPaint);
1245*c8dee2aaSAndroid Build Coastguard Worker fStrokePointsPaint.setColor(SK_ColorBLUE);
1246*c8dee2aaSAndroid Build Coastguard Worker fStrokePointsPaint.setStrokeWidth(3);
1247*c8dee2aaSAndroid Build Coastguard Worker canvas->drawPoints(SkCanvas::kPoints_PointMode, ctrlPts.size(), ctrlPts.data(),
1248*c8dee2aaSAndroid Build Coastguard Worker fStrokePointsPaint);
1249*c8dee2aaSAndroid Build Coastguard Worker fStrokePointsPaint.setColor(SK_ColorBLACK);
1250*c8dee2aaSAndroid Build Coastguard Worker fStrokePointsPaint.setStrokeWidth(5);
1251*c8dee2aaSAndroid Build Coastguard Worker }
1252*c8dee2aaSAndroid Build Coastguard Worker
drawErrorCurve(SkCanvas * canvas,const ScalarBezCurve & E)1253*c8dee2aaSAndroid Build Coastguard Worker void drawErrorCurve(SkCanvas* canvas, const ScalarBezCurve& E) {
1254*c8dee2aaSAndroid Build Coastguard Worker const float winW = fWinSize.width() * 0.75f, winH = fWinSize.height() * 0.25f;
1255*c8dee2aaSAndroid Build Coastguard Worker const float padding = 25;
1256*c8dee2aaSAndroid Build Coastguard Worker const SkRect box = SkRect::MakeXYWH(padding, fWinSize.height() - winH - padding,
1257*c8dee2aaSAndroid Build Coastguard Worker winW - 2 * padding, winH);
1258*c8dee2aaSAndroid Build Coastguard Worker constexpr int nsegs = 100;
1259*c8dee2aaSAndroid Build Coastguard Worker constexpr float dt = 1.0f / nsegs;
1260*c8dee2aaSAndroid Build Coastguard Worker constexpr float dx = 10.0f;
1261*c8dee2aaSAndroid Build Coastguard Worker const int deg = E.degree();
1262*c8dee2aaSAndroid Build Coastguard Worker SkPath path;
1263*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < nsegs; i++) {
1264*c8dee2aaSAndroid Build Coastguard Worker const float tmin = i * dt, tmax = (i + 1) * dt;
1265*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve left(deg), right(deg);
1266*c8dee2aaSAndroid Build Coastguard Worker E.split(tmax, &left, &right);
1267*c8dee2aaSAndroid Build Coastguard Worker const float tRel = tmin / tmax;
1268*c8dee2aaSAndroid Build Coastguard Worker ScalarBezCurve rl(deg), rr(deg);
1269*c8dee2aaSAndroid Build Coastguard Worker left.split(tRel, &rl, &rr);
1270*c8dee2aaSAndroid Build Coastguard Worker
1271*c8dee2aaSAndroid Build Coastguard Worker const float x = i * dx;
1272*c8dee2aaSAndroid Build Coastguard Worker if (i == 0) {
1273*c8dee2aaSAndroid Build Coastguard Worker path.moveTo(x, -rr[0]);
1274*c8dee2aaSAndroid Build Coastguard Worker }
1275*c8dee2aaSAndroid Build Coastguard Worker path.lineTo(x + dx, -rr[deg]);
1276*c8dee2aaSAndroid Build Coastguard Worker }
1277*c8dee2aaSAndroid Build Coastguard Worker
1278*c8dee2aaSAndroid Build Coastguard Worker SkPaint paint;
1279*c8dee2aaSAndroid Build Coastguard Worker paint.setStyle(SkPaint::kStroke_Style);
1280*c8dee2aaSAndroid Build Coastguard Worker paint.setAntiAlias(true);
1281*c8dee2aaSAndroid Build Coastguard Worker paint.setStrokeWidth(0);
1282*c8dee2aaSAndroid Build Coastguard Worker paint.setColor(SK_ColorRED);
1283*c8dee2aaSAndroid Build Coastguard Worker const SkRect pathBounds = path.computeTightBounds();
1284*c8dee2aaSAndroid Build Coastguard Worker constexpr float yAxisMax = 8000;
1285*c8dee2aaSAndroid Build Coastguard Worker const float sx = box.width() / pathBounds.width();
1286*c8dee2aaSAndroid Build Coastguard Worker const float sy = box.height() / (2 * yAxisMax);
1287*c8dee2aaSAndroid Build Coastguard Worker canvas->save();
1288*c8dee2aaSAndroid Build Coastguard Worker canvas->translate(box.left(), box.top() + box.height() / 2);
1289*c8dee2aaSAndroid Build Coastguard Worker canvas->scale(sx, sy);
1290*c8dee2aaSAndroid Build Coastguard Worker canvas->drawPath(path, paint);
1291*c8dee2aaSAndroid Build Coastguard Worker
1292*c8dee2aaSAndroid Build Coastguard Worker SkPath axes;
1293*c8dee2aaSAndroid Build Coastguard Worker axes.moveTo(0, 0);
1294*c8dee2aaSAndroid Build Coastguard Worker axes.lineTo(pathBounds.width(), 0);
1295*c8dee2aaSAndroid Build Coastguard Worker axes.moveTo(0, -yAxisMax);
1296*c8dee2aaSAndroid Build Coastguard Worker axes.lineTo(0, yAxisMax);
1297*c8dee2aaSAndroid Build Coastguard Worker paint.setColor(SK_ColorBLACK);
1298*c8dee2aaSAndroid Build Coastguard Worker paint.setAntiAlias(false);
1299*c8dee2aaSAndroid Build Coastguard Worker canvas->drawPath(axes, paint);
1300*c8dee2aaSAndroid Build Coastguard Worker
1301*c8dee2aaSAndroid Build Coastguard Worker canvas->restore();
1302*c8dee2aaSAndroid Build Coastguard Worker }
1303*c8dee2aaSAndroid Build Coastguard Worker
drawUI()1304*c8dee2aaSAndroid Build Coastguard Worker void drawUI() {
1305*c8dee2aaSAndroid Build Coastguard Worker static constexpr auto kUIOpacity = 0.35f;
1306*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kUIWidth = 200.0f, kUIHeight = 400.0f;
1307*c8dee2aaSAndroid Build Coastguard Worker ImGui::SetNextWindowBgAlpha(kUIOpacity);
1308*c8dee2aaSAndroid Build Coastguard Worker if (ImGui::Begin("E-C Controls", nullptr,
1309*c8dee2aaSAndroid Build Coastguard Worker ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize |
1310*c8dee2aaSAndroid Build Coastguard Worker ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
1311*c8dee2aaSAndroid Build Coastguard Worker ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
1312*c8dee2aaSAndroid Build Coastguard Worker const SkRect uiArea = SkRect::MakeXYWH(10, 10, kUIWidth, kUIHeight);
1313*c8dee2aaSAndroid Build Coastguard Worker ImGui::SetWindowPos(ImVec2(uiArea.x(), uiArea.y()));
1314*c8dee2aaSAndroid Build Coastguard Worker ImGui::SetWindowSize(ImVec2(uiArea.width(), uiArea.height()));
1315*c8dee2aaSAndroid Build Coastguard Worker
1316*c8dee2aaSAndroid Build Coastguard Worker const auto drawControls = [](std::vector<DistFncMenuItem>& distFncs,
1317*c8dee2aaSAndroid Build Coastguard Worker const std::string& menuPfx,
1318*c8dee2aaSAndroid Build Coastguard Worker const std::string& ptPfx) {
1319*c8dee2aaSAndroid Build Coastguard Worker std::string degreeMenuLabel = menuPfx + ": ";
1320*c8dee2aaSAndroid Build Coastguard Worker for (const auto& df : distFncs) {
1321*c8dee2aaSAndroid Build Coastguard Worker if (df.fSelected) {
1322*c8dee2aaSAndroid Build Coastguard Worker degreeMenuLabel += df.fName;
1323*c8dee2aaSAndroid Build Coastguard Worker break;
1324*c8dee2aaSAndroid Build Coastguard Worker }
1325*c8dee2aaSAndroid Build Coastguard Worker }
1326*c8dee2aaSAndroid Build Coastguard Worker if (ImGui::BeginMenu(degreeMenuLabel.c_str())) {
1327*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < distFncs.size(); i++) {
1328*c8dee2aaSAndroid Build Coastguard Worker if (ImGui::MenuItem(distFncs[i].fName.c_str(), nullptr,
1329*c8dee2aaSAndroid Build Coastguard Worker distFncs[i].fSelected)) {
1330*c8dee2aaSAndroid Build Coastguard Worker for (size_t j = 0; j < distFncs.size(); j++) {
1331*c8dee2aaSAndroid Build Coastguard Worker distFncs[j].fSelected = j == i;
1332*c8dee2aaSAndroid Build Coastguard Worker }
1333*c8dee2aaSAndroid Build Coastguard Worker }
1334*c8dee2aaSAndroid Build Coastguard Worker }
1335*c8dee2aaSAndroid Build Coastguard Worker ImGui::EndMenu();
1336*c8dee2aaSAndroid Build Coastguard Worker }
1337*c8dee2aaSAndroid Build Coastguard Worker
1338*c8dee2aaSAndroid Build Coastguard Worker for (auto& df : distFncs) {
1339*c8dee2aaSAndroid Build Coastguard Worker if (df.fSelected) {
1340*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i <= df.fDegree; i++) {
1341*c8dee2aaSAndroid Build Coastguard Worker const std::string label = ptPfx + std::to_string(i);
1342*c8dee2aaSAndroid Build Coastguard Worker ImGui::SliderFloat(label.c_str(), &(df.fWeights[i]), 0, 1);
1343*c8dee2aaSAndroid Build Coastguard Worker }
1344*c8dee2aaSAndroid Build Coastguard Worker }
1345*c8dee2aaSAndroid Build Coastguard Worker }
1346*c8dee2aaSAndroid Build Coastguard Worker };
1347*c8dee2aaSAndroid Build Coastguard Worker
1348*c8dee2aaSAndroid Build Coastguard Worker const std::array<std::pair<std::string, SkVarWidthStroker::LengthMetric>, 2> metrics = {
1349*c8dee2aaSAndroid Build Coastguard Worker std::make_pair("% path length", SkVarWidthStroker::LengthMetric::kPathLength),
1350*c8dee2aaSAndroid Build Coastguard Worker std::make_pair("% segment count",
1351*c8dee2aaSAndroid Build Coastguard Worker SkVarWidthStroker::LengthMetric::kNumSegments),
1352*c8dee2aaSAndroid Build Coastguard Worker };
1353*c8dee2aaSAndroid Build Coastguard Worker if (ImGui::BeginMenu("Interpolation metric:")) {
1354*c8dee2aaSAndroid Build Coastguard Worker for (const auto& metric : metrics) {
1355*c8dee2aaSAndroid Build Coastguard Worker if (ImGui::MenuItem(metric.first.c_str(), nullptr,
1356*c8dee2aaSAndroid Build Coastguard Worker fLengthMetric == metric.second)) {
1357*c8dee2aaSAndroid Build Coastguard Worker fLengthMetric = metric.second;
1358*c8dee2aaSAndroid Build Coastguard Worker }
1359*c8dee2aaSAndroid Build Coastguard Worker }
1360*c8dee2aaSAndroid Build Coastguard Worker ImGui::EndMenu();
1361*c8dee2aaSAndroid Build Coastguard Worker }
1362*c8dee2aaSAndroid Build Coastguard Worker
1363*c8dee2aaSAndroid Build Coastguard Worker drawControls(fDistFncs, "Degree", "P");
1364*c8dee2aaSAndroid Build Coastguard Worker
1365*c8dee2aaSAndroid Build Coastguard Worker if (ImGui::CollapsingHeader("Inner stroke", true)) {
1366*c8dee2aaSAndroid Build Coastguard Worker fDifferentInnerFunc = true;
1367*c8dee2aaSAndroid Build Coastguard Worker drawControls(fDistFncsInner, "Degree (inner)", "Q");
1368*c8dee2aaSAndroid Build Coastguard Worker } else {
1369*c8dee2aaSAndroid Build Coastguard Worker fDifferentInnerFunc = false;
1370*c8dee2aaSAndroid Build Coastguard Worker }
1371*c8dee2aaSAndroid Build Coastguard Worker }
1372*c8dee2aaSAndroid Build Coastguard Worker ImGui::End();
1373*c8dee2aaSAndroid Build Coastguard Worker }
1374*c8dee2aaSAndroid Build Coastguard Worker
1375*c8dee2aaSAndroid Build Coastguard Worker bool fShowHidden, fShowSkeleton, fShowStrokePoints, fShowUI, fDifferentInnerFunc,
1376*c8dee2aaSAndroid Build Coastguard Worker fShowErrorCurve;
1377*c8dee2aaSAndroid Build Coastguard Worker float fWidth = 175;
1378*c8dee2aaSAndroid Build Coastguard Worker SkPaint fPtsPaint, fStrokePaint, fNewFillPaint, fHiddenPaint, fSkeletonPaint,
1379*c8dee2aaSAndroid Build Coastguard Worker fStrokePointsPaint;
1380*c8dee2aaSAndroid Build Coastguard Worker inline static constexpr int kNPts = 5;
1381*c8dee2aaSAndroid Build Coastguard Worker std::array<SkPoint, kNPts> fPathPts;
1382*c8dee2aaSAndroid Build Coastguard Worker SkSize fWinSize;
1383*c8dee2aaSAndroid Build Coastguard Worker SkVarWidthStroker::LengthMetric fLengthMetric;
1384*c8dee2aaSAndroid Build Coastguard Worker const std::vector<DistFncMenuItem> fDefaultsDistFncs = {
1385*c8dee2aaSAndroid Build Coastguard Worker DistFncMenuItem("Linear", 1, true), DistFncMenuItem("Quadratic", 2, false),
1386*c8dee2aaSAndroid Build Coastguard Worker DistFncMenuItem("Cubic", 3, false), DistFncMenuItem("One Louder (11)", 11, false),
1387*c8dee2aaSAndroid Build Coastguard Worker DistFncMenuItem("30?!", 30, false)};
1388*c8dee2aaSAndroid Build Coastguard Worker std::vector<DistFncMenuItem> fDistFncs = fDefaultsDistFncs;
1389*c8dee2aaSAndroid Build Coastguard Worker std::vector<DistFncMenuItem> fDistFncsInner = fDefaultsDistFncs;
1390*c8dee2aaSAndroid Build Coastguard Worker };
1391*c8dee2aaSAndroid Build Coastguard Worker
1392*c8dee2aaSAndroid Build Coastguard Worker DEF_SLIDE(return new VariableWidthStrokerSlide;)
1393