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 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 18 static void RoundCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal, 19 const SkPoint& stop, SkPath*) { 20 SkVector parallel; 21 SkPointPriv::RotateCW(normal, ¶llel); 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 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, ¶llel); 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 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 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 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 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 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 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 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 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