SkStroke.cpp revision 5408a30c8d5370db05907f2b306270df3e937543
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  4
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#ifdef SK_IGNORE_QUAD_STROKE_FIX
261        SkVector unitB;
262        SkAssertResult(set_normal_unitnormal(pts[0], pts[2], fRadius,
263                                             &normalB, &unitB));
264#else
265        normalB = pts[2] - pts[0];
266        normalB.rotateCCW();
267        SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC);
268        SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
269                                     SkScalarSqrt((SK_Scalar1 + dot)/2))));
270#endif
271
272        fOuter.quadTo(  pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
273                        pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY);
274        fInner.quadTo(  pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
275                        pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
276    }
277}
278
279void SkPathStroker::cubic_to(const SkPoint pts[4],
280                      const SkVector& normalAB, const SkVector& unitNormalAB,
281                      SkVector* normalCD, SkVector* unitNormalCD,
282                      int subDivide) {
283    SkVector    ab = pts[1] - pts[0];
284    SkVector    cd = pts[3] - pts[2];
285    SkVector    normalBC, unitNormalBC;
286
287    bool    degenerateAB = degenerate_vector(ab);
288    bool    degenerateCD = degenerate_vector(cd);
289
290    if (degenerateAB && degenerateCD) {
291DRAW_LINE:
292        this->line_to(pts[3], normalAB);
293        *normalCD = normalAB;
294        *unitNormalCD = unitNormalAB;
295        return;
296    }
297
298    if (degenerateAB) {
299        ab = pts[2] - pts[0];
300        degenerateAB = degenerate_vector(ab);
301    }
302    if (degenerateCD) {
303        cd = pts[3] - pts[1];
304        degenerateCD = degenerate_vector(cd);
305    }
306    if (degenerateAB || degenerateCD) {
307        goto DRAW_LINE;
308    }
309    SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
310    bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
311                                               &normalBC, &unitNormalBC);
312
313    if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
314             normals_too_curvy(unitNormalBC, *unitNormalCD)) {
315        // subdivide if we can
316        if (--subDivide < 0) {
317            goto DRAW_LINE;
318        }
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        if (0) {    // this is normal to the line between our adjacent pts
334            normalB = pts[2] - pts[0];
335            normalB.rotateCCW();
336            SkAssertResult(normalB.setLength(fRadius));
337
338            normalC = pts[3] - pts[1];
339            normalC.rotateCCW();
340            SkAssertResult(normalC.setLength(fRadius));
341        } else {    // miter-join
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
357        fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
358                        pts[2].fX + normalC.fX, pts[2].fY + normalC.fY,
359                        pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY);
360
361        fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
362                        pts[2].fX - normalC.fX, pts[2].fY - normalC.fY,
363                        pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
364    }
365}
366
367void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
368    bool    degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
369    bool    degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
370
371    if (degenerateAB | degenerateBC) {
372        if (degenerateAB ^ degenerateBC) {
373            this->lineTo(pt2);
374        }
375        return;
376    }
377
378    SkVector    normalAB, unitAB, normalBC, unitBC;
379
380    this->preJoinTo(pt1, &normalAB, &unitAB, false);
381
382    {
383        SkPoint pts[3], tmp[5];
384        pts[0] = fPrevPt;
385        pts[1] = pt1;
386        pts[2] = pt2;
387
388        if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) {
389            unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY);
390            unitBC.rotateCCW();
391            if (normals_too_pinchy(unitAB, unitBC)) {
392                normalBC = unitBC;
393                normalBC.scale(fRadius);
394
395                fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY);
396                fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY);
397                fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY);
398
399                fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY);
400                fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY);
401                fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY);
402
403                fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius,
404                                 SkPath::kCW_Direction);
405            } else {
406                this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC,
407                              kMaxQuadSubdivide);
408                SkVector n = normalBC;
409                SkVector u = unitBC;
410                this->quad_to(&tmp[2], n, u, &normalBC, &unitBC,
411                              kMaxQuadSubdivide);
412            }
413        } else {
414            this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC,
415                          kMaxQuadSubdivide);
416        }
417    }
418
419    this->postJoinTo(pt2, normalBC, unitBC);
420}
421
422void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
423                            const SkPoint& pt3) {
424    bool    degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
425    bool    degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
426    bool    degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);
427
428    if (degenerateAB + degenerateBC + degenerateCD >= 2) {
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#if 1
455        count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
456#else
457        count = 1;
458        memcpy(tmp, pts, 4 * sizeof(SkPoint));
459#endif
460        n = normalAB;
461        u = unitAB;
462        for (i = 0; i < count; i++) {
463            this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
464                           kMaxCubicSubdivide);
465            if (i == count - 1) {
466                break;
467            }
468            n = normalCD;
469            u = unitCD;
470
471        }
472
473#if 0
474        /*
475         *  Why was this code here? It caused us to draw circles where we didn't
476         *  want them. See http://code.google.com/p/chromium/issues/detail?id=112145
477         *  and gm/dashcubics.cpp
478         *
479         *  Simply removing this code seemed to fix the problem (no more circles).
480         *  Wish I had a repro case earlier when I added this check/hack...
481         */
482        // check for too pinchy
483        for (i = 1; i < count; i++) {
484            SkPoint p;
485            SkVector    v, c;
486
487            SkEvalCubicAt(pts, tValues[i - 1], &p, &v, &c);
488
489            SkScalar    dot = SkPoint::DotProduct(c, c);
490            v.scale(SkScalarInvert(dot));
491
492            if (SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY)) {
493                fExtra.addCircle(p.fX, p.fY, fRadius, SkPath::kCW_Direction);
494            }
495        }
496#endif
497    }
498
499    this->postJoinTo(pt3, normalCD, unitCD);
500}
501
502///////////////////////////////////////////////////////////////////////////////
503///////////////////////////////////////////////////////////////////////////////
504
505#include "SkPaintDefaults.h"
506
507SkStroke::SkStroke() {
508    fWidth      = SK_Scalar1;
509    fMiterLimit = SkPaintDefaults_MiterLimit;
510    fCap        = SkPaint::kDefault_Cap;
511    fJoin       = SkPaint::kDefault_Join;
512    fDoFill     = false;
513}
514
515SkStroke::SkStroke(const SkPaint& p) {
516    fWidth      = p.getStrokeWidth();
517    fMiterLimit = p.getStrokeMiter();
518    fCap        = (uint8_t)p.getStrokeCap();
519    fJoin       = (uint8_t)p.getStrokeJoin();
520    fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
521}
522
523SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
524    fWidth      = width;
525    fMiterLimit = p.getStrokeMiter();
526    fCap        = (uint8_t)p.getStrokeCap();
527    fJoin       = (uint8_t)p.getStrokeJoin();
528    fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
529}
530
531void SkStroke::setWidth(SkScalar width) {
532    SkASSERT(width >= 0);
533    fWidth = width;
534}
535
536void SkStroke::setMiterLimit(SkScalar miterLimit) {
537    SkASSERT(miterLimit >= 0);
538    fMiterLimit = miterLimit;
539}
540
541void SkStroke::setCap(SkPaint::Cap cap) {
542    SkASSERT((unsigned)cap < SkPaint::kCapCount);
543    fCap = SkToU8(cap);
544}
545
546void SkStroke::setJoin(SkPaint::Join join) {
547    SkASSERT((unsigned)join < SkPaint::kJoinCount);
548    fJoin = SkToU8(join);
549}
550
551///////////////////////////////////////////////////////////////////////////////
552
553#ifdef SK_SCALAR_IS_FIXED
554    /*  return non-zero if the path is too big, and should be shrunk to avoid
555        overflows during intermediate calculations. Note that we compute the
556        bounds for this. If we had a custom callback/walker for paths, we could
557        perhaps go faster by using that, and just perform the abs | in that
558        routine
559    */
560    static int needs_to_shrink(const SkPath& path) {
561        const SkRect& r = path.getBounds();
562        SkFixed mask = SkAbs32(r.fLeft);
563        mask |= SkAbs32(r.fTop);
564        mask |= SkAbs32(r.fRight);
565        mask |= SkAbs32(r.fBottom);
566        // we need the top 3 bits clear (after abs) to avoid overflow
567        return mask >> 29;
568    }
569
570    static void identity_proc(SkPoint pts[], int count) {}
571    static void shift_down_2_proc(SkPoint pts[], int count) {
572        for (int i = 0; i < count; i++) {
573            pts->fX >>= 2;
574            pts->fY >>= 2;
575            pts += 1;
576        }
577    }
578    #define APPLY_PROC(proc, pts, count)    proc(pts, count)
579#else   // float does need any of this
580    #define APPLY_PROC(proc, pts, count)
581#endif
582
583// If src==dst, then we use a tmp path to record the stroke, and then swap
584// its contents with src when we're done.
585class AutoTmpPath {
586public:
587    AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
588        if (&src == *dst) {
589            *dst = &fTmpDst;
590            fSwapWithSrc = true;
591        } else {
592            (*dst)->reset();
593            fSwapWithSrc = false;
594        }
595    }
596
597    ~AutoTmpPath() {
598        if (fSwapWithSrc) {
599            fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
600        }
601    }
602
603private:
604    SkPath          fTmpDst;
605    const SkPath&   fSrc;
606    bool            fSwapWithSrc;
607};
608
609void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
610    SkASSERT(&src != NULL && dst != NULL);
611
612    SkScalar radius = SkScalarHalf(fWidth);
613
614    AutoTmpPath tmp(src, &dst);
615
616    if (radius <= 0) {
617        return;
618    }
619
620    // If src is really a rect, call our specialty strokeRect() method
621    {
622        bool isClosed;
623        SkPath::Direction dir;
624        if (src.isRect(&isClosed, &dir) && isClosed) {
625            this->strokeRect(src.getBounds(), dst, dir);
626            // our answer should preserve the inverseness of the src
627            if (src.isInverseFillType()) {
628                SkASSERT(!dst->isInverseFillType());
629                dst->toggleInverseFillType();
630            }
631            return;
632        }
633    }
634
635#ifdef SK_SCALAR_IS_FIXED
636    void (*proc)(SkPoint pts[], int count) = identity_proc;
637    if (needs_to_shrink(src)) {
638        proc = shift_down_2_proc;
639        radius >>= 2;
640        if (radius == 0) {
641            return;
642        }
643    }
644#endif
645
646    SkPathStroker   stroker(src, radius, fMiterLimit, this->getCap(),
647                            this->getJoin());
648
649    SkPath::Iter    iter(src, false);
650    SkPoint         pts[4];
651    SkPath::Verb    verb, lastSegment = SkPath::kMove_Verb;
652
653    while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
654        switch (verb) {
655            case SkPath::kMove_Verb:
656                APPLY_PROC(proc, &pts[0], 1);
657                stroker.moveTo(pts[0]);
658                break;
659            case SkPath::kLine_Verb:
660                APPLY_PROC(proc, &pts[1], 1);
661                stroker.lineTo(pts[1]);
662                lastSegment = verb;
663                break;
664            case SkPath::kQuad_Verb:
665                APPLY_PROC(proc, &pts[1], 2);
666                stroker.quadTo(pts[1], pts[2]);
667                lastSegment = verb;
668                break;
669            case SkPath::kCubic_Verb:
670                APPLY_PROC(proc, &pts[1], 3);
671                stroker.cubicTo(pts[1], pts[2], pts[3]);
672                lastSegment = verb;
673                break;
674            case SkPath::kClose_Verb:
675                stroker.close(lastSegment == SkPath::kLine_Verb);
676                break;
677            default:
678                break;
679        }
680    }
681    stroker.done(dst, lastSegment == SkPath::kLine_Verb);
682
683#ifdef SK_SCALAR_IS_FIXED
684    // undo our previous down_shift
685    if (shift_down_2_proc == proc) {
686        // need a real shift methid on path. antialias paths could use this too
687        SkMatrix matrix;
688        matrix.setScale(SkIntToScalar(4), SkIntToScalar(4));
689        dst->transform(matrix);
690    }
691#endif
692
693    if (fDoFill) {
694        if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
695            dst->reverseAddPath(src);
696        } else {
697            dst->addPath(src);
698        }
699    } else {
700        //  Seems like we can assume that a 2-point src would always result in
701        //  a convex stroke, but testing has proved otherwise.
702        //  TODO: fix the stroker to make this assumption true (without making
703        //  it slower that the work that will be done in computeConvexity())
704#if 0
705        // this test results in a non-convex stroke :(
706        static void test(SkCanvas* canvas) {
707            SkPoint pts[] = { 146.333328,  192.333328, 300.333344, 293.333344 };
708            SkPaint paint;
709            paint.setStrokeWidth(7);
710            paint.setStrokeCap(SkPaint::kRound_Cap);
711            canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
712        }
713#endif
714#if 0
715        if (2 == src.countPoints()) {
716            dst->setIsConvex(true);
717        }
718#endif
719    }
720
721    // our answer should preserve the inverseness of the src
722    if (src.isInverseFillType()) {
723        SkASSERT(!dst->isInverseFillType());
724        dst->toggleInverseFillType();
725    }
726}
727
728static SkPath::Direction reverse_direction(SkPath::Direction dir) {
729    SkASSERT(SkPath::kUnknown_Direction != dir);
730    return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
731}
732
733static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) {
734    SkPoint pts[8];
735
736    if (SkPath::kCW_Direction == dir) {
737        pts[0].set(r.fLeft, outer.fTop);
738        pts[1].set(r.fRight, outer.fTop);
739        pts[2].set(outer.fRight, r.fTop);
740        pts[3].set(outer.fRight, r.fBottom);
741        pts[4].set(r.fRight, outer.fBottom);
742        pts[5].set(r.fLeft, outer.fBottom);
743        pts[6].set(outer.fLeft, r.fBottom);
744        pts[7].set(outer.fLeft, r.fTop);
745    } else {
746        pts[7].set(r.fLeft, outer.fTop);
747        pts[6].set(r.fRight, outer.fTop);
748        pts[5].set(outer.fRight, r.fTop);
749        pts[4].set(outer.fRight, r.fBottom);
750        pts[3].set(r.fRight, outer.fBottom);
751        pts[2].set(r.fLeft, outer.fBottom);
752        pts[1].set(outer.fLeft, r.fBottom);
753        pts[0].set(outer.fLeft, r.fTop);
754    }
755    path->addPoly(pts, 8, true);
756}
757
758void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
759                          SkPath::Direction dir) const {
760    SkASSERT(dst != NULL);
761    dst->reset();
762
763    SkScalar radius = SkScalarHalf(fWidth);
764    if (radius <= 0) {
765        return;
766    }
767
768    SkScalar rw = origRect.width();
769    SkScalar rh = origRect.height();
770    if ((rw < 0) ^ (rh < 0)) {
771        dir = reverse_direction(dir);
772    }
773    SkRect rect(origRect);
774    rect.sort();
775    // reassign these, now that we know they'll be >= 0
776    rw = rect.width();
777    rh = rect.height();
778
779    SkRect r(rect);
780    r.outset(radius, radius);
781
782    SkPaint::Join join = (SkPaint::Join)fJoin;
783    if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
784        join = SkPaint::kBevel_Join;
785    }
786
787    switch (join) {
788        case SkPaint::kMiter_Join:
789            dst->addRect(r, dir);
790            break;
791        case SkPaint::kBevel_Join:
792            addBevel(dst, rect, r, dir);
793            break;
794        case SkPaint::kRound_Join:
795            dst->addRoundRect(r, radius, radius, dir);
796            break;
797        default:
798            break;
799    }
800
801    if (fWidth < SkMinScalar(rw, rh) && !fDoFill) {
802        r = rect;
803        r.inset(radius, radius);
804        dst->addRect(r, reverse_direction(dir));
805    }
806}
807
808