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 */
/************************************************************************/
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<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 */
/************************************************************************/
@ -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);
}

View file

@ -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_

View file

@ -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);
}
};

View file

@ -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;
}