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
20namespace {
21
22//////////////////////////////////////////////////////////////////////////////
23
24constexpr inline SkPoint rotate90(const SkPoint& p) { return {p.fY, -p.fX}; }
25inline SkPoint rotate180(const SkPoint& p) { return p * -1; }
26inline bool isClockwise(const SkPoint& a, const SkPoint& b) { return a.cross(b) > 0; }
27
28static 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
38constexpr 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 */
53class ScalarBezCurve {
54public:
55    inline static constexpr int kDegreeInvalid = -1;
56
57    /** Creates an empty curve with invalid degree. */
58    ScalarBezCurve() : fDegree(kDegreeInvalid) {}
59
60    /** Creates a curve of the specified degree with weights initialized to 0. */
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. */
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 */
74    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 */
87    float eval(float t) const { return Eval(*this, t); }
88
89    /** Evaluates the curve at 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) */
105    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]. */
110    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) */
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     */
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     */
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     */
189    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     */
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 */
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     */
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. */
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. */
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 */
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 */
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 */
277    int degree() const { return fDegree; }
278
279    /** Returns the curve weights */
280    const std::vector<float>& weights() const { return fWeights; }
281
282    float operator[](size_t i) const { return fWeights[i]; }
283    float& operator[](size_t i) { return fWeights[i]; }
284
285private:
286    /** Recursive helper for ZeroSet */
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. */
325class PathVerbMeasure {
326public:
327    explicit PathVerbMeasure(const SkPath& path) : fPath(path), fIter(path, false) { nextVerb(); }
328
329    SkScalar totalLength() const;
330
331    SkScalar currentVerbLength() { return fMeas.getLength(); }
332
333    void nextVerb();
334
335private:
336    const SkPath& fPath;
337    SkPoint fFirstPointInContour;
338    SkPoint fPreviousPoint;
339    SkPath fCurrVerb;
340    SkPath::Iter fIter;
341    SkPathMeasure fMeas;
342};
343
344SkScalar PathVerbMeasure::totalLength() const {
345    SkPathMeasure meas(fPath, false);
346    return meas.getLength();
347}
348
349void 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
391namespace viz {
392std::unique_ptr<ScalarBezCurve> outerErr;
393SkPath 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 */
410class SkVarWidthStroker {
411public:
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     */
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
438private:
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& distanceFunc) 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 */
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
512void 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
521SkPath 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
626SkVarWidthStroker::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
638std::vector<SkVarWidthStroker::PathSegment> SkVarWidthStroker::strokeSegment(
639        const PathSegment& seg, const ScalarBezCurve& distanceFunc) 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, distanceFunc, ScalarBezCurve::Mul(distanceFunc, distanceFunc)));
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
743void 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
767void 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
849void 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
885int 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
899void 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
922void 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
970SkPoint 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
1023class VariableWidthStroker : public Sample {
1024public:
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
1058private:
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
1066        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
1074    SkString name() override { return SkString("VariableWidthStroker"); }
1075
1076    void onSizeChange() override {
1077        fWinSize = SkSize::Make(this->width(), this->height());
1078        INHERITED::onSizeChange();
1079    }
1080
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
1116    void toggle(bool& value) { value = !value; }
1117    void toggle(SkVarWidthStroker::LengthMetric& value) {
1118        value = value == SkVarWidthStroker::LengthMetric::kPathLength
1119                        ? SkVarWidthStroker::LengthMetric::kNumSegments
1120                        : SkVarWidthStroker::LengthMetric::kPathLength;
1121    }
1122
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
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
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
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
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
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
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
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    inline 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
1391DEF_SAMPLE(return new VariableWidthStroker;)
1392