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 "SkStrokerPriv.h"
9 #include "SkGeometry.h"
10 #include "SkPath.h"
11
12 #define kMaxQuadSubdivide 5
13 #define kMaxCubicSubdivide 7
14
degenerate_vector(const SkVector & v)15 static inline bool degenerate_vector(const SkVector& v) {
16 return !SkPoint::CanNormalize(v.fX, v.fY);
17 }
18
normals_too_curvy(const SkVector & norm0,SkVector & norm1)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)
22 */
23 static const SkScalar kFlatEnoughNormalDotProd =
24 SK_ScalarSqrt2/2 + SK_Scalar1/10;
25
26 SkASSERT(kFlatEnoughNormalDotProd > 0 &&
27 kFlatEnoughNormalDotProd < SK_Scalar1);
28
29 return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd;
30 }
31
normals_too_pinchy(const SkVector & norm0,SkVector & norm1)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;
38
39 // just some sanity asserts to help document the expected range
40 SkASSERT(kTooPinchyNormalDotProd >= -1);
41 SkASSERT(kTooPinchyNormalDotProd < SkDoubleToScalar(-0.999));
42
43 SkScalar dot = SkPoint::DotProduct(norm0, norm1);
44 return dot <= kTooPinchyNormalDotProd;
45 }
46
set_normal_unitnormal(const SkPoint & before,const SkPoint & after,SkScalar radius,SkVector * normal,SkVector * unitNormal)47 static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
48 SkScalar radius,
49 SkVector* normal, SkVector* unitNormal) {
50 if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) {
51 return false;
52 }
53 unitNormal->rotateCCW();
54 unitNormal->scale(radius, normal);
55 return true;
56 }
57
set_normal_unitnormal(const SkVector & vec,SkScalar radius,SkVector * normal,SkVector * unitNormal)58 static bool set_normal_unitnormal(const SkVector& vec,
59 SkScalar radius,
60 SkVector* normal, SkVector* unitNormal) {
61 if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
62 return false;
63 }
64 unitNormal->rotateCCW();
65 unitNormal->scale(radius, normal);
66 return true;
67 }
68
69 ///////////////////////////////////////////////////////////////////////////////
70
71 class SkPathStroker {
72 public:
73 SkPathStroker(const SkPath& src,
74 SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
75 SkPaint::Join join);
76
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&);
close(bool isLine)81 void close(bool isLine) { this->finishContour(true, isLine); }
82
done(SkPath * dst,bool isLine)83 void done(SkPath* dst, bool isLine) {
84 this->finishContour(false, isLine);
85 fOuter.addPath(fExtra);
86 dst->swap(fOuter);
87 }
88
89 private:
90 SkScalar fRadius;
91 SkScalar fInvMiterLimit;
92
93 SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
94 SkPoint fFirstPt, fPrevPt; // on original path
95 SkPoint fFirstOuterPt;
96 int fSegmentCount;
97 bool fPrevIsLine;
98
99 SkStrokerPriv::CapProc fCapper;
100 SkStrokerPriv::JoinProc fJoiner;
101
102 SkPath fInner, fOuter; // outer is our working answer, inner is temp
103 SkPath fExtra; // added as extra complete contours
104
105 void finishContour(bool close, bool isLine);
106 void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
107 bool isLine);
108 void postJoinTo(const SkPoint&, const SkVector& normal,
109 const SkVector& unitNormal);
110
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,
115 int subDivide);
116 void cubic_to(const SkPoint pts[4],
117 const SkVector& normalAB, const SkVector& unitNormalAB,
118 SkVector* normalCD, SkVector* unitNormalCD,
119 int subDivide);
120 };
121
122 ///////////////////////////////////////////////////////////////////////////////
123
preJoinTo(const SkPoint & currPt,SkVector * normal,SkVector * unitNormal,bool currIsLine)124 void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
125 SkVector* unitNormal, bool currIsLine) {
126 SkASSERT(fSegmentCount >= 0);
127
128 SkScalar prevX = fPrevPt.fX;
129 SkScalar prevY = fPrevPt.fY;
130
131 SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal,
132 unitNormal));
133
134 if (fSegmentCount == 0) {
135 fFirstNormal = *normal;
136 fFirstUnitNormal = *unitNormal;
137 fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
138
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);
144 }
145 fPrevIsLine = currIsLine;
146 }
147
postJoinTo(const SkPoint & currPt,const SkVector & normal,const SkVector & unitNormal)148 void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
149 const SkVector& unitNormal) {
150 fPrevPt = currPt;
151 fPrevUnitNormal = unitNormal;
152 fPrevNormal = normal;
153 fSegmentCount += 1;
154 }
155
finishContour(bool close,bool currIsLine)156 void SkPathStroker::finishContour(bool close, bool currIsLine) {
157 if (fSegmentCount > 0) {
158 SkPoint pt;
159
160 if (close) {
161 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
162 fFirstUnitNormal, fRadius, fInvMiterLimit,
163 fPrevIsLine, currIsLine);
164 fOuter.close();
165 // now add fInner as its own contour
166 fInner.getLastPt(&pt);
167 fOuter.moveTo(pt.fX, pt.fY);
168 fOuter.reversePathTo(fInner);
169 fOuter.close();
170 } else { // add caps to start and end
171 // cap the end
172 fInner.getLastPt(&pt);
173 fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
174 currIsLine ? &fInner : NULL);
175 fOuter.reversePathTo(fInner);
176 // cap the start
177 fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
178 fPrevIsLine ? &fInner : NULL);
179 fOuter.close();
180 }
181 }
182 // since we may re-use fInner, we rewind instead of reset, to save on
183 // reallocating its internal storage.
184 fInner.rewind();
185 fSegmentCount = -1;
186 }
187
188 ///////////////////////////////////////////////////////////////////////////////
189
SkPathStroker(const SkPath & src,SkScalar radius,SkScalar miterLimit,SkPaint::Cap cap,SkPaint::Join join)190 SkPathStroker::SkPathStroker(const SkPath& src,
191 SkScalar radius, SkScalar miterLimit,
192 SkPaint::Cap cap, SkPaint::Join join)
193 : fRadius(radius) {
194
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.
197 */
198 fInvMiterLimit = 0;
199
200 if (join == SkPaint::kMiter_Join) {
201 if (miterLimit <= SK_Scalar1) {
202 join = SkPaint::kBevel_Join;
203 } else {
204 fInvMiterLimit = SkScalarInvert(miterLimit);
205 }
206 }
207 fCapper = SkStrokerPriv::CapFactory(cap);
208 fJoiner = SkStrokerPriv::JoinFactory(join);
209 fSegmentCount = -1;
210 fPrevIsLine = false;
211
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.
215 //
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());
220 }
221
moveTo(const SkPoint & pt)222 void SkPathStroker::moveTo(const SkPoint& pt) {
223 if (fSegmentCount > 0) {
224 this->finishContour(false, false);
225 }
226 fSegmentCount = 0;
227 fFirstPt = fPrevPt = pt;
228 }
229
line_to(const SkPoint & currPt,const SkVector & normal)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);
233 }
234
lineTo(const SkPoint & currPt)235 void SkPathStroker::lineTo(const SkPoint& currPt) {
236 if (SkPath::IsLineDegenerate(fPrevPt, currPt)) {
237 return;
238 }
239 SkVector normal, unitNormal;
240
241 this->preJoinTo(currPt, &normal, &unitNormal, true);
242 this->line_to(currPt, normal);
243 this->postJoinTo(currPt, normal, unitNormal);
244 }
245
quad_to(const SkPoint pts[3],const SkVector & normalAB,const SkVector & unitNormalAB,SkVector * normalBC,SkVector * unitNormalBC,int subDivide)246 void SkPathStroker::quad_to(const SkPoint pts[3],
247 const SkVector& normalAB, const SkVector& unitNormalAB,
248 SkVector* normalBC, SkVector* unitNormalBC,
249 int subDivide) {
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;
256 return;
257 }
258
259 if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) {
260 SkPoint tmp[5];
261 SkVector norm, unit;
262
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);
266 } else {
267 SkVector normalB;
268
269 normalB = pts[2] - pts[0];
270 normalB.rotateCCW();
271 SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC);
272 SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
273 SkScalarSqrt((SK_Scalar1 + dot)/2))));
274
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);
279 }
280 }
281
cubic_to(const SkPoint pts[4],const SkVector & normalAB,const SkVector & unitNormalAB,SkVector * normalCD,SkVector * unitNormalCD,int subDivide)282 void SkPathStroker::cubic_to(const SkPoint pts[4],
283 const SkVector& normalAB, const SkVector& unitNormalAB,
284 SkVector* normalCD, SkVector* unitNormalCD,
285 int subDivide) {
286 SkVector ab = pts[1] - pts[0];
287 SkVector cd = pts[3] - pts[2];
288 SkVector normalBC, unitNormalBC;
289
290 bool degenerateAB = degenerate_vector(ab);
291 bool degenerateCD = degenerate_vector(cd);
292
293 if (degenerateAB && degenerateCD) {
294 DRAW_LINE:
295 this->line_to(pts[3], normalAB);
296 *normalCD = normalAB;
297 *unitNormalCD = unitNormalAB;
298 return;
299 }
300
301 if (degenerateAB) {
302 ab = pts[2] - pts[0];
303 degenerateAB = degenerate_vector(ab);
304 }
305 if (degenerateCD) {
306 cd = pts[3] - pts[1];
307 degenerateCD = degenerate_vector(cd);
308 }
309 if (degenerateAB || degenerateCD) {
310 goto DRAW_LINE;
311 }
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) {
317 goto DRAW_LINE;
318 }
319 #endif
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) {
325 goto DRAW_LINE;
326 }
327 #endif
328 SkPoint tmp[7];
329 SkVector norm, unit, dummy, unitDummy;
330
331 SkChopCubicAtHalf(pts, tmp);
332 this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit,
333 subDivide);
334 // we use dummys since we already have a valid (and more accurate)
335 // normals for CD
336 this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
337 } else {
338 SkVector normalB, normalC;
339
340 // need normals to inset/outset the off-curve pts B and C
341
342 SkVector unitBC = pts[2] - pts[1];
343 unitBC.normalize();
344 unitBC.rotateCCW();
345
346 normalB = unitNormalAB + unitBC;
347 normalC = *unitNormalCD + unitBC;
348
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))));
355
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);
359
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);
363 }
364 }
365
quadTo(const SkPoint & pt1,const SkPoint & pt2)366 void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
367 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
368 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
369
370 if (degenerateAB | degenerateBC) {
371 if (degenerateAB ^ degenerateBC) {
372 this->lineTo(pt2);
373 }
374 return;
375 }
376
377 SkVector normalAB, unitAB, normalBC, unitBC;
378
379 this->preJoinTo(pt1, &normalAB, &unitAB, false);
380
381 {
382 SkPoint pts[3], tmp[5];
383 pts[0] = fPrevPt;
384 pts[1] = pt1;
385 pts[2] = pt2;
386
387 if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) {
388 unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY);
389 unitBC.rotateCCW();
390 if (normals_too_pinchy(unitAB, unitBC)) {
391 normalBC = unitBC;
392 normalBC.scale(fRadius);
393
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);
397
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);
401
402 fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius,
403 SkPath::kCW_Direction);
404 } else {
405 this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC,
406 kMaxQuadSubdivide);
407 SkVector n = normalBC;
408 SkVector u = unitBC;
409 this->quad_to(&tmp[2], n, u, &normalBC, &unitBC,
410 kMaxQuadSubdivide);
411 }
412 } else {
413 this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC,
414 kMaxQuadSubdivide);
415 }
416 }
417
418 this->postJoinTo(pt2, normalBC, unitBC);
419 }
420
cubicTo(const SkPoint & pt1,const SkPoint & pt2,const SkPoint & pt3)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);
426
427 if (degenerateAB + degenerateBC + degenerateCD >= 2
428 || (degenerateAB && SkPath::IsLineDegenerate(fPrevPt, pt2))) {
429 this->lineTo(pt3);
430 return;
431 }
432
433 SkVector normalAB, unitAB, normalCD, unitCD;
434
435 // find the first tangent (which might be pt1 or pt2
436 {
437 const SkPoint* nextPt = &pt1;
438 if (degenerateAB)
439 nextPt = &pt2;
440 this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
441 }
442
443 {
444 SkPoint pts[4], tmp[13];
445 int i, count;
446 SkVector n, u;
447 SkScalar tValues[3];
448
449 pts[0] = fPrevPt;
450 pts[1] = pt1;
451 pts[2] = pt2;
452 pts[3] = pt3;
453
454 count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
455 n = normalAB;
456 u = unitAB;
457 for (i = 0; i < count; i++) {
458 this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
459 kMaxCubicSubdivide);
460 if (i == count - 1) {
461 break;
462 }
463 n = normalCD;
464 u = unitCD;
465
466 }
467 }
468
469 this->postJoinTo(pt3, normalCD, unitCD);
470 }
471
472 ///////////////////////////////////////////////////////////////////////////////
473 ///////////////////////////////////////////////////////////////////////////////
474
475 #include "SkPaintDefaults.h"
476
SkStroke()477 SkStroke::SkStroke() {
478 fWidth = SK_Scalar1;
479 fMiterLimit = SkPaintDefaults_MiterLimit;
480 fCap = SkPaint::kDefault_Cap;
481 fJoin = SkPaint::kDefault_Join;
482 fDoFill = false;
483 }
484
SkStroke(const SkPaint & p)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);
491 }
492
SkStroke(const SkPaint & p,SkScalar width)493 SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
494 fWidth = 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);
499 }
500
setWidth(SkScalar width)501 void SkStroke::setWidth(SkScalar width) {
502 SkASSERT(width >= 0);
503 fWidth = width;
504 }
505
setMiterLimit(SkScalar miterLimit)506 void SkStroke::setMiterLimit(SkScalar miterLimit) {
507 SkASSERT(miterLimit >= 0);
508 fMiterLimit = miterLimit;
509 }
510
setCap(SkPaint::Cap cap)511 void SkStroke::setCap(SkPaint::Cap cap) {
512 SkASSERT((unsigned)cap < SkPaint::kCapCount);
513 fCap = SkToU8(cap);
514 }
515
setJoin(SkPaint::Join join)516 void SkStroke::setJoin(SkPaint::Join join) {
517 SkASSERT((unsigned)join < SkPaint::kJoinCount);
518 fJoin = SkToU8(join);
519 }
520
521 ///////////////////////////////////////////////////////////////////////////////
522
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.
525 class AutoTmpPath {
526 public:
AutoTmpPath(const SkPath & src,SkPath ** dst)527 AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
528 if (&src == *dst) {
529 *dst = &fTmpDst;
530 fSwapWithSrc = true;
531 } else {
532 (*dst)->reset();
533 fSwapWithSrc = false;
534 }
535 }
536
~AutoTmpPath()537 ~AutoTmpPath() {
538 if (fSwapWithSrc) {
539 fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
540 }
541 }
542
543 private:
544 SkPath fTmpDst;
545 const SkPath& fSrc;
546 bool fSwapWithSrc;
547 };
548
strokePath(const SkPath & src,SkPath * dst) const549 void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
550 SkASSERT(&src != NULL && dst != NULL);
551
552 SkScalar radius = SkScalarHalf(fWidth);
553
554 AutoTmpPath tmp(src, &dst);
555
556 if (radius <= 0) {
557 return;
558 }
559
560 // If src is really a rect, call our specialty strokeRect() method
561 {
562 bool isClosed;
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();
570 }
571 return;
572 }
573 }
574
575 SkAutoConicToQuads converter;
576 const SkScalar conicTol = SK_Scalar1 / 4;
577
578 SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(),
579 this->getJoin());
580 SkPath::Iter iter(src, false);
581 SkPath::Verb lastSegment = SkPath::kMove_Verb;
582
583 for (;;) {
584 SkPoint pts[4];
585 switch (iter.next(pts, false)) {
586 case SkPath::kMove_Verb:
587 stroker.moveTo(pts[0]);
588 break;
589 case SkPath::kLine_Verb:
590 stroker.lineTo(pts[1]);
591 lastSegment = SkPath::kLine_Verb;
592 break;
593 case SkPath::kQuad_Verb:
594 stroker.quadTo(pts[1], pts[2]);
595 lastSegment = SkPath::kQuad_Verb;
596 break;
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]);
604 quadPts += 2;
605 }
606 lastSegment = SkPath::kQuad_Verb;
607 } break;
608 case SkPath::kCubic_Verb:
609 stroker.cubicTo(pts[1], pts[2], pts[3]);
610 lastSegment = SkPath::kCubic_Verb;
611 break;
612 case SkPath::kClose_Verb:
613 stroker.close(lastSegment == SkPath::kLine_Verb);
614 break;
615 case SkPath::kDone_Verb:
616 goto DONE;
617 }
618 }
619 DONE:
620 stroker.done(dst, lastSegment == SkPath::kLine_Verb);
621
622 if (fDoFill) {
623 if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
624 dst->reverseAddPath(src);
625 } else {
626 dst->addPath(src);
627 }
628 } else {
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())
633 #if 0
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 };
637 SkPaint paint;
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);
641 }
642 #endif
643 #if 0
644 if (2 == src.countPoints()) {
645 dst->setIsConvex(true);
646 }
647 #endif
648 }
649
650 // our answer should preserve the inverseness of the src
651 if (src.isInverseFillType()) {
652 SkASSERT(!dst->isInverseFillType());
653 dst->toggleInverseFillType();
654 }
655 }
656
reverse_direction(SkPath::Direction dir)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;
660 }
661
addBevel(SkPath * path,const SkRect & r,const SkRect & outer,SkPath::Direction dir)662 static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) {
663 SkPoint pts[8];
664
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);
674 } else {
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);
683 }
684 path->addPoly(pts, 8, true);
685 }
686
strokeRect(const SkRect & origRect,SkPath * dst,SkPath::Direction dir) const687 void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
688 SkPath::Direction dir) const {
689 SkASSERT(dst != NULL);
690 dst->reset();
691
692 SkScalar radius = SkScalarHalf(fWidth);
693 if (radius <= 0) {
694 return;
695 }
696
697 SkScalar rw = origRect.width();
698 SkScalar rh = origRect.height();
699 if ((rw < 0) ^ (rh < 0)) {
700 dir = reverse_direction(dir);
701 }
702 SkRect rect(origRect);
703 rect.sort();
704 // reassign these, now that we know they'll be >= 0
705 rw = rect.width();
706 rh = rect.height();
707
708 SkRect r(rect);
709 r.outset(radius, radius);
710
711 SkPaint::Join join = (SkPaint::Join)fJoin;
712 if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
713 join = SkPaint::kBevel_Join;
714 }
715
716 switch (join) {
717 case SkPaint::kMiter_Join:
718 dst->addRect(r, dir);
719 break;
720 case SkPaint::kBevel_Join:
721 addBevel(dst, rect, r, dir);
722 break;
723 case SkPaint::kRound_Join:
724 dst->addRoundRect(r, radius, radius, dir);
725 break;
726 default:
727 break;
728 }
729
730 if (fWidth < SkMinScalar(rw, rh) && !fDoFill) {
731 r = rect;
732 r.inset(radius, radius);
733 dst->addRect(r, reverse_direction(dir));
734 }
735 }
736