mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 13:43:43 +00:00
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:
parent
6377257f8c
commit
a8c0030d80
4 changed files with 73 additions and 43 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue