2 * Copyright 2008 The Android Open Source Project
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "SkStrokerPriv.h"
9 #include "SkGeometry.h"
12 #define kMaxQuadSubdivide 5
13 #define kMaxCubicSubdivide 7
15 static inline bool degenerate_vector(const SkVector& v) {
16 return !SkPoint::CanNormalize(v.fX, v.fY);
19 static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) {
20 /* root2/2 is a 45-degree angle
21 make this constant bigger for more subdivisions (but not >= 1)
23 static const SkScalar kFlatEnoughNormalDotProd =
24 SK_ScalarSqrt2/2 + SK_Scalar1/10;
26 SkASSERT(kFlatEnoughNormalDotProd > 0 &&
27 kFlatEnoughNormalDotProd < SK_Scalar1);
29 return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd;
32 static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) {
33 // if the dot-product is -1, then we are definitely too pinchy. We tweak
34 // that by an epsilon to ensure we have significant bits in our test
35 static const int kMinSigBitsForDot = 8;
36 static const SkScalar kDotEpsilon = FLT_EPSILON * (1 << kMinSigBitsForDot);
37 static const SkScalar kTooPinchyNormalDotProd = kDotEpsilon - 1;
39 // just some sanity asserts to help document the expected range
40 SkASSERT(kTooPinchyNormalDotProd >= -1);
41 SkASSERT(kTooPinchyNormalDotProd < SkDoubleToScalar(-0.999));
43 SkScalar dot = SkPoint::DotProduct(norm0, norm1);
44 return dot <= kTooPinchyNormalDotProd;
47 static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
49 SkVector* normal, SkVector* unitNormal) {
50 if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) {
53 unitNormal->rotateCCW();
54 unitNormal->scale(radius, normal);
58 static bool set_normal_unitnormal(const SkVector& vec,
60 SkVector* normal, SkVector* unitNormal) {
61 if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
64 unitNormal->rotateCCW();
65 unitNormal->scale(radius, normal);
69 ///////////////////////////////////////////////////////////////////////////////
73 SkPathStroker(const SkPath& src,
74 SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
77 void moveTo(const SkPoint&);
78 void lineTo(const SkPoint&);
79 void quadTo(const SkPoint&, const SkPoint&);
80 void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
81 void close(bool isLine) { this->finishContour(true, isLine); }
83 void done(SkPath* dst, bool isLine) {
84 this->finishContour(false, isLine);
85 fOuter.addPath(fExtra);
91 SkScalar fInvMiterLimit;
93 SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
94 SkPoint fFirstPt, fPrevPt; // on original path
95 SkPoint fFirstOuterPt;
99 SkStrokerPriv::CapProc fCapper;
100 SkStrokerPriv::JoinProc fJoiner;
102 SkPath fInner, fOuter; // outer is our working answer, inner is temp
103 SkPath fExtra; // added as extra complete contours
105 void finishContour(bool close, bool isLine);
106 void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
108 void postJoinTo(const SkPoint&, const SkVector& normal,
109 const SkVector& unitNormal);
111 void line_to(const SkPoint& currPt, const SkVector& normal);
112 void quad_to(const SkPoint pts[3],
113 const SkVector& normalAB, const SkVector& unitNormalAB,
114 SkVector* normalBC, SkVector* unitNormalBC,
116 void cubic_to(const SkPoint pts[4],
117 const SkVector& normalAB, const SkVector& unitNormalAB,
118 SkVector* normalCD, SkVector* unitNormalCD,
122 ///////////////////////////////////////////////////////////////////////////////
124 void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
125 SkVector* unitNormal, bool currIsLine) {
126 SkASSERT(fSegmentCount >= 0);
128 SkScalar prevX = fPrevPt.fX;
129 SkScalar prevY = fPrevPt.fY;
131 SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal,
134 if (fSegmentCount == 0) {
135 fFirstNormal = *normal;
136 fFirstUnitNormal = *unitNormal;
137 fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
139 fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY);
140 fInner.moveTo(prevX - normal->fX, prevY - normal->fY);
141 } else { // we have a previous segment
142 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
143 fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
145 fPrevIsLine = currIsLine;
148 void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
149 const SkVector& unitNormal) {
151 fPrevUnitNormal = unitNormal;
152 fPrevNormal = normal;
156 void SkPathStroker::finishContour(bool close, bool currIsLine) {
157 if (fSegmentCount > 0) {
161 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
162 fFirstUnitNormal, fRadius, fInvMiterLimit,
163 fPrevIsLine, currIsLine);
165 // now add fInner as its own contour
166 fInner.getLastPt(&pt);
167 fOuter.moveTo(pt.fX, pt.fY);
168 fOuter.reversePathTo(fInner);
170 } else { // add caps to start and end
172 fInner.getLastPt(&pt);
173 fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
174 currIsLine ? &fInner : NULL);
175 fOuter.reversePathTo(fInner);
177 fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
178 fPrevIsLine ? &fInner : NULL);
182 // since we may re-use fInner, we rewind instead of reset, to save on
183 // reallocating its internal storage.
188 ///////////////////////////////////////////////////////////////////////////////
190 SkPathStroker::SkPathStroker(const SkPath& src,
191 SkScalar radius, SkScalar miterLimit,
192 SkPaint::Cap cap, SkPaint::Join join)
195 /* This is only used when join is miter_join, but we initialize it here
196 so that it is always defined, to fis valgrind warnings.
200 if (join == SkPaint::kMiter_Join) {
201 if (miterLimit <= SK_Scalar1) {
202 join = SkPaint::kBevel_Join;
204 fInvMiterLimit = SkScalarInvert(miterLimit);
207 fCapper = SkStrokerPriv::CapFactory(cap);
208 fJoiner = SkStrokerPriv::JoinFactory(join);
212 // Need some estimate of how large our final result (fOuter)
213 // and our per-contour temp (fInner) will be, so we don't spend
214 // extra time repeatedly growing these arrays.
216 // 3x for result == inner + outer + join (swag)
217 // 1x for inner == 'wag' (worst contour length would be better guess)
218 fOuter.incReserve(src.countPoints() * 3);
219 fInner.incReserve(src.countPoints());
222 void SkPathStroker::moveTo(const SkPoint& pt) {
223 if (fSegmentCount > 0) {
224 this->finishContour(false, false);
227 fFirstPt = fPrevPt = pt;
230 void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
231 fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
232 fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
235 void SkPathStroker::lineTo(const SkPoint& currPt) {
236 if (SkPath::IsLineDegenerate(fPrevPt, currPt)) {
239 SkVector normal, unitNormal;
241 this->preJoinTo(currPt, &normal, &unitNormal, true);
242 this->line_to(currPt, normal);
243 this->postJoinTo(currPt, normal, unitNormal);
246 void SkPathStroker::quad_to(const SkPoint pts[3],
247 const SkVector& normalAB, const SkVector& unitNormalAB,
248 SkVector* normalBC, SkVector* unitNormalBC,
250 if (!set_normal_unitnormal(pts[1], pts[2], fRadius,
251 normalBC, unitNormalBC)) {
252 // pts[1] nearly equals pts[2], so just draw a line to pts[2]
253 this->line_to(pts[2], normalAB);
254 *normalBC = normalAB;
255 *unitNormalBC = unitNormalAB;
259 if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) {
263 SkChopQuadAtHalf(pts, tmp);
264 this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide);
265 this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide);
269 normalB = pts[2] - pts[0];
271 SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC);
272 SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
273 SkScalarSqrt((SK_Scalar1 + dot)/2))));
275 fOuter.quadTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
276 pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY);
277 fInner.quadTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
278 pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
282 void SkPathStroker::cubic_to(const SkPoint pts[4],
283 const SkVector& normalAB, const SkVector& unitNormalAB,
284 SkVector* normalCD, SkVector* unitNormalCD,
286 SkVector ab = pts[1] - pts[0];
287 SkVector cd = pts[3] - pts[2];
288 SkVector normalBC, unitNormalBC;
290 bool degenerateAB = degenerate_vector(ab);
291 bool degenerateCD = degenerate_vector(cd);
293 if (degenerateAB && degenerateCD) {
295 this->line_to(pts[3], normalAB);
296 *normalCD = normalAB;
297 *unitNormalCD = unitNormalAB;
302 ab = pts[2] - pts[0];
303 degenerateAB = degenerate_vector(ab);
306 cd = pts[3] - pts[1];
307 degenerateCD = degenerate_vector(cd);
309 if (degenerateAB || degenerateCD) {
312 SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
313 bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
314 &normalBC, &unitNormalBC);
315 #ifndef SK_IGNORE_CUBIC_STROKE_FIX
316 if (--subDivide < 0) {
320 if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
321 normals_too_curvy(unitNormalBC, *unitNormalCD)) {
322 #ifdef SK_IGNORE_CUBIC_STROKE_FIX
323 // subdivide if we can
324 if (--subDivide < 0) {
329 SkVector norm, unit, dummy, unitDummy;
331 SkChopCubicAtHalf(pts, tmp);
332 this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit,
334 // we use dummys since we already have a valid (and more accurate)
336 this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
338 SkVector normalB, normalC;
340 // need normals to inset/outset the off-curve pts B and C
342 SkVector unitBC = pts[2] - pts[1];
346 normalB = unitNormalAB + unitBC;
347 normalC = *unitNormalCD + unitBC;
349 SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC);
350 SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
351 SkScalarSqrt((SK_Scalar1 + dot)/2))));
352 dot = SkPoint::DotProduct(*unitNormalCD, unitBC);
353 SkAssertResult(normalC.setLength(SkScalarDiv(fRadius,
354 SkScalarSqrt((SK_Scalar1 + dot)/2))));
356 fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
357 pts[2].fX + normalC.fX, pts[2].fY + normalC.fY,
358 pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY);
360 fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
361 pts[2].fX - normalC.fX, pts[2].fY - normalC.fY,
362 pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
366 void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
367 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
368 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
370 if (degenerateAB | degenerateBC) {
371 if (degenerateAB ^ degenerateBC) {
377 SkVector normalAB, unitAB, normalBC, unitBC;
379 this->preJoinTo(pt1, &normalAB, &unitAB, false);
382 SkPoint pts[3], tmp[5];
387 if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) {
388 unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY);
390 if (normals_too_pinchy(unitAB, unitBC)) {
392 normalBC.scale(fRadius);
394 fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY);
395 fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY);
396 fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY);
398 fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY);
399 fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY);
400 fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY);
402 fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius,
403 SkPath::kCW_Direction);
405 this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC,
407 SkVector n = normalBC;
409 this->quad_to(&tmp[2], n, u, &normalBC, &unitBC,
413 this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC,
418 this->postJoinTo(pt2, normalBC, unitBC);
421 void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
422 const SkPoint& pt3) {
423 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
424 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
425 bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);
427 if (degenerateAB + degenerateBC + degenerateCD >= 2
428 || (degenerateAB && SkPath::IsLineDegenerate(fPrevPt, pt2))) {
433 SkVector normalAB, unitAB, normalCD, unitCD;
435 // find the first tangent (which might be pt1 or pt2
437 const SkPoint* nextPt = &pt1;
440 this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
444 SkPoint pts[4], tmp[13];
454 count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
457 for (i = 0; i < count; i++) {
458 this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
460 if (i == count - 1) {
469 this->postJoinTo(pt3, normalCD, unitCD);
472 ///////////////////////////////////////////////////////////////////////////////
473 ///////////////////////////////////////////////////////////////////////////////
475 #include "SkPaintDefaults.h"
477 SkStroke::SkStroke() {
479 fMiterLimit = SkPaintDefaults_MiterLimit;
480 fCap = SkPaint::kDefault_Cap;
481 fJoin = SkPaint::kDefault_Join;
485 SkStroke::SkStroke(const SkPaint& p) {
486 fWidth = p.getStrokeWidth();
487 fMiterLimit = p.getStrokeMiter();
488 fCap = (uint8_t)p.getStrokeCap();
489 fJoin = (uint8_t)p.getStrokeJoin();
490 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
493 SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
495 fMiterLimit = p.getStrokeMiter();
496 fCap = (uint8_t)p.getStrokeCap();
497 fJoin = (uint8_t)p.getStrokeJoin();
498 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
501 void SkStroke::setWidth(SkScalar width) {
502 SkASSERT(width >= 0);
506 void SkStroke::setMiterLimit(SkScalar miterLimit) {
507 SkASSERT(miterLimit >= 0);
508 fMiterLimit = miterLimit;
511 void SkStroke::setCap(SkPaint::Cap cap) {
512 SkASSERT((unsigned)cap < SkPaint::kCapCount);
516 void SkStroke::setJoin(SkPaint::Join join) {
517 SkASSERT((unsigned)join < SkPaint::kJoinCount);
518 fJoin = SkToU8(join);
521 ///////////////////////////////////////////////////////////////////////////////
523 // If src==dst, then we use a tmp path to record the stroke, and then swap
524 // its contents with src when we're done.
527 AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
533 fSwapWithSrc = false;
539 fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
549 void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
550 SkASSERT(&src != NULL && dst != NULL);
552 SkScalar radius = SkScalarHalf(fWidth);
554 AutoTmpPath tmp(src, &dst);
560 // If src is really a rect, call our specialty strokeRect() method
563 SkPath::Direction dir;
564 if (src.isRect(&isClosed, &dir) && isClosed) {
565 this->strokeRect(src.getBounds(), dst, dir);
566 // our answer should preserve the inverseness of the src
567 if (src.isInverseFillType()) {
568 SkASSERT(!dst->isInverseFillType());
569 dst->toggleInverseFillType();
575 SkAutoConicToQuads converter;
576 const SkScalar conicTol = SK_Scalar1 / 4;
578 SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(),
580 SkPath::Iter iter(src, false);
581 SkPath::Verb lastSegment = SkPath::kMove_Verb;
585 switch (iter.next(pts, false)) {
586 case SkPath::kMove_Verb:
587 stroker.moveTo(pts[0]);
589 case SkPath::kLine_Verb:
590 stroker.lineTo(pts[1]);
591 lastSegment = SkPath::kLine_Verb;
593 case SkPath::kQuad_Verb:
594 stroker.quadTo(pts[1], pts[2]);
595 lastSegment = SkPath::kQuad_Verb;
597 case SkPath::kConic_Verb: {
598 // todo: if we had maxcurvature for conics, perhaps we should
599 // natively extrude the conic instead of converting to quads.
600 const SkPoint* quadPts =
601 converter.computeQuads(pts, iter.conicWeight(), conicTol);
602 for (int i = 0; i < converter.countQuads(); ++i) {
603 stroker.quadTo(quadPts[1], quadPts[2]);
606 lastSegment = SkPath::kQuad_Verb;
608 case SkPath::kCubic_Verb:
609 stroker.cubicTo(pts[1], pts[2], pts[3]);
610 lastSegment = SkPath::kCubic_Verb;
612 case SkPath::kClose_Verb:
613 stroker.close(lastSegment == SkPath::kLine_Verb);
615 case SkPath::kDone_Verb:
620 stroker.done(dst, lastSegment == SkPath::kLine_Verb);
623 if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
624 dst->reverseAddPath(src);
629 // Seems like we can assume that a 2-point src would always result in
630 // a convex stroke, but testing has proved otherwise.
631 // TODO: fix the stroker to make this assumption true (without making
632 // it slower that the work that will be done in computeConvexity())
634 // this test results in a non-convex stroke :(
635 static void test(SkCanvas* canvas) {
636 SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 };
638 paint.setStrokeWidth(7);
639 paint.setStrokeCap(SkPaint::kRound_Cap);
640 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
644 if (2 == src.countPoints()) {
645 dst->setIsConvex(true);
650 // our answer should preserve the inverseness of the src
651 if (src.isInverseFillType()) {
652 SkASSERT(!dst->isInverseFillType());
653 dst->toggleInverseFillType();
657 static SkPath::Direction reverse_direction(SkPath::Direction dir) {
658 SkASSERT(SkPath::kUnknown_Direction != dir);
659 return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
662 static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) {
665 if (SkPath::kCW_Direction == dir) {
666 pts[0].set(r.fLeft, outer.fTop);
667 pts[1].set(r.fRight, outer.fTop);
668 pts[2].set(outer.fRight, r.fTop);
669 pts[3].set(outer.fRight, r.fBottom);
670 pts[4].set(r.fRight, outer.fBottom);
671 pts[5].set(r.fLeft, outer.fBottom);
672 pts[6].set(outer.fLeft, r.fBottom);
673 pts[7].set(outer.fLeft, r.fTop);
675 pts[7].set(r.fLeft, outer.fTop);
676 pts[6].set(r.fRight, outer.fTop);
677 pts[5].set(outer.fRight, r.fTop);
678 pts[4].set(outer.fRight, r.fBottom);
679 pts[3].set(r.fRight, outer.fBottom);
680 pts[2].set(r.fLeft, outer.fBottom);
681 pts[1].set(outer.fLeft, r.fBottom);
682 pts[0].set(outer.fLeft, r.fTop);
684 path->addPoly(pts, 8, true);
687 void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
688 SkPath::Direction dir) const {
689 SkASSERT(dst != NULL);
692 SkScalar radius = SkScalarHalf(fWidth);
697 SkScalar rw = origRect.width();
698 SkScalar rh = origRect.height();
699 if ((rw < 0) ^ (rh < 0)) {
700 dir = reverse_direction(dir);
702 SkRect rect(origRect);
704 // reassign these, now that we know they'll be >= 0
709 r.outset(radius, radius);
711 SkPaint::Join join = (SkPaint::Join)fJoin;
712 if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
713 join = SkPaint::kBevel_Join;
717 case SkPaint::kMiter_Join:
718 dst->addRect(r, dir);
720 case SkPaint::kBevel_Join:
721 addBevel(dst, rect, r, dir);
723 case SkPaint::kRound_Join:
724 dst->addRoundRect(r, radius, radius, dir);
730 if (fWidth < SkMinScalar(rw, rh) && !fDoFill) {
732 r.inset(radius, radius);
733 dst->addRect(r, reverse_direction(dir));