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