Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
VariableWidthStrokerSlide.cpp
Go to the documentation of this file.
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"
11#include "include/core/SkPath.h"
15#include "src/core/SkGeometry.h"
17
18#include <stack>
19#include <vector>
20
21namespace {
22
23//////////////////////////////////////////////////////////////////////////////
24
25constexpr inline SkPoint rotate90(const SkPoint& p) { return {p.fY, -p.fX}; }
26inline SkPoint rotate180(const SkPoint& p) { return p * -1; }
27inline bool isClockwise(const SkPoint& a, const SkPoint& b) { return a.cross(b) > 0; }
28
29static 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
39constexpr 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 */
54class ScalarBezCurve {
55public:
56 inline static constexpr int kDegreeInvalid = -1;
57
58 /** Creates an empty curve with invalid degree. */
59 ScalarBezCurve() : fDegree(kDegreeInvalid) {}
60
61 /** Creates a curve of the specified degree with weights initialized to 0. */
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. */
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 */
75 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 */
88 float eval(float t) const { return Eval(*this, t); }
89
90 /** Evaluates the curve at 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) */
106 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]. */
111 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) */
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 */
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 */
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 */
190 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 */
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 */
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 */
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. */
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. */
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 */
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 */
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 */
278 int degree() const { return fDegree; }
279
280 /** Returns the curve weights */
281 const std::vector<float>& weights() const { return fWeights; }
282
283 float operator[](size_t i) const { return fWeights[i]; }
284 float& operator[](size_t i) { return fWeights[i]; }
285
286private:
287 /** Recursive helper for ZeroSet */
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. */
326class PathVerbMeasure {
327public:
328 explicit PathVerbMeasure(const SkPath& path) : fPath(path), fIter(path, false) { nextVerb(); }
329
330 SkScalar totalLength() const;
331
332 SkScalar currentVerbLength() { return fMeas.getLength(); }
333
334 void nextVerb();
335
336private:
337 const SkPath& fPath;
338 SkPoint fFirstPointInContour;
339 SkPoint fPreviousPoint;
340 SkPath fCurrVerb;
342 SkPathMeasure fMeas;
343};
344
345SkScalar PathVerbMeasure::totalLength() const {
346 SkPathMeasure meas(fPath, false);
347 return meas.getLength();
348}
349
350void 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) {
366 fCurrVerb.lineTo(pts[1]);
367 break;
369 fCurrVerb.quadTo(pts[1], pts[2]);
370 break;
372 fCurrVerb.cubicTo(pts[1], pts[2], pts[3]);
373 break;
375 fCurrVerb.conicTo(pts[1], pts[2], fIter.conicWeight());
376 break;
378 break;
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
392namespace viz {
393std::unique_ptr<ScalarBezCurve> outerErr;
394SkPath 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 */
411class SkVarWidthStroker {
412public:
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 */
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
439private:
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 */
501 static ScalarBezCurve identityVarWidth(float strokeWidth) {
502 return ScalarBezCurve(0, {strokeWidth / 2.0f});
503 }
504
505 float fRadius;
506 SkPaint::Cap fCap;
508 SkPath fInner, fOuter;
509 ScalarBezCurve fVarWidth, fVarWidthInner;
510 float fCurrT;
511};
512
513void 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
522SkPath 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) {
578 offsetSegs = strokeSegment(segment, partVarWidth, partVarWidthInner, firstSegment);
579 break;
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
627SkVarWidthStroker::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
639std::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) {
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
744void 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) {
759 buttCap(loc);
760 break;
761 default:
762 SkDebugf("Unhandled endcap %d\n", fCap);
763 buttCap(loc);
764 break;
765 }
766}
767
768void 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) {
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
850void 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 }
877 // Ignore
878 break;
879 default:
880 SkASSERT(false);
881 break;
882 }
883 }
884}
885
886int 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
900void 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
923void 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
971SkPoint 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
1025public:
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);
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
1061 void load(SkScalar w, SkScalar h) override { fWinSize = {w, h}; }
1062
1063 void resize(SkScalar w, SkScalar h) override { fWinSize = {w, h}; }
1064
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
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);
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
1146protected:
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
1161 bool onClick(ClickHandlerSlide::Click *) override { return false; }
1162
1163private:
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
1171 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
1179 void toggle(bool& value) { value = !value; }
1180 void toggle(SkVarWidthStroker::LengthMetric& value) {
1181 value = value == SkVarWidthStroker::LengthMetric::kPathLength
1182 ? SkVarWidthStroker::LengthMetric::kNumSegments
1183 : SkVarWidthStroker::LengthMetric::kPathLength;
1184 }
1185
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
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
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
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;
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
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
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
SkPath fPath
SkPaint::Join fJoin
static const int strokeWidth
Definition BlurTest.cpp:60
const char * fName
static const int points[]
std::unique_ptr< SkLatticeIter > fIter
static float prev(float f)
#define SkASSERT(cond)
Definition SkAssert.h:116
constexpr SkColor SK_ColorBLUE
Definition SkColor.h:135
constexpr SkColor SK_ColorRED
Definition SkColor.h:126
constexpr SkColor SK_ColorBLACK
Definition SkColor.h:103
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
void SkEvalCubicAt(const SkPoint src[4], SkScalar t, SkPoint *loc, SkVector *tangent, SkVector *curvature)
static int sign(SkScalar x)
Definition SkPath.cpp:2141
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:101
int32_t SkUnichar
Definition SkTypes.h:175
#define DEF_SLIDE(code)
Definition Slide.h:25
#define setLength(p, len)
void drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint &paint)
void restore()
Definition SkCanvas.cpp:465
void translate(SkScalar dx, SkScalar dy)
void drawColor(SkColor color, SkBlendMode mode=SkBlendMode::kSrcOver)
Definition SkCanvas.h:1182
int save()
Definition SkCanvas.cpp:451
void drawPath(const SkPath &path, const SkPaint &paint)
void scale(SkScalar sx, SkScalar sy)
@ kPoints_PointMode
draw each point separately
Definition SkCanvas.h:1241
@ kRound_Cap
adds circle
Definition SkPaint.h:335
@ kButt_Cap
no stroke extension
Definition SkPaint.h:334
void setStyle(Style style)
Definition SkPaint.cpp:105
void setColor(SkColor color)
Definition SkPaint.cpp:119
void setAntiAlias(bool aa)
Definition SkPaint.h:170
void setStrokeCap(Cap cap)
Definition SkPaint.cpp:179
@ kStroke_Style
set to stroke geometry
Definition SkPaint.h:194
@ kMiter_Join
extends to miter limit
Definition SkPaint.h:359
void setStrokeWidth(SkScalar width)
Definition SkPaint.cpp:159
SkPath & moveTo(SkScalar x, SkScalar y)
Definition SkPath.cpp:678
void setFillType(SkPathFillType ft)
Definition SkPath.h:235
SkPath & lineTo(SkScalar x, SkScalar y)
Definition SkPath.cpp:718
SkPath & quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2)
Definition SkPath.cpp:736
@ kClose_Verb
Definition SkPath.h:1463
@ kMove_Verb
Definition SkPath.h:1458
@ kConic_Verb
Definition SkPath.h:1461
@ kDone_Verb
Definition SkPath.h:1464
@ kCubic_Verb
Definition SkPath.h:1462
@ kQuad_Verb
Definition SkPath.h:1460
@ kLine_Verb
Definition SkPath.h:1459
const char * c_str() const
Definition SkString.h:133
SkString fName
Definition Slide.h:54
void resize(SkScalar w, SkScalar h) override
Click * onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override
bool onChar(SkUnichar uni) override
bool onClick(ClickHandlerSlide::Click *) override
void draw(SkCanvas *canvas) override
void load(SkScalar w, SkScalar h) override
const Paint & paint
float SkScalar
Definition extension.cpp:12
static bool b
struct MyStruct a[10]
glong glong end
uint8_t value
GAsyncResult * result
const char * name
Definition fuchsia.cc:50
double y
double x
skia_private::AutoTArray< sk_sp< SkImageFilter > > filters TypedMatrix matrix TypedMatrix matrix SkScalar dx
Definition SkRecords.h:208
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switches.h:57
ModifierKey
Definition ModifierKey.h:9
SINT Vec< 2 *N, T > join(const Vec< N, T > &lo, const Vec< N, T > &hi)
Definition SkVx.h:242
SkScalar w
SkScalar h
float dot(const SkVector &vec) const
static constexpr SkPoint Make(float x, float y)
bool normalize()
Definition SkPoint.cpp:22
constexpr float left() const
Definition SkRect.h:734
constexpr float top() const
Definition SkRect.h:741
constexpr float x() const
Definition SkRect.h:720
constexpr float y() const
Definition SkRect.h:727
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition SkRect.h:659
bool intersects(const SkRect &r) const
Definition SkRect.h:1121
constexpr float height() const
Definition SkRect.h:769
constexpr float width() const
Definition SkRect.h:762
SkScalar width() const
Definition SkSize.h:76
SkScalar height() const
Definition SkSize.h:77