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