blob: 4b486b1da4028a7359f119487000fa3938af7938 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
reed@android.com8a1c16f2008-12-17 15:59:43 +00002/*
epoger@google.comec3ed6a2011-07-28 14:26:00 +00003 * Copyright 2008 The Android Open Source Project
reed@android.com8a1c16f2008-12-17 15:59:43 +00004 *
epoger@google.comec3ed6a2011-07-28 14:26:00 +00005 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
reed@android.com8a1c16f2008-12-17 15:59:43 +00007 */
8
epoger@google.comec3ed6a2011-07-28 14:26:00 +00009
reed@android.com8a1c16f2008-12-17 15:59:43 +000010#include "SkStrokerPriv.h"
11#include "SkGeometry.h"
12#include "SkPath.h"
13
14#define kMaxQuadSubdivide 5
15#define kMaxCubicSubdivide 4
16
17static inline bool degenerate_vector(const SkVector& v) {
reed@google.com55b5f4b2011-09-07 12:23:41 +000018 return !SkPoint::CanNormalize(v.fX, v.fY);
reed@android.com8a1c16f2008-12-17 15:59:43 +000019}
20
reed@android.com8a1c16f2008-12-17 15:59:43 +000021static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) {
22 /* root2/2 is a 45-degree angle
23 make this constant bigger for more subdivisions (but not >= 1)
24 */
25 static const SkScalar kFlatEnoughNormalDotProd =
26 SK_ScalarSqrt2/2 + SK_Scalar1/10;
27
28 SkASSERT(kFlatEnoughNormalDotProd > 0 &&
29 kFlatEnoughNormalDotProd < SK_Scalar1);
30
31 return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd;
32}
33
34static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) {
35 static const SkScalar kTooPinchyNormalDotProd = -SK_Scalar1 * 999 / 1000;
36
37 return SkPoint::DotProduct(norm0, norm1) <= kTooPinchyNormalDotProd;
38}
39
40static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
41 SkScalar radius,
42 SkVector* normal, SkVector* unitNormal) {
43 if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) {
44 return false;
45 }
46 unitNormal->rotateCCW();
47 unitNormal->scale(radius, normal);
48 return true;
49}
50
51static bool set_normal_unitnormal(const SkVector& vec,
52 SkScalar radius,
53 SkVector* normal, SkVector* unitNormal) {
54 if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
55 return false;
56 }
57 unitNormal->rotateCCW();
58 unitNormal->scale(radius, normal);
59 return true;
60}
61
62///////////////////////////////////////////////////////////////////////////////
63
64class SkPathStroker {
65public:
66 SkPathStroker(SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
67 SkPaint::Join join);
68
69 void moveTo(const SkPoint&);
70 void lineTo(const SkPoint&);
71 void quadTo(const SkPoint&, const SkPoint&);
72 void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
73 void close(bool isLine) { this->finishContour(true, isLine); }
74
75 void done(SkPath* dst, bool isLine) {
76 this->finishContour(false, isLine);
77 fOuter.addPath(fExtra);
78 dst->swap(fOuter);
79 }
80
81private:
82 SkScalar fRadius;
83 SkScalar fInvMiterLimit;
84
85 SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
86 SkPoint fFirstPt, fPrevPt; // on original path
87 SkPoint fFirstOuterPt;
88 int fSegmentCount;
89 bool fPrevIsLine;
90
91 SkStrokerPriv::CapProc fCapper;
92 SkStrokerPriv::JoinProc fJoiner;
93
94 SkPath fInner, fOuter; // outer is our working answer, inner is temp
95 SkPath fExtra; // added as extra complete contours
96
97 void finishContour(bool close, bool isLine);
98 void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
99 bool isLine);
100 void postJoinTo(const SkPoint&, const SkVector& normal,
101 const SkVector& unitNormal);
102
103 void line_to(const SkPoint& currPt, const SkVector& normal);
104 void quad_to(const SkPoint pts[3],
105 const SkVector& normalAB, const SkVector& unitNormalAB,
106 SkVector* normalBC, SkVector* unitNormalBC,
107 int subDivide);
108 void cubic_to(const SkPoint pts[4],
109 const SkVector& normalAB, const SkVector& unitNormalAB,
110 SkVector* normalCD, SkVector* unitNormalCD,
111 int subDivide);
112};
113
114///////////////////////////////////////////////////////////////////////////////
115
116void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
117 SkVector* unitNormal, bool currIsLine) {
118 SkASSERT(fSegmentCount >= 0);
119
120 SkScalar prevX = fPrevPt.fX;
121 SkScalar prevY = fPrevPt.fY;
122
123 SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal,
124 unitNormal));
125
126 if (fSegmentCount == 0) {
127 fFirstNormal = *normal;
128 fFirstUnitNormal = *unitNormal;
129 fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
130
131 fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY);
132 fInner.moveTo(prevX - normal->fX, prevY - normal->fY);
133 } else { // we have a previous segment
134 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
135 fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
136 }
137 fPrevIsLine = currIsLine;
138}
139
140void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
141 const SkVector& unitNormal) {
142 fPrevPt = currPt;
143 fPrevUnitNormal = unitNormal;
144 fPrevNormal = normal;
145 fSegmentCount += 1;
146}
147
148void SkPathStroker::finishContour(bool close, bool currIsLine) {
149 if (fSegmentCount > 0) {
150 SkPoint pt;
151
152 if (close) {
153 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
154 fFirstUnitNormal, fRadius, fInvMiterLimit,
155 fPrevIsLine, currIsLine);
156 fOuter.close();
157 // now add fInner as its own contour
158 fInner.getLastPt(&pt);
159 fOuter.moveTo(pt.fX, pt.fY);
160 fOuter.reversePathTo(fInner);
161 fOuter.close();
162 } else { // add caps to start and end
163 // cap the end
164 fInner.getLastPt(&pt);
165 fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
166 currIsLine ? &fInner : NULL);
167 fOuter.reversePathTo(fInner);
168 // cap the start
169 fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
170 fPrevIsLine ? &fInner : NULL);
171 fOuter.close();
172 }
173 }
174 fInner.reset();
175 fSegmentCount = -1;
176}
177
178///////////////////////////////////////////////////////////////////////////////
179
180SkPathStroker::SkPathStroker(SkScalar radius, SkScalar miterLimit,
181 SkPaint::Cap cap, SkPaint::Join join)
182 : fRadius(radius) {
183
184 /* This is only used when join is miter_join, but we initialize it here
185 so that it is always defined, to fis valgrind warnings.
186 */
187 fInvMiterLimit = 0;
188
189 if (join == SkPaint::kMiter_Join) {
190 if (miterLimit <= SK_Scalar1) {
191 join = SkPaint::kBevel_Join;
192 } else {
193 fInvMiterLimit = SkScalarInvert(miterLimit);
194 }
195 }
196 fCapper = SkStrokerPriv::CapFactory(cap);
197 fJoiner = SkStrokerPriv::JoinFactory(join);
198 fSegmentCount = -1;
199 fPrevIsLine = false;
200}
201
202void SkPathStroker::moveTo(const SkPoint& pt) {
203 if (fSegmentCount > 0) {
204 this->finishContour(false, false);
205 }
206 fSegmentCount = 0;
207 fFirstPt = fPrevPt = pt;
208}
209
210void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
211 fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
212 fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
213}
214
215void SkPathStroker::lineTo(const SkPoint& currPt) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000216 if (SkPath::IsLineDegenerate(fPrevPt, currPt)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000217 return;
218 }
219 SkVector normal, unitNormal;
220
221 this->preJoinTo(currPt, &normal, &unitNormal, true);
222 this->line_to(currPt, normal);
223 this->postJoinTo(currPt, normal, unitNormal);
224}
225
226void SkPathStroker::quad_to(const SkPoint pts[3],
227 const SkVector& normalAB, const SkVector& unitNormalAB,
228 SkVector* normalBC, SkVector* unitNormalBC,
229 int subDivide) {
230 if (!set_normal_unitnormal(pts[1], pts[2], fRadius,
231 normalBC, unitNormalBC)) {
232 // pts[1] nearly equals pts[2], so just draw a line to pts[2]
233 this->line_to(pts[2], normalAB);
234 *normalBC = normalAB;
235 *unitNormalBC = unitNormalAB;
236 return;
237 }
238
239 if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) {
240 SkPoint tmp[5];
241 SkVector norm, unit;
242
243 SkChopQuadAtHalf(pts, tmp);
244 this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide);
245 this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide);
246 } else {
247 SkVector normalB, unitB;
248 SkAssertResult(set_normal_unitnormal(pts[0], pts[2], fRadius,
249 &normalB, &unitB));
250
251 fOuter.quadTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
252 pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY);
253 fInner.quadTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
254 pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
255 }
256}
257
258void SkPathStroker::cubic_to(const SkPoint pts[4],
259 const SkVector& normalAB, const SkVector& unitNormalAB,
260 SkVector* normalCD, SkVector* unitNormalCD,
261 int subDivide) {
262 SkVector ab = pts[1] - pts[0];
263 SkVector cd = pts[3] - pts[2];
264 SkVector normalBC, unitNormalBC;
265
266 bool degenerateAB = degenerate_vector(ab);
267 bool degenerateCD = degenerate_vector(cd);
268
269 if (degenerateAB && degenerateCD) {
270DRAW_LINE:
271 this->line_to(pts[3], normalAB);
272 *normalCD = normalAB;
273 *unitNormalCD = unitNormalAB;
274 return;
275 }
276
277 if (degenerateAB) {
278 ab = pts[2] - pts[0];
279 degenerateAB = degenerate_vector(ab);
280 }
281 if (degenerateCD) {
282 cd = pts[3] - pts[1];
283 degenerateCD = degenerate_vector(cd);
284 }
285 if (degenerateAB || degenerateCD) {
286 goto DRAW_LINE;
287 }
288 SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
289 bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
290 &normalBC, &unitNormalBC);
291
reed@android.com0ea42c12009-06-17 19:21:01 +0000292 if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
293 normals_too_curvy(unitNormalBC, *unitNormalCD)) {
294 // subdivide if we can
295 if (--subDivide < 0) {
296 goto DRAW_LINE;
297 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000298 SkPoint tmp[7];
299 SkVector norm, unit, dummy, unitDummy;
300
301 SkChopCubicAtHalf(pts, tmp);
302 this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit,
303 subDivide);
304 // we use dummys since we already have a valid (and more accurate)
305 // normals for CD
306 this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
307 } else {
308 SkVector normalB, normalC;
309
310 // need normals to inset/outset the off-curve pts B and C
311
312 if (0) { // this is normal to the line between our adjacent pts
313 normalB = pts[2] - pts[0];
314 normalB.rotateCCW();
315 SkAssertResult(normalB.setLength(fRadius));
316
317 normalC = pts[3] - pts[1];
318 normalC.rotateCCW();
319 SkAssertResult(normalC.setLength(fRadius));
320 } else { // miter-join
321 SkVector unitBC = pts[2] - pts[1];
322 unitBC.normalize();
323 unitBC.rotateCCW();
324
325 normalB = unitNormalAB + unitBC;
326 normalC = *unitNormalCD + unitBC;
327
328 SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC);
329 SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
330 SkScalarSqrt((SK_Scalar1 + dot)/2))));
331 dot = SkPoint::DotProduct(*unitNormalCD, unitBC);
332 SkAssertResult(normalC.setLength(SkScalarDiv(fRadius,
333 SkScalarSqrt((SK_Scalar1 + dot)/2))));
334 }
335
336 fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
337 pts[2].fX + normalC.fX, pts[2].fY + normalC.fY,
338 pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY);
339
340 fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
341 pts[2].fX - normalC.fX, pts[2].fY - normalC.fY,
342 pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
343 }
344}
345
346void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000347 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
348 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349
350 if (degenerateAB | degenerateBC) {
351 if (degenerateAB ^ degenerateBC) {
352 this->lineTo(pt2);
353 }
354 return;
355 }
356
357 SkVector normalAB, unitAB, normalBC, unitBC;
358
359 this->preJoinTo(pt1, &normalAB, &unitAB, false);
360
361 {
362 SkPoint pts[3], tmp[5];
363 pts[0] = fPrevPt;
364 pts[1] = pt1;
365 pts[2] = pt2;
366
367 if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) {
368 unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY);
369 unitBC.rotateCCW();
370 if (normals_too_pinchy(unitAB, unitBC)) {
371 normalBC = unitBC;
372 normalBC.scale(fRadius);
373
374 fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY);
375 fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY);
376 fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY);
377
378 fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY);
379 fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY);
380 fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY);
381
382 fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius,
383 SkPath::kCW_Direction);
384 } else {
385 this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC,
386 kMaxQuadSubdivide);
387 SkVector n = normalBC;
388 SkVector u = unitBC;
389 this->quad_to(&tmp[2], n, u, &normalBC, &unitBC,
390 kMaxQuadSubdivide);
391 }
392 } else {
393 this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC,
394 kMaxQuadSubdivide);
395 }
396 }
397
398 this->postJoinTo(pt2, normalBC, unitBC);
399}
400
401void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
402 const SkPoint& pt3) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000403 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
404 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
405 bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000406
407 if (degenerateAB + degenerateBC + degenerateCD >= 2) {
408 this->lineTo(pt3);
409 return;
410 }
411
412 SkVector normalAB, unitAB, normalCD, unitCD;
413
414 // find the first tangent (which might be pt1 or pt2
415 {
416 const SkPoint* nextPt = &pt1;
417 if (degenerateAB)
418 nextPt = &pt2;
419 this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
420 }
421
422 {
423 SkPoint pts[4], tmp[13];
424 int i, count;
425 SkVector n, u;
426 SkScalar tValues[3];
427
428 pts[0] = fPrevPt;
429 pts[1] = pt1;
430 pts[2] = pt2;
431 pts[3] = pt3;
432
433#if 1
434 count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
435#else
436 count = 1;
437 memcpy(tmp, pts, 4 * sizeof(SkPoint));
438#endif
439 n = normalAB;
440 u = unitAB;
441 for (i = 0; i < count; i++) {
442 this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
443 kMaxCubicSubdivide);
444 if (i == count - 1) {
445 break;
446 }
447 n = normalCD;
448 u = unitCD;
449
450 }
451
452 // check for too pinchy
453 for (i = 1; i < count; i++) {
454 SkPoint p;
455 SkVector v, c;
456
457 SkEvalCubicAt(pts, tValues[i - 1], &p, &v, &c);
458
459 SkScalar dot = SkPoint::DotProduct(c, c);
460 v.scale(SkScalarInvert(dot));
461
462 if (SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY)) {
463 fExtra.addCircle(p.fX, p.fY, fRadius, SkPath::kCW_Direction);
464 }
465 }
466
467 }
468
469 this->postJoinTo(pt3, normalCD, unitCD);
470}
471
472///////////////////////////////////////////////////////////////////////////////
473///////////////////////////////////////////////////////////////////////////////
474
reed@google.comaefdd062012-02-29 13:03:00 +0000475#include "SkPaintDefaults.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +0000476
477SkStroke::SkStroke() {
reed@google.comaefdd062012-02-29 13:03:00 +0000478 fWidth = SK_Scalar1;
479 fMiterLimit = SkPaintDefaults_MiterLimit;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000480 fCap = SkPaint::kDefault_Cap;
481 fJoin = SkPaint::kDefault_Join;
482 fDoFill = false;
483}
484
485SkStroke::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
493SkStroke::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
501void SkStroke::setWidth(SkScalar width) {
502 SkASSERT(width >= 0);
503 fWidth = width;
504}
505
506void SkStroke::setMiterLimit(SkScalar miterLimit) {
507 SkASSERT(miterLimit >= 0);
508 fMiterLimit = miterLimit;
509}
510
511void SkStroke::setCap(SkPaint::Cap cap) {
512 SkASSERT((unsigned)cap < SkPaint::kCapCount);
513 fCap = SkToU8(cap);
514}
515
516void SkStroke::setJoin(SkPaint::Join join) {
517 SkASSERT((unsigned)join < SkPaint::kJoinCount);
518 fJoin = SkToU8(join);
519}
520
521///////////////////////////////////////////////////////////////////////////////
522
523#ifdef SK_SCALAR_IS_FIXED
524 /* return non-zero if the path is too big, and should be shrunk to avoid
525 overflows during intermediate calculations. Note that we compute the
526 bounds for this. If we had a custom callback/walker for paths, we could
527 perhaps go faster by using that, and just perform the abs | in that
528 routine
529 */
530 static int needs_to_shrink(const SkPath& path) {
reed@android.comd252db02009-04-01 18:31:44 +0000531 const SkRect& r = path.getBounds();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000532 SkFixed mask = SkAbs32(r.fLeft);
533 mask |= SkAbs32(r.fTop);
534 mask |= SkAbs32(r.fRight);
535 mask |= SkAbs32(r.fBottom);
536 // we need the top 3 bits clear (after abs) to avoid overflow
537 return mask >> 29;
538 }
539
540 static void identity_proc(SkPoint pts[], int count) {}
541 static void shift_down_2_proc(SkPoint pts[], int count) {
542 for (int i = 0; i < count; i++) {
543 pts->fX >>= 2;
544 pts->fY >>= 2;
545 pts += 1;
546 }
547 }
548 #define APPLY_PROC(proc, pts, count) proc(pts, count)
549#else // float does need any of this
550 #define APPLY_PROC(proc, pts, count)
551#endif
552
553void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
554 SkASSERT(&src != NULL && dst != NULL);
555
556 SkScalar radius = SkScalarHalf(fWidth);
557
558 dst->reset();
559 if (radius <= 0) {
560 return;
561 }
562
563#ifdef SK_SCALAR_IS_FIXED
564 void (*proc)(SkPoint pts[], int count) = identity_proc;
565 if (needs_to_shrink(src)) {
566 proc = shift_down_2_proc;
567 radius >>= 2;
568 if (radius == 0) {
569 return;
570 }
571 }
572#endif
573
574 SkPathStroker stroker(radius, fMiterLimit, this->getCap(),
575 this->getJoin());
576
577 SkPath::Iter iter(src, false);
578 SkPoint pts[4];
579 SkPath::Verb verb, lastSegment = SkPath::kMove_Verb;
580
581 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
582 switch (verb) {
583 case SkPath::kMove_Verb:
584 APPLY_PROC(proc, &pts[0], 1);
585 stroker.moveTo(pts[0]);
586 break;
587 case SkPath::kLine_Verb:
588 APPLY_PROC(proc, &pts[1], 1);
589 stroker.lineTo(pts[1]);
590 lastSegment = verb;
591 break;
592 case SkPath::kQuad_Verb:
593 APPLY_PROC(proc, &pts[1], 2);
594 stroker.quadTo(pts[1], pts[2]);
595 lastSegment = verb;
596 break;
597 case SkPath::kCubic_Verb:
598 APPLY_PROC(proc, &pts[1], 3);
599 stroker.cubicTo(pts[1], pts[2], pts[3]);
600 lastSegment = verb;
601 break;
602 case SkPath::kClose_Verb:
603 stroker.close(lastSegment == SkPath::kLine_Verb);
604 break;
605 default:
606 break;
607 }
608 }
609 stroker.done(dst, lastSegment == SkPath::kLine_Verb);
610
611#ifdef SK_SCALAR_IS_FIXED
612 // undo our previous down_shift
613 if (shift_down_2_proc == proc) {
614 // need a real shift methid on path. antialias paths could use this too
615 SkMatrix matrix;
616 matrix.setScale(SkIntToScalar(4), SkIntToScalar(4));
617 dst->transform(matrix);
618 }
619#endif
620
621 if (fDoFill) {
reed@google.com1ae20902012-01-10 21:30:57 +0000622 if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
reed@google.com69a99432012-01-10 18:00:10 +0000623 dst->reverseAddPath(src);
624 } else {
625 dst->addPath(src);
reed@google.com63d73742012-01-10 15:33:12 +0000626 }
reed@android.comf2b98d62010-12-20 18:26:13 +0000627 } else {
reed@google.comeffe8472011-10-31 12:55:49 +0000628 // 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 }
reed@google.com4d03c112011-10-31 12:12:12 +0000641#endif
reed@google.comeffe8472011-10-31 12:55:49 +0000642#if 0
643 if (2 == src.countPoints()) {
reed@android.comf2b98d62010-12-20 18:26:13 +0000644 dst->setIsConvex(true);
645 }
reed@google.comeffe8472011-10-31 12:55:49 +0000646#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 }
reed@google.combb472322011-12-21 15:53:13 +0000648
649 // our answer should preserve the inverseness of the src
650 if (src.isInverseFillType()) {
651 SkASSERT(!dst->isInverseFillType());
652 dst->toggleInverseFillType();
653 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000654}
655
656void SkStroke::strokeLine(const SkPoint& p0, const SkPoint& p1,
657 SkPath* dst) const {
658 SkPath tmp;
659
660 tmp.moveTo(p0);
661 tmp.lineTo(p1);
662 this->strokePath(tmp, dst);
663}
664