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