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