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