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
This commit is contained in:
Mira Grudzinska 2024-02-16 23:08:10 +01:00 committed by Hermet Park
parent 6377257f8c
commit a8c0030d80
4 changed files with 73 additions and 43 deletions

View file

@ -29,7 +29,7 @@
/* Internal Class Implementation */ /* 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. /* 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 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<typename LengthFunc>
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<typename LengthFunc>
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 */ /* External Class Implementation */
/************************************************************************/ /************************************************************************/
@ -48,7 +101,7 @@ static float _lineLength(const Point& pt1, const Point& pt2)
namespace tvg 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; auto c = (cur.ctrl1.x + cur.ctrl2.x) * 0.5f;
left.ctrl1.x = (cur.start.x + cur.ctrl1.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) float bezLength(const Bezier& cur)
{ {
Bezier left, right; return _bezLength(cur, _lineLength);
auto len = _lineLength(cur.start, cur.ctrl1) + _lineLength(cur.ctrl1, cur.ctrl2) + _lineLength(cur.ctrl2, cur.end); }
auto chord = _lineLength(cur.start, cur.end);
if (fabsf(len - chord) > BEZIER_EPSILON) {
bezSplit(cur, left, right); float bezLengthApprox(const Bezier& cur)
return bezLength(left) + bezLength(right); {
} return _bezLength(cur, _lineLengthApprox);
return len;
} }
@ -110,31 +161,13 @@ void bezSplitLeft(Bezier& cur, float at, Bezier& left)
float bezAt(const Bezier& bz, float at, float length) float bezAt(const Bezier& bz, float at, float length)
{ {
auto biggest = 1.0f; return _bezAt(bz, at, length, _lineLength);
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) { float bezAtApprox(const Bezier& bz, float at, float length)
auto right = bz; {
Bezier left; return _bezAt(bz, at, length, _lineLengthApprox);
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;
} }

View file

@ -44,6 +44,8 @@ void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right);
Point bezPointAt(const Bezier& bz, float t); Point bezPointAt(const Bezier& bz, float t);
float bezAngleAt(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_ #endif //_TVG_BEZIER_H_

View file

@ -158,7 +158,7 @@ struct LottieVectorFrame
if (hasTangent) { if (hasTangent) {
Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; 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); return bezPointAt(bz, t);
} else { } else {
return mathLerp(value, next->value, t); return mathLerp(value, next->value, t);
@ -171,14 +171,14 @@ struct LottieVectorFrame
auto t = (frameNo - no) / (next->no - no); auto t = (frameNo - no) / (next->no - no);
if (interpolator) t = interpolator->progress(t); if (interpolator) t = interpolator->progress(t);
Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; 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); return -bezAngleAt(bz, t);
} }
void prepare(LottieVectorFrame* next) void prepare(LottieVectorFrame* next)
{ {
Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
length = bezLength(bz); length = bezLengthApprox(bz);
} }
}; };

View file

@ -37,13 +37,8 @@ struct Line
static float _lineLength(const Point& pt1, const Point& pt2) 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}; Point diff = {pt2.x - pt1.x, pt2.y - pt1.y};
if (diff.x < 0) diff.x = -diff.x; return sqrtf(diff.x * diff.x + diff.y * diff.y);
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);
} }
@ -388,7 +383,7 @@ static float _outlineLength(const RenderShape* rshape)
break; break;
} }
case PathCommand::CubicTo: { case PathCommand::CubicTo: {
length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)}); length += bezLengthApprox({*(pts - 1), *pts, *(pts + 1), *(pts + 2)});
pts += 3; pts += 3;
break; break;
} }