diff --git a/include/core/SkPoint.h b/include/core/SkPoint.h index 7b9fdf99ca..8ba2cbcaa6 100644 --- a/include/core/SkPoint.h +++ b/include/core/SkPoint.h @@ -199,6 +199,16 @@ struct SK_API SkPoint { SkScalar length() const { return SkPoint::Length(fX, fY); } SkScalar distanceToOrigin() const { return this->length(); } + /** + * Return true if the computed length of the vector is >= the internal + * tolerance (used to avoid dividing by tiny values). + */ + static bool CanNormalize(SkScalar dx, SkScalar dy); + + bool canNormalize() const { + return CanNormalize(fX, fY); + } + /** Set the point (vector) to be unit-length in the same direction as it already points. If the point has a degenerate length (i.e. nearly 0) then return false and do nothing; otherwise return true. @@ -317,7 +327,8 @@ struct SK_API SkPoint { static SkScalar Length(SkScalar x, SkScalar y); /** Normalize pt, returning its previous length. If the prev length is too - small (degenerate), return 0 and leave pt unchanged. + small (degenerate), return 0 and leave pt unchanged. This uses the same + tolerance as CanNormalize. Note that this method may be significantly more expensive than the non-static normalize(), because it has to return the previous length diff --git a/src/core/SkPoint.cpp b/src/core/SkPoint.cpp index d6e6b318bf..5747504c77 100644 --- a/src/core/SkPoint.cpp +++ b/src/core/SkPoint.cpp @@ -100,16 +100,21 @@ SkScalar SkPoint::Normalize(SkPoint* pt) { #ifdef SK_SCALAR_IS_FLOAT +bool SkPoint::CanNormalize(SkScalar dx, SkScalar dy) { + float mag2 = dx * dx + dy * dy; + return mag2 > SK_ScalarNearlyZero * SK_ScalarNearlyZero; +} + SkScalar SkPoint::Length(SkScalar dx, SkScalar dy) { return sk_float_sqrt(dx * dx + dy * dy); } bool SkPoint::setLength(float x, float y, float length) { - float mag = sk_float_sqrt(x * x + y * y); - if (mag > SK_ScalarNearlyZero) { - length /= mag; - fX = x * length; - fY = y * length; + float mag2 = x * x + y * y; + if (mag2 > SK_ScalarNearlyZero * SK_ScalarNearlyZero) { + float scale = length / sk_float_sqrt(mag2); + fX = x * scale; + fY = y * scale; return true; } return false; @@ -119,6 +124,23 @@ bool SkPoint::setLength(float x, float y, float length) { #include "Sk64.h" +bool SkPoint::CanNormalize(SkScalar dx, SkScalar dy) { + Sk64 tmp1, tmp2, tolSqr; + + tmp1.setMul(dx, dx); + tmp2.setMul(dy, dy); + tmp1.add(tmp2); + + // we want nearlyzero^2, but to compute it fast we want to just do a + // 32bit multiply, so we require that it not exceed 31bits. That is true + // if nearlyzero is <= 0xB504, which should be trivial, since usually + // nearlyzero is a very small fixed-point value. + SkASSERT(SK_ScalarNearlyZero <= 0xB504); + + tolSqr.set(0, SK_ScalarNearlyZero * SK_ScalarNearlyZero); + return tmp1 > tolSqr; +} + SkScalar SkPoint::Length(SkScalar dx, SkScalar dy) { Sk64 tmp1, tmp2; diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp index 0c858ebe4d..bf3ceac3b7 100644 --- a/src/core/SkStroke.cpp +++ b/src/core/SkStroke.cpp @@ -15,7 +15,7 @@ #define kMaxCubicSubdivide 4 static inline bool degenerate_vector(const SkVector& v) { - return SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY); + return !SkPoint::CanNormalize(v.fX, v.fY); } static inline bool degenerate_line(const SkPoint& a, const SkPoint& b, diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp index e7b061b971..4c0113e978 100644 --- a/tests/PathTest.cpp +++ b/tests/PathTest.cpp @@ -6,10 +6,47 @@ * found in the LICENSE file. */ #include "Test.h" +#include "SkPaint.h" #include "SkPath.h" #include "SkParse.h" #include "SkSize.h" +static void stroke_cubic(const SkPoint pts[4]) { + SkPath path; + path.moveTo(pts[0]); + path.cubicTo(pts[1], pts[2], pts[3]); + + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(SK_Scalar1 * 2); + + SkPath fill; + paint.getFillPath(path, &fill); +} + +// just ensure this can run w/o any SkASSERTS firing in the debug build +// we used to assert due to differences in how we determine a degenerate vector +// but that was fixed with the introduction of SkPoint::CanNormalize +static void stroke_tiny_cubic() { + SkPoint p0[] = { + { 372.0f, 92.0f }, + { 372.0f, 92.0f }, + { 372.0f, 92.0f }, + { 372.0f, 92.0f }, + }; + + stroke_cubic(p0); + + SkPoint p1[] = { + { 372.0f, 92.0f }, + { 372.0007f, 92.000755f }, + { 371.99927f, 92.003922f }, + { 371.99826f, 92.003899f }, + }; + + stroke_cubic(p1); +} + static void check_close(skiatest::Reporter* reporter, const SkPath& path) { for (int i = 0; i < 2; ++i) { SkPath::Iter iter(path, (bool)i); @@ -102,6 +139,8 @@ static void test_close(skiatest::Reporter* reporter) { moves.moveTo(SK_Scalar1, 10 * SK_Scalar1); moves.moveTo(10 *SK_Scalar1, SK_Scalar1); check_close(reporter, moves); + + stroke_tiny_cubic(); } static void check_convexity(skiatest::Reporter* reporter, const SkPath& path,