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(¶llel);
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(¶llel);
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