|  |  | 
|  | /* | 
|  | * Copyright 2006 The Android Open Source Project | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  |  | 
|  | #include "SkStrokerPriv.h" | 
|  | #include "SkGeometry.h" | 
|  | #include "SkPath.h" | 
|  |  | 
|  | static void ButtCapper(SkPath* path, const SkPoint& pivot, | 
|  | const SkVector& normal, const SkPoint& stop, | 
|  | SkPath*) | 
|  | { | 
|  | path->lineTo(stop.fX, stop.fY); | 
|  | } | 
|  |  | 
|  | static void RoundCapper(SkPath* path, const SkPoint& pivot, | 
|  | const SkVector& normal, const SkPoint& stop, | 
|  | SkPath*) | 
|  | { | 
|  | SkScalar    px = pivot.fX; | 
|  | SkScalar    py = pivot.fY; | 
|  | SkScalar    nx = normal.fX; | 
|  | SkScalar    ny = normal.fY; | 
|  | SkScalar    sx = SkScalarMul(nx, CUBIC_ARC_FACTOR); | 
|  | SkScalar    sy = SkScalarMul(ny, CUBIC_ARC_FACTOR); | 
|  |  | 
|  | path->cubicTo(px + nx + CWX(sx, sy), py + ny + CWY(sx, sy), | 
|  | px + CWX(nx, ny) + sx, py + CWY(nx, ny) + sy, | 
|  | px + CWX(nx, ny), py + CWY(nx, ny)); | 
|  | path->cubicTo(px + CWX(nx, ny) - sx, py + CWY(nx, ny) - sy, | 
|  | px - nx + CWX(sx, sy), py - ny + CWY(sx, sy), | 
|  | stop.fX, stop.fY); | 
|  | } | 
|  |  | 
|  | static void SquareCapper(SkPath* path, const SkPoint& pivot, | 
|  | const SkVector& normal, const SkPoint& stop, | 
|  | SkPath* otherPath) | 
|  | { | 
|  | SkVector parallel; | 
|  | normal.rotateCW(¶llel); | 
|  |  | 
|  | if (otherPath) | 
|  | { | 
|  | path->setLastPt(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY); | 
|  | path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY); | 
|  | } | 
|  | else | 
|  | { | 
|  | path->lineTo(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY); | 
|  | path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY); | 
|  | path->lineTo(stop.fX, stop.fY); | 
|  | } | 
|  | } | 
|  |  | 
|  | ///////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | static bool is_clockwise(const SkVector& before, const SkVector& after) | 
|  | { | 
|  | return SkScalarMul(before.fX, after.fY) - SkScalarMul(before.fY, after.fX) > 0; | 
|  | } | 
|  |  | 
|  | enum AngleType { | 
|  | kNearly180_AngleType, | 
|  | kSharp_AngleType, | 
|  | kShallow_AngleType, | 
|  | kNearlyLine_AngleType | 
|  | }; | 
|  |  | 
|  | static AngleType Dot2AngleType(SkScalar dot) | 
|  | { | 
|  | // need more precise fixed normalization | 
|  | //  SkASSERT(SkScalarAbs(dot) <= SK_Scalar1 + SK_ScalarNearlyZero); | 
|  |  | 
|  | if (dot >= 0)   // shallow or line | 
|  | return SkScalarNearlyZero(SK_Scalar1 - dot) ? kNearlyLine_AngleType : kShallow_AngleType; | 
|  | else            // sharp or 180 | 
|  | return SkScalarNearlyZero(SK_Scalar1 + dot) ? kNearly180_AngleType : kSharp_AngleType; | 
|  | } | 
|  |  | 
|  | static void HandleInnerJoin(SkPath* inner, const SkPoint& pivot, const SkVector& after) | 
|  | { | 
|  | #if 1 | 
|  | /*  In the degenerate case that the stroke radius is larger than our segments | 
|  | just connecting the two inner segments may "show through" as a funny | 
|  | diagonal. To pseudo-fix this, we go through the pivot point. This adds | 
|  | an extra point/edge, but I can't see a cheap way to know when this is | 
|  | not needed :( | 
|  | */ | 
|  | inner->lineTo(pivot.fX, pivot.fY); | 
|  | #endif | 
|  |  | 
|  | inner->lineTo(pivot.fX - after.fX, pivot.fY - after.fY); | 
|  | } | 
|  |  | 
|  | static void BluntJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal, | 
|  | const SkPoint& pivot, const SkVector& afterUnitNormal, | 
|  | SkScalar radius, SkScalar invMiterLimit, bool, bool) | 
|  | { | 
|  | SkVector    after; | 
|  | afterUnitNormal.scale(radius, &after); | 
|  |  | 
|  | if (!is_clockwise(beforeUnitNormal, afterUnitNormal)) | 
|  | { | 
|  | SkTSwap<SkPath*>(outer, inner); | 
|  | after.negate(); | 
|  | } | 
|  |  | 
|  | outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY); | 
|  | HandleInnerJoin(inner, pivot, after); | 
|  | } | 
|  |  | 
|  | static void RoundJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal, | 
|  | const SkPoint& pivot, const SkVector& afterUnitNormal, | 
|  | SkScalar radius, SkScalar invMiterLimit, bool, bool) | 
|  | { | 
|  | SkScalar    dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal); | 
|  | AngleType   angleType = Dot2AngleType(dotProd); | 
|  |  | 
|  | if (angleType == kNearlyLine_AngleType) | 
|  | return; | 
|  |  | 
|  | SkVector            before = beforeUnitNormal; | 
|  | SkVector            after = afterUnitNormal; | 
|  | SkRotationDirection dir = kCW_SkRotationDirection; | 
|  |  | 
|  | if (!is_clockwise(before, after)) | 
|  | { | 
|  | SkTSwap<SkPath*>(outer, inner); | 
|  | before.negate(); | 
|  | after.negate(); | 
|  | dir = kCCW_SkRotationDirection; | 
|  | } | 
|  |  | 
|  | SkPoint     pts[kSkBuildQuadArcStorage]; | 
|  | SkMatrix    matrix; | 
|  | matrix.setScale(radius, radius); | 
|  | matrix.postTranslate(pivot.fX, pivot.fY); | 
|  | int count = SkBuildQuadArc(before, after, dir, &matrix, pts); | 
|  | SkASSERT((count & 1) == 1); | 
|  |  | 
|  | if (count > 1) | 
|  | { | 
|  | for (int i = 1; i < count; i += 2) | 
|  | outer->quadTo(pts[i].fX, pts[i].fY, pts[i+1].fX, pts[i+1].fY); | 
|  |  | 
|  | after.scale(radius); | 
|  | HandleInnerJoin(inner, pivot, after); | 
|  | } | 
|  | } | 
|  |  | 
|  | #define kOneOverSqrt2   (0.707106781f) | 
|  |  | 
|  | static void MiterJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal, | 
|  | const SkPoint& pivot, const SkVector& afterUnitNormal, | 
|  | SkScalar radius, SkScalar invMiterLimit, | 
|  | bool prevIsLine, bool currIsLine) | 
|  | { | 
|  | // negate the dot since we're using normals instead of tangents | 
|  | SkScalar    dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal); | 
|  | AngleType   angleType = Dot2AngleType(dotProd); | 
|  | SkVector    before = beforeUnitNormal; | 
|  | SkVector    after = afterUnitNormal; | 
|  | SkVector    mid; | 
|  | SkScalar    sinHalfAngle; | 
|  | bool        ccw; | 
|  |  | 
|  | if (angleType == kNearlyLine_AngleType) | 
|  | return; | 
|  | if (angleType == kNearly180_AngleType) | 
|  | { | 
|  | currIsLine = false; | 
|  | goto DO_BLUNT; | 
|  | } | 
|  |  | 
|  | ccw = !is_clockwise(before, after); | 
|  | if (ccw) | 
|  | { | 
|  | SkTSwap<SkPath*>(outer, inner); | 
|  | before.negate(); | 
|  | after.negate(); | 
|  | } | 
|  |  | 
|  | /*  Before we enter the world of square-roots and divides, | 
|  | check if we're trying to join an upright right angle | 
|  | (common case for stroking rectangles). If so, special case | 
|  | that (for speed an accuracy). | 
|  | Note: we only need to check one normal if dot==0 | 
|  | */ | 
|  | if (0 == dotProd && invMiterLimit <= kOneOverSqrt2) | 
|  | { | 
|  | mid.set(SkScalarMul(before.fX + after.fX, radius), | 
|  | SkScalarMul(before.fY + after.fY, radius)); | 
|  | goto DO_MITER; | 
|  | } | 
|  |  | 
|  | /*  midLength = radius / sinHalfAngle | 
|  | if (midLength > miterLimit * radius) abort | 
|  | if (radius / sinHalf > miterLimit * radius) abort | 
|  | if (1 / sinHalf > miterLimit) abort | 
|  | if (1 / miterLimit > sinHalf) abort | 
|  | My dotProd is opposite sign, since it is built from normals and not tangents | 
|  | hence 1 + dot instead of 1 - dot in the formula | 
|  | */ | 
|  | sinHalfAngle = SkScalarSqrt(SkScalarHalf(SK_Scalar1 + dotProd)); | 
|  | if (sinHalfAngle < invMiterLimit) | 
|  | { | 
|  | currIsLine = false; | 
|  | goto DO_BLUNT; | 
|  | } | 
|  |  | 
|  | // choose the most accurate way to form the initial mid-vector | 
|  | if (angleType == kSharp_AngleType) | 
|  | { | 
|  | mid.set(after.fY - before.fY, before.fX - after.fX); | 
|  | if (ccw) | 
|  | mid.negate(); | 
|  | } | 
|  | else | 
|  | mid.set(before.fX + after.fX, before.fY + after.fY); | 
|  |  | 
|  | mid.setLength(SkScalarDiv(radius, sinHalfAngle)); | 
|  | DO_MITER: | 
|  | if (prevIsLine) | 
|  | outer->setLastPt(pivot.fX + mid.fX, pivot.fY + mid.fY); | 
|  | else | 
|  | outer->lineTo(pivot.fX + mid.fX, pivot.fY + mid.fY); | 
|  |  | 
|  | DO_BLUNT: | 
|  | after.scale(radius); | 
|  | if (!currIsLine) | 
|  | outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY); | 
|  | HandleInnerJoin(inner, pivot, after); | 
|  | } | 
|  |  | 
|  | ///////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | SkStrokerPriv::CapProc SkStrokerPriv::CapFactory(SkPaint::Cap cap) | 
|  | { | 
|  | static const SkStrokerPriv::CapProc gCappers[] = { | 
|  | ButtCapper, RoundCapper, SquareCapper | 
|  | }; | 
|  |  | 
|  | SkASSERT((unsigned)cap < SkPaint::kCapCount); | 
|  | return gCappers[cap]; | 
|  | } | 
|  |  | 
|  | SkStrokerPriv::JoinProc SkStrokerPriv::JoinFactory(SkPaint::Join join) | 
|  | { | 
|  | static const SkStrokerPriv::JoinProc gJoiners[] = { | 
|  | MiterJoiner, RoundJoiner, BluntJoiner | 
|  | }; | 
|  |  | 
|  | SkASSERT((unsigned)join < SkPaint::kJoinCount); | 
|  | return gJoiners[join]; | 
|  | } |