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