1
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkStrokerPriv.h"
11#include "SkGeometry.h"
12#include "SkPath.h"
13
14static void ButtCapper(SkPath* path, const SkPoint& pivot,
15                       const SkVector& normal, const SkPoint& stop,
16                       SkPath*)
17{
18    path->lineTo(stop.fX, stop.fY);
19}
20
21static void RoundCapper(SkPath* path, const SkPoint& pivot,
22                        const SkVector& normal, const SkPoint& stop,
23                        SkPath*)
24{
25    SkVector parallel;
26    normal.rotateCW(&parallel);
27
28    SkPoint projectedCenter = pivot + parallel;
29
30    path->conicTo(projectedCenter + normal, projectedCenter, SK_ScalarRoot2Over2);
31    path->conicTo(projectedCenter - normal, stop, SK_ScalarRoot2Over2);
32}
33
34static void SquareCapper(SkPath* path, const SkPoint& pivot,
35                         const SkVector& normal, const SkPoint& stop,
36                         SkPath* otherPath)
37{
38    SkVector parallel;
39    normal.rotateCW(&parallel);
40
41    if (otherPath)
42    {
43        path->setLastPt(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY);
44        path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY);
45    }
46    else
47    {
48        path->lineTo(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY);
49        path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY);
50        path->lineTo(stop.fX, stop.fY);
51    }
52}
53
54/////////////////////////////////////////////////////////////////////////////
55
56static bool is_clockwise(const SkVector& before, const SkVector& after)
57{
58    return SkScalarMul(before.fX, after.fY) - SkScalarMul(before.fY, after.fX) > 0;
59}
60
61enum AngleType {
62    kNearly180_AngleType,
63    kSharp_AngleType,
64    kShallow_AngleType,
65    kNearlyLine_AngleType
66};
67
68static AngleType Dot2AngleType(SkScalar dot)
69{
70// need more precise fixed normalization
71//  SkASSERT(SkScalarAbs(dot) <= SK_Scalar1 + SK_ScalarNearlyZero);
72
73    if (dot >= 0)   // shallow or line
74        return SkScalarNearlyZero(SK_Scalar1 - dot) ? kNearlyLine_AngleType : kShallow_AngleType;
75    else            // sharp or 180
76        return SkScalarNearlyZero(SK_Scalar1 + dot) ? kNearly180_AngleType : kSharp_AngleType;
77}
78
79static void HandleInnerJoin(SkPath* inner, const SkPoint& pivot, const SkVector& after)
80{
81#if 1
82    /*  In the degenerate case that the stroke radius is larger than our segments
83        just connecting the two inner segments may "show through" as a funny
84        diagonal. To pseudo-fix this, we go through the pivot point. This adds
85        an extra point/edge, but I can't see a cheap way to know when this is
86        not needed :(
87    */
88    inner->lineTo(pivot.fX, pivot.fY);
89#endif
90
91    inner->lineTo(pivot.fX - after.fX, pivot.fY - after.fY);
92}
93
94static void BluntJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
95                        const SkPoint& pivot, const SkVector& afterUnitNormal,
96                        SkScalar radius, SkScalar invMiterLimit, bool, bool)
97{
98    SkVector    after;
99    afterUnitNormal.scale(radius, &after);
100
101    if (!is_clockwise(beforeUnitNormal, afterUnitNormal))
102    {
103        SkTSwap<SkPath*>(outer, inner);
104        after.negate();
105    }
106
107    outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
108    HandleInnerJoin(inner, pivot, after);
109}
110
111static void RoundJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
112                        const SkPoint& pivot, const SkVector& afterUnitNormal,
113                        SkScalar radius, SkScalar invMiterLimit, bool, bool)
114{
115    SkScalar    dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal);
116    AngleType   angleType = Dot2AngleType(dotProd);
117
118    if (angleType == kNearlyLine_AngleType)
119        return;
120
121    SkVector            before = beforeUnitNormal;
122    SkVector            after = afterUnitNormal;
123    SkRotationDirection dir = kCW_SkRotationDirection;
124
125    if (!is_clockwise(before, after))
126    {
127        SkTSwap<SkPath*>(outer, inner);
128        before.negate();
129        after.negate();
130        dir = kCCW_SkRotationDirection;
131    }
132
133    SkMatrix    matrix;
134    matrix.setScale(radius, radius);
135    matrix.postTranslate(pivot.fX, pivot.fY);
136    SkConic conics[SkConic::kMaxConicsForArc];
137    int count = SkConic::BuildUnitArc(before, after, dir, &matrix, conics);
138    if (count > 0) {
139        for (int i = 0; i < count; ++i) {
140            outer->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
141        }
142        after.scale(radius);
143        HandleInnerJoin(inner, pivot, after);
144    }
145}
146
147#define kOneOverSqrt2   (0.707106781f)
148
149static void MiterJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
150                        const SkPoint& pivot, const SkVector& afterUnitNormal,
151                        SkScalar radius, SkScalar invMiterLimit,
152                        bool prevIsLine, bool currIsLine)
153{
154    // negate the dot since we're using normals instead of tangents
155    SkScalar    dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal);
156    AngleType   angleType = Dot2AngleType(dotProd);
157    SkVector    before = beforeUnitNormal;
158    SkVector    after = afterUnitNormal;
159    SkVector    mid;
160    SkScalar    sinHalfAngle;
161    bool        ccw;
162
163    if (angleType == kNearlyLine_AngleType)
164        return;
165    if (angleType == kNearly180_AngleType)
166    {
167        currIsLine = false;
168        goto DO_BLUNT;
169    }
170
171    ccw = !is_clockwise(before, after);
172    if (ccw)
173    {
174        SkTSwap<SkPath*>(outer, inner);
175        before.negate();
176        after.negate();
177    }
178
179    /*  Before we enter the world of square-roots and divides,
180        check if we're trying to join an upright right angle
181        (common case for stroking rectangles). If so, special case
182        that (for speed an accuracy).
183        Note: we only need to check one normal if dot==0
184    */
185    if (0 == dotProd && invMiterLimit <= kOneOverSqrt2)
186    {
187        mid.set(SkScalarMul(before.fX + after.fX, radius),
188                SkScalarMul(before.fY + after.fY, radius));
189        goto DO_MITER;
190    }
191
192    /*  midLength = radius / sinHalfAngle
193        if (midLength > miterLimit * radius) abort
194        if (radius / sinHalf > miterLimit * radius) abort
195        if (1 / sinHalf > miterLimit) abort
196        if (1 / miterLimit > sinHalf) abort
197        My dotProd is opposite sign, since it is built from normals and not tangents
198        hence 1 + dot instead of 1 - dot in the formula
199    */
200    sinHalfAngle = SkScalarSqrt(SkScalarHalf(SK_Scalar1 + dotProd));
201    if (sinHalfAngle < invMiterLimit)
202    {
203        currIsLine = false;
204        goto DO_BLUNT;
205    }
206
207    // choose the most accurate way to form the initial mid-vector
208    if (angleType == kSharp_AngleType)
209    {
210        mid.set(after.fY - before.fY, before.fX - after.fX);
211        if (ccw)
212            mid.negate();
213    }
214    else
215        mid.set(before.fX + after.fX, before.fY + after.fY);
216
217    mid.setLength(radius / sinHalfAngle);
218DO_MITER:
219    if (prevIsLine)
220        outer->setLastPt(pivot.fX + mid.fX, pivot.fY + mid.fY);
221    else
222        outer->lineTo(pivot.fX + mid.fX, pivot.fY + mid.fY);
223
224DO_BLUNT:
225    after.scale(radius);
226    if (!currIsLine)
227        outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
228    HandleInnerJoin(inner, pivot, after);
229}
230
231/////////////////////////////////////////////////////////////////////////////
232
233SkStrokerPriv::CapProc SkStrokerPriv::CapFactory(SkPaint::Cap cap)
234{
235    static const SkStrokerPriv::CapProc gCappers[] = {
236        ButtCapper, RoundCapper, SquareCapper
237    };
238
239    SkASSERT((unsigned)cap < SkPaint::kCapCount);
240    return gCappers[cap];
241}
242
243SkStrokerPriv::JoinProc SkStrokerPriv::JoinFactory(SkPaint::Join join)
244{
245    static const SkStrokerPriv::JoinProc gJoiners[] = {
246        MiterJoiner, RoundJoiner, BluntJoiner
247    };
248
249    SkASSERT((unsigned)join < SkPaint::kJoinCount);
250    return gJoiners[join];
251}
252