diff options
author | Chris Dalton <csmartdalton@google.com> | 2018-04-19 13:13:25 -0600 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-04-20 16:06:25 +0000 |
commit | d8bae7d501462db1699bfcb7db84a40b9add3a29 (patch) | |
tree | 0749d24a82c7a49cc949346b682face1c61bb1a3 /src | |
parent | 3d0e8507face451a4b17698beb6ad0ea51b9ef1d (diff) |
ccpr: Fix flatness and triangle-ness detection for conics
We should detect these cases by examining the curve at max height; not
midtangent.
Bug: skia:7821
Change-Id: I3d9e3a10798f0d825916840cb99d054b2a6284c3
Reviewed-on: https://skia-review.googlesource.com/122620
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/gpu/ccpr/GrCCGeometry.cpp | 102 |
1 files changed, 55 insertions, 47 deletions
diff --git a/src/gpu/ccpr/GrCCGeometry.cpp b/src/gpu/ccpr/GrCCGeometry.cpp index 2593273c26..59d040d739 100644 --- a/src/gpu/ccpr/GrCCGeometry.cpp +++ b/src/gpu/ccpr/GrCCGeometry.cpp @@ -18,6 +18,8 @@ GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT); GR_STATIC_ASSERT(2 * sizeof(float) == sizeof(SkPoint)); GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX)); +static constexpr float kFlatnessThreshold = 1/16.f; // 1/16 of a pixel. + void GrCCGeometry::beginPath() { SkASSERT(!fBuildingContour); fVerbs.push_back(Verb::kBeginPath); @@ -59,7 +61,7 @@ static inline float dot(const Sk2f& a, const Sk2f& b) { } static inline bool are_collinear(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, - float tolerance = 1/16.f) { // 1/16 of a pixel. + float tolerance = kFlatnessThreshold) { Sk2f l = p2 - p0; // Line from p0 -> p2. // lwidth = Manhattan width of l. @@ -87,7 +89,7 @@ static inline bool are_collinear(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, return std::abs(d) <= lwidth * tolerance; } -static inline bool are_collinear(const SkPoint P[4], float tolerance = 1/16.f) { // 1/16 of a pixel. +static inline bool are_collinear(const SkPoint P[4], float tolerance = kFlatnessThreshold) { Sk4f Px, Py; // |Px Py| |p0 - p3| Sk4f::Load2(P, &Px, &Py); // |. . | = |p1 - p3| Px -= Px[3]; // |. . | |p2 - p3| @@ -586,56 +588,40 @@ void GrCCGeometry::conicTo(const SkPoint P[3], float w) { Sk2f p1 = Sk2f::Load(P+1); Sk2f p2 = Sk2f::Load(P+2); - // Don't crunch on the curve if it is nearly flat (or just very small). Collinear control points - // can break the midtangent-finding math below. - if (are_collinear(p0, p1, p2)) { - this->appendLine(p2); - return; - } - Sk2f tan0 = p1 - p0; Sk2f tan1 = p2 - p1; - // The derivative of a conic has a cumbersome order-4 denominator. However, this isn't necessary - // if we are only interested in a vector in the same *direction* as a given tangent line. Since - // the denominator scales dx and dy uniformly, we can throw it out completely after evaluating - // the derivative with the standard quotient rule. This leaves us with a simpler quadratic - // function that we use to find the midtangent. - float midT = find_midtangent(tan0, tan1, 1, (w - 1) * (p2 - p0), - 1, (p2 - p0) - 2*w*(p1 - p0), - 1, w*(p1 - p0)); - // Use positive logic since NaN fails comparisons. (However midT should not be NaN since we cull - // near-linear conics above. And while w=0 is flat, it's not a line and has valid midtangents.) - if (!(midT > 0 && midT < 1)) { - // The conic is flat. Otherwise there would be a real midtangent inside T=0..1. - this->appendLine(p2); - return; - } - - // Evaluate the conic at midT. - Sk4f p3d0 = Sk4f(p0[0], p0[1], 1, 0); - Sk4f p3d1 = Sk4f(p1[0], p1[1], 1, 0) * w; - Sk4f p3d2 = Sk4f(p2[0], p2[1], 1, 0); - Sk4f midT4 = midT; - Sk4f p3d01 = lerp(p3d0, p3d1, midT4); - Sk4f p3d12 = lerp(p3d1, p3d2, midT4); - Sk4f p3d012 = lerp(p3d01, p3d12, midT4); + if (!is_convex_curve_monotonic(p0, tan0, p2, tan1)) { + // The derivative of a conic has a cumbersome order-4 denominator. However, this isn't + // necessary if we are only interested in a vector in the same *direction* as a given + // tangent line. Since the denominator scales dx and dy uniformly, we can throw it out + // completely after evaluating the derivative with the standard quotient rule. This leaves + // us with a simpler quadratic function that we use to find the midtangent. + float midT = find_midtangent(tan0, tan1, 1, (w - 1) * (p2 - p0), + 1, (p2 - p0) - 2*w*(p1 - p0), + 1, w*(p1 - p0)); + // Use positive logic since NaN fails comparisons. (However midT should not be NaN since we + // cull near-linear conics above. And while w=0 is flat, it's not a line and has valid + // midtangents.) + if (!(midT > 0 && midT < 1)) { + // The conic is flat. Otherwise there would be a real midtangent inside T=0..1. + this->appendLine(p2); + return; + } - Sk2f midpoint = Sk2f(p3d012[0], p3d012[1]) / p3d012[2]; + // Chop the conic at midtangent to produce two monotonic segments. + Sk4f p3d0 = Sk4f(p0[0], p0[1], 1, 0); + Sk4f p3d1 = Sk4f(p1[0], p1[1], 1, 0) * w; + Sk4f p3d2 = Sk4f(p2[0], p2[1], 1, 0); + Sk4f midT4 = midT; - if (are_collinear(p0, midpoint, p2, 1) || // Check if the curve is within one pixel of flat. - ((midpoint - p1).abs() < 1).allTrue()) { // Check if the curve is almost a triangle. - // Draw the conic as a triangle instead. Our AA approximation won't do well if the curve - // gets wrapped too tightly, and if we get too close to p1 we will pick up artifacts from - // the implicit function's reflection. - this->appendLine(midpoint); - this->appendLine(p2); - return; - } + Sk4f p3d01 = lerp(p3d0, p3d1, midT4); + Sk4f p3d12 = lerp(p3d1, p3d2, midT4); + Sk4f p3d012 = lerp(p3d01, p3d12, midT4); - if (!is_convex_curve_monotonic(p0, tan0, p2, tan1)) { - // Chop the conic at midtangent to produce two monotonic segments. + Sk2f midpoint = Sk2f(p3d012[0], p3d012[1]) / p3d012[2]; Sk2f ww = Sk2f(p3d01[2], p3d12[2]) * Sk2f(p3d012[2]).rsqrt(); + this->appendMonotonicConic(p0, Sk2f(p3d01[0], p3d01[1]) / p3d01[2], midpoint, ww[0]); this->appendMonotonicConic(midpoint, Sk2f(p3d12[0], p3d12[1]) / p3d12[2], p2, ww[1]); return; @@ -645,10 +631,32 @@ void GrCCGeometry::conicTo(const SkPoint P[3], float w) { } void GrCCGeometry::appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, float w) { + SkASSERT(w >= 0); SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1])); - // Don't send curves to the GPU if we know they are nearly flat (or just very small). - if (are_collinear(p0, p1, p2)) { + Sk2f base = p2 - p0; + Sk2f baseAbs = base.abs(); + float baseWidth = baseAbs[0] + baseAbs[1]; + + // Find the height of the curve. Max height always occurs at T=.5 for conics. + Sk2f d = (p1 - p0) * SkNx_shuffle<1,0>(base); + float h1 = std::abs(d[1] - d[0]); // Height of p1 above the base. + float ht = h1*w, hs = 1 + w; // Height of the conic = ht/hs. + + if (ht < (baseWidth*hs) * kFlatnessThreshold) { // i.e. ht/hs < baseWidth * kFlatnessThreshold + // We are flat. (See rationale in are_collinear.) + this->appendLine(p2); + return; + } + + if (w > 1 && h1*hs - ht < baseWidth*hs) { // i.e. w > 1 && h1 - ht/hs < baseWidth + // If we get within 1px of p1 when w > 1, we will pick up artifacts from the implicit + // function's reflection. Chop at max height (T=.5) and draw a triangle instead. + Sk2f p1w = p1*w; + Sk2f ab = p0 + p1w; + Sk2f bc = p1w + p2; + Sk2f highpoint = (ab + bc) / (2*(1 + w)); + this->appendLine(highpoint); this->appendLine(p2); return; } |