Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkStroke.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2008 The Android Open Source Project
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 "src/core/SkStroke.h"
9
10#include "include/core/SkPath.h"
12#include "include/core/SkRect.h"
17#include "src/core/SkGeometry.h"
19#include "src/core/SkPathPriv.h"
22
23#include <algorithm>
24#include <array>
25
26enum {
31};
32
33// quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure
34// largest seen for normal cubics : 5, 26
35// largest seen for normal quads : 11
36// 3x limits seen in practice, except for cubics (3x limit would be ~75).
37// For cubics, we never get close to 75 when running through dm. The limit of 24
38// was chosen because it's close to the peak in a count of cubic recursion depths visited
39// (define DEBUG_CUBIC_RECURSION_DEPTHS) and no diffs were produced on gold when using it.
40static const int kRecursiveLimits[] = { 5*3, 24, 11*3, 11*3 };
41
42static_assert(0 == kTangent_RecursiveLimit, "cubic_stroke_relies_on_tangent_equalling_zero");
43static_assert(1 == kCubic_RecursiveLimit, "cubic_stroke_relies_on_cubic_equalling_one");
44static_assert(std::size(kRecursiveLimits) == kQuad_RecursiveLimit + 1,
45 "recursive_limits_mismatch");
46
47#if defined SK_DEBUG && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
48 int gMaxRecursion[std::size(kRecursiveLimits)] = { 0 };
49#endif
50#ifndef DEBUG_QUAD_STROKER
51 #define DEBUG_QUAD_STROKER 0
52#endif
53
54#if DEBUG_QUAD_STROKER
55 /* Enable to show the decisions made in subdividing the curve -- helpful when the resulting
56 stroke has more than the optimal number of quadratics and lines */
57 #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
58 SkDebugf("[%d] %s " format "\n", depth, __FUNCTION__, __VA_ARGS__), \
59 SkDebugf(" " #resultType " t=(%g,%g)\n", quadPts->fStartT, quadPts->fEndT), \
60 resultType
61 #define STROKER_DEBUG_PARAMS(...) , __VA_ARGS__
62#else
63 #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
64 resultType
65 #define STROKER_DEBUG_PARAMS(...)
66#endif
67
68#ifndef DEBUG_CUBIC_RECURSION_DEPTHS
69#define DEBUG_CUBIC_RECURSION_DEPTHS 0
70#endif
71#if DEBUG_CUBIC_RECURSION_DEPTHS
72 /* Prints a histogram of recursion depths at process termination. */
73 static struct DepthHistogram {
74 inline static constexpr int kMaxDepth = 75;
75 int fCubicDepths[kMaxDepth + 1];
76
77 DepthHistogram() { memset(fCubicDepths, 0, sizeof(fCubicDepths)); }
78
79 ~DepthHistogram() {
80 SkDebugf("# times recursion terminated per depth:\n");
81 for (int i = 0; i <= kMaxDepth; i++) {
82 SkDebugf(" depth %d: %d\n", i, fCubicDepths[i]);
83 }
84 }
85
86 inline void incDepth(int depth) {
87 SkASSERT(depth >= 0 && depth <= kMaxDepth);
88 fCubicDepths[depth]++;
89 }
90 } sCubicDepthHistogram;
91
92#define DEBUG_CUBIC_RECURSION_TRACK_DEPTH(depth) sCubicDepthHistogram.incDepth(depth)
93#else
94#define DEBUG_CUBIC_RECURSION_TRACK_DEPTH(depth) (void)(depth)
95#endif
96
97static inline bool degenerate_vector(const SkVector& v) {
98 return !SkPointPriv::CanNormalize(v.fX, v.fY);
99}
100
101static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after, SkScalar scale,
102 SkScalar radius,
103 SkVector* normal, SkVector* unitNormal) {
104 if (!unitNormal->setNormalize((after.fX - before.fX) * scale,
105 (after.fY - before.fY) * scale)) {
106 return false;
107 }
108 SkPointPriv::RotateCCW(unitNormal);
109 unitNormal->scale(radius, normal);
110 return true;
111}
112
113static bool set_normal_unitnormal(const SkVector& vec,
114 SkScalar radius,
115 SkVector* normal, SkVector* unitNormal) {
116 if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
117 return false;
118 }
119 SkPointPriv::RotateCCW(unitNormal);
120 unitNormal->scale(radius, normal);
121 return true;
122}
123
124///////////////////////////////////////////////////////////////////////////////
125
126struct SkQuadConstruct { // The state of the quad stroke under construction.
127 SkPoint fQuad[3]; // the stroked quad parallel to the original curve
128 SkPoint fTangentStart; // a point tangent to fQuad[0]
129 SkPoint fTangentEnd; // a point tangent to fQuad[2]
130 SkScalar fStartT; // a segment of the original curve
133 bool fStartSet; // state to share common points across structs
134 bool fEndSet; // "
135 bool fOppositeTangents; // set if coincident tangents have opposite directions
136
137 // return false if start and end are too close to have a unique middle
139 fStartT = start;
141 fEndT = end;
142 fStartSet = fEndSet = false;
143 return fStartT < fMidT && fMidT < fEndT;
144 }
145
147 if (!init(parent->fStartT, parent->fMidT)) {
148 return false;
149 }
150 fQuad[0] = parent->fQuad[0];
152 fStartSet = true;
153 return true;
154 }
155
157 if (!init(parent->fMidT, parent->fEndT)) {
158 return false;
159 }
160 fQuad[2] = parent->fQuad[2];
161 fTangentEnd = parent->fTangentEnd;
162 fEndSet = true;
163 return true;
164 }
165};
166
168public:
169 SkPathStroker(const SkPath& src,
170 SkScalar radius, SkScalar miterLimit, SkPaint::Cap,
171 SkPaint::Join, SkScalar resScale,
172 bool canIgnoreCenter);
173
174 bool hasOnlyMoveTo() const { return 0 == fSegmentCount; }
175 SkPoint moveToPt() const { return fFirstPt; }
176
177 void moveTo(const SkPoint&);
178 void lineTo(const SkPoint&, const SkPath::Iter* iter = nullptr);
179 void quadTo(const SkPoint&, const SkPoint&);
180 void conicTo(const SkPoint&, const SkPoint&, SkScalar weight);
181 void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
182 void close(bool isLine) { this->finishContour(true, isLine); }
183
184 void done(SkPath* dst, bool isLine) {
185 this->finishContour(false, isLine);
186 dst->swap(fOuter);
187 }
188
189 SkScalar getResScale() const { return fResScale; }
190
192 return fInner.isZeroLengthSincePoint(0) &&
193 fOuter.isZeroLengthSincePoint(fFirstOuterPtIndexInContour);
194 }
195
196private:
197 SkScalar fRadius;
198 SkScalar fInvMiterLimit;
199 SkScalar fResScale;
200 SkScalar fInvResScale;
201 SkScalar fInvResScaleSquared;
202
203 SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
204 SkPoint fFirstPt, fPrevPt; // on original path
205 SkPoint fFirstOuterPt;
206 int fFirstOuterPtIndexInContour;
207 int fSegmentCount;
208 bool fPrevIsLine;
209 bool fCanIgnoreCenter;
210
213
214 SkPath fInner, fOuter, fCusper; // outer is our working answer, inner is temp
215
216 enum StrokeType {
217 kOuter_StrokeType = 1, // use sign-opposite values later to flip perpendicular axis
218 kInner_StrokeType = -1
219 } fStrokeType;
220
221 enum ResultType {
222 kSplit_ResultType, // the caller should split the quad stroke in two
223 kDegenerate_ResultType, // the caller should add a line
224 kQuad_ResultType, // the caller should (continue to try to) add a quad stroke
225 };
226
227 enum ReductionType {
228 kPoint_ReductionType, // all curve points are practically identical
229 kLine_ReductionType, // the control point is on the line between the ends
230 kQuad_ReductionType, // the control point is outside the line between the ends
231 kDegenerate_ReductionType, // the control point is on the line but outside the ends
232 kDegenerate2_ReductionType, // two control points are on the line but outside ends (cubic)
233 kDegenerate3_ReductionType, // three areas of max curvature found (for cubic)
234 };
235
236 enum IntersectRayType {
237 kCtrlPt_RayType,
238 kResultType_RayType,
239 };
240
241 int fRecursionDepth; // track stack depth to abort if numerics run amok
242 bool fFoundTangents; // do less work until tangents meet (cubic)
243 bool fJoinCompleted; // previous join was not degenerate
244
245 void addDegenerateLine(const SkQuadConstruct* );
246 static ReductionType CheckConicLinear(const SkConic& , SkPoint* reduction);
247 static ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3],
248 const SkPoint** tanPtPtr);
249 static ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction);
250 ResultType compareQuadConic(const SkConic& , SkQuadConstruct* ) const;
251 ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* );
252 ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* );
253 void conicPerpRay(const SkConic& , SkScalar t, SkPoint* tPt, SkPoint* onPt,
254 SkPoint* tangent) const;
255 void conicQuadEnds(const SkConic& , SkQuadConstruct* ) const;
256 bool conicStroke(const SkConic& , SkQuadConstruct* );
257 bool cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* ) const;
258 void cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
259 SkPoint* tangent) const;
260 void cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* );
261 void cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* , SkPoint* mid) const;
262 bool cubicStroke(const SkPoint cubic[4], SkQuadConstruct* );
263 void init(StrokeType strokeType, SkQuadConstruct* , SkScalar tStart, SkScalar tEnd);
264 ResultType intersectRay(SkQuadConstruct* , IntersectRayType STROKER_DEBUG_PARAMS(int) ) const;
265 bool ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const;
266 void quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
267 SkPoint* tangent) const;
268 bool quadStroke(const SkPoint quad[3], SkQuadConstruct* );
269 void setConicEndNormal(const SkConic& ,
270 const SkVector& normalAB, const SkVector& unitNormalAB,
271 SkVector* normalBC, SkVector* unitNormalBC);
272 void setCubicEndNormal(const SkPoint cubic[4],
273 const SkVector& normalAB, const SkVector& unitNormalAB,
274 SkVector* normalCD, SkVector* unitNormalCD);
275 void setQuadEndNormal(const SkPoint quad[3],
276 const SkVector& normalAB, const SkVector& unitNormalAB,
277 SkVector* normalBC, SkVector* unitNormalBC);
278 void setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt, SkPoint* tangent) const;
279 static bool SlightAngle(SkQuadConstruct* );
280 ResultType strokeCloseEnough(const SkPoint stroke[3], const SkPoint ray[2],
281 SkQuadConstruct* STROKER_DEBUG_PARAMS(int depth) ) const;
282 ResultType tangentsMeet(const SkPoint cubic[4], SkQuadConstruct* );
283
284 void finishContour(bool close, bool isLine);
285 bool preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
286 bool isLine);
287 void postJoinTo(const SkPoint&, const SkVector& normal,
288 const SkVector& unitNormal);
289
290 void line_to(const SkPoint& currPt, const SkVector& normal);
291};
292
293///////////////////////////////////////////////////////////////////////////////
294
295bool SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
296 SkVector* unitNormal, bool currIsLine) {
297 SkASSERT(fSegmentCount >= 0);
298
299 SkScalar prevX = fPrevPt.fX;
300 SkScalar prevY = fPrevPt.fY;
301
302 if (!set_normal_unitnormal(fPrevPt, currPt, fResScale, fRadius, normal, unitNormal)) {
304 return false;
305 }
306 /* Square caps and round caps draw even if the segment length is zero.
307 Since the zero length segment has no direction, set the orientation
308 to upright as the default orientation */
309 normal->set(fRadius, 0);
310 unitNormal->set(1, 0);
311 }
312
313 if (fSegmentCount == 0) {
314 fFirstNormal = *normal;
315 fFirstUnitNormal = *unitNormal;
316 fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
317
318 fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY);
319 fInner.moveTo(prevX - normal->fX, prevY - normal->fY);
320 } else { // we have a previous segment
321 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
322 fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
323 }
324 fPrevIsLine = currIsLine;
325 return true;
326}
327
328void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
329 const SkVector& unitNormal) {
330 fJoinCompleted = true;
331 fPrevPt = currPt;
332 fPrevUnitNormal = unitNormal;
333 fPrevNormal = normal;
334 fSegmentCount += 1;
335}
336
337void SkPathStroker::finishContour(bool close, bool currIsLine) {
338 if (fSegmentCount > 0) {
339 SkPoint pt;
340
341 if (close) {
342 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
343 fFirstUnitNormal, fRadius, fInvMiterLimit,
344 fPrevIsLine, currIsLine);
345 fOuter.close();
346
347 if (fCanIgnoreCenter) {
348 // If we can ignore the center just make sure the larger of the two paths
349 // is preserved and don't add the smaller one.
350 if (fInner.getBounds().contains(fOuter.getBounds())) {
351 fInner.swap(fOuter);
352 }
353 } else {
354 // now add fInner as its own contour
355 fInner.getLastPt(&pt);
356 fOuter.moveTo(pt.fX, pt.fY);
357 fOuter.reversePathTo(fInner);
358 fOuter.close();
359 }
360 } else { // add caps to start and end
361 // cap the end
362 fInner.getLastPt(&pt);
363 fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
364 currIsLine ? &fInner : nullptr);
365 fOuter.reversePathTo(fInner);
366 // cap the start
367 fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
368 fPrevIsLine ? &fInner : nullptr);
369 fOuter.close();
370 }
371 if (!fCusper.isEmpty()) {
372 fOuter.addPath(fCusper);
373 fCusper.rewind();
374 }
375 }
376 // since we may re-use fInner, we rewind instead of reset, to save on
377 // reallocating its internal storage.
378 fInner.rewind();
379 fSegmentCount = -1;
380 fFirstOuterPtIndexInContour = fOuter.countPoints();
381}
382
383///////////////////////////////////////////////////////////////////////////////
384
386 SkScalar radius, SkScalar miterLimit,
387 SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale,
388 bool canIgnoreCenter)
389 : fRadius(radius)
390 , fResScale(resScale)
391 , fCanIgnoreCenter(canIgnoreCenter) {
392
393 /* This is only used when join is miter_join, but we initialize it here
394 so that it is always defined, to fis valgrind warnings.
395 */
396 fInvMiterLimit = 0;
397
398 if (join == SkPaint::kMiter_Join) {
399 if (miterLimit <= SK_Scalar1) {
401 } else {
402 fInvMiterLimit = SkScalarInvert(miterLimit);
403 }
404 }
405 fCapper = SkStrokerPriv::CapFactory(cap);
406 fJoiner = SkStrokerPriv::JoinFactory(join);
407 fSegmentCount = -1;
408 fFirstOuterPtIndexInContour = 0;
409 fPrevIsLine = false;
410
411 // Need some estimate of how large our final result (fOuter)
412 // and our per-contour temp (fInner) will be, so we don't spend
413 // extra time repeatedly growing these arrays.
414 //
415 // 3x for result == inner + outer + join (swag)
416 // 1x for inner == 'wag' (worst contour length would be better guess)
417 fOuter.incReserve(src.countPoints() * 3);
418 fOuter.setIsVolatile(true);
419 fInner.incReserve(src.countPoints());
420 fInner.setIsVolatile(true);
421 // TODO : write a common error function used by stroking and filling
422 // The '4' below matches the fill scan converter's error term
423 fInvResScale = SkScalarInvert(resScale * 4);
424 fInvResScaleSquared = fInvResScale * fInvResScale;
425 fRecursionDepth = 0;
426}
427
429 if (fSegmentCount > 0) {
430 this->finishContour(false, false);
431 }
432 fSegmentCount = 0;
433 fFirstPt = fPrevPt = pt;
434 fJoinCompleted = false;
435}
436
437void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
438 fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
439 fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
440}
441
442static bool has_valid_tangent(const SkPath::Iter* iter) {
443 SkPath::Iter copy = *iter;
444 SkPath::Verb verb;
445 SkPoint pts[4];
446 while ((verb = copy.next(pts))) {
447 switch (verb) {
449 return false;
451 if (pts[0] == pts[1]) {
452 continue;
453 }
454 return true;
457 if (pts[0] == pts[1] && pts[0] == pts[2]) {
458 continue;
459 }
460 return true;
462 if (pts[0] == pts[1] && pts[0] == pts[2] && pts[0] == pts[3]) {
463 continue;
464 }
465 return true;
468 return false;
469 }
470 }
471 return false;
472}
473
474void SkPathStroker::lineTo(const SkPoint& currPt, const SkPath::Iter* iter) {
475 bool teenyLine = SkPointPriv::EqualsWithinTolerance(fPrevPt, currPt, SK_ScalarNearlyZero * fInvResScale);
476 if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper && teenyLine) {
477 return;
478 }
479 if (teenyLine && (fJoinCompleted || (iter && has_valid_tangent(iter)))) {
480 return;
481 }
482 SkVector normal, unitNormal;
483
484 if (!this->preJoinTo(currPt, &normal, &unitNormal, true)) {
485 return;
486 }
487 this->line_to(currPt, normal);
488 this->postJoinTo(currPt, normal, unitNormal);
489}
490
491void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& normalAB,
492 const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) {
493 if (!set_normal_unitnormal(quad[1], quad[2], fResScale, fRadius, normalBC, unitNormalBC)) {
494 *normalBC = normalAB;
495 *unitNormalBC = unitNormalAB;
496 }
497}
498
499void SkPathStroker::setConicEndNormal(const SkConic& conic, const SkVector& normalAB,
500 const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) {
501 setQuadEndNormal(conic.fPts, normalAB, unitNormalAB, normalBC, unitNormalBC);
502}
503
504void SkPathStroker::setCubicEndNormal(const SkPoint cubic[4], const SkVector& normalAB,
505 const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD) {
506 SkVector ab = cubic[1] - cubic[0];
507 SkVector cd = cubic[3] - cubic[2];
508
509 bool degenerateAB = degenerate_vector(ab);
510 bool degenerateCD = degenerate_vector(cd);
511
512 if (degenerateAB && degenerateCD) {
513 goto DEGENERATE_NORMAL;
514 }
515
516 if (degenerateAB) {
517 ab = cubic[2] - cubic[0];
518 degenerateAB = degenerate_vector(ab);
519 }
520 if (degenerateCD) {
521 cd = cubic[3] - cubic[1];
522 degenerateCD = degenerate_vector(cd);
523 }
524 if (degenerateAB || degenerateCD) {
525DEGENERATE_NORMAL:
526 *normalCD = normalAB;
527 *unitNormalCD = unitNormalAB;
528 return;
529 }
530 SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
531}
532
533void SkPathStroker::init(StrokeType strokeType, SkQuadConstruct* quadPts, SkScalar tStart,
534 SkScalar tEnd) {
535 fStrokeType = strokeType;
536 fFoundTangents = false;
537 quadPts->init(tStart, tEnd);
538}
539
540// returns the distance squared from the point to the line
541static SkScalar pt_to_line(const SkPoint& pt, const SkPoint& lineStart, const SkPoint& lineEnd) {
542 SkVector dxy = lineEnd - lineStart;
543 SkVector ab0 = pt - lineStart;
544 SkScalar numer = dxy.dot(ab0);
545 SkScalar denom = dxy.dot(dxy);
546 SkScalar t = sk_ieee_float_divide(numer, denom);
547 if (t >= 0 && t <= 1) {
548 SkPoint hit;
549 hit.fX = lineStart.fX * (1 - t) + lineEnd.fX * t;
550 hit.fY = lineStart.fY * (1 - t) + lineEnd.fY * t;
551 return SkPointPriv::DistanceToSqd(hit, pt);
552 } else {
553 return SkPointPriv::DistanceToSqd(pt, lineStart);
554 }
555}
556
557/* Given a cubic, determine if all four points are in a line.
558 Return true if the inner points is close to a line connecting the outermost points.
559
560 Find the outermost point by looking for the largest difference in X or Y.
561 Given the indices of the outermost points, and that outer_1 is greater than outer_2,
562 this table shows the index of the smaller of the remaining points:
563
564 outer_2
565 0 1 2 3
566 outer_1 ----------------
567 0 | - 2 1 1
568 1 | - - 0 0
569 2 | - - - 0
570 3 | - - - -
571
572 If outer_1 == 0 and outer_2 == 1, the smaller of the remaining indices (2 and 3) is 2.
573
574 This table can be collapsed to: (1 + (2 >> outer_2)) >> outer_1
575
576 Given three indices (outer_1 outer_2 mid_1) from 0..3, the remaining index is:
577
578 mid_2 == (outer_1 ^ outer_2 ^ mid_1)
579 */
580static bool cubic_in_line(const SkPoint cubic[4]) {
581 SkScalar ptMax = -1;
582 int outer1 SK_INIT_TO_AVOID_WARNING;
583 int outer2 SK_INIT_TO_AVOID_WARNING;
584 for (int index = 0; index < 3; ++index) {
585 for (int inner = index + 1; inner < 4; ++inner) {
586 SkVector testDiff = cubic[inner] - cubic[index];
587 SkScalar testMax = std::max(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY));
588 if (ptMax < testMax) {
589 outer1 = index;
590 outer2 = inner;
591 ptMax = testMax;
592 }
593 }
594 }
595 SkASSERT(outer1 >= 0 && outer1 <= 2);
596 SkASSERT(outer2 >= 1 && outer2 <= 3);
597 SkASSERT(outer1 < outer2);
598 int mid1 = (1 + (2 >> outer2)) >> outer1;
599 SkASSERT(mid1 >= 0 && mid1 <= 2);
600 SkASSERT(outer1 != mid1 && outer2 != mid1);
601 int mid2 = outer1 ^ outer2 ^ mid1;
602 SkASSERT(mid2 >= 1 && mid2 <= 3);
603 SkASSERT(mid2 != outer1 && mid2 != outer2 && mid2 != mid1);
604 SkASSERT(((1 << outer1) | (1 << outer2) | (1 << mid1) | (1 << mid2)) == 0x0f);
605 SkScalar lineSlop = ptMax * ptMax * 0.00001f; // this multiplier is pulled out of the air
606 return pt_to_line(cubic[mid1], cubic[outer1], cubic[outer2]) <= lineSlop
607 && pt_to_line(cubic[mid2], cubic[outer1], cubic[outer2]) <= lineSlop;
608}
609
610/* Given quad, see if all there points are in a line.
611 Return true if the inside point is close to a line connecting the outermost points.
612
613 Find the outermost point by looking for the largest difference in X or Y.
614 Since the XOR of the indices is 3 (0 ^ 1 ^ 2)
615 the missing index equals: outer_1 ^ outer_2 ^ 3
616 */
617static bool quad_in_line(const SkPoint quad[3]) {
618 SkScalar ptMax = -1;
619 int outer1 SK_INIT_TO_AVOID_WARNING;
620 int outer2 SK_INIT_TO_AVOID_WARNING;
621 for (int index = 0; index < 2; ++index) {
622 for (int inner = index + 1; inner < 3; ++inner) {
623 SkVector testDiff = quad[inner] - quad[index];
624 SkScalar testMax = std::max(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY));
625 if (ptMax < testMax) {
626 outer1 = index;
627 outer2 = inner;
628 ptMax = testMax;
629 }
630 }
631 }
632 SkASSERT(outer1 >= 0 && outer1 <= 1);
633 SkASSERT(outer2 >= 1 && outer2 <= 2);
634 SkASSERT(outer1 < outer2);
635 int mid = outer1 ^ outer2 ^ 3;
636 const float kCurvatureSlop = 0.000005f; // this multiplier is pulled out of the air
637 SkScalar lineSlop = ptMax * ptMax * kCurvatureSlop;
638 return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop;
639}
640
641static bool conic_in_line(const SkConic& conic) {
642 return quad_in_line(conic.fPts);
643}
644
645SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic[4],
646 SkPoint reduction[3], const SkPoint** tangentPtPtr) {
647 bool degenerateAB = degenerate_vector(cubic[1] - cubic[0]);
648 bool degenerateBC = degenerate_vector(cubic[2] - cubic[1]);
649 bool degenerateCD = degenerate_vector(cubic[3] - cubic[2]);
650 if (degenerateAB & degenerateBC & degenerateCD) {
651 return kPoint_ReductionType;
652 }
653 if (degenerateAB + degenerateBC + degenerateCD == 2) {
654 return kLine_ReductionType;
655 }
656 if (!cubic_in_line(cubic)) {
657 *tangentPtPtr = degenerateAB ? &cubic[2] : &cubic[1];
658 return kQuad_ReductionType;
659 }
660 SkScalar tValues[3];
661 int count = SkFindCubicMaxCurvature(cubic, tValues);
662 int rCount = 0;
663 // Now loop over the t-values, and reject any that evaluate to either end-point
664 for (int index = 0; index < count; ++index) {
665 SkScalar t = tValues[index];
666 if (0 >= t || t >= 1) {
667 continue;
668 }
669 SkEvalCubicAt(cubic, t, &reduction[rCount], nullptr, nullptr);
670 if (reduction[rCount] != cubic[0] && reduction[rCount] != cubic[3]) {
671 ++rCount;
672 }
673 }
674 if (rCount == 0) {
675 return kLine_ReductionType;
676 }
677 static_assert(kQuad_ReductionType + 1 == kDegenerate_ReductionType, "enum_out_of_whack");
678 static_assert(kQuad_ReductionType + 2 == kDegenerate2_ReductionType, "enum_out_of_whack");
679 static_assert(kQuad_ReductionType + 3 == kDegenerate3_ReductionType, "enum_out_of_whack");
680
681 return (ReductionType) (kQuad_ReductionType + rCount);
682}
683
684SkPathStroker::ReductionType SkPathStroker::CheckConicLinear(const SkConic& conic,
685 SkPoint* reduction) {
686 bool degenerateAB = degenerate_vector(conic.fPts[1] - conic.fPts[0]);
687 bool degenerateBC = degenerate_vector(conic.fPts[2] - conic.fPts[1]);
688 if (degenerateAB & degenerateBC) {
689 return kPoint_ReductionType;
690 }
691 if (degenerateAB | degenerateBC) {
692 return kLine_ReductionType;
693 }
694 if (!conic_in_line(conic)) {
695 return kQuad_ReductionType;
696 }
697 // SkFindConicMaxCurvature would be a better solution, once we know how to
698 // implement it. Quad curvature is a reasonable substitute
700 if (0 == t || SkIsNaN(t)) {
701 return kLine_ReductionType;
702 }
703 conic.evalAt(t, reduction, nullptr);
704 return kDegenerate_ReductionType;
705}
706
707SkPathStroker::ReductionType SkPathStroker::CheckQuadLinear(const SkPoint quad[3],
708 SkPoint* reduction) {
709 bool degenerateAB = degenerate_vector(quad[1] - quad[0]);
710 bool degenerateBC = degenerate_vector(quad[2] - quad[1]);
711 if (degenerateAB & degenerateBC) {
712 return kPoint_ReductionType;
713 }
714 if (degenerateAB | degenerateBC) {
715 return kLine_ReductionType;
716 }
717 if (!quad_in_line(quad)) {
718 return kQuad_ReductionType;
719 }
721 if (0 == t || 1 == t) {
722 return kLine_ReductionType;
723 }
724 *reduction = SkEvalQuadAt(quad, t);
725 return kDegenerate_ReductionType;
726}
727
728void SkPathStroker::conicTo(const SkPoint& pt1, const SkPoint& pt2, SkScalar weight) {
729 const SkConic conic(fPrevPt, pt1, pt2, weight);
730 SkPoint reduction;
731 ReductionType reductionType = CheckConicLinear(conic, &reduction);
732 if (kPoint_ReductionType == reductionType) {
733 /* If the stroke consists of a moveTo followed by a degenerate curve, treat it
734 as if it were followed by a zero-length line. Lines without length
735 can have square and round end caps. */
736 this->lineTo(pt2);
737 return;
738 }
739 if (kLine_ReductionType == reductionType) {
740 this->lineTo(pt2);
741 return;
742 }
743 if (kDegenerate_ReductionType == reductionType) {
744 this->lineTo(reduction);
745 SkStrokerPriv::JoinProc saveJoiner = fJoiner;
747 this->lineTo(pt2);
748 fJoiner = saveJoiner;
749 return;
750 }
751 SkASSERT(kQuad_ReductionType == reductionType);
752 SkVector normalAB, unitAB, normalBC, unitBC;
753 if (!this->preJoinTo(pt1, &normalAB, &unitAB, false)) {
754 this->lineTo(pt2);
755 return;
756 }
757 SkQuadConstruct quadPts;
758 this->init(kOuter_StrokeType, &quadPts, 0, 1);
759 (void) this->conicStroke(conic, &quadPts);
760 this->init(kInner_StrokeType, &quadPts, 0, 1);
761 (void) this->conicStroke(conic, &quadPts);
762 this->setConicEndNormal(conic, normalAB, unitAB, &normalBC, &unitBC);
763 this->postJoinTo(pt2, normalBC, unitBC);
764}
765
766void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
767 const SkPoint quad[3] = { fPrevPt, pt1, pt2 };
768 SkPoint reduction;
769 ReductionType reductionType = CheckQuadLinear(quad, &reduction);
770 if (kPoint_ReductionType == reductionType) {
771 /* If the stroke consists of a moveTo followed by a degenerate curve, treat it
772 as if it were followed by a zero-length line. Lines without length
773 can have square and round end caps. */
774 this->lineTo(pt2);
775 return;
776 }
777 if (kLine_ReductionType == reductionType) {
778 this->lineTo(pt2);
779 return;
780 }
781 if (kDegenerate_ReductionType == reductionType) {
782 this->lineTo(reduction);
783 SkStrokerPriv::JoinProc saveJoiner = fJoiner;
785 this->lineTo(pt2);
786 fJoiner = saveJoiner;
787 return;
788 }
789 SkASSERT(kQuad_ReductionType == reductionType);
790 SkVector normalAB, unitAB, normalBC, unitBC;
791 if (!this->preJoinTo(pt1, &normalAB, &unitAB, false)) {
792 this->lineTo(pt2);
793 return;
794 }
795 SkQuadConstruct quadPts;
796 this->init(kOuter_StrokeType, &quadPts, 0, 1);
797 (void) this->quadStroke(quad, &quadPts);
798 this->init(kInner_StrokeType, &quadPts, 0, 1);
799 (void) this->quadStroke(quad, &quadPts);
800 this->setQuadEndNormal(quad, normalAB, unitAB, &normalBC, &unitBC);
801
802 this->postJoinTo(pt2, normalBC, unitBC);
803}
804
805// Given a point on the curve and its derivative, scale the derivative by the radius, and
806// compute the perpendicular point and its tangent.
807void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt,
808 SkPoint* tangent) const {
809 if (!dxy->setLength(fRadius)) {
810 dxy->set(fRadius, 0);
811 }
812 SkScalar axisFlip = SkIntToScalar(fStrokeType); // go opposite ways for outer, inner
813 onPt->fX = tPt.fX + axisFlip * dxy->fY;
814 onPt->fY = tPt.fY - axisFlip * dxy->fX;
815 if (tangent) {
816 tangent->fX = onPt->fX + dxy->fX;
817 tangent->fY = onPt->fY + dxy->fY;
818 }
819}
820
821// Given a conic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
822// Returns false if the perpendicular could not be computed (because the derivative collapsed to 0)
823void SkPathStroker::conicPerpRay(const SkConic& conic, SkScalar t, SkPoint* tPt, SkPoint* onPt,
824 SkPoint* tangent) const {
825 SkVector dxy;
826 conic.evalAt(t, tPt, &dxy);
827 if (dxy.fX == 0 && dxy.fY == 0) {
828 dxy = conic.fPts[2] - conic.fPts[0];
829 }
830 this->setRayPts(*tPt, &dxy, onPt, tangent);
831}
832
833// Given a conic and a t range, find the start and end if they haven't been found already.
834void SkPathStroker::conicQuadEnds(const SkConic& conic, SkQuadConstruct* quadPts) const {
835 if (!quadPts->fStartSet) {
836 SkPoint conicStartPt;
837 this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0],
838 &quadPts->fTangentStart);
839 quadPts->fStartSet = true;
840 }
841 if (!quadPts->fEndSet) {
842 SkPoint conicEndPt;
843 this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2],
844 &quadPts->fTangentEnd);
845 quadPts->fEndSet = true;
846 }
847}
848
849
850// Given a cubic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
851void SkPathStroker::cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
852 SkPoint* tangent) const {
853 SkVector dxy;
854 SkPoint chopped[7];
855 SkEvalCubicAt(cubic, t, tPt, &dxy, nullptr);
856 if (dxy.fX == 0 && dxy.fY == 0) {
857 const SkPoint* cPts = cubic;
858 if (SkScalarNearlyZero(t)) {
859 dxy = cubic[2] - cubic[0];
860 } else if (SkScalarNearlyZero(1 - t)) {
861 dxy = cubic[3] - cubic[1];
862 } else {
863 // If the cubic inflection falls on the cusp, subdivide the cubic
864 // to find the tangent at that point.
865 SkChopCubicAt(cubic, chopped, t);
866 dxy = chopped[3] - chopped[2];
867 if (dxy.fX == 0 && dxy.fY == 0) {
868 dxy = chopped[3] - chopped[1];
869 cPts = chopped;
870 }
871 }
872 if (dxy.fX == 0 && dxy.fY == 0) {
873 dxy = cPts[3] - cPts[0];
874 }
875 }
876 setRayPts(*tPt, &dxy, onPt, tangent);
877}
878
879// Given a cubic and a t range, find the start and end if they haven't been found already.
880void SkPathStroker::cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
881 if (!quadPts->fStartSet) {
882 SkPoint cubicStartPt;
883 this->cubicPerpRay(cubic, quadPts->fStartT, &cubicStartPt, &quadPts->fQuad[0],
884 &quadPts->fTangentStart);
885 quadPts->fStartSet = true;
886 }
887 if (!quadPts->fEndSet) {
888 SkPoint cubicEndPt;
889 this->cubicPerpRay(cubic, quadPts->fEndT, &cubicEndPt, &quadPts->fQuad[2],
890 &quadPts->fTangentEnd);
891 quadPts->fEndSet = true;
892 }
893}
894
895void SkPathStroker::cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* quadPts,
896 SkPoint* mid) const {
897 SkPoint cubicMidPt;
898 this->cubicPerpRay(cubic, quadPts->fMidT, &cubicMidPt, mid, nullptr);
899}
900
901// Given a quad and t, return the point on curve, its perpendicular, and the perpendicular tangent.
902void SkPathStroker::quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
903 SkPoint* tangent) const {
904 SkVector dxy;
905 SkEvalQuadAt(quad, t, tPt, &dxy);
906 if (dxy.fX == 0 && dxy.fY == 0) {
907 dxy = quad[2] - quad[0];
908 }
909 setRayPts(*tPt, &dxy, onPt, tangent);
910}
911
912// Find the intersection of the stroke tangents to construct a stroke quad.
913// Return whether the stroke is a degenerate (a line), a quad, or must be split.
914// Optionally compute the quad's control point.
915SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
916 IntersectRayType intersectRayType STROKER_DEBUG_PARAMS(int depth)) const {
917 const SkPoint& start = quadPts->fQuad[0];
918 const SkPoint& end = quadPts->fQuad[2];
919 SkVector aLen = quadPts->fTangentStart - start;
920 SkVector bLen = quadPts->fTangentEnd - end;
921 /* Slopes match when denom goes to zero:
922 axLen / ayLen == bxLen / byLen
923 (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen
924 byLen * axLen == ayLen * bxLen
925 byLen * axLen - ayLen * bxLen ( == denom )
926 */
927 SkScalar denom = aLen.cross(bLen);
928 if (denom == 0 || !SkIsFinite(denom)) {
929 quadPts->fOppositeTangents = aLen.dot(bLen) < 0;
930 return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, "denom == 0");
931 }
932 quadPts->fOppositeTangents = false;
933 SkVector ab0 = start - end;
934 SkScalar numerA = bLen.cross(ab0);
935 SkScalar numerB = aLen.cross(ab0);
936 if ((numerA >= 0) == (numerB >= 0)) { // if the control point is outside the quad ends
937 // if the perpendicular distances from the quad points to the opposite tangent line
938 // are small, a straight line is good enough
939 SkScalar dist1 = pt_to_line(start, end, quadPts->fTangentEnd);
940 SkScalar dist2 = pt_to_line(end, start, quadPts->fTangentStart);
941 if (std::max(dist1, dist2) <= fInvResScaleSquared) {
942 return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
943 "std::max(dist1=%g, dist2=%g) <= fInvResScaleSquared", dist1, dist2);
944 }
945 return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
946 "(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB);
947 }
948 // check to see if the denominator is teeny relative to the numerator
949 // if the offset by one will be lost, the ratio is too large
950 numerA /= denom;
951 bool validDivide = numerA > numerA - 1;
952 if (validDivide) {
953 if (kCtrlPt_RayType == intersectRayType) {
954 SkPoint* ctrlPt = &quadPts->fQuad[1];
955 // the intersection of the tangents need not be on the tangent segment
956 // so 0 <= numerA <= 1 is not necessarily true
957 ctrlPt->fX = start.fX * (1 - numerA) + quadPts->fTangentStart.fX * numerA;
958 ctrlPt->fY = start.fY * (1 - numerA) + quadPts->fTangentStart.fY * numerA;
959 }
960 return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
961 "(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB);
962 }
963 quadPts->fOppositeTangents = aLen.dot(bLen) < 0;
964 // if the lines are parallel, straight line is good enough
965 return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
966 "SkScalarNearlyZero(denom=%g)", denom);
967}
968
969// Given a cubic and a t-range, determine if the stroke can be described by a quadratic.
970SkPathStroker::ResultType SkPathStroker::tangentsMeet(const SkPoint cubic[4],
971 SkQuadConstruct* quadPts) {
972 this->cubicQuadEnds(cubic, quadPts);
973 return this->intersectRay(quadPts, kResultType_RayType STROKER_DEBUG_PARAMS(fRecursionDepth));
974}
975
976// Intersect the line with the quad and return the t values on the quad where the line crosses.
977static int intersect_quad_ray(const SkPoint line[2], const SkPoint quad[3], SkScalar roots[2]) {
978 SkVector vec = line[1] - line[0];
979 SkScalar r[3];
980 for (int n = 0; n < 3; ++n) {
981 r[n] = (quad[n].fY - line[0].fY) * vec.fX - (quad[n].fX - line[0].fX) * vec.fY;
982 }
983 SkScalar A = r[2];
984 SkScalar B = r[1];
985 SkScalar C = r[0];
986 A += C - 2 * B; // A = a - 2*b + c
987 B -= C; // B = -(b - c)
988 return SkFindUnitQuadRoots(A, 2 * B, C, roots);
989}
990
991// Return true if the point is close to the bounds of the quad. This is used as a quick reject.
992bool SkPathStroker::ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const {
993 SkScalar xMin = std::min(std::min(quad[0].fX, quad[1].fX), quad[2].fX);
994 if (pt.fX + fInvResScale < xMin) {
995 return false;
996 }
997 SkScalar xMax = std::max(std::max(quad[0].fX, quad[1].fX), quad[2].fX);
998 if (pt.fX - fInvResScale > xMax) {
999 return false;
1000 }
1001 SkScalar yMin = std::min(std::min(quad[0].fY, quad[1].fY), quad[2].fY);
1002 if (pt.fY + fInvResScale < yMin) {
1003 return false;
1004 }
1005 SkScalar yMax = std::max(std::max(quad[0].fY, quad[1].fY), quad[2].fY);
1006 if (pt.fY - fInvResScale > yMax) {
1007 return false;
1008 }
1009 return true;
1010}
1011
1012static bool points_within_dist(const SkPoint& nearPt, const SkPoint& farPt, SkScalar limit) {
1013 return SkPointPriv::DistanceToSqd(nearPt, farPt) <= limit * limit;
1014}
1015
1016static bool sharp_angle(const SkPoint quad[3]) {
1017 SkVector smaller = quad[1] - quad[0];
1018 SkVector larger = quad[1] - quad[2];
1019 SkScalar smallerLen = SkPointPriv::LengthSqd(smaller);
1020 SkScalar largerLen = SkPointPriv::LengthSqd(larger);
1021 if (smallerLen > largerLen) {
1022 using std::swap;
1023 swap(smaller, larger);
1024 largerLen = smallerLen;
1025 }
1026 if (!smaller.setLength(largerLen)) {
1027 return false;
1028 }
1029 SkScalar dot = smaller.dot(larger);
1030 return dot > 0;
1031}
1032
1033SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[3],
1034 const SkPoint ray[2], SkQuadConstruct* quadPts STROKER_DEBUG_PARAMS(int depth)) const {
1035 SkPoint strokeMid = SkEvalQuadAt(stroke, SK_ScalarHalf);
1036 // measure the distance from the curve to the quad-stroke midpoint, compare to radius
1037 if (points_within_dist(ray[0], strokeMid, fInvResScale)) { // if the difference is small
1038 if (sharp_angle(quadPts->fQuad)) {
1039 return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
1040 "sharp_angle (1) =%g,%g, %g,%g, %g,%g",
1041 quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
1042 quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
1043 quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
1044 }
1045 return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
1046 "points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fInvResScale=%g)",
1047 ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY, fInvResScale);
1048 }
1049 // measure the distance to quad's bounds (quick reject)
1050 // an alternative : look for point in triangle
1051 if (!ptInQuadBounds(stroke, ray[0])) { // if far, subdivide
1052 return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
1053 "!pt_in_quad_bounds(stroke=(%g,%g %g,%g %g,%g), ray[0]=%g,%g)",
1054 stroke[0].fX, stroke[0].fY, stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY,
1055 ray[0].fX, ray[0].fY);
1056 }
1057 // measure the curve ray distance to the quad-stroke
1058 SkScalar roots[2];
1059 int rootCount = intersect_quad_ray(ray, stroke, roots);
1060 if (rootCount != 1) {
1061 return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
1062 "rootCount=%d != 1", rootCount);
1063 }
1064 SkPoint quadPt = SkEvalQuadAt(stroke, roots[0]);
1065 SkScalar error = fInvResScale * (SK_Scalar1 - SkScalarAbs(roots[0] - 0.5f) * 2);
1066 if (points_within_dist(ray[0], quadPt, error)) { // if the difference is small, we're done
1067 if (sharp_angle(quadPts->fQuad)) {
1068 return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
1069 "sharp_angle (2) =%g,%g, %g,%g, %g,%g",
1070 quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
1071 quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
1072 quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
1073 }
1074 return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
1075 "points_within_dist(ray[0]=%g,%g, quadPt=%g,%g, error=%g)",
1076 ray[0].fX, ray[0].fY, quadPt.fX, quadPt.fY, error);
1077 }
1078 // otherwise, subdivide
1079 return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "%s", "fall through");
1080}
1081
1082SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4],
1083 SkQuadConstruct* quadPts) {
1084 // get the quadratic approximation of the stroke
1085 this->cubicQuadEnds(cubic, quadPts);
1086 ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
1087 STROKER_DEBUG_PARAMS(fRecursionDepth) );
1088 if (resultType != kQuad_ResultType) {
1089 return resultType;
1090 }
1091 // project a ray from the curve to the stroke
1092 SkPoint ray[2]; // points near midpoint on quad, midpoint on cubic
1093 this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], nullptr);
1094 return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
1095 STROKER_DEBUG_PARAMS(fRecursionDepth));
1096}
1097
1098SkPathStroker::ResultType SkPathStroker::compareQuadConic(const SkConic& conic,
1099 SkQuadConstruct* quadPts) const {
1100 // get the quadratic approximation of the stroke
1101 this->conicQuadEnds(conic, quadPts);
1102 ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
1103 STROKER_DEBUG_PARAMS(fRecursionDepth) );
1104 if (resultType != kQuad_ResultType) {
1105 return resultType;
1106 }
1107 // project a ray from the curve to the stroke
1108 SkPoint ray[2]; // points near midpoint on quad, midpoint on conic
1109 this->conicPerpRay(conic, quadPts->fMidT, &ray[1], &ray[0], nullptr);
1110 return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
1111 STROKER_DEBUG_PARAMS(fRecursionDepth));
1112}
1113
1114SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3],
1115 SkQuadConstruct* quadPts) {
1116 // get the quadratic approximation of the stroke
1117 if (!quadPts->fStartSet) {
1118 SkPoint quadStartPt;
1119 this->quadPerpRay(quad, quadPts->fStartT, &quadStartPt, &quadPts->fQuad[0],
1120 &quadPts->fTangentStart);
1121 quadPts->fStartSet = true;
1122 }
1123 if (!quadPts->fEndSet) {
1124 SkPoint quadEndPt;
1125 this->quadPerpRay(quad, quadPts->fEndT, &quadEndPt, &quadPts->fQuad[2],
1126 &quadPts->fTangentEnd);
1127 quadPts->fEndSet = true;
1128 }
1129 ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
1130 STROKER_DEBUG_PARAMS(fRecursionDepth));
1131 if (resultType != kQuad_ResultType) {
1132 return resultType;
1133 }
1134 // project a ray from the curve to the stroke
1135 SkPoint ray[2];
1136 this->quadPerpRay(quad, quadPts->fMidT, &ray[1], &ray[0], nullptr);
1137 return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
1138 STROKER_DEBUG_PARAMS(fRecursionDepth));
1139}
1140
1141void SkPathStroker::addDegenerateLine(const SkQuadConstruct* quadPts) {
1142 const SkPoint* quad = quadPts->fQuad;
1143 SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
1144 path->lineTo(quad[2].fX, quad[2].fY);
1145}
1146
1147bool SkPathStroker::cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* quadPts) const {
1148 SkPoint strokeMid;
1149 this->cubicQuadMid(cubic, quadPts, &strokeMid);
1150 SkScalar dist = pt_to_line(strokeMid, quadPts->fQuad[0], quadPts->fQuad[2]);
1151 return dist < fInvResScaleSquared;
1152}
1153
1154bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
1155 if (!fFoundTangents) {
1156 ResultType resultType = this->tangentsMeet(cubic, quadPts);
1157 if (kQuad_ResultType != resultType) {
1158 if ((kDegenerate_ResultType == resultType
1159 || points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2],
1160 fInvResScale)) && cubicMidOnLine(cubic, quadPts)) {
1161 addDegenerateLine(quadPts);
1162 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1163 return true;
1164 }
1165 } else {
1166 fFoundTangents = true;
1167 }
1168 }
1169 if (fFoundTangents) {
1170 ResultType resultType = this->compareQuadCubic(cubic, quadPts);
1171 if (kQuad_ResultType == resultType) {
1172 SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
1173 const SkPoint* stroke = quadPts->fQuad;
1174 path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
1175 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1176 return true;
1177 }
1178 if (kDegenerate_ResultType == resultType) {
1179 if (!quadPts->fOppositeTangents) {
1180 addDegenerateLine(quadPts);
1181 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1182 return true;
1183 }
1184 }
1185 }
1186 if (!SkIsFinite(quadPts->fQuad[2].fX, quadPts->fQuad[2].fY)) {
1187 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1188 return false; // just abort if projected quad isn't representable
1189 }
1190#if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
1191 SkDEBUGCODE(gMaxRecursion[fFoundTangents] = std::max(gMaxRecursion[fFoundTangents],
1192 fRecursionDepth + 1));
1193#endif
1194 if (++fRecursionDepth > kRecursiveLimits[fFoundTangents]) {
1195 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1196 // If we stop making progress, just emit a line and move on
1197 addDegenerateLine(quadPts);
1198 return true;
1199 }
1200 SkQuadConstruct half;
1201 if (!half.initWithStart(quadPts)) {
1202 addDegenerateLine(quadPts);
1203 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1204 --fRecursionDepth;
1205 return true;
1206 }
1207 if (!this->cubicStroke(cubic, &half)) {
1208 return false;
1209 }
1210 if (!half.initWithEnd(quadPts)) {
1211 addDegenerateLine(quadPts);
1212 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1213 --fRecursionDepth;
1214 return true;
1215 }
1216 if (!this->cubicStroke(cubic, &half)) {
1217 return false;
1218 }
1219 --fRecursionDepth;
1220 return true;
1221}
1222
1223bool SkPathStroker::conicStroke(const SkConic& conic, SkQuadConstruct* quadPts) {
1224 ResultType resultType = this->compareQuadConic(conic, quadPts);
1225 if (kQuad_ResultType == resultType) {
1226 const SkPoint* stroke = quadPts->fQuad;
1227 SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
1228 path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
1229 return true;
1230 }
1231 if (kDegenerate_ResultType == resultType) {
1232 addDegenerateLine(quadPts);
1233 return true;
1234 }
1235#if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
1236 SkDEBUGCODE(gMaxRecursion[kConic_RecursiveLimit] = std::max(gMaxRecursion[kConic_RecursiveLimit],
1237 fRecursionDepth + 1));
1238#endif
1239 if (++fRecursionDepth > kRecursiveLimits[kConic_RecursiveLimit]) {
1240 // If we stop making progress, just emit a line and move on
1241 addDegenerateLine(quadPts);
1242 return true;
1243 }
1244 SkQuadConstruct half;
1245 (void) half.initWithStart(quadPts);
1246 if (!this->conicStroke(conic, &half)) {
1247 return false;
1248 }
1249 (void) half.initWithEnd(quadPts);
1250 if (!this->conicStroke(conic, &half)) {
1251 return false;
1252 }
1253 --fRecursionDepth;
1254 return true;
1255}
1256
1257bool SkPathStroker::quadStroke(const SkPoint quad[3], SkQuadConstruct* quadPts) {
1258 ResultType resultType = this->compareQuadQuad(quad, quadPts);
1259 if (kQuad_ResultType == resultType) {
1260 const SkPoint* stroke = quadPts->fQuad;
1261 SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
1262 path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
1263 return true;
1264 }
1265 if (kDegenerate_ResultType == resultType) {
1266 addDegenerateLine(quadPts);
1267 return true;
1268 }
1269#if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
1270 SkDEBUGCODE(gMaxRecursion[kQuad_RecursiveLimit] = std::max(gMaxRecursion[kQuad_RecursiveLimit],
1271 fRecursionDepth + 1));
1272#endif
1273 if (++fRecursionDepth > kRecursiveLimits[kQuad_RecursiveLimit]) {
1274 // If we stop making progress, just emit a line and move on
1275 addDegenerateLine(quadPts);
1276 return true;
1277 }
1278 SkQuadConstruct half;
1279 (void) half.initWithStart(quadPts);
1280 if (!this->quadStroke(quad, &half)) {
1281 return false;
1282 }
1283 (void) half.initWithEnd(quadPts);
1284 if (!this->quadStroke(quad, &half)) {
1285 return false;
1286 }
1287 --fRecursionDepth;
1288 return true;
1289}
1290
1291void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
1292 const SkPoint& pt3) {
1293 const SkPoint cubic[4] = { fPrevPt, pt1, pt2, pt3 };
1294 SkPoint reduction[3];
1295 const SkPoint* tangentPt;
1296 ReductionType reductionType = CheckCubicLinear(cubic, reduction, &tangentPt);
1297 if (kPoint_ReductionType == reductionType) {
1298 /* If the stroke consists of a moveTo followed by a degenerate curve, treat it
1299 as if it were followed by a zero-length line. Lines without length
1300 can have square and round end caps. */
1301 this->lineTo(pt3);
1302 return;
1303 }
1304 if (kLine_ReductionType == reductionType) {
1305 this->lineTo(pt3);
1306 return;
1307 }
1308 if (kDegenerate_ReductionType <= reductionType && kDegenerate3_ReductionType >= reductionType) {
1309 this->lineTo(reduction[0]);
1310 SkStrokerPriv::JoinProc saveJoiner = fJoiner;
1312 if (kDegenerate2_ReductionType <= reductionType) {
1313 this->lineTo(reduction[1]);
1314 }
1315 if (kDegenerate3_ReductionType == reductionType) {
1316 this->lineTo(reduction[2]);
1317 }
1318 this->lineTo(pt3);
1319 fJoiner = saveJoiner;
1320 return;
1321 }
1322 SkASSERT(kQuad_ReductionType == reductionType);
1323 SkVector normalAB, unitAB, normalCD, unitCD;
1324 if (!this->preJoinTo(*tangentPt, &normalAB, &unitAB, false)) {
1325 this->lineTo(pt3);
1326 return;
1327 }
1328 SkScalar tValues[2];
1329 int count = SkFindCubicInflections(cubic, tValues);
1330 SkScalar lastT = 0;
1331 for (int index = 0; index <= count; ++index) {
1332 SkScalar nextT = index < count ? tValues[index] : 1;
1333 SkQuadConstruct quadPts;
1334 this->init(kOuter_StrokeType, &quadPts, lastT, nextT);
1335 (void) this->cubicStroke(cubic, &quadPts);
1336 this->init(kInner_StrokeType, &quadPts, lastT, nextT);
1337 (void) this->cubicStroke(cubic, &quadPts);
1338 lastT = nextT;
1339 }
1340 SkScalar cusp = SkFindCubicCusp(cubic);
1341 if (cusp > 0) {
1342 SkPoint cuspLoc;
1343 SkEvalCubicAt(cubic, cusp, &cuspLoc, nullptr, nullptr);
1344 fCusper.addCircle(cuspLoc.fX, cuspLoc.fY, fRadius);
1345 }
1346 // emit the join even if one stroke succeeded but the last one failed
1347 // this avoids reversing an inner stroke with a partial path followed by another moveto
1348 this->setCubicEndNormal(cubic, normalAB, unitAB, &normalCD, &unitCD);
1349
1350 this->postJoinTo(pt3, normalCD, unitCD);
1351}
1352
1353///////////////////////////////////////////////////////////////////////////////
1354///////////////////////////////////////////////////////////////////////////////
1355
1357
1359 fWidth = SK_Scalar1;
1360 fMiterLimit = SkPaintDefaults_MiterLimit;
1361 fResScale = 1;
1362 fCap = SkPaint::kDefault_Cap;
1363 fJoin = SkPaint::kDefault_Join;
1364 fDoFill = false;
1365}
1366
1368 fWidth = p.getStrokeWidth();
1369 fMiterLimit = p.getStrokeMiter();
1370 fResScale = 1;
1371 fCap = (uint8_t)p.getStrokeCap();
1372 fJoin = (uint8_t)p.getStrokeJoin();
1373 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
1374}
1375
1377 fWidth = width;
1378 fMiterLimit = p.getStrokeMiter();
1379 fResScale = 1;
1380 fCap = (uint8_t)p.getStrokeCap();
1381 fJoin = (uint8_t)p.getStrokeJoin();
1382 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
1383}
1384
1386 SkASSERT(width >= 0);
1387 fWidth = width;
1388}
1389
1391 SkASSERT(miterLimit >= 0);
1392 fMiterLimit = miterLimit;
1393}
1394
1396 SkASSERT((unsigned)cap < SkPaint::kCapCount);
1397 fCap = SkToU8(cap);
1398}
1399
1401 SkASSERT((unsigned)join < SkPaint::kJoinCount);
1402 fJoin = SkToU8(join);
1403}
1404
1405///////////////////////////////////////////////////////////////////////////////
1406
1407// If src==dst, then we use a tmp path to record the stroke, and then swap
1408// its contents with src when we're done.
1410public:
1411 AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
1412 if (&src == *dst) {
1413 *dst = &fTmpDst;
1414 fSwapWithSrc = true;
1415 } else {
1416 (*dst)->reset();
1417 fSwapWithSrc = false;
1418 }
1419 }
1420
1422 if (fSwapWithSrc) {
1423 fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
1424 }
1425 }
1426
1427private:
1428 SkPath fTmpDst;
1429 const SkPath& fSrc;
1430 bool fSwapWithSrc;
1431};
1432
1433void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
1434 SkASSERT(dst);
1435
1436 SkScalar radius = SkScalarHalf(fWidth);
1437
1438 AutoTmpPath tmp(src, &dst);
1439
1440 if (radius <= 0) {
1441 return;
1442 }
1443
1444 // If src is really a rect, call our specialty strokeRect() method
1445 {
1446 SkRect rect;
1447 bool isClosed = false;
1448 SkPathDirection dir;
1449 if (src.isRect(&rect, &isClosed, &dir) && isClosed) {
1450 this->strokeRect(rect, dst, dir);
1451 // our answer should preserve the inverseness of the src
1452 if (src.isInverseFillType()) {
1453 SkASSERT(!dst->isInverseFillType());
1454 dst->toggleInverseFillType();
1455 }
1456 return;
1457 }
1458 }
1459
1460 // We can always ignore centers for stroke and fill convex line-only paths
1461 // TODO: remove the line-only restriction
1462 bool ignoreCenter = fDoFill && (src.getSegmentMasks() == SkPath::kLine_SegmentMask) &&
1463 src.isLastContourClosed() && src.isConvex();
1464
1465 SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(),
1466 fResScale, ignoreCenter);
1467 SkPath::Iter iter(src, false);
1468 SkPath::Verb lastSegment = SkPath::kMove_Verb;
1469
1470 for (;;) {
1471 SkPoint pts[4];
1472 switch (iter.next(pts)) {
1473 case SkPath::kMove_Verb:
1474 stroker.moveTo(pts[0]);
1475 break;
1476 case SkPath::kLine_Verb:
1477 stroker.lineTo(pts[1], &iter);
1478 lastSegment = SkPath::kLine_Verb;
1479 break;
1480 case SkPath::kQuad_Verb:
1481 stroker.quadTo(pts[1], pts[2]);
1482 lastSegment = SkPath::kQuad_Verb;
1483 break;
1484 case SkPath::kConic_Verb: {
1485 stroker.conicTo(pts[1], pts[2], iter.conicWeight());
1486 lastSegment = SkPath::kConic_Verb;
1487 } break;
1489 stroker.cubicTo(pts[1], pts[2], pts[3]);
1490 lastSegment = SkPath::kCubic_Verb;
1491 break;
1493 if (SkPaint::kButt_Cap != this->getCap()) {
1494 /* If the stroke consists of a moveTo followed by a close, treat it
1495 as if it were followed by a zero-length line. Lines without length
1496 can have square and round end caps. */
1497 if (stroker.hasOnlyMoveTo()) {
1498 stroker.lineTo(stroker.moveToPt());
1499 goto ZERO_LENGTH;
1500 }
1501 /* If the stroke consists of a moveTo followed by one or more zero-length
1502 verbs, then followed by a close, treat is as if it were followed by a
1503 zero-length line. Lines without length can have square & round end caps. */
1504 if (stroker.isCurrentContourEmpty()) {
1505 ZERO_LENGTH:
1506 lastSegment = SkPath::kLine_Verb;
1507 break;
1508 }
1509 }
1510 stroker.close(lastSegment == SkPath::kLine_Verb);
1511 break;
1512 case SkPath::kDone_Verb:
1513 goto DONE;
1514 }
1515 }
1516DONE:
1517 stroker.done(dst, lastSegment == SkPath::kLine_Verb);
1518
1519 if (fDoFill && !ignoreCenter) {
1521 dst->reverseAddPath(src);
1522 } else {
1523 dst->addPath(src);
1524 }
1525 } else {
1526 // Seems like we can assume that a 2-point src would always result in
1527 // a convex stroke, but testing has proved otherwise.
1528 // TODO: fix the stroker to make this assumption true (without making
1529 // it slower that the work that will be done in computeConvexity())
1530#if 0
1531 // this test results in a non-convex stroke :(
1532 static void test(SkCanvas* canvas) {
1533 SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 };
1534 SkPaint paint;
1535 paint.setStrokeWidth(7);
1536 paint.setStrokeCap(SkPaint::kRound_Cap);
1537 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
1538 }
1539#endif
1540#if 0
1541 if (2 == src.countPoints()) {
1542 dst->setIsConvex(true);
1543 }
1544#endif
1545 }
1546
1547 // our answer should preserve the inverseness of the src
1548 if (src.isInverseFillType()) {
1549 SkASSERT(!dst->isInverseFillType());
1550 dst->toggleInverseFillType();
1551 }
1552}
1553
1555 static const SkPathDirection gOpposite[] = { SkPathDirection::kCCW, SkPathDirection::kCW };
1556 return gOpposite[(int)dir];
1557}
1558
1559static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPathDirection dir) {
1560 SkPoint pts[8];
1561
1562 if (SkPathDirection::kCW == dir) {
1563 pts[0].set(r.fLeft, outer.fTop);
1564 pts[1].set(r.fRight, outer.fTop);
1565 pts[2].set(outer.fRight, r.fTop);
1566 pts[3].set(outer.fRight, r.fBottom);
1567 pts[4].set(r.fRight, outer.fBottom);
1568 pts[5].set(r.fLeft, outer.fBottom);
1569 pts[6].set(outer.fLeft, r.fBottom);
1570 pts[7].set(outer.fLeft, r.fTop);
1571 } else {
1572 pts[7].set(r.fLeft, outer.fTop);
1573 pts[6].set(r.fRight, outer.fTop);
1574 pts[5].set(outer.fRight, r.fTop);
1575 pts[4].set(outer.fRight, r.fBottom);
1576 pts[3].set(r.fRight, outer.fBottom);
1577 pts[2].set(r.fLeft, outer.fBottom);
1578 pts[1].set(outer.fLeft, r.fBottom);
1579 pts[0].set(outer.fLeft, r.fTop);
1580 }
1581 path->addPoly(pts, 8, true);
1582}
1583
1584void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
1585 SkPathDirection dir) const {
1586 SkASSERT(dst != nullptr);
1587 dst->reset();
1588
1589 SkScalar radius = SkScalarHalf(fWidth);
1590 if (radius <= 0) {
1591 return;
1592 }
1593
1594 SkScalar rw = origRect.width();
1595 SkScalar rh = origRect.height();
1596 if ((rw < 0) ^ (rh < 0)) {
1597 dir = reverse_direction(dir);
1598 }
1599 SkRect rect(origRect);
1600 rect.sort();
1601 // reassign these, now that we know they'll be >= 0
1602 rw = rect.width();
1603 rh = rect.height();
1604
1605 SkRect r(rect);
1606 r.outset(radius, radius);
1607
1608 SkPaint::Join join = (SkPaint::Join)fJoin;
1609 if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
1610 join = SkPaint::kBevel_Join;
1611 }
1612
1613 switch (join) {
1615 dst->addRect(r, dir);
1616 break;
1618 addBevel(dst, rect, r, dir);
1619 break;
1621 dst->addRoundRect(r, radius, radius, dir);
1622 break;
1623 default:
1624 break;
1625 }
1626
1627 if (fWidth < std::min(rw, rh) && !fDoFill) {
1628 r = rect;
1629 r.inset(radius, radius);
1630 dst->addRect(r, reverse_direction(dir));
1631 }
1632}
#define test(name)
int count
#define SkPaintDefaults_MiterLimit
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkASSERT(cond)
Definition SkAssert.h:116
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
#define SkDEBUGCODE(...)
Definition SkDebug.h:23
static bool SkIsFinite(T x, Pack... values)
static bool SkIsNaN(T x)
static constexpr float sk_ieee_float_divide(float numer, float denom)
void SkChopCubicAt(const SkPoint src[4], SkPoint dst[7], SkScalar t)
void SkEvalCubicAt(const SkPoint src[4], SkScalar t, SkPoint *loc, SkVector *tangent, SkVector *curvature)
int SkFindUnitQuadRoots(SkScalar A, SkScalar B, SkScalar C, SkScalar roots[2])
SkScalar SkFindCubicCusp(const SkPoint src[4])
SkScalar SkFindQuadMaxCurvature(const SkPoint src[3])
int SkFindCubicMaxCurvature(const SkPoint src[4], SkScalar tValues[3])
void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint *pt, SkVector *tangent)
int SkFindCubicInflections(const SkPoint src[4], SkScalar tValues[2])
#define SK_INIT_TO_AVOID_WARNING
Definition SkMacros.h:58
SkPathDirection
Definition SkPathTypes.h:34
void swap(sk_sp< T > &a, sk_sp< T > &b)
Definition SkRefCnt.h:341
#define SkScalarInvert(x)
Definition SkScalar.h:73
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:101
#define SK_Scalar1
Definition SkScalar.h:18
#define SkScalarHalf(a)
Definition SkScalar.h:75
#define SK_ScalarHalf
Definition SkScalar.h:19
#define SK_ScalarNearlyZero
Definition SkScalar.h:99
#define SkIntToScalar(x)
Definition SkScalar.h:57
#define SK_ScalarSqrt2
Definition SkScalar.h:20
#define SkScalarAbs(x)
Definition SkScalar.h:39
static bool sharp_angle(const SkPoint quad[3])
static int intersect_quad_ray(const SkPoint line[2], const SkPoint quad[3], SkScalar roots[2])
Definition SkStroke.cpp:977
#define DEBUG_CUBIC_RECURSION_TRACK_DEPTH(depth)
Definition SkStroke.cpp:94
static bool cubic_in_line(const SkPoint cubic[4])
Definition SkStroke.cpp:580
static bool degenerate_vector(const SkVector &v)
Definition SkStroke.cpp:97
static bool set_normal_unitnormal(const SkPoint &before, const SkPoint &after, SkScalar scale, SkScalar radius, SkVector *normal, SkVector *unitNormal)
Definition SkStroke.cpp:101
static SkScalar pt_to_line(const SkPoint &pt, const SkPoint &lineStart, const SkPoint &lineEnd)
Definition SkStroke.cpp:541
static bool has_valid_tangent(const SkPath::Iter *iter)
Definition SkStroke.cpp:442
#define STROKER_RESULT(resultType, depth, quadPts, format,...)
Definition SkStroke.cpp:63
@ kCubic_RecursiveLimit
Definition SkStroke.cpp:28
@ kTangent_RecursiveLimit
Definition SkStroke.cpp:27
@ kConic_RecursiveLimit
Definition SkStroke.cpp:29
@ kQuad_RecursiveLimit
Definition SkStroke.cpp:30
static bool quad_in_line(const SkPoint quad[3])
Definition SkStroke.cpp:617
static bool conic_in_line(const SkConic &conic)
Definition SkStroke.cpp:641
static const int kRecursiveLimits[]
Definition SkStroke.cpp:40
static bool points_within_dist(const SkPoint &nearPt, const SkPoint &farPt, SkScalar limit)
static SkPathDirection reverse_direction(SkPathDirection dir)
static void addBevel(SkPath *path, const SkRect &r, const SkRect &outer, SkPathDirection dir)
#define STROKER_DEBUG_PARAMS(...)
Definition SkStroke.cpp:65
constexpr uint8_t SkToU8(S x)
Definition SkTo.h:22
Type::kYUV Type::kRGBA() int(0.7 *637)
AutoTmpPath(const SkPath &src, SkPath **dst)
void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint &paint)
@ kDefault_Cap
equivalent to kButt_Cap
Definition SkPaint.h:338
@ kRound_Cap
adds circle
Definition SkPaint.h:335
@ kButt_Cap
no stroke extension
Definition SkPaint.h:334
static constexpr int kCapCount
Definition SkPaint.h:343
@ kStrokeAndFill_Style
sets to stroke and fill geometry
Definition SkPaint.h:195
static constexpr int kJoinCount
Definition SkPaint.h:368
@ kDefault_Join
equivalent to kMiter_Join
Definition SkPaint.h:363
@ kRound_Join
adds circle
Definition SkPaint.h:360
@ kMiter_Join
extends to miter limit
Definition SkPaint.h:359
@ kBevel_Join
connects outside edges
Definition SkPaint.h:361
static SkPathFirstDirection ComputeFirstDirection(const SkPath &)
Definition SkPath.cpp:2563
bool hasOnlyMoveTo() const
Definition SkStroke.cpp:174
void done(SkPath *dst, bool isLine)
Definition SkStroke.cpp:184
SkPathStroker(const SkPath &src, SkScalar radius, SkScalar miterLimit, SkPaint::Cap, SkPaint::Join, SkScalar resScale, bool canIgnoreCenter)
Definition SkStroke.cpp:385
void close(bool isLine)
Definition SkStroke.cpp:182
SkScalar getResScale() const
Definition SkStroke.cpp:189
void quadTo(const SkPoint &, const SkPoint &)
Definition SkStroke.cpp:766
void conicTo(const SkPoint &, const SkPoint &, SkScalar weight)
Definition SkStroke.cpp:728
bool isCurrentContourEmpty() const
Definition SkStroke.cpp:191
void cubicTo(const SkPoint &, const SkPoint &, const SkPoint &)
void moveTo(const SkPoint &)
Definition SkStroke.cpp:428
SkPoint moveToPt() const
Definition SkStroke.cpp:175
void lineTo(const SkPoint &, const SkPath::Iter *iter=nullptr)
Definition SkStroke.cpp:474
Verb next(SkPoint pts[4])
Definition SkPath.cpp:1837
SkScalar conicWeight() const
Definition SkPath.h:1527
@ kLine_SegmentMask
Definition SkPath.h:1437
bool isEmpty() const
Definition SkPath.cpp:406
int countPoints() const
Definition SkPath.cpp:525
SkPath & addCircle(SkScalar x, SkScalar y, SkScalar radius, SkPathDirection dir=SkPathDirection::kCW)
Definition SkPath.cpp:1149
SkPath & moveTo(SkScalar x, SkScalar y)
Definition SkPath.cpp:678
bool getLastPt(SkPoint *lastPt) const
Definition SkPath.cpp:570
SkPath & lineTo(SkScalar x, SkScalar y)
Definition SkPath.cpp:718
SkPath & addPath(const SkPath &src, SkScalar dx, SkScalar dy, AddPathMode mode=kAppend_AddPathMode)
Definition SkPath.cpp:1442
SkPath & rewind()
Definition SkPath.cpp:372
SkPath & setIsVolatile(bool isVolatile)
Definition SkPath.h:370
const SkRect & getBounds() const
Definition SkPath.cpp:420
@ kClose_Verb
Definition SkPath.h:1463
@ kMove_Verb
Definition SkPath.h:1458
@ kConic_Verb
Definition SkPath.h:1461
@ kDone_Verb
Definition SkPath.h:1464
@ kCubic_Verb
Definition SkPath.h:1462
@ kQuad_Verb
Definition SkPath.h:1460
@ kLine_Verb
Definition SkPath.h:1459
SkPath & close()
Definition SkPath.cpp:813
void swap(SkPath &other)
Definition SkPath.cpp:217
void incReserve(int extraPtCount, int extraVerbCount=0, int extraConicCount=0)
Definition SkPath.cpp:668
static void RotateCCW(const SkPoint &src, SkPoint *dst)
Definition SkPointPriv.h:72
static bool CanNormalize(SkScalar dx, SkScalar dy)
Definition SkPointPriv.h:28
static SkScalar LengthSqd(const SkPoint &pt)
Definition SkPointPriv.h:63
static bool EqualsWithinTolerance(const SkPoint &p1, const SkPoint &p2)
Definition SkPointPriv.h:54
static SkScalar DistanceToSqd(const SkPoint &pt, const SkPoint &a)
Definition SkPointPriv.h:48
SkPaint::Cap getCap() const
Definition SkStroke.h:41
void strokePath(const SkPath &path, SkPath *) const
void setWidth(SkScalar)
void setCap(SkPaint::Cap)
void setJoin(SkPaint::Join)
void strokeRect(const SkRect &rect, SkPath *result, SkPathDirection=SkPathDirection::kCW) const
SkPaint::Join getJoin() const
Definition SkStroke.h:44
void setMiterLimit(SkScalar)
void(* JoinProc)(SkPath *outer, SkPath *inner, const SkVector &beforeUnitNormal, const SkPoint &pivot, const SkVector &afterUnitNormal, SkScalar radius, SkScalar invMiterLimit, bool prevIsLine, bool currIsLine)
static CapProc CapFactory(SkPaint::Cap)
static JoinProc JoinFactory(SkPaint::Join)
void(* CapProc)(SkPath *path, const SkPoint &pivot, const SkVector &normal, const SkPoint &stop, SkPath *otherPath)
const Paint & paint
#define C(TEST_CATEGORY)
Definition colrv1.cpp:247
float SkScalar
Definition extension.cpp:12
glong glong end
const uint8_t uint32_t uint32_t GError ** error
#define B
Definition ab.py:1
Definition copy.py:1
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switches.h:57
AI float conic(float tolerance, const SkPoint pts[], float w, const VectorXform &vectorXform=VectorXform())
AI float cubic(float precision, const SkPoint pts[], const VectorXform &vectorXform=VectorXform())
int32_t width
const Scalar scale
bool setLength(float length)
Definition SkPoint.cpp:30
float fX
x-axis value
bool setNormalize(float x, float y)
Definition SkPoint.cpp:26
float dot(const SkVector &vec) const
void set(float x, float y)
float cross(const SkVector &vec) const
void scale(float scale, SkPoint *dst) const
Definition SkPoint.cpp:17
float fY
y-axis value
bool initWithEnd(SkQuadConstruct *parent)
Definition SkStroke.cpp:156
bool initWithStart(SkQuadConstruct *parent)
Definition SkStroke.cpp:146
SkPoint fTangentEnd
Definition SkStroke.cpp:129
SkPoint fQuad[3]
Definition SkStroke.cpp:127
bool init(SkScalar start, SkScalar end)
Definition SkStroke.cpp:138
SkScalar fStartT
Definition SkStroke.cpp:130
SkPoint fTangentStart
Definition SkStroke.cpp:128
SkScalar fBottom
larger y-axis bounds
Definition extension.cpp:17
void inset(float dx, float dy)
Definition SkRect.h:1060
SkScalar fLeft
smaller x-axis bounds
Definition extension.cpp:14
void outset(float dx, float dy)
Definition SkRect.h:1077
SkScalar fRight
larger x-axis bounds
Definition extension.cpp:16
bool contains(SkScalar x, SkScalar y) const
Definition extension.cpp:19
constexpr float height() const
Definition SkRect.h:769
constexpr float width() const
Definition SkRect.h:762
SkScalar fTop
smaller y-axis bounds
Definition extension.cpp:15