From a8c0030d807755f71a2f6d602c5adcec1738ffa3 Mon Sep 17 00:00:00 2001 From: Mira Grudzinska Date: Fri, 16 Feb 2024 23:08:10 +0100 Subject: [PATCH] sw_engine: Increasing accuracy for dashed curves Dashed curves require greater precision in calculating their lengths and while splitting Bezier curves. Otherwise, it results in visual discrepancies compared to the expected outcomes. Approximate functions 'bezLengthApprox' and 'bezAtApprox' used for calculations in the lottie loader. issue: https://github.com/thorvg/thorvg/issues/1686 --- src/common/tvgBezier.cpp | 99 +++++++++++++++++--------- src/common/tvgBezier.h | 2 + src/loaders/lottie/tvgLottieProperty.h | 6 +- src/renderer/sw_engine/tvgSwShape.cpp | 9 +-- 4 files changed, 73 insertions(+), 43 deletions(-) diff --git a/src/common/tvgBezier.cpp b/src/common/tvgBezier.cpp index db518537..068004bc 100644 --- a/src/common/tvgBezier.cpp +++ b/src/common/tvgBezier.cpp @@ -29,7 +29,7 @@ /* Internal Class Implementation */ /************************************************************************/ -static float _lineLength(const Point& pt1, const Point& pt2) +static float _lineLengthApprox(const Point& pt1, const Point& pt2) { /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. With alpha = 1, beta = 3/8, giving results with the largest error less @@ -41,6 +41,59 @@ static float _lineLength(const Point& pt1, const Point& pt2) } +static float _lineLength(const Point& pt1, const Point& pt2) +{ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + return sqrtf(diff.x * diff.x + diff.y * diff.y); +} + + +template +float _bezLength(const Bezier& cur, LengthFunc lineLengthFunc) +{ + Bezier left, right; + auto len = lineLengthFunc(cur.start, cur.ctrl1) + lineLengthFunc(cur.ctrl1, cur.ctrl2) + lineLengthFunc(cur.ctrl2, cur.end); + auto chord = lineLengthFunc(cur.start, cur.end); + + if (fabsf(len - chord) > BEZIER_EPSILON) { + tvg::bezSplit(cur, left, right); + return _bezLength(left, lineLengthFunc) + _bezLength(right, lineLengthFunc); + } + return len; +} + + +template +float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc) +{ + auto biggest = 1.0f; + auto smallest = 0.0f; + auto t = 0.5f; + + //just in case to prevent an infinite loop + if (at <= 0) return 0.0f; + if (at >= length) return 1.0f; + + while (true) { + auto right = bz; + Bezier left; + bezSplitLeft(right, t, left); + length = _bezLength(left, lineLengthFunc); + if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) { + break; + } + if (length < at) { + smallest = t; + t = (t + biggest) * 0.5f; + } else { + biggest = t; + t = (smallest + t) * 0.5f; + } + } + return t; +} + + /************************************************************************/ /* External Class Implementation */ /************************************************************************/ @@ -48,7 +101,7 @@ static float _lineLength(const Point& pt1, const Point& pt2) namespace tvg { -void bezSplit(const Bezier&cur, Bezier& left, Bezier& right) +void bezSplit(const Bezier& cur, Bezier& left, Bezier& right) { auto c = (cur.ctrl1.x + cur.ctrl2.x) * 0.5f; left.ctrl1.x = (cur.start.x + cur.ctrl1.x) * 0.5f; @@ -72,15 +125,13 @@ void bezSplit(const Bezier&cur, Bezier& left, Bezier& right) float bezLength(const Bezier& cur) { - Bezier left, right; - auto len = _lineLength(cur.start, cur.ctrl1) + _lineLength(cur.ctrl1, cur.ctrl2) + _lineLength(cur.ctrl2, cur.end); - auto chord = _lineLength(cur.start, cur.end); + return _bezLength(cur, _lineLength); +} - if (fabsf(len - chord) > BEZIER_EPSILON) { - bezSplit(cur, left, right); - return bezLength(left) + bezLength(right); - } - return len; + +float bezLengthApprox(const Bezier& cur) +{ + return _bezLength(cur, _lineLengthApprox); } @@ -110,31 +161,13 @@ void bezSplitLeft(Bezier& cur, float at, Bezier& left) float bezAt(const Bezier& bz, float at, float length) { - auto biggest = 1.0f; - auto smallest = 0.0f; - auto t = 0.5f; + return _bezAt(bz, at, length, _lineLength); +} - //just in case to prevent an infinite loop - if (at <= 0) return 0.0f; - if (at >= length) return 1.0f; - while (true) { - auto right = bz; - Bezier left; - bezSplitLeft(right, t, left); - length = bezLength(left); - if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) { - break; - } - if (length < at) { - smallest = t; - t = (t + biggest) * 0.5f; - } else { - biggest = t; - t = (smallest + t) * 0.5f; - } - } - return t; +float bezAtApprox(const Bezier& bz, float at, float length) +{ + return _bezAt(bz, at, length, _lineLengthApprox); } diff --git a/src/common/tvgBezier.h b/src/common/tvgBezier.h index cb2766c5..80a19925 100644 --- a/src/common/tvgBezier.h +++ b/src/common/tvgBezier.h @@ -44,6 +44,8 @@ void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right); Point bezPointAt(const Bezier& bz, float t); float bezAngleAt(const Bezier& bz, float t); +float bezLengthApprox(const Bezier& cur); +float bezAtApprox(const Bezier& bz, float at, float length); } #endif //_TVG_BEZIER_H_ diff --git a/src/loaders/lottie/tvgLottieProperty.h b/src/loaders/lottie/tvgLottieProperty.h index 0a7f4bc6..b0e3fd7c 100644 --- a/src/loaders/lottie/tvgLottieProperty.h +++ b/src/loaders/lottie/tvgLottieProperty.h @@ -158,7 +158,7 @@ struct LottieVectorFrame if (hasTangent) { Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; - t = bezAt(bz, t * length, length); + t = bezAtApprox(bz, t * length, length); return bezPointAt(bz, t); } else { return mathLerp(value, next->value, t); @@ -171,14 +171,14 @@ struct LottieVectorFrame auto t = (frameNo - no) / (next->no - no); if (interpolator) t = interpolator->progress(t); Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; - t = bezAt(bz, t * length, length); + t = bezAtApprox(bz, t * length, length); return -bezAngleAt(bz, t); } void prepare(LottieVectorFrame* next) { Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; - length = bezLength(bz); + length = bezLengthApprox(bz); } }; diff --git a/src/renderer/sw_engine/tvgSwShape.cpp b/src/renderer/sw_engine/tvgSwShape.cpp index d3b715ea..8cad462f 100644 --- a/src/renderer/sw_engine/tvgSwShape.cpp +++ b/src/renderer/sw_engine/tvgSwShape.cpp @@ -37,13 +37,8 @@ struct Line static float _lineLength(const Point& pt1, const Point& pt2) { - /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. - With alpha = 1, beta = 3/8, giving results with the largest error less - than 7% compared to the exact value. */ Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; - if (diff.x < 0) diff.x = -diff.x; - if (diff.y < 0) diff.y = -diff.y; - return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f); + return sqrtf(diff.x * diff.x + diff.y * diff.y); } @@ -388,7 +383,7 @@ static float _outlineLength(const RenderShape* rshape) break; } case PathCommand::CubicTo: { - length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)}); + length += bezLengthApprox({*(pts - 1), *pts, *(pts + 1), *(pts + 2)}); pts += 3; break; }