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