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