Remove tolerance form SkClassifyCubic

It's too inexact as-is. If the caller wants tolerance they can do
their own with knowledge of the pixel grid. The homogeneous math
is stable with infinities so it's really unnecessary here.

Bug: skia:7073
Change-Id: I4dc34ad96b859a138714b6d4f8804fec4f89f17a
Reviewed-on: https://skia-review.googlesource.com/51182
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
This commit is contained in:
Chris Dalton 2017-09-28 12:08:33 -06:00 committed by Skia Commit-Bot
parent a039d3bd06
commit 29011a2bda
4 changed files with 70 additions and 66 deletions

View File

@ -601,19 +601,14 @@ static void sort_and_orient_t_s(double t[2], double s[2]) {
// d1 = d2 = 0, d3 != 0 Quadratic
// d1 = d2 = d3 = 0 Line or Point
static SkCubicType classify_cubic(const double d[4], double t[2], double s[2]) {
// Check for degenerate cubics (quadratics, lines, and points).
// This also attempts to detect near-quadratics in a resolution independent fashion, however it
// is still up to the caller to check for almost-linear curves if needed.
if (fabs(d[1]) + fabs(d[2]) <= fabs(d[3]) * 1e-3) {
if (t && s) {
t[0] = t[1] = 1;
s[0] = s[1] = 0; // infinity
}
return 0 == d[3] ? SkCubicType::kLineOrPoint : SkCubicType::kQuadratic;
}
if (0 == d[1]) {
SkASSERT(0 != d[2]); // captured in check for degeneracy above.
if (0 == d[2]) {
if (t && s) {
t[0] = t[1] = 1;
s[0] = s[1] = 0; // infinity
}
return 0 == d[3] ? SkCubicType::kLineOrPoint : SkCubicType::kQuadratic;
}
if (t && s) {
t[0] = d[3];
s[0] = 3 * d[2];

View File

@ -106,11 +106,21 @@ void GrCCPRGeometry::quadraticTo(const SkPoint& devP0, const SkPoint& devP1) {
return;
}
this->appendMonotonicQuadratics(p0, p1, p2);
}
inline void GrCCPRGeometry::appendMonotonicQuadratics(const Sk2f& p0, const Sk2f& p1,
const Sk2f& p2, bool allowChop) {
SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
Sk2f tan0 = p1 - p0;
Sk2f tan1 = p2 - p1;
// This should almost always be this case for well-behaved curves in the real world.
if (is_convex_curve_monotonic(p0, tan0, p2, tan1)) {
this->appendMonotonicQuadratic(p1, p2);
if (!allowChop || is_convex_curve_monotonic(p0, tan0, p2, tan1)) {
p1.store(&fPoints.push_back());
p2.store(&fPoints.push_back());
fVerbs.push_back(Verb::kMonotonicQuadraticTo);
++fCurrContourTallies.fQuadratics;
return;
}
@ -138,15 +148,12 @@ void GrCCPRGeometry::quadraticTo(const SkPoint& devP0, const SkPoint& devP1) {
Sk2f p12 = SkNx_fma(t, tan1, p1);
Sk2f p012 = lerp(p01, p12, t);
this->appendMonotonicQuadratic(p01, p012);
this->appendMonotonicQuadratic(p12, p2);
}
inline void GrCCPRGeometry::appendMonotonicQuadratic(const Sk2f& p1, const Sk2f& p2) {
p1.store(&fPoints.push_back());
p01.store(&fPoints.push_back());
p012.store(&fPoints.push_back());
p12.store(&fPoints.push_back());
p2.store(&fPoints.push_back());
fVerbs.push_back(Verb::kMonotonicQuadraticTo);
++fCurrContourTallies.fQuadratics;
fVerbs.push_back_n(2, Verb::kMonotonicQuadraticTo);
fCurrContourTallies.fQuadratics += 2;
}
using ExcludedTerm = GrPathUtils::ExcludedTerm;
@ -252,6 +259,30 @@ static inline void calc_loop_intersect_padding_pts(float padRadius, const Sk2f&
}
}
static inline Sk2f first_unless_nearly_zero(const Sk2f& a, const Sk2f& b) {
Sk2f aa = a*a;
aa += SkNx_shuffle<1,0>(aa);
SkASSERT(aa[0] == aa[1]);
Sk2f bb = b*b;
bb += SkNx_shuffle<1,0>(bb);
SkASSERT(bb[0] == bb[1]);
return (aa > bb * SK_ScalarNearlyZero).thenElse(a, b);
}
static inline bool is_cubic_nearly_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
const Sk2f& p3, Sk2f& tan0, Sk2f& tan3, Sk2f& c) {
tan0 = first_unless_nearly_zero(p1 - p0, p2 - p0);
tan3 = first_unless_nearly_zero(p3 - p2, p3 - p1);
Sk2f c1 = SkNx_fma(Sk2f(1.5f), tan0, p0);
Sk2f c2 = SkNx_fma(Sk2f(-1.5f), tan3, p3);
c = (c1 + c2) * .5f; // Hopefully optimized out if not used?
return ((c1 - c2).abs() <= 1).allTrue();
}
void GrCCPRGeometry::cubicTo(const SkPoint& devP1, const SkPoint& devP2, const SkPoint& devP3,
float inflectPad, float loopIntersectPad) {
SkASSERT(fBuildingContour);
@ -273,23 +304,20 @@ void GrCCPRGeometry::cubicTo(const SkPoint& devP1, const SkPoint& devP2, const S
return;
}
double tt[2], ss[2];
fCurrCubicType = SkClassifyCubic(devPts, tt, ss);
if (SkCubicIsDegenerate(fCurrCubicType)) {
// Allow one subdivision in case the curve is quadratic, but not monotonic.
this->appendCubicApproximation(p0, p1, p2, p3, /*maxSubdivisions=*/1);
// Also detect near-quadratics ahead of time.
Sk2f tan0, tan3, c;
if (is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan3, c)) {
this->appendMonotonicQuadratics(p0, c, p3);
return;
}
double tt[2], ss[2];
fCurrCubicType = SkClassifyCubic(devPts, tt, ss);
SkASSERT(!SkCubicIsDegenerate(fCurrCubicType)); // Should have been caught above.
SkMatrix CIT;
ExcludedTerm skipTerm = GrPathUtils::calcCubicInverseTransposePowerBasisMatrix(devPts, &CIT);
if (ExcludedTerm::kNonInvertible == skipTerm) {
// This could technically also happen if the curve were a quadratic, but SkClassifyCubic
// should have detected that case already with tolerance.
p3.store(&fPoints.push_back());
fVerbs.push_back(Verb::kLineTo);
return;
}
SkASSERT(ExcludedTerm::kNonInvertible != skipTerm); // Should have been caught above.
SkASSERT(0 == CIT[6]);
SkASSERT(0 == CIT[7]);
SkASSERT(1 == CIT[8]);
@ -427,18 +455,6 @@ void GrCCPRGeometry::cubicTo(const SkPoint& devP1, const SkPoint& devP2, const S
&GrCCPRGeometry::appendMonotonicCubics>(abcd2, bcd2, cd2, p3, (T3-T2) / (1-T2));
}
static inline Sk2f first_unless_nearly_zero(const Sk2f& a, const Sk2f& b) {
Sk2f aa = a*a;
aa += SkNx_shuffle<1,0>(aa);
SkASSERT(aa[0] == aa[1]);
Sk2f bb = b*b;
bb += SkNx_shuffle<1,0>(bb);
SkASSERT(bb[0] == bb[1]);
return (aa > bb * SK_ScalarNearlyZero).thenElse(a, b);
}
template<GrCCPRGeometry::AppendCubicFn AppendLeftRight>
inline void GrCCPRGeometry::chopCubicAtMidTangent(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
const Sk2f& p3, const Sk2f& tan0,
@ -490,6 +506,7 @@ inline void GrCCPRGeometry::chopCubic(const Sk2f& p0, const Sk2f& p1, const Sk2f
void GrCCPRGeometry::appendMonotonicCubics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
const Sk2f& p3, int maxSubdivisions) {
SkASSERT(maxSubdivisions >= 0);
if ((p0 == p3).allTrue()) {
return;
}
@ -521,6 +538,7 @@ void GrCCPRGeometry::appendMonotonicCubics(const Sk2f& p0, const Sk2f& p1, const
void GrCCPRGeometry::appendCubicApproximation(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
const Sk2f& p3, int maxSubdivisions) {
SkASSERT(maxSubdivisions >= 0);
if ((p0 == p3).allTrue()) {
return;
}
@ -535,25 +553,15 @@ void GrCCPRGeometry::appendCubicApproximation(const Sk2f& p0, const Sk2f& p1, co
return;
}
Sk2f tan0 = first_unless_nearly_zero(p1 - p0, p2 - p0);
Sk2f tan3 = first_unless_nearly_zero(p3 - p2, p3 - p1);
Sk2f c1 = SkNx_fma(Sk2f(1.5f), tan0, p0);
Sk2f c2 = SkNx_fma(Sk2f(-1.5f), tan3, p3);
if (maxSubdivisions) {
bool nearlyQuadratic = ((c1 - c2).abs() <= 1).allTrue();
if (!nearlyQuadratic || !is_convex_curve_monotonic(p0, tan0, p3, tan3)) {
this->chopCubicAtMidTangent<&GrCCPRGeometry::appendCubicApproximation>(p0, p1, p2, p3,
tan0, tan3,
maxSubdivisions-1);
return;
}
Sk2f tan0, tan3, c;
if (!is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan3, c) && maxSubdivisions) {
this->chopCubicAtMidTangent<&GrCCPRGeometry::appendCubicApproximation>(p0, p1, p2, p3,
tan0, tan3,
maxSubdivisions - 1);
return;
}
SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
this->appendMonotonicQuadratic((c1 + c2) * .5f, p3);
this->appendMonotonicQuadratics(p0, c, p3, SkToBool(maxSubdivisions));
}
GrCCPRGeometry::PrimitiveTallies GrCCPRGeometry::endContour() {

View File

@ -93,7 +93,8 @@ public:
PrimitiveTallies endContour(); // Returns the numbers of primitives needed to draw the contour.
private:
inline void appendMonotonicQuadratic(const Sk2f& p1, const Sk2f& p2);
inline void appendMonotonicQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
bool allowChop = true);
using AppendCubicFn = void(GrCCPRGeometry::*)(const Sk2f& p0, const Sk2f& p1,
const Sk2f& p2, const Sk2f& p3,

View File

@ -215,10 +215,10 @@ static void check_cubic_type(skiatest::Reporter* reporter,
static void test_classify_cubic(skiatest::Reporter* reporter) {
check_cubic_type(reporter, {{{149.325f, 107.705f}, {149.325f, 103.783f},
{151.638f, 100.127f}, {156.263f, 96.736f}}},
SkCubicType::kQuadratic);
SkCubicType::kSerpentine);
check_cubic_type(reporter, {{{225.694f, 223.15f}, {209.831f, 224.837f},
{195.994f, 230.237f}, {184.181f, 239.35f}}},
SkCubicType::kQuadratic);
SkCubicType::kSerpentine);
check_cubic_type(reporter, {{{4.873f, 5.581f}, {5.083f, 5.2783f},
{5.182f, 4.8593f}, {5.177f, 4.3242f}}},
SkCubicType::kSerpentine);