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