blob: 87bc1729267a3470ecb4aa04e18c90e67dc1a1da [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
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
reed@android.com8a1c16f2008-12-17 15:59:43 +00008#include "SkStrokerPriv.h"
9#include "SkGeometry.h"
10#include "SkPath.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050011#include "SkPointPriv.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012
Ben Wagnerf08d1d02018-06-18 15:11:00 -040013#include <utility>
14
Mike Reeda99b6ce2017-02-04 11:04:26 -050015static void ButtCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
16 const SkPoint& stop, SkPath*) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000017 path->lineTo(stop.fX, stop.fY);
18}
19
Mike Reeda99b6ce2017-02-04 11:04:26 -050020static void RoundCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
21 const SkPoint& stop, SkPath*) {
reed88f0a992015-02-10 08:45:06 -080022 SkVector parallel;
Cary Clarkdf429f32017-11-08 11:44:31 -050023 SkPointPriv::RotateCW(normal, &parallel);
reed88f0a992015-02-10 08:45:06 -080024
25 SkPoint projectedCenter = pivot + parallel;
26
27 path->conicTo(projectedCenter + normal, projectedCenter, SK_ScalarRoot2Over2);
28 path->conicTo(projectedCenter - normal, stop, SK_ScalarRoot2Over2);
reed@android.com8a1c16f2008-12-17 15:59:43 +000029}
30
Mike Reeda99b6ce2017-02-04 11:04:26 -050031static void SquareCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
32 const SkPoint& stop, SkPath* otherPath) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000033 SkVector parallel;
Cary Clarkdf429f32017-11-08 11:44:31 -050034 SkPointPriv::RotateCW(normal, &parallel);
reed@android.com8a1c16f2008-12-17 15:59:43 +000035
Mike Reeda99b6ce2017-02-04 11:04:26 -050036 if (otherPath) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000037 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);
Mike Reeda99b6ce2017-02-04 11:04:26 -050039 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +000040 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
Mike Reeda99b6ce2017-02-04 11:04:26 -050048static bool is_clockwise(const SkVector& before, const SkVector& after) {
49 return before.fX * after.fY > before.fY * after.fX;
reed@android.com8a1c16f2008-12-17 15:59:43 +000050}
51
52enum AngleType {
53 kNearly180_AngleType,
54 kSharp_AngleType,
55 kShallow_AngleType,
56 kNearlyLine_AngleType
57};
58
Mike Reeda99b6ce2017-02-04 11:04:26 -050059static AngleType Dot2AngleType(SkScalar dot) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000060// need more precise fixed normalization
61// SkASSERT(SkScalarAbs(dot) <= SK_Scalar1 + SK_ScalarNearlyZero);
62
Mike Reeda99b6ce2017-02-04 11:04:26 -050063 if (dot >= 0) { // shallow or line
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 return SkScalarNearlyZero(SK_Scalar1 - dot) ? kNearlyLine_AngleType : kShallow_AngleType;
Mike Reeda99b6ce2017-02-04 11:04:26 -050065 } else { // sharp or 180
reed@android.com8a1c16f2008-12-17 15:59:43 +000066 return SkScalarNearlyZero(SK_Scalar1 + dot) ? kNearly180_AngleType : kSharp_AngleType;
Mike Reeda99b6ce2017-02-04 11:04:26 -050067 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000068}
69
Mike Reeda99b6ce2017-02-04 11:04:26 -050070static void HandleInnerJoin(SkPath* inner, const SkPoint& pivot, const SkVector& after) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000071#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,
Mike Reeda99b6ce2017-02-04 11:04:26 -050086 SkScalar radius, SkScalar invMiterLimit, bool, bool) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000087 SkVector after;
88 afterUnitNormal.scale(radius, &after);
89
Mike Reeda99b6ce2017-02-04 11:04:26 -050090 if (!is_clockwise(beforeUnitNormal, afterUnitNormal)) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -040091 using std::swap;
92 swap(outer, inner);
reed@android.com8a1c16f2008-12-17 15:59:43 +000093 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,
Mike Reeda99b6ce2017-02-04 11:04:26 -0500102 SkScalar radius, SkScalar invMiterLimit, bool, bool) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103 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
Mike Reeda99b6ce2017-02-04 11:04:26 -0500113 if (!is_clockwise(before, after)) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400114 using std::swap;
115 swap(outer, inner);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116 before.negate();
117 after.negate();
118 dir = kCCW_SkRotationDirection;
119 }
120
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 SkMatrix matrix;
122 matrix.setScale(radius, radius);
123 matrix.postTranslate(pivot.fX, pivot.fY);
reed88f0a992015-02-10 08:45:06 -0800124 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 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133}
134
reed@google.com8f4d2302013-12-17 16:44:46 +0000135#define kOneOverSqrt2 (0.707106781f)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000136
137static void MiterJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
138 const SkPoint& pivot, const SkVector& afterUnitNormal,
139 SkScalar radius, SkScalar invMiterLimit,
Mike Reeda99b6ce2017-02-04 11:04:26 -0500140 bool prevIsLine, bool currIsLine) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000141 // 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
Mike Reeda99b6ce2017-02-04 11:04:26 -0500150 if (angleType == kNearlyLine_AngleType) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000151 return;
Mike Reeda99b6ce2017-02-04 11:04:26 -0500152 }
153 if (angleType == kNearly180_AngleType) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154 currIsLine = false;
155 goto DO_BLUNT;
156 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000157
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158 ccw = !is_clockwise(before, after);
Mike Reeda99b6ce2017-02-04 11:04:26 -0500159 if (ccw) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400160 using std::swap;
161 swap(outer, inner);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 before.negate();
163 after.negate();
164 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000165
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166 /* 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 */
Mike Reeda99b6ce2017-02-04 11:04:26 -0500172 if (0 == dotProd && invMiterLimit <= kOneOverSqrt2) {
173 mid = (before + after) * radius;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 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));
Mike Reeda99b6ce2017-02-04 11:04:26 -0500186 if (sinHalfAngle < invMiterLimit) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000187 currIsLine = false;
188 goto DO_BLUNT;
189 }
190
191 // choose the most accurate way to form the initial mid-vector
Mike Reeda99b6ce2017-02-04 11:04:26 -0500192 if (angleType == kSharp_AngleType) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 mid.set(after.fY - before.fY, before.fX - after.fX);
Mike Reeda99b6ce2017-02-04 11:04:26 -0500194 if (ccw) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195 mid.negate();
Mike Reeda99b6ce2017-02-04 11:04:26 -0500196 }
197 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000198 mid.set(before.fX + after.fX, before.fY + after.fY);
Mike Reeda99b6ce2017-02-04 11:04:26 -0500199 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200
reed80ea19c2015-05-12 10:37:34 -0700201 mid.setLength(radius / sinHalfAngle);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202DO_MITER:
Mike Reeda99b6ce2017-02-04 11:04:26 -0500203 if (prevIsLine) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204 outer->setLastPt(pivot.fX + mid.fX, pivot.fY + mid.fY);
Mike Reeda99b6ce2017-02-04 11:04:26 -0500205 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000206 outer->lineTo(pivot.fX + mid.fX, pivot.fY + mid.fY);
Mike Reeda99b6ce2017-02-04 11:04:26 -0500207 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208
209DO_BLUNT:
210 after.scale(radius);
Mike Reeda99b6ce2017-02-04 11:04:26 -0500211 if (!currIsLine) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
Mike Reeda99b6ce2017-02-04 11:04:26 -0500213 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214 HandleInnerJoin(inner, pivot, after);
215}
216
217/////////////////////////////////////////////////////////////////////////////
218
Mike Reeda99b6ce2017-02-04 11:04:26 -0500219SkStrokerPriv::CapProc SkStrokerPriv::CapFactory(SkPaint::Cap cap) {
220 const SkStrokerPriv::CapProc gCappers[] = {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000221 ButtCapper, RoundCapper, SquareCapper
222 };
223
224 SkASSERT((unsigned)cap < SkPaint::kCapCount);
225 return gCappers[cap];
226}
227
Mike Reeda99b6ce2017-02-04 11:04:26 -0500228SkStrokerPriv::JoinProc SkStrokerPriv::JoinFactory(SkPaint::Join join) {
229 const SkStrokerPriv::JoinProc gJoiners[] = {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000230 MiterJoiner, RoundJoiner, BluntJoiner
231 };
232
233 SkASSERT((unsigned)join < SkPaint::kJoinCount);
234 return gJoiners[join];
235}