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 this->lineTo(pt3);
429 return;
430 }
431
432 SkVector normalAB, unitAB, normalCD, unitCD;
433
434 // find the first tangent (which might be pt1 or pt2
435 {
436 const SkPoint* nextPt = &pt1;
437 if (degenerateAB)
438 nextPt = &pt2;
439 this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
440 }
441
442 {
443 SkPoint pts[4], tmp[13];
444 int i, count;
445 SkVector n, u;
446 SkScalar tValues[3];
447
448 pts[0] = fPrevPt;
449 pts[1] = pt1;
450 pts[2] = pt2;
451 pts[3] = pt3;
452
453 count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
454 n = normalAB;
455 u = unitAB;
456 for (i = 0; i < count; i++) {
457 this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
458 kMaxCubicSubdivide);
459 if (i == count - 1) {
460 break;
461 }
462 n = normalCD;
463 u = unitCD;
464
465 }
466 }
467
468 this->postJoinTo(pt3, normalCD, unitCD);
469 }
470
471 ///////////////////////////////////////////////////////////////////////////////
472 ///////////////////////////////////////////////////////////////////////////////
473
474 #include "SkPaintDefaults.h"
475
SkStroke()476 SkStroke::SkStroke() {
477 fWidth = SK_Scalar1;
478 fMiterLimit = SkPaintDefaults_MiterLimit;
479 fCap = SkPaint::kDefault_Cap;
480 fJoin = SkPaint::kDefault_Join;
481 fDoFill = false;
482 }
483
SkStroke(const SkPaint & p)484 SkStroke::SkStroke(const SkPaint& p) {
485 fWidth = p.getStrokeWidth();
486 fMiterLimit = p.getStrokeMiter();
487 fCap = (uint8_t)p.getStrokeCap();
488 fJoin = (uint8_t)p.getStrokeJoin();
489 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
490 }
491
SkStroke(const SkPaint & p,SkScalar width)492 SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
493 fWidth = width;
494 fMiterLimit = p.getStrokeMiter();
495 fCap = (uint8_t)p.getStrokeCap();
496 fJoin = (uint8_t)p.getStrokeJoin();
497 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
498 }
499
setWidth(SkScalar width)500 void SkStroke::setWidth(SkScalar width) {
501 SkASSERT(width >= 0);
502 fWidth = width;
503 }
504
setMiterLimit(SkScalar miterLimit)505 void SkStroke::setMiterLimit(SkScalar miterLimit) {
506 SkASSERT(miterLimit >= 0);
507 fMiterLimit = miterLimit;
508 }
509
setCap(SkPaint::Cap cap)510 void SkStroke::setCap(SkPaint::Cap cap) {
511 SkASSERT((unsigned)cap < SkPaint::kCapCount);
512 fCap = SkToU8(cap);
513 }
514
setJoin(SkPaint::Join join)515 void SkStroke::setJoin(SkPaint::Join join) {
516 SkASSERT((unsigned)join < SkPaint::kJoinCount);
517 fJoin = SkToU8(join);
518 }
519
520 ///////////////////////////////////////////////////////////////////////////////
521
522 // If src==dst, then we use a tmp path to record the stroke, and then swap
523 // its contents with src when we're done.
524 class AutoTmpPath {
525 public:
AutoTmpPath(const SkPath & src,SkPath ** dst)526 AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
527 if (&src == *dst) {
528 *dst = &fTmpDst;
529 fSwapWithSrc = true;
530 } else {
531 (*dst)->reset();
532 fSwapWithSrc = false;
533 }
534 }
535
~AutoTmpPath()536 ~AutoTmpPath() {
537 if (fSwapWithSrc) {
538 fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
539 }
540 }
541
542 private:
543 SkPath fTmpDst;
544 const SkPath& fSrc;
545 bool fSwapWithSrc;
546 };
547
strokePath(const SkPath & src,SkPath * dst) const548 void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
549 SkASSERT(&src != NULL && dst != NULL);
550
551 SkScalar radius = SkScalarHalf(fWidth);
552
553 AutoTmpPath tmp(src, &dst);
554
555 if (radius <= 0) {
556 return;
557 }
558
559 // If src is really a rect, call our specialty strokeRect() method
560 {
561 bool isClosed;
562 SkPath::Direction dir;
563 if (src.isRect(&isClosed, &dir) && isClosed) {
564 this->strokeRect(src.getBounds(), dst, dir);
565 // our answer should preserve the inverseness of the src
566 if (src.isInverseFillType()) {
567 SkASSERT(!dst->isInverseFillType());
568 dst->toggleInverseFillType();
569 }
570 return;
571 }
572 }
573
574 SkAutoConicToQuads converter;
575 const SkScalar conicTol = SK_Scalar1 / 4;
576
577 SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(),
578 this->getJoin());
579 SkPath::Iter iter(src, false);
580 SkPath::Verb lastSegment = SkPath::kMove_Verb;
581
582 for (;;) {
583 SkPoint pts[4];
584 switch (iter.next(pts, false)) {
585 case SkPath::kMove_Verb:
586 stroker.moveTo(pts[0]);
587 break;
588 case SkPath::kLine_Verb:
589 stroker.lineTo(pts[1]);
590 lastSegment = SkPath::kLine_Verb;
591 break;
592 case SkPath::kQuad_Verb:
593 stroker.quadTo(pts[1], pts[2]);
594 lastSegment = SkPath::kQuad_Verb;
595 break;
596 case SkPath::kConic_Verb: {
597 // todo: if we had maxcurvature for conics, perhaps we should
598 // natively extrude the conic instead of converting to quads.
599 const SkPoint* quadPts =
600 converter.computeQuads(pts, iter.conicWeight(), conicTol);
601 for (int i = 0; i < converter.countQuads(); ++i) {
602 stroker.quadTo(quadPts[1], quadPts[2]);
603 quadPts += 2;
604 }
605 lastSegment = SkPath::kQuad_Verb;
606 } break;
607 case SkPath::kCubic_Verb:
608 stroker.cubicTo(pts[1], pts[2], pts[3]);
609 lastSegment = SkPath::kCubic_Verb;
610 break;
611 case SkPath::kClose_Verb:
612 stroker.close(lastSegment == SkPath::kLine_Verb);
613 break;
614 case SkPath::kDone_Verb:
615 goto DONE;
616 }
617 }
618 DONE:
619 stroker.done(dst, lastSegment == SkPath::kLine_Verb);
620
621 if (fDoFill) {
622 if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
623 dst->reverseAddPath(src);
624 } else {
625 dst->addPath(src);
626 }
627 } else {
628 // Seems like we can assume that a 2-point src would always result in
629 // a convex stroke, but testing has proved otherwise.
630 // TODO: fix the stroker to make this assumption true (without making
631 // it slower that the work that will be done in computeConvexity())
632 #if 0
633 // this test results in a non-convex stroke :(
634 static void test(SkCanvas* canvas) {
635 SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 };
636 SkPaint paint;
637 paint.setStrokeWidth(7);
638 paint.setStrokeCap(SkPaint::kRound_Cap);
639 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
640 }
641 #endif
642 #if 0
643 if (2 == src.countPoints()) {
644 dst->setIsConvex(true);
645 }
646 #endif
647 }
648
649 // our answer should preserve the inverseness of the src
650 if (src.isInverseFillType()) {
651 SkASSERT(!dst->isInverseFillType());
652 dst->toggleInverseFillType();
653 }
654 }
655
reverse_direction(SkPath::Direction dir)656 static SkPath::Direction reverse_direction(SkPath::Direction dir) {
657 SkASSERT(SkPath::kUnknown_Direction != dir);
658 return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
659 }
660
addBevel(SkPath * path,const SkRect & r,const SkRect & outer,SkPath::Direction dir)661 static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) {
662 SkPoint pts[8];
663
664 if (SkPath::kCW_Direction == dir) {
665 pts[0].set(r.fLeft, outer.fTop);
666 pts[1].set(r.fRight, outer.fTop);
667 pts[2].set(outer.fRight, r.fTop);
668 pts[3].set(outer.fRight, r.fBottom);
669 pts[4].set(r.fRight, outer.fBottom);
670 pts[5].set(r.fLeft, outer.fBottom);
671 pts[6].set(outer.fLeft, r.fBottom);
672 pts[7].set(outer.fLeft, r.fTop);
673 } else {
674 pts[7].set(r.fLeft, outer.fTop);
675 pts[6].set(r.fRight, outer.fTop);
676 pts[5].set(outer.fRight, r.fTop);
677 pts[4].set(outer.fRight, r.fBottom);
678 pts[3].set(r.fRight, outer.fBottom);
679 pts[2].set(r.fLeft, outer.fBottom);
680 pts[1].set(outer.fLeft, r.fBottom);
681 pts[0].set(outer.fLeft, r.fTop);
682 }
683 path->addPoly(pts, 8, true);
684 }
685
strokeRect(const SkRect & origRect,SkPath * dst,SkPath::Direction dir) const686 void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
687 SkPath::Direction dir) const {
688 SkASSERT(dst != NULL);
689 dst->reset();
690
691 SkScalar radius = SkScalarHalf(fWidth);
692 if (radius <= 0) {
693 return;
694 }
695
696 SkScalar rw = origRect.width();
697 SkScalar rh = origRect.height();
698 if ((rw < 0) ^ (rh < 0)) {
699 dir = reverse_direction(dir);
700 }
701 SkRect rect(origRect);
702 rect.sort();
703 // reassign these, now that we know they'll be >= 0
704 rw = rect.width();
705 rh = rect.height();
706
707 SkRect r(rect);
708 r.outset(radius, radius);
709
710 SkPaint::Join join = (SkPaint::Join)fJoin;
711 if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
712 join = SkPaint::kBevel_Join;
713 }
714
715 switch (join) {
716 case SkPaint::kMiter_Join:
717 dst->addRect(r, dir);
718 break;
719 case SkPaint::kBevel_Join:
720 addBevel(dst, rect, r, dir);
721 break;
722 case SkPaint::kRound_Join:
723 dst->addRoundRect(r, radius, radius, dir);
724 break;
725 default:
726 break;
727 }
728
729 if (fWidth < SkMinScalar(rw, rh) && !fDoFill) {
730 r = rect;
731 r.inset(radius, radius);
732 dst->addRect(r, reverse_direction(dir));
733 }
734 }
735