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:
parent
a039d3bd06
commit
29011a2bda
@ -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];
|
||||
|
@ -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() {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user