xref: /aosp_15_r20/external/skia/tools/viewer/VariableWidthStrokerSlide.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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