/* * 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); } } #ifdef SK_SCALAR_IS_FLOAT #define kOneOverSqrt2 (0.707106781f) #else #define kOneOverSqrt2 (46341) #endif 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]; }