SkStroke.cpp revision da2b21fa9ba43df374f21b0e05d9816ab1dfb876
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
15static inline bool degenerate_vector(const SkVector& v) {
16    return !SkPoint::CanNormalize(v.fX, v.fY);
17}
18
19static 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
32static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) {
33    static const SkScalar kTooPinchyNormalDotProd = -SK_Scalar1 * 999 / 1000;
34
35    return SkPoint::DotProduct(norm0, norm1) <= kTooPinchyNormalDotProd;
36}
37
38static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
39                                  SkScalar radius,
40                                  SkVector* normal, SkVector* unitNormal) {
41    if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) {
42        return false;
43    }
44    unitNormal->rotateCCW();
45    unitNormal->scale(radius, normal);
46    return true;
47}
48
49static bool set_normal_unitnormal(const SkVector& vec,
50                                  SkScalar radius,
51                                  SkVector* normal, SkVector* unitNormal) {
52    if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
53        return false;
54    }
55    unitNormal->rotateCCW();
56    unitNormal->scale(radius, normal);
57    return true;
58}
59
60///////////////////////////////////////////////////////////////////////////////
61
62class SkPathStroker {
63public:
64    SkPathStroker(const SkPath& src,
65                  SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
66                  SkPaint::Join join);
67
68    void moveTo(const SkPoint&);
69    void lineTo(const SkPoint&);
70    void quadTo(const SkPoint&, const SkPoint&);
71    void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
72    void close(bool isLine) { this->finishContour(true, isLine); }
73
74    void done(SkPath* dst, bool isLine) {
75        this->finishContour(false, isLine);
76        fOuter.addPath(fExtra);
77        dst->swap(fOuter);
78    }
79
80private:
81    SkScalar    fRadius;
82    SkScalar    fInvMiterLimit;
83
84    SkVector    fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
85    SkPoint     fFirstPt, fPrevPt;  // on original path
86    SkPoint     fFirstOuterPt;
87    int         fSegmentCount;
88    bool        fPrevIsLine;
89
90    SkStrokerPriv::CapProc  fCapper;
91    SkStrokerPriv::JoinProc fJoiner;
92
93    SkPath  fInner, fOuter; // outer is our working answer, inner is temp
94    SkPath  fExtra;         // added as extra complete contours
95
96    void    finishContour(bool close, bool isLine);
97    void    preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
98                      bool isLine);
99    void    postJoinTo(const SkPoint&, const SkVector& normal,
100                       const SkVector& unitNormal);
101
102    void    line_to(const SkPoint& currPt, const SkVector& normal);
103    void    quad_to(const SkPoint pts[3],
104                    const SkVector& normalAB, const SkVector& unitNormalAB,
105                    SkVector* normalBC, SkVector* unitNormalBC,
106                    int subDivide);
107    void    cubic_to(const SkPoint pts[4],
108                    const SkVector& normalAB, const SkVector& unitNormalAB,
109                    SkVector* normalCD, SkVector* unitNormalCD,
110                    int subDivide);
111};
112
113///////////////////////////////////////////////////////////////////////////////
114
115void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
116                              SkVector* unitNormal, bool currIsLine) {
117    SkASSERT(fSegmentCount >= 0);
118
119    SkScalar    prevX = fPrevPt.fX;
120    SkScalar    prevY = fPrevPt.fY;
121
122    SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal,
123                                         unitNormal));
124
125    if (fSegmentCount == 0) {
126        fFirstNormal = *normal;
127        fFirstUnitNormal = *unitNormal;
128        fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
129
130        fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY);
131        fInner.moveTo(prevX - normal->fX, prevY - normal->fY);
132    } else {    // we have a previous segment
133        fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
134                fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
135    }
136    fPrevIsLine = currIsLine;
137}
138
139void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
140                               const SkVector& unitNormal) {
141    fPrevPt = currPt;
142    fPrevUnitNormal = unitNormal;
143    fPrevNormal = normal;
144    fSegmentCount += 1;
145}
146
147void SkPathStroker::finishContour(bool close, bool currIsLine) {
148    if (fSegmentCount > 0) {
149        SkPoint pt;
150
151        if (close) {
152            fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
153                    fFirstUnitNormal, fRadius, fInvMiterLimit,
154                    fPrevIsLine, currIsLine);
155            fOuter.close();
156            // now add fInner as its own contour
157            fInner.getLastPt(&pt);
158            fOuter.moveTo(pt.fX, pt.fY);
159            fOuter.reversePathTo(fInner);
160            fOuter.close();
161        } else {    // add caps to start and end
162            // cap the end
163            fInner.getLastPt(&pt);
164            fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
165                    currIsLine ? &fInner : NULL);
166            fOuter.reversePathTo(fInner);
167            // cap the start
168            fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
169                    fPrevIsLine ? &fInner : NULL);
170            fOuter.close();
171        }
172    }
173    // since we may re-use fInner, we rewind instead of reset, to save on
174    // reallocating its internal storage.
175    fInner.rewind();
176    fSegmentCount = -1;
177}
178
179///////////////////////////////////////////////////////////////////////////////
180
181SkPathStroker::SkPathStroker(const SkPath& src,
182                             SkScalar radius, SkScalar miterLimit,
183                             SkPaint::Cap cap, SkPaint::Join join)
184        : fRadius(radius) {
185
186    /*  This is only used when join is miter_join, but we initialize it here
187        so that it is always defined, to fis valgrind warnings.
188    */
189    fInvMiterLimit = 0;
190
191    if (join == SkPaint::kMiter_Join) {
192        if (miterLimit <= SK_Scalar1) {
193            join = SkPaint::kBevel_Join;
194        } else {
195            fInvMiterLimit = SkScalarInvert(miterLimit);
196        }
197    }
198    fCapper = SkStrokerPriv::CapFactory(cap);
199    fJoiner = SkStrokerPriv::JoinFactory(join);
200    fSegmentCount = -1;
201    fPrevIsLine = false;
202
203    // Need some estimate of how large our final result (fOuter)
204    // and our per-contour temp (fInner) will be, so we don't spend
205    // extra time repeatedly growing these arrays.
206    //
207    // 3x for result == inner + outer + join (swag)
208    // 1x for inner == 'wag' (worst contour length would be better guess)
209    fOuter.incReserve(src.countPoints() * 3);
210    fInner.incReserve(src.countPoints());
211}
212
213void SkPathStroker::moveTo(const SkPoint& pt) {
214    if (fSegmentCount > 0) {
215        this->finishContour(false, false);
216    }
217    fSegmentCount = 0;
218    fFirstPt = fPrevPt = pt;
219}
220
221void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
222    fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
223    fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
224}
225
226void SkPathStroker::lineTo(const SkPoint& currPt) {
227    if (SkPath::IsLineDegenerate(fPrevPt, currPt)) {
228        return;
229    }
230    SkVector    normal, unitNormal;
231
232    this->preJoinTo(currPt, &normal, &unitNormal, true);
233    this->line_to(currPt, normal);
234    this->postJoinTo(currPt, normal, unitNormal);
235}
236
237void SkPathStroker::quad_to(const SkPoint pts[3],
238                      const SkVector& normalAB, const SkVector& unitNormalAB,
239                      SkVector* normalBC, SkVector* unitNormalBC,
240                      int subDivide) {
241    if (!set_normal_unitnormal(pts[1], pts[2], fRadius,
242                               normalBC, unitNormalBC)) {
243        // pts[1] nearly equals pts[2], so just draw a line to pts[2]
244        this->line_to(pts[2], normalAB);
245        *normalBC = normalAB;
246        *unitNormalBC = unitNormalAB;
247        return;
248    }
249
250    if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) {
251        SkPoint     tmp[5];
252        SkVector    norm, unit;
253
254        SkChopQuadAtHalf(pts, tmp);
255        this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide);
256        this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide);
257    } else {
258        SkVector    normalB;
259
260        normalB = pts[2] - pts[0];
261        normalB.rotateCCW();
262        SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC);
263        SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
264                                     SkScalarSqrt((SK_Scalar1 + dot)/2))));
265
266        fOuter.quadTo(  pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
267                        pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY);
268        fInner.quadTo(  pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
269                        pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
270    }
271}
272
273void SkPathStroker::cubic_to(const SkPoint pts[4],
274                      const SkVector& normalAB, const SkVector& unitNormalAB,
275                      SkVector* normalCD, SkVector* unitNormalCD,
276                      int subDivide) {
277    SkVector    ab = pts[1] - pts[0];
278    SkVector    cd = pts[3] - pts[2];
279    SkVector    normalBC, unitNormalBC;
280
281    bool    degenerateAB = degenerate_vector(ab);
282    bool    degenerateCD = degenerate_vector(cd);
283
284    if (degenerateAB && degenerateCD) {
285DRAW_LINE:
286        this->line_to(pts[3], normalAB);
287        *normalCD = normalAB;
288        *unitNormalCD = unitNormalAB;
289        return;
290    }
291
292    if (degenerateAB) {
293        ab = pts[2] - pts[0];
294        degenerateAB = degenerate_vector(ab);
295    }
296    if (degenerateCD) {
297        cd = pts[3] - pts[1];
298        degenerateCD = degenerate_vector(cd);
299    }
300    if (degenerateAB || degenerateCD) {
301        goto DRAW_LINE;
302    }
303    SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
304    bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
305                                               &normalBC, &unitNormalBC);
306#ifndef SK_IGNORE_CUBIC_STROKE_FIX
307    if (--subDivide < 0) {
308        goto DRAW_LINE;
309    }
310#endif
311    if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
312             normals_too_curvy(unitNormalBC, *unitNormalCD)) {
313#ifdef SK_IGNORE_CUBIC_STROKE_FIX
314        // subdivide if we can
315        if (--subDivide < 0) {
316            goto DRAW_LINE;
317        }
318#endif
319        SkPoint     tmp[7];
320        SkVector    norm, unit, dummy, unitDummy;
321
322        SkChopCubicAtHalf(pts, tmp);
323        this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit,
324                       subDivide);
325        // we use dummys since we already have a valid (and more accurate)
326        // normals for CD
327        this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
328    } else {
329        SkVector    normalB, normalC;
330
331        // need normals to inset/outset the off-curve pts B and C
332
333        SkVector    unitBC = pts[2] - pts[1];
334        unitBC.normalize();
335        unitBC.rotateCCW();
336
337        normalB = unitNormalAB + unitBC;
338        normalC = *unitNormalCD + unitBC;
339
340        SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC);
341        SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
342                                    SkScalarSqrt((SK_Scalar1 + dot)/2))));
343        dot = SkPoint::DotProduct(*unitNormalCD, unitBC);
344        SkAssertResult(normalC.setLength(SkScalarDiv(fRadius,
345                                    SkScalarSqrt((SK_Scalar1 + dot)/2))));
346
347        fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
348                        pts[2].fX + normalC.fX, pts[2].fY + normalC.fY,
349                        pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY);
350
351        fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
352                        pts[2].fX - normalC.fX, pts[2].fY - normalC.fY,
353                        pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
354    }
355}
356
357void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
358    bool    degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
359    bool    degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
360
361    if (degenerateAB | degenerateBC) {
362        if (degenerateAB ^ degenerateBC) {
363            this->lineTo(pt2);
364        }
365        return;
366    }
367
368    SkVector    normalAB, unitAB, normalBC, unitBC;
369
370    this->preJoinTo(pt1, &normalAB, &unitAB, false);
371
372    {
373        SkPoint pts[3], tmp[5];
374        pts[0] = fPrevPt;
375        pts[1] = pt1;
376        pts[2] = pt2;
377
378        if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) {
379            unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY);
380            unitBC.rotateCCW();
381            if (normals_too_pinchy(unitAB, unitBC)) {
382                normalBC = unitBC;
383                normalBC.scale(fRadius);
384
385                fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY);
386                fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY);
387                fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY);
388
389                fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY);
390                fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY);
391                fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY);
392
393                fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius,
394                                 SkPath::kCW_Direction);
395            } else {
396                this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC,
397                              kMaxQuadSubdivide);
398                SkVector n = normalBC;
399                SkVector u = unitBC;
400                this->quad_to(&tmp[2], n, u, &normalBC, &unitBC,
401                              kMaxQuadSubdivide);
402            }
403        } else {
404            this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC,
405                          kMaxQuadSubdivide);
406        }
407    }
408
409    this->postJoinTo(pt2, normalBC, unitBC);
410}
411
412void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
413                            const SkPoint& pt3) {
414    bool    degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
415    bool    degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
416    bool    degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);
417
418    if (degenerateAB + degenerateBC + degenerateCD >= 2) {
419        this->lineTo(pt3);
420        return;
421    }
422
423    SkVector    normalAB, unitAB, normalCD, unitCD;
424
425    // find the first tangent (which might be pt1 or pt2
426    {
427        const SkPoint*  nextPt = &pt1;
428        if (degenerateAB)
429            nextPt = &pt2;
430        this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
431    }
432
433    {
434        SkPoint pts[4], tmp[13];
435        int         i, count;
436        SkVector    n, u;
437        SkScalar    tValues[3];
438
439        pts[0] = fPrevPt;
440        pts[1] = pt1;
441        pts[2] = pt2;
442        pts[3] = pt3;
443
444        count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
445        n = normalAB;
446        u = unitAB;
447        for (i = 0; i < count; i++) {
448            this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
449                           kMaxCubicSubdivide);
450            if (i == count - 1) {
451                break;
452            }
453            n = normalCD;
454            u = unitCD;
455
456        }
457    }
458
459    this->postJoinTo(pt3, normalCD, unitCD);
460}
461
462///////////////////////////////////////////////////////////////////////////////
463///////////////////////////////////////////////////////////////////////////////
464
465#include "SkPaintDefaults.h"
466
467SkStroke::SkStroke() {
468    fWidth      = SK_Scalar1;
469    fMiterLimit = SkPaintDefaults_MiterLimit;
470    fCap        = SkPaint::kDefault_Cap;
471    fJoin       = SkPaint::kDefault_Join;
472    fDoFill     = false;
473}
474
475SkStroke::SkStroke(const SkPaint& p) {
476    fWidth      = p.getStrokeWidth();
477    fMiterLimit = p.getStrokeMiter();
478    fCap        = (uint8_t)p.getStrokeCap();
479    fJoin       = (uint8_t)p.getStrokeJoin();
480    fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
481}
482
483SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
484    fWidth      = width;
485    fMiterLimit = p.getStrokeMiter();
486    fCap        = (uint8_t)p.getStrokeCap();
487    fJoin       = (uint8_t)p.getStrokeJoin();
488    fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
489}
490
491void SkStroke::setWidth(SkScalar width) {
492    SkASSERT(width >= 0);
493    fWidth = width;
494}
495
496void SkStroke::setMiterLimit(SkScalar miterLimit) {
497    SkASSERT(miterLimit >= 0);
498    fMiterLimit = miterLimit;
499}
500
501void SkStroke::setCap(SkPaint::Cap cap) {
502    SkASSERT((unsigned)cap < SkPaint::kCapCount);
503    fCap = SkToU8(cap);
504}
505
506void SkStroke::setJoin(SkPaint::Join join) {
507    SkASSERT((unsigned)join < SkPaint::kJoinCount);
508    fJoin = SkToU8(join);
509}
510
511///////////////////////////////////////////////////////////////////////////////
512
513// If src==dst, then we use a tmp path to record the stroke, and then swap
514// its contents with src when we're done.
515class AutoTmpPath {
516public:
517    AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
518        if (&src == *dst) {
519            *dst = &fTmpDst;
520            fSwapWithSrc = true;
521        } else {
522            (*dst)->reset();
523            fSwapWithSrc = false;
524        }
525    }
526
527    ~AutoTmpPath() {
528        if (fSwapWithSrc) {
529            fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
530        }
531    }
532
533private:
534    SkPath          fTmpDst;
535    const SkPath&   fSrc;
536    bool            fSwapWithSrc;
537};
538
539void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
540    SkASSERT(&src != NULL && dst != NULL);
541
542    SkScalar radius = SkScalarHalf(fWidth);
543
544    AutoTmpPath tmp(src, &dst);
545
546    if (radius <= 0) {
547        return;
548    }
549
550    // If src is really a rect, call our specialty strokeRect() method
551    {
552        SkRect rect;
553        bool isClosed;
554        SkPath::Direction dir;
555        if (src.isRect(&rect, &isClosed, &dir) && isClosed) {
556            this->strokeRect(rect, dst, dir);
557            // our answer should preserve the inverseness of the src
558            if (src.isInverseFillType()) {
559                SkASSERT(!dst->isInverseFillType());
560                dst->toggleInverseFillType();
561            }
562            return;
563        }
564    }
565
566    SkAutoConicToQuads converter;
567    const SkScalar conicTol = SK_Scalar1 / 4;
568
569    SkPathStroker   stroker(src, radius, fMiterLimit, this->getCap(),
570                            this->getJoin());
571    SkPath::Iter    iter(src, false);
572    SkPath::Verb    lastSegment = SkPath::kMove_Verb;
573
574    for (;;) {
575        SkPoint  pts[4];
576        switch (iter.next(pts, false)) {
577            case SkPath::kMove_Verb:
578                stroker.moveTo(pts[0]);
579                break;
580            case SkPath::kLine_Verb:
581                stroker.lineTo(pts[1]);
582                lastSegment = SkPath::kLine_Verb;
583                break;
584            case SkPath::kQuad_Verb:
585                stroker.quadTo(pts[1], pts[2]);
586                lastSegment = SkPath::kQuad_Verb;
587                break;
588            case SkPath::kConic_Verb: {
589                // todo: if we had maxcurvature for conics, perhaps we should
590                // natively extrude the conic instead of converting to quads.
591                const SkPoint* quadPts =
592                    converter.computeQuads(pts, iter.conicWeight(), conicTol);
593                for (int i = 0; i < converter.countQuads(); ++i) {
594                    stroker.quadTo(quadPts[1], quadPts[2]);
595                    quadPts += 2;
596                }
597                lastSegment = SkPath::kQuad_Verb;
598            } break;
599            case SkPath::kCubic_Verb:
600                stroker.cubicTo(pts[1], pts[2], pts[3]);
601                lastSegment = SkPath::kCubic_Verb;
602                break;
603            case SkPath::kClose_Verb:
604                stroker.close(lastSegment == SkPath::kLine_Verb);
605                break;
606            case SkPath::kDone_Verb:
607                goto DONE;
608        }
609    }
610DONE:
611    stroker.done(dst, lastSegment == SkPath::kLine_Verb);
612
613    if (fDoFill) {
614        if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
615            dst->reverseAddPath(src);
616        } else {
617            dst->addPath(src);
618        }
619    } else {
620        //  Seems like we can assume that a 2-point src would always result in
621        //  a convex stroke, but testing has proved otherwise.
622        //  TODO: fix the stroker to make this assumption true (without making
623        //  it slower that the work that will be done in computeConvexity())
624#if 0
625        // this test results in a non-convex stroke :(
626        static void test(SkCanvas* canvas) {
627            SkPoint pts[] = { 146.333328,  192.333328, 300.333344, 293.333344 };
628            SkPaint paint;
629            paint.setStrokeWidth(7);
630            paint.setStrokeCap(SkPaint::kRound_Cap);
631            canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
632        }
633#endif
634#if 0
635        if (2 == src.countPoints()) {
636            dst->setIsConvex(true);
637        }
638#endif
639    }
640
641    // our answer should preserve the inverseness of the src
642    if (src.isInverseFillType()) {
643        SkASSERT(!dst->isInverseFillType());
644        dst->toggleInverseFillType();
645    }
646}
647
648static SkPath::Direction reverse_direction(SkPath::Direction dir) {
649    SkASSERT(SkPath::kUnknown_Direction != dir);
650    return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
651}
652
653static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) {
654    SkPoint pts[8];
655
656    if (SkPath::kCW_Direction == dir) {
657        pts[0].set(r.fLeft, outer.fTop);
658        pts[1].set(r.fRight, outer.fTop);
659        pts[2].set(outer.fRight, r.fTop);
660        pts[3].set(outer.fRight, r.fBottom);
661        pts[4].set(r.fRight, outer.fBottom);
662        pts[5].set(r.fLeft, outer.fBottom);
663        pts[6].set(outer.fLeft, r.fBottom);
664        pts[7].set(outer.fLeft, r.fTop);
665    } else {
666        pts[7].set(r.fLeft, outer.fTop);
667        pts[6].set(r.fRight, outer.fTop);
668        pts[5].set(outer.fRight, r.fTop);
669        pts[4].set(outer.fRight, r.fBottom);
670        pts[3].set(r.fRight, outer.fBottom);
671        pts[2].set(r.fLeft, outer.fBottom);
672        pts[1].set(outer.fLeft, r.fBottom);
673        pts[0].set(outer.fLeft, r.fTop);
674    }
675    path->addPoly(pts, 8, true);
676}
677
678void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
679                          SkPath::Direction dir) const {
680    SkASSERT(dst != NULL);
681    dst->reset();
682
683    SkScalar radius = SkScalarHalf(fWidth);
684    if (radius <= 0) {
685        return;
686    }
687
688    SkScalar rw = origRect.width();
689    SkScalar rh = origRect.height();
690    if ((rw < 0) ^ (rh < 0)) {
691        dir = reverse_direction(dir);
692    }
693    SkRect rect(origRect);
694    rect.sort();
695    // reassign these, now that we know they'll be >= 0
696    rw = rect.width();
697    rh = rect.height();
698
699    SkRect r(rect);
700    r.outset(radius, radius);
701
702    SkPaint::Join join = (SkPaint::Join)fJoin;
703    if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
704        join = SkPaint::kBevel_Join;
705    }
706
707    switch (join) {
708        case SkPaint::kMiter_Join:
709            dst->addRect(r, dir);
710            break;
711        case SkPaint::kBevel_Join:
712            addBevel(dst, rect, r, dir);
713            break;
714        case SkPaint::kRound_Join:
715            dst->addRoundRect(r, radius, radius, dir);
716            break;
717        default:
718            break;
719    }
720
721    if (fWidth < SkMinScalar(rw, rh) && !fDoFill) {
722        r = rect;
723        r.inset(radius, radius);
724        dst->addRect(r, reverse_direction(dir));
725    }
726}
727