aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar caryclark <caryclark@google.com>2015-08-25 08:03:01 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2015-08-25 08:03:01 -0700
commit7544124fb8ee744f68f549a353f8a9163cd7432d (patch)
tree05b3c0bde241520b07e9caa86045542d72b1dc88
parentcf72ed6a3eb13d0ed1aa45beb984bafafa7afff3 (diff)
fix zero-length tangent
If the end point and the control point are the same, computing the tangent will result in (0, 0). In this case, use the prior control point instead. R=reed@google.com BUG=skia:4191 Review URL: https://codereview.chromium.org/1311273002
-rw-r--r--gm/strokes.cpp56
-rw-r--r--src/core/SkGeometry.cpp49
-rw-r--r--tests/GeometryTest.cpp64
3 files changed, 150 insertions, 19 deletions
diff --git a/gm/strokes.cpp b/gm/strokes.cpp
index 73823907a3..0bcb039783 100644
--- a/gm/strokes.cpp
+++ b/gm/strokes.cpp
@@ -266,6 +266,60 @@ private:
typedef skiagm::GM INHERITED;
};
+// Test stroking for curves that produce degenerate tangents when t is 0 or 1 (see bug 4191)
+class Strokes5GM : public skiagm::GM {
+public:
+ Strokes5GM() {}
+
+protected:
+
+ SkString onShortName() override {
+ return SkString("zero_control_stroke");
+ }
+
+ SkISize onISize() override {
+ return SkISize::Make(W, H*2);
+ }
+
+ void onDraw(SkCanvas* canvas) override {
+ SkPaint p;
+ p.setColor(SK_ColorRED);
+ p.setAntiAlias(true);
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setStrokeWidth(40);
+ p.setStrokeCap(SkPaint::kButt_Cap);
+
+ SkPath path;
+ path.moveTo(157.474f,111.753f);
+ path.cubicTo(128.5f,111.5f,35.5f,29.5f,35.5f,29.5f);
+ canvas->drawPath(path, p);
+ path.reset();
+ path.moveTo(250, 50);
+ path.quadTo(280, 80, 280, 80);
+ canvas->drawPath(path, p);
+ path.reset();
+ path.moveTo(150, 50);
+ path.conicTo(180, 80, 180, 80, 0.707f);
+ canvas->drawPath(path, p);
+
+ path.reset();
+ path.moveTo(157.474f,311.753f);
+ path.cubicTo(157.474f,311.753f,85.5f,229.5f,35.5f,229.5f);
+ canvas->drawPath(path, p);
+ path.reset();
+ path.moveTo(280, 250);
+ path.quadTo(280, 250, 310, 280);
+ canvas->drawPath(path, p);
+ path.reset();
+ path.moveTo(180, 250);
+ path.conicTo(180, 250, 210, 280, 0.707f);
+ canvas->drawPath(path, p);
+ }
+
+private:
+ typedef skiagm::GM INHERITED;
+};
+
//////////////////////////////////////////////////////////////////////////////
@@ -273,8 +327,10 @@ static skiagm::GM* F0(void*) { return new StrokesGM; }
static skiagm::GM* F1(void*) { return new Strokes2GM; }
static skiagm::GM* F2(void*) { return new Strokes3GM; }
static skiagm::GM* F3(void*) { return new Strokes4GM; }
+static skiagm::GM* F4(void*) { return new Strokes5GM; }
static skiagm::GMRegistry R0(F0);
static skiagm::GMRegistry R1(F1);
static skiagm::GMRegistry R2(F2);
static skiagm::GMRegistry R3(F3);
+static skiagm::GMRegistry R4(F4);
diff --git a/src/core/SkGeometry.cpp b/src/core/SkGeometry.cpp
index 6afd9d7ffb..7462009479 100644
--- a/src/core/SkGeometry.cpp
+++ b/src/core/SkGeometry.cpp
@@ -130,13 +130,6 @@ static SkScalar eval_quad(const SkScalar src[], SkScalar t) {
#endif
}
-static SkScalar eval_quad_derivative(const SkScalar src[], SkScalar t) {
- SkScalar A = src[4] - 2 * src[2] + src[0];
- SkScalar B = src[2] - src[0];
-
- return 2 * SkScalarMulAdd(A, t, B);
-}
-
void SkQuadToCoeff(const SkPoint pts[3], SkPoint coeff[3]) {
Sk2s p0 = from_point(pts[0]);
Sk2s p1 = from_point(pts[1]);
@@ -157,8 +150,7 @@ void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint* pt, SkVector* tange
pt->set(eval_quad(&src[0].fX, t), eval_quad(&src[0].fY, t));
}
if (tangent) {
- tangent->set(eval_quad_derivative(&src[0].fX, t),
- eval_quad_derivative(&src[0].fY, t));
+ *tangent = SkEvalQuadTangentAt(src, t);
}
}
@@ -179,6 +171,12 @@ SkPoint SkEvalQuadAt(const SkPoint src[3], SkScalar t) {
}
SkVector SkEvalQuadTangentAt(const SkPoint src[3], SkScalar t) {
+ // The derivative equation is 2(b - a +(a - 2b +c)t). This returns a
+ // zero tangent vector when t is 0 or 1, and the control point is equal
+ // to the end point. In this case, use the quad end points to compute the tangent.
+ if ((t == 0 && src[0] == src[1]) || (t == 1 && src[1] == src[2])) {
+ return src[2] - src[0];
+ }
SkASSERT(src);
SkASSERT(t >= 0 && t <= SK_Scalar1);
@@ -398,8 +396,22 @@ void SkEvalCubicAt(const SkPoint src[4], SkScalar t, SkPoint* loc,
loc->set(eval_cubic(&src[0].fX, t), eval_cubic(&src[0].fY, t));
}
if (tangent) {
- tangent->set(eval_cubic_derivative(&src[0].fX, t),
- eval_cubic_derivative(&src[0].fY, t));
+ // The derivative equation returns a zero tangent vector when t is 0 or 1, and the
+ // adjacent control point is equal to the end point. In this case, use the
+ // next control point or the end points to compute the tangent.
+ if ((t == 0 && src[0] == src[1]) || (t == 1 && src[2] == src[3])) {
+ if (t == 0) {
+ *tangent = src[2] - src[0];
+ } else {
+ *tangent = src[3] - src[1];
+ }
+ if (!tangent->fX && !tangent->fY) {
+ *tangent = src[3] - src[0];
+ }
+ } else {
+ tangent->set(eval_cubic_derivative(&src[0].fX, t),
+ eval_cubic_derivative(&src[0].fY, t));
+ }
}
if (curvature) {
curvature->set(eval_cubic_2ndDerivative(&src[0].fX, t),
@@ -1176,12 +1188,6 @@ static void conic_deriv_coeff(const SkScalar src[],
coeff[2] = wP10;
}
-static SkScalar conic_eval_tan(const SkScalar coord[], SkScalar w, SkScalar t) {
- SkScalar coeff[3];
- conic_deriv_coeff(coord, w, coeff);
- return t * (t * coeff[0] + coeff[1]) + coeff[2];
-}
-
static bool conic_find_extrema(const SkScalar src[], SkScalar w, SkScalar* t) {
SkScalar coeff[3];
conic_deriv_coeff(src, w, coeff);
@@ -1232,8 +1238,7 @@ void SkConic::evalAt(SkScalar t, SkPoint* pt, SkVector* tangent) const {
conic_eval_pos(&fPts[0].fY, fW, t));
}
if (tangent) {
- tangent->set(conic_eval_tan(&fPts[0].fX, fW, t),
- conic_eval_tan(&fPts[0].fY, fW, t));
+ *tangent = evalTangentAt(t);
}
}
@@ -1291,6 +1296,12 @@ SkPoint SkConic::evalAt(SkScalar t) const {
}
SkVector SkConic::evalTangentAt(SkScalar t) const {
+ // The derivative equation returns a zero tangent vector when t is 0 or 1,
+ // and the control point is equal to the end point.
+ // In this case, use the conic endpoints to compute the tangent.
+ if ((t == 0 && fPts[0] == fPts[1]) || (t == 1 && fPts[1] == fPts[2])) {
+ return fPts[2] - fPts[0];
+ }
Sk2s p0 = from_point(fPts[0]);
Sk2s p1 = from_point(fPts[1]);
Sk2s p2 = from_point(fPts[2]);
diff --git a/tests/GeometryTest.cpp b/tests/GeometryTest.cpp
index 00fc7797dc..5aa80d0b1f 100644
--- a/tests/GeometryTest.cpp
+++ b/tests/GeometryTest.cpp
@@ -105,6 +105,67 @@ static void test_conic(skiatest::Reporter* reporter) {
}
}
+static void test_quad_tangents(skiatest::Reporter* reporter) {
+ SkPoint pts[] = {
+ {10, 20}, {10, 20}, {20, 30},
+ {10, 20}, {15, 25}, {20, 30},
+ {10, 20}, {20, 30}, {20, 30},
+ };
+ int count = (int) SK_ARRAY_COUNT(pts) / 3;
+ for (int index = 0; index < count; ++index) {
+ SkConic conic(&pts[index * 3], 0.707f);
+ SkVector start = SkEvalQuadTangentAt(&pts[index * 3], 0);
+ SkVector mid = SkEvalQuadTangentAt(&pts[index * 3], .5f);
+ SkVector end = SkEvalQuadTangentAt(&pts[index * 3], 1);
+ REPORTER_ASSERT(reporter, start.fX && start.fY);
+ REPORTER_ASSERT(reporter, mid.fX && mid.fY);
+ REPORTER_ASSERT(reporter, end.fX && end.fY);
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
+ }
+}
+
+static void test_conic_tangents(skiatest::Reporter* reporter) {
+ SkPoint pts[] = {
+ { 10, 20}, {10, 20}, {20, 30},
+ { 10, 20}, {15, 25}, {20, 30},
+ { 10, 20}, {20, 30}, {20, 30}
+ };
+ int count = (int) SK_ARRAY_COUNT(pts) / 3;
+ for (int index = 0; index < count; ++index) {
+ SkConic conic(&pts[index * 3], 0.707f);
+ SkVector start = conic.evalTangentAt(0);
+ SkVector mid = conic.evalTangentAt(.5f);
+ SkVector end = conic.evalTangentAt(1);
+ REPORTER_ASSERT(reporter, start.fX && start.fY);
+ REPORTER_ASSERT(reporter, mid.fX && mid.fY);
+ REPORTER_ASSERT(reporter, end.fX && end.fY);
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
+ }
+}
+
+static void test_cubic_tangents(skiatest::Reporter* reporter) {
+ SkPoint pts[] = {
+ { 10, 20}, {10, 20}, {20, 30}, {30, 40},
+ { 10, 20}, {15, 25}, {20, 30}, {30, 40},
+ { 10, 20}, {20, 30}, {30, 40}, {30, 40},
+ };
+ int count = (int) SK_ARRAY_COUNT(pts) / 4;
+ for (int index = 0; index < count; ++index) {
+ SkConic conic(&pts[index * 3], 0.707f);
+ SkVector start, mid, end;
+ SkEvalCubicAt(&pts[index * 4], 0, NULL, &start, NULL);
+ SkEvalCubicAt(&pts[index * 4], .5f, NULL, &mid, NULL);
+ SkEvalCubicAt(&pts[index * 4], 1, NULL, &end, NULL);
+ REPORTER_ASSERT(reporter, start.fX && start.fY);
+ REPORTER_ASSERT(reporter, mid.fX && mid.fY);
+ REPORTER_ASSERT(reporter, end.fX && end.fY);
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
+ }
+}
+
DEF_TEST(Geometry, reporter) {
SkPoint pts[3], dst[5];
@@ -129,4 +190,7 @@ DEF_TEST(Geometry, reporter) {
testChopCubic(reporter);
test_evalquadat(reporter);
test_conic(reporter);
+ test_cubic_tangents(reporter);
+ test_quad_tangents(reporter);
+ test_conic_tangents(reporter);
}