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