/* * 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" #include "SkPointPriv.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*) { SkVector parallel; SkPointPriv::RotateCW(normal, ¶llel); SkPoint projectedCenter = pivot + parallel; path->conicTo(projectedCenter + normal, projectedCenter, SK_ScalarRoot2Over2); path->conicTo(projectedCenter - normal, stop, SK_ScalarRoot2Over2); } static void SquareCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal, const SkPoint& stop, SkPath* otherPath) { SkVector parallel; SkPointPriv::RotateCW(normal, ¶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 before.fX * after.fY > before.fY * after.fX; } 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; } SkMatrix matrix; matrix.setScale(radius, radius); matrix.postTranslate(pivot.fX, pivot.fY); SkConic conics[SkConic::kMaxConicsForArc]; int count = SkConic::BuildUnitArc(before, after, dir, &matrix, conics); if (count > 0) { for (int i = 0; i < count; ++i) { outer->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW); } 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 = (before + after) * 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(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) { const SkStrokerPriv::CapProc gCappers[] = { ButtCapper, RoundCapper, SquareCapper }; SkASSERT((unsigned)cap < SkPaint::kCapCount); return gCappers[cap]; } SkStrokerPriv::JoinProc SkStrokerPriv::JoinFactory(SkPaint::Join join) { const SkStrokerPriv::JoinProc gJoiners[] = { MiterJoiner, RoundJoiner, BluntJoiner }; SkASSERT((unsigned)join < SkPaint::kJoinCount); return gJoiners[join]; }