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, &parallel);
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, &parallel);
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