fix bugs in path contains
Pull out the logic to check to see if the point is on the edge so all curve types can share. Reorder cubic to be like conic and quad so that mixed types consider the curves consistently. Don't count on curve points twice if they are on the end and compute a zero cross product. Remove logic that checks, when there are no roots, if the point is closer to the top or the bottom (it's always the top). Initialize the iterator correctly when it is accessing the list of on point curves. Use 'multiply' instead of 'subtract' to see if the vectors are pointing in opposite directions. Add more test cases. R=reed@google.com,fs@opera.com BUG=skia:4265 GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1532003004 Review URL: https://codereview.chromium.org/1532003004
This commit is contained in:
parent
c8b4336444
commit
9cb5d755e7
@ -2621,19 +2621,34 @@ template <size_t N> static void find_minmax(const SkPoint pts[],
|
||||
*maxPtr = max;
|
||||
}
|
||||
|
||||
static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
|
||||
if (start.fY == end.fY) {
|
||||
return between(start.fX, x, end.fX) && x != end.fX;
|
||||
} else {
|
||||
return x == start.fX && y == start.fY;
|
||||
}
|
||||
}
|
||||
|
||||
static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
|
||||
if (!between(pts[0].fY, y, pts[3].fY)) {
|
||||
SkScalar y0 = pts[0].fY;
|
||||
SkScalar y3 = pts[3].fY;
|
||||
|
||||
int dir = 1;
|
||||
if (y0 > y3) {
|
||||
SkTSwap(y0, y3);
|
||||
dir = -1;
|
||||
}
|
||||
if (y < y0 || y > y3) {
|
||||
return 0;
|
||||
}
|
||||
if (y == pts[3].fY) {
|
||||
// if the cubic is a horizontal line, check if the point is on it
|
||||
// but don't check the last point, because that point is shared with the next curve
|
||||
if (pts[0].fY == pts[3].fY && between(pts[0].fX, x, pts[3].fX) && x != pts[3].fX) {
|
||||
*onCurveCount += 1;
|
||||
}
|
||||
if (checkOnCurve(x, y, pts[0], pts[3])) {
|
||||
*onCurveCount += 1;
|
||||
return 0;
|
||||
}
|
||||
int dir = pts[0].fY > pts[3].fY ? -1 : 1;
|
||||
if (y == y3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// quickreject or quickaccept
|
||||
SkScalar min, max;
|
||||
find_minmax<4>(pts, &min, &max);
|
||||
@ -2651,6 +2666,7 @@ static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int*
|
||||
if (SkScalarNearlyEqual(xt, x)) {
|
||||
if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
|
||||
*onCurveCount += 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return xt < x ? dir : 0;
|
||||
@ -2697,10 +2713,11 @@ static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int*
|
||||
if (y < y0 || y > y2) {
|
||||
return 0;
|
||||
}
|
||||
if (checkOnCurve(x, y, pts[0], pts[2])) {
|
||||
*onCurveCount += 1;
|
||||
return 0;
|
||||
}
|
||||
if (y == y2) {
|
||||
if (y0 == y2 && between(pts[0].fX, x, pts[2].fX) && x != pts[2].fX) { // check horizontal
|
||||
*onCurveCount += 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2715,10 +2732,10 @@ static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int*
|
||||
SkASSERT(n <= 1);
|
||||
SkScalar xt;
|
||||
if (0 == n) {
|
||||
SkScalar mid = SkScalarAve(y0, y2);
|
||||
// Need [0] and [2] if dir == 1
|
||||
// and [2] and [0] if dir == -1
|
||||
xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
|
||||
// zero roots are returned only when y0 == y
|
||||
// Need [0] if dir == 1
|
||||
// and [2] if dir == -1
|
||||
xt = pts[1 - dir].fX;
|
||||
} else {
|
||||
SkScalar t = roots[0];
|
||||
xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
|
||||
@ -2726,6 +2743,7 @@ static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int*
|
||||
if (SkScalarNearlyEqual(xt, x)) {
|
||||
if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
|
||||
*onCurveCount += 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return xt < x ? dir : 0;
|
||||
@ -2773,10 +2791,11 @@ static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* o
|
||||
if (y < y0 || y > y2) {
|
||||
return 0;
|
||||
}
|
||||
if (checkOnCurve(x, y, pts[0], pts[2])) {
|
||||
*onCurveCount += 1;
|
||||
return 0;
|
||||
}
|
||||
if (y == y2) {
|
||||
if (y0 == y2 && between(pts[0].fX, x, pts[2].fX) && x != pts[2].fX) { // check horizontal
|
||||
*onCurveCount += 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// bounds check on X (not required. is it faster?)
|
||||
@ -2794,10 +2813,10 @@ static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* o
|
||||
SkASSERT(n <= 1);
|
||||
SkScalar xt;
|
||||
if (0 == n) {
|
||||
SkScalar mid = SkScalarAve(y0, y2);
|
||||
// Need [0] and [2] if dir == 1
|
||||
// and [2] and [0] if dir == -1
|
||||
xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
|
||||
// zero roots are returned only when y0 == y
|
||||
// Need [0] if dir == 1
|
||||
// and [2] if dir == -1
|
||||
xt = pts[1 - dir].fX;
|
||||
} else {
|
||||
SkScalar t = roots[0];
|
||||
SkScalar C = pts[0].fX;
|
||||
@ -2808,6 +2827,7 @@ static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* o
|
||||
if (SkScalarNearlyEqual(xt, x)) {
|
||||
if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
|
||||
*onCurveCount += 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return xt < x ? dir : 0;
|
||||
@ -2844,10 +2864,11 @@ static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurv
|
||||
if (y < y0 || y > y1) {
|
||||
return 0;
|
||||
}
|
||||
if (y == pts[1].fY) {
|
||||
if (y0 == y1 && between(x0, x, x1) && x != x1) { // check if on horizontal line
|
||||
*onCurveCount += 1;
|
||||
}
|
||||
if (checkOnCurve(x, y, pts[0], pts[1])) {
|
||||
*onCurveCount += 1;
|
||||
return 0;
|
||||
}
|
||||
if (y == y1) {
|
||||
return 0;
|
||||
}
|
||||
SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
|
||||
@ -2856,7 +2877,9 @@ static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurv
|
||||
// zero cross means the point is on the line, and since the case where
|
||||
// y of the query point is at the end point is handled above, we can be
|
||||
// sure that we're on the line (excluding the end point) here
|
||||
*onCurveCount += 1;
|
||||
if (x != x1 || y != pts[1].fY) {
|
||||
*onCurveCount += 1;
|
||||
}
|
||||
dir = 0;
|
||||
} else if (SkScalarSignAsInt(cross) == dir) {
|
||||
dir = 0;
|
||||
@ -3023,6 +3046,7 @@ bool SkPath::contains(SkScalar x, SkScalar y) const {
|
||||
// If the point touches an even number of curves, and the fill is winding, check for
|
||||
// coincidence. Count coincidence as places where the on curve points have identical tangents.
|
||||
iter.setPath(*this, true);
|
||||
done = false;
|
||||
SkTDArray<SkVector> tangents;
|
||||
do {
|
||||
SkPoint pts[4];
|
||||
@ -3056,8 +3080,8 @@ bool SkPath::contains(SkScalar x, SkScalar y) const {
|
||||
for (int index = 0; index < last; ++index) {
|
||||
const SkVector& test = tangents[index];
|
||||
if (SkScalarNearlyZero(test.cross(tangent))
|
||||
&& SkScalarSignAsInt(tangent.fX - test.fX) <= 0
|
||||
&& SkScalarSignAsInt(tangent.fY - test.fY) <= 0) {
|
||||
&& SkScalarSignAsInt(tangent.fX * test.fX) <= 0
|
||||
&& SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
|
||||
tangents.remove(last);
|
||||
tangents.removeShuffle(index);
|
||||
break;
|
||||
|
@ -3591,6 +3591,10 @@ static void test_contains(skiatest::Reporter* reporter) {
|
||||
REPORTER_ASSERT(reporter, p.contains(5, 5));
|
||||
REPORTER_ASSERT(reporter, p.contains(5, 8));
|
||||
REPORTER_ASSERT(reporter, p.contains(4, 5));
|
||||
// test quad endpoints
|
||||
REPORTER_ASSERT(reporter, p.contains(4, 4));
|
||||
REPORTER_ASSERT(reporter, p.contains(8, 8));
|
||||
REPORTER_ASSERT(reporter, p.contains(4, 8));
|
||||
|
||||
p.reset();
|
||||
const SkPoint qPts[] = {{6, 6}, {8, 8}, {6, 8}, {4, 8}, {4, 6}, {4, 4}, {6, 6}};
|
||||
@ -3622,6 +3626,10 @@ static void test_contains(skiatest::Reporter* reporter) {
|
||||
halfway = conic.evalAt(0.5f);
|
||||
REPORTER_ASSERT(reporter, p.contains(halfway.fX, halfway.fY));
|
||||
}
|
||||
// test conic end points
|
||||
REPORTER_ASSERT(reporter, p.contains(4, 4));
|
||||
REPORTER_ASSERT(reporter, p.contains(8, 8));
|
||||
REPORTER_ASSERT(reporter, p.contains(4, 8));
|
||||
|
||||
// test cubics
|
||||
SkPoint pts[] = {{5, 4}, {6, 5}, {7, 6}, {6, 6}, {4, 6}, {5, 7}, {5, 5}, {5, 4}, {6, 5}, {7, 6}};
|
||||
@ -3639,6 +3647,10 @@ static void test_contains(skiatest::Reporter* reporter) {
|
||||
REPORTER_ASSERT(reporter, p.contains(halfway.fX, halfway.fY));
|
||||
SkEvalCubicAt(&pts[i + 3], 0.5f, &halfway, nullptr, nullptr);
|
||||
REPORTER_ASSERT(reporter, p.contains(halfway.fX, halfway.fY));
|
||||
// test cubic end points
|
||||
REPORTER_ASSERT(reporter, p.contains(pts[i].fX, pts[i].fY));
|
||||
REPORTER_ASSERT(reporter, p.contains(pts[i + 3].fX, pts[i + 3].fY));
|
||||
REPORTER_ASSERT(reporter, p.contains(pts[i + 6].fX, pts[i + 6].fY));
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user