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