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