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