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