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