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