Reland of ix zero-length tangent (patchset #1 id:1 of https://codereview.chromium.org/1312243002/ )
Reason for revert:
Layout suppression has landed, and verified that Skia gm test changes are correct.
Original issue's description:
> Revert of fix zero-length tangent (patchset #2 id:20001 of https://codereview.chromium.org/1311273002/ )
>
> Reason for revert:
> causes layout test to draw differently -- new drawing is more correct. Reverting until layout test ignore is landed.
>
> Original issue's description:
> > 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
> >
> > Committed: https://skia.googlesource.com/skia/+/7544124fb8ee744f68f549a353f8a9163cd7432d
>
> TBR=reed@google.com
> NOPRESUBMIT=true
> NOTREECHECKS=true
> NOTRY=true
> BUG=skia:4191
>
> Committed: https://skia.googlesource.com/skia/+/91298b47c547b2ab4697038c04685af957bd1416
TBR=reed@google.com
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=skia:4191
Review URL: https://codereview.chromium.org/1320473002
diff --git a/gm/strokes.cpp b/gm/strokes.cpp
index 7382390..0bcb039 100644
--- a/gm/strokes.cpp
+++ b/gm/strokes.cpp
@@ -266,6 +266,60 @@
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* 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 6afd9d7..7462009 100644
--- a/src/core/SkGeometry.cpp
+++ b/src/core/SkGeometry.cpp
@@ -130,13 +130,6 @@
#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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
}
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 00fc779..5aa80d0 100644
--- a/tests/GeometryTest.cpp
+++ b/tests/GeometryTest.cpp
@@ -105,6 +105,67 @@
}
}
+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 @@
testChopCubic(reporter);
test_evalquadat(reporter);
test_conic(reporter);
+ test_cubic_tangents(reporter);
+ test_quad_tangents(reporter);
+ test_conic_tangents(reporter);
}