mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-09 22:23:27 +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 */
|
/* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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_
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue