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