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 
13 #include <utility>
14 
ButtCapper(SkPath * path,const SkPoint & pivot,const SkVector & normal,const SkPoint & stop,SkPath *)15 static void ButtCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
16                        const SkPoint& stop, SkPath*) {
17     path->lineTo(stop.fX, stop.fY);
18 }
19 
RoundCapper(SkPath * path,const SkPoint & pivot,const SkVector & normal,const SkPoint & stop,SkPath *)20 static 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 
SquareCapper(SkPath * path,const SkPoint & pivot,const SkVector & normal,const SkPoint & stop,SkPath * otherPath)31 static 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 
is_clockwise(const SkVector & before,const SkVector & after)48 static bool is_clockwise(const SkVector& before, const SkVector& after) {
49     return before.fX * after.fY > before.fY * after.fX;
50 }
51 
52 enum AngleType {
53     kNearly180_AngleType,
54     kSharp_AngleType,
55     kShallow_AngleType,
56     kNearlyLine_AngleType
57 };
58 
Dot2AngleType(SkScalar dot)59 static 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 
HandleInnerJoin(SkPath * inner,const SkPoint & pivot,const SkVector & after)70 static 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 
BluntJoiner(SkPath * outer,SkPath * inner,const SkVector & beforeUnitNormal,const SkPoint & pivot,const SkVector & afterUnitNormal,SkScalar radius,SkScalar invMiterLimit,bool,bool)84 static 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 
RoundJoiner(SkPath * outer,SkPath * inner,const SkVector & beforeUnitNormal,const SkPoint & pivot,const SkVector & afterUnitNormal,SkScalar radius,SkScalar invMiterLimit,bool,bool)100 static 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 
MiterJoiner(SkPath * outer,SkPath * inner,const SkVector & beforeUnitNormal,const SkPoint & pivot,const SkVector & afterUnitNormal,SkScalar radius,SkScalar invMiterLimit,bool prevIsLine,bool currIsLine)137 static 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);
202 DO_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 
209 DO_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 
CapFactory(SkPaint::Cap cap)219 SkStrokerPriv::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 
JoinFactory(SkPaint::Join join)228 SkStrokerPriv::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