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