/*
* 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];
}