Add cusp detection to GrPathUtils::findCubicConvex180Chops
The stroker will need to know the cusps in order to draw circles around them. Bug: skia:10419 Change-Id: I05b7e9f4a5ed06bd36450e73edfaf36c4b3f5a6c Reviewed-on: https://skia-review.googlesource.com/c/skia/+/337945 Commit-Queue: Chris Dalton <csmartdalton@google.com> Reviewed-by: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
parent
1cf4bc796c
commit
98c3aea806
@ -585,7 +585,10 @@ void GrPathUtils::convertCubicToQuadsConstrainToTangents(const SkPoint p[4],
|
||||
}
|
||||
}
|
||||
|
||||
int GrPathUtils::findCubicConvex180Chops(const SkPoint pts[], float T[2]) {
|
||||
int GrPathUtils::findCubicConvex180Chops(const SkPoint pts[], float T[2], bool* areCusps) {
|
||||
// It's slow for us to write out a value to "areCaps", especially when 99% of the time we aren't
|
||||
// drawing cusps. The caller must initialize this value to false.
|
||||
SkASSERT(!areCusps || *areCusps == false);
|
||||
using grvx::float2;
|
||||
|
||||
// If a chop falls within a distance of "kEpsilon" from 0 or 1, throw it out. Tangents become
|
||||
@ -593,9 +596,9 @@ int GrPathUtils::findCubicConvex180Chops(const SkPoint pts[], float T[2]) {
|
||||
// shaders don't allow more than 2^10 parametric segments, and they snap the beginning and
|
||||
// ending edges at 0 and 1. So if we overstep an inflection or point of 180-degree rotation by a
|
||||
// fraction of a tessellation segment, it just gets snapped.
|
||||
constexpr static float kEpsilon = 1.f / (1 << 12);
|
||||
constexpr static float kEpsilon = 1.f / (1 << 11);
|
||||
// Floating-point representation of "1 - 2*kEpsilon".
|
||||
constexpr static uint32_t kIEEE_one_minus_2_epsilon = (127 << 23) - 2*(1 << 12);
|
||||
constexpr static uint32_t kIEEE_one_minus_2_epsilon = (127 << 23) - 2 * (1 << (24 - 11));
|
||||
// Unfortunately we don't have a way to static_assert this, but we can runtime assert that the
|
||||
// kIEEE_one_minus_2_epsilon bits are correct.
|
||||
SkASSERT(sk_bit_cast<float>(kIEEE_one_minus_2_epsilon) == 1 - 2*kEpsilon);
|
||||
@ -633,23 +636,43 @@ int GrPathUtils::findCubicConvex180Chops(const SkPoint pts[], float T[2]) {
|
||||
float b_over_minus_2 = -.5f * b;
|
||||
float discr_over_4 = b_over_minus_2*b_over_minus_2 - a*c;
|
||||
|
||||
if (discr_over_4 <= 0) {
|
||||
// If -cuspThreshold <= discr_over_4 <= cuspThreshold, it means the two roots are within
|
||||
// kEpsilon of one another (in parametric space). This is close enough for our purposes to
|
||||
// consider them a single cusp.
|
||||
float cuspThreshold = a * (kEpsilon/2);
|
||||
cuspThreshold *= cuspThreshold;
|
||||
if (discr_over_4 < -cuspThreshold) {
|
||||
// The curve does not inflect or cusp. This means it might rotate more than 180 degrees
|
||||
// instead. Chop were rotation == 180 deg. (This is the 2nd root where the tangent is
|
||||
// parallel to tan0.)
|
||||
//
|
||||
// Tangent_Direction(T) x tan0 == 0
|
||||
// (AT^2 x tan0) + (2BT x tan0) + (C x tan0) == 0
|
||||
// (A x C)T^2 + (2B x C)T + (C x C) == 0 [[because tan0 == P1 - P0 == C]]
|
||||
// bT^2 + 2c + 0 == 0 [[because A x C == b, B x C == c]]
|
||||
// T = [0, -2c/b]
|
||||
//
|
||||
// NOTE: if C == 0, then C != tan0. But this is fine because the curve is definitely
|
||||
// convex-180 if any points are colocated, and T[0] will equal NaN which returns 0 chops.
|
||||
float root = sk_ieee_float_divide(c, b_over_minus_2);
|
||||
// Is "root" inside the range [kEpsilon, 1 - kEpsilon)?
|
||||
if (sk_bit_cast<uint32_t>(root - kEpsilon) < kIEEE_one_minus_2_epsilon) {
|
||||
T[0] = root;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (discr_over_4 <= cuspThreshold) {
|
||||
// The two roots are close enough that we can consider them a single cusp.
|
||||
if (areCusps) {
|
||||
*areCusps = true;
|
||||
}
|
||||
|
||||
if (a != 0 || b_over_minus_2 != 0 || c != 0) {
|
||||
// The curve does not inflect or cusp. This means it might rotate more than 180 degrees
|
||||
// instead. Chop were rotation == 180 deg. (This is the 2nd root where the tangent is
|
||||
// parallel to tan0.)
|
||||
//
|
||||
// Tangent_Direction(T) x tan0 == 0
|
||||
// (AT^2 x tan0) + (2BT x tan0) + (C x tan0) == 0
|
||||
// (A x C)T^2 + (2B x C)T + (C x C) == 0 [[because tan0 == P1 - P0 == C]]
|
||||
// bT^2 + 2c + 0 == 0 [[because A x C == b, B x C == c]]
|
||||
// T = [0, -2c/b]
|
||||
//
|
||||
// NOTE: if C == 0, then C != tan0. But this is fine because the curve is definitely
|
||||
// convex-180 if any points are colocated, and T[0] will equal NaN which returns 0
|
||||
// chops.
|
||||
float root = sk_ieee_float_divide(c, b_over_minus_2);
|
||||
// Is "root" inside the range [epsilon, 1 - epsilon)?
|
||||
// Pick the average of both roots.
|
||||
float root = sk_ieee_float_divide(b_over_minus_2, a);
|
||||
// Is "root" inside the range [kEpsilon, 1 - kEpsilon)?
|
||||
if (sk_bit_cast<uint32_t>(root - kEpsilon) < kIEEE_one_minus_2_epsilon) {
|
||||
T[0] = root;
|
||||
return 1;
|
||||
|
@ -160,7 +160,7 @@ inline void convertQuadToCubic(const SkPoint p[3], SkPoint out[4]) {
|
||||
//
|
||||
// - Otherwise the T value is the point at which rotation reaches 180 degrees, iff in [0 < T < 1].
|
||||
//
|
||||
int findCubicConvex180Chops(const SkPoint[], float T[2]);
|
||||
int findCubicConvex180Chops(const SkPoint[], float T[2], bool* areCusps = nullptr);
|
||||
|
||||
} // namespace GrPathUtils
|
||||
|
||||
|
@ -19,18 +19,23 @@ static bool is_linear(const SkPoint p[4]) {
|
||||
}
|
||||
|
||||
static void check_cubic_convex_180(skiatest::Reporter* r, const SkPoint p[4]) {
|
||||
bool areCusps = false;
|
||||
float inflectT[2], convex180T[2];
|
||||
if (int inflectN = SkFindCubicInflections(p, inflectT)) {
|
||||
// The curve has inflections. findCubicConvex180Chops should return the inflection
|
||||
// points.
|
||||
int convex180N = GrPathUtils::findCubicConvex180Chops(p, convex180T);
|
||||
int convex180N = GrPathUtils::findCubicConvex180Chops(p, convex180T, &areCusps);
|
||||
REPORTER_ASSERT(r, inflectN == convex180N);
|
||||
if (!areCusps) {
|
||||
REPORTER_ASSERT(r, inflectN == 1 ||
|
||||
fabsf(inflectT[0] - inflectT[1]) >= SK_ScalarNearlyZero);
|
||||
}
|
||||
for (int i = 0; i < convex180N; ++i) {
|
||||
REPORTER_ASSERT(r, SkScalarNearlyEqual(inflectT[i], convex180T[i]));
|
||||
}
|
||||
} else {
|
||||
float totalRotation = SkMeasureNonInflectCubicRotation(p);
|
||||
int convex180N = GrPathUtils::findCubicConvex180Chops(p, convex180T);
|
||||
int convex180N = GrPathUtils::findCubicConvex180Chops(p, convex180T, &areCusps);
|
||||
SkPoint chops[10];
|
||||
SkChopCubicAt(p, chops, convex180T, convex180N);
|
||||
float radsSum = 0;
|
||||
@ -53,6 +58,9 @@ static void check_cubic_convex_180(skiatest::Reporter* r, const SkPoint p[4]) {
|
||||
REPORTER_ASSERT(r, SkScalarNearlyEqual(
|
||||
SkMeasureNonInflectCubicRotation(chops + 3), totalRotation - SK_ScalarPI));
|
||||
}
|
||||
REPORTER_ASSERT(r, !areCusps);
|
||||
} else {
|
||||
REPORTER_ASSERT(r, areCusps);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,6 +89,37 @@ DEF_TEST(GrPathUtils_findCubicConvex180Chops, r) {
|
||||
SkPoint quad[4] = {{0,0}, {2,2}, {4,2}, {6,0}};
|
||||
float T[2];
|
||||
REPORTER_ASSERT(r, GrPathUtils::findCubicConvex180Chops(quad, T) == 0);
|
||||
|
||||
// Now test that cusps and near-cusps get flagged as cusps.
|
||||
SkPoint cusp[4] = {{0,0}, {1,1}, {1,0}, {0,1}};
|
||||
bool areCusps = false;
|
||||
REPORTER_ASSERT(r, GrPathUtils::findCubicConvex180Chops(cusp, T, &areCusps) == 1);
|
||||
REPORTER_ASSERT(r, areCusps == true);
|
||||
|
||||
// Find the height of the right side of "cusp" at which the distance between its inflection
|
||||
// points is kEpsilon (in parametric space).
|
||||
constexpr static double kEpsilon = 1.0 / (1 << 11);
|
||||
constexpr static double kEpsilonSquared = kEpsilon * kEpsilon;
|
||||
double h = (1 - kEpsilonSquared) / (3 * kEpsilonSquared + 1);
|
||||
double dy = (1 - h) / 2;
|
||||
cusp[1].fY = (float)(1 - dy);
|
||||
cusp[2].fY = (float)(0 + dy);
|
||||
REPORTER_ASSERT(r, SkFindCubicInflections(cusp, T) == 2);
|
||||
REPORTER_ASSERT(r, SkScalarNearlyEqual(T[1] - T[0], (float)kEpsilon, (float)kEpsilonSquared));
|
||||
|
||||
// Ensure two inflection points barely more than kEpsilon apart do not get flagged as cusps.
|
||||
cusp[1].fY = (float)(1 - 1.1 * dy);
|
||||
cusp[2].fY = (float)(0 + 1.1 * dy);
|
||||
areCusps = false;
|
||||
REPORTER_ASSERT(r, GrPathUtils::findCubicConvex180Chops(cusp, T, &areCusps) == 2);
|
||||
REPORTER_ASSERT(r, areCusps == false);
|
||||
|
||||
// Ensure two inflection points barely less than kEpsilon apart do get flagged as cusps.
|
||||
cusp[1].fY = (float)(1 - .9 * dy);
|
||||
cusp[2].fY = (float)(0 + .9 * dy);
|
||||
areCusps = false;
|
||||
REPORTER_ASSERT(r, GrPathUtils::findCubicConvex180Chops(cusp, T, &areCusps) == 1);
|
||||
REPORTER_ASSERT(r, areCusps == true);
|
||||
}
|
||||
|
||||
DEF_TEST(GrPathUtils_convertToCubic, r) {
|
||||
|
Loading…
Reference in New Issue
Block a user