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 
ButtCapper(SkPath * path,const SkPoint & pivot,const SkVector & normal,const SkPoint & stop,SkPath *)14 static 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 
RoundCapper(SkPath * path,const SkPoint & pivot,const SkVector & normal,const SkPoint & stop,SkPath *)21 static 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 
SquareCapper(SkPath * path,const SkPoint & pivot,const SkVector & normal,const SkPoint & stop,SkPath * otherPath)34 static 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 
is_clockwise(const SkVector & before,const SkVector & after)56 static 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 
61 enum AngleType {
62     kNearly180_AngleType,
63     kSharp_AngleType,
64     kShallow_AngleType,
65     kNearlyLine_AngleType
66 };
67 
Dot2AngleType(SkScalar dot)68 static 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 
HandleInnerJoin(SkPath * inner,const SkPoint & pivot,const SkVector & after)79 static 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 
BluntJoiner(SkPath * outer,SkPath * inner,const SkVector & beforeUnitNormal,const SkPoint & pivot,const SkVector & afterUnitNormal,SkScalar radius,SkScalar invMiterLimit,bool,bool)94 static 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 
RoundJoiner(SkPath * outer,SkPath * inner,const SkVector & beforeUnitNormal,const SkPoint & pivot,const SkVector & afterUnitNormal,SkScalar radius,SkScalar invMiterLimit,bool,bool)111 static 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 
MiterJoiner(SkPath * outer,SkPath * inner,const SkVector & beforeUnitNormal,const SkPoint & pivot,const SkVector & afterUnitNormal,SkScalar radius,SkScalar invMiterLimit,bool prevIsLine,bool currIsLine)149 static 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);
218 DO_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 
224 DO_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 
CapFactory(SkPaint::Cap cap)233 SkStrokerPriv::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 
JoinFactory(SkPaint::Join join)243 SkStrokerPriv::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