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