mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-14 12:04:29 +00:00
lottie: handle roundness in path
Implemented rounding of corners between bezier curves that are straight lines. @Issue: https://github.com/thorvg/thorvg/issues/2230
This commit is contained in:
parent
0fb37ea195
commit
6dc8a3b093
4 changed files with 130 additions and 20 deletions
|
@ -62,6 +62,12 @@ static inline bool mathZero(float a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline bool mathZero(const Point& p)
|
||||||
|
{
|
||||||
|
return mathZero(p.x) && mathZero(p.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline bool mathEqual(float a, float b)
|
static inline bool mathEqual(float a, float b)
|
||||||
{
|
{
|
||||||
return (fabsf(a - b) < FLT_EPSILON);
|
return (fabsf(a - b) < FLT_EPSILON);
|
||||||
|
|
|
@ -520,17 +520,13 @@ static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo
|
||||||
|
|
||||||
if (ctx->repeater) {
|
if (ctx->repeater) {
|
||||||
auto p = Shape::gen();
|
auto p = Shape::gen();
|
||||||
path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform, exps);
|
path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform, ctx->roundness, exps);
|
||||||
_repeat(parent, std::move(p), ctx);
|
_repeat(parent, std::move(p), ctx);
|
||||||
} else {
|
} else {
|
||||||
auto merging = _draw(parent, ctx);
|
auto merging = _draw(parent, ctx);
|
||||||
if (path->pathset(frameNo, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, ctx->transform, exps)) {
|
if (path->pathset(frameNo, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, ctx->transform, ctx->roundness, exps)) {
|
||||||
P(merging)->update(RenderUpdateFlag::Path);
|
P(merging)->update(RenderUpdateFlag::Path);
|
||||||
}
|
}
|
||||||
if (ctx->roundness > 1.0f && P(merging)->rs.stroke) {
|
|
||||||
TVGERR("LOTTIE", "FIXME: Path roundesss should be applied properly!");
|
|
||||||
P(merging)->rs.stroke->join = StrokeJoin::Round;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -587,7 +583,7 @@ static void _updateText(LottieGroup* parent, LottieObject** child, float frameNo
|
||||||
for (auto g = glyph->children.begin(); g < glyph->children.end(); ++g) {
|
for (auto g = glyph->children.begin(); g < glyph->children.end(); ++g) {
|
||||||
auto group = static_cast<LottieGroup*>(*g);
|
auto group = static_cast<LottieGroup*>(*g);
|
||||||
for (auto p = group->children.begin(); p < group->children.end(); ++p) {
|
for (auto p = group->children.begin(); p < group->children.end(); ++p) {
|
||||||
if (static_cast<LottiePath*>(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, exps)) {
|
if (static_cast<LottiePath*>(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, 0.0f, exps)) {
|
||||||
P(shape)->update(RenderUpdateFlag::Path);
|
P(shape)->update(RenderUpdateFlag::Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1095,7 +1091,7 @@ static void _updateMaskings(LottieLayer* layer, float frameNo, LottieExpressions
|
||||||
auto shape = Shape::gen().release();
|
auto shape = Shape::gen().release();
|
||||||
shape->fill(255, 255, 255, mask->opacity(frameNo));
|
shape->fill(255, 255, 255, mask->opacity(frameNo));
|
||||||
shape->transform(layer->cache.matrix);
|
shape->transform(layer->cache.matrix);
|
||||||
if (mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, exps)) {
|
if (mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, 0.0f, exps)) {
|
||||||
P(shape)->update(RenderUpdateFlag::Path);
|
P(shape)->update(RenderUpdateFlag::Path);
|
||||||
}
|
}
|
||||||
auto method = mask->method;
|
auto method = mask->method;
|
||||||
|
|
|
@ -109,12 +109,12 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Property>
|
template<typename Property>
|
||||||
bool result(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, LottieExpression* exp)
|
bool result(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness, LottieExpression* exp)
|
||||||
{
|
{
|
||||||
auto bm_rt = evaluate(frameNo, exp);
|
auto bm_rt = evaluate(frameNo, exp);
|
||||||
|
|
||||||
if (auto pathset = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
|
if (auto pathset = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
|
||||||
(*pathset)(frameNo, cmds, pts, transform);
|
(*pathset)(frameNo, cmds, pts, transform, roundness);
|
||||||
} else {
|
} else {
|
||||||
TVGERR("LOTTIE", "Failed dispatching PathSet!");
|
TVGERR("LOTTIE", "Failed dispatching PathSet!");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -239,6 +239,79 @@ static void _copy(PathSet& pathset, Array<PathCommand>& outCmds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void _roundCorner(Array<PathCommand>& cmds, Array<Point>& pts, const Point& prev, const Point& curr, const Point& next, float roundness)
|
||||||
|
{
|
||||||
|
auto lenPrev = mathLength(prev - curr);
|
||||||
|
auto rPrev = lenPrev > 0.0f ? 0.5f * mathMin(lenPrev * 0.5f, roundness) / lenPrev : 0.0f;
|
||||||
|
auto lenNext = mathLength(next - curr);
|
||||||
|
auto rNext = lenNext > 0.0f ? 0.5f * mathMin(lenNext * 0.5f, roundness) / lenNext : 0.0f;
|
||||||
|
|
||||||
|
auto dPrev = rPrev * (curr - prev);
|
||||||
|
auto dNext = rNext * (curr - next);
|
||||||
|
|
||||||
|
pts.push(curr - 2.0f * dPrev);
|
||||||
|
pts.push(curr - dPrev);
|
||||||
|
pts.push(curr - dNext);
|
||||||
|
pts.push(curr - 2.0f * dNext);
|
||||||
|
cmds.push(PathCommand::LineTo);
|
||||||
|
cmds.push(PathCommand::CubicTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void _handleCorners(const PathSet& path, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness)
|
||||||
|
{
|
||||||
|
cmds.reserve(path.cmdsCnt * 2);
|
||||||
|
pts.reserve((uint16_t)(path.ptsCnt * 1.5));
|
||||||
|
auto ptsCnt = pts.count;
|
||||||
|
|
||||||
|
auto startIndex = 0;
|
||||||
|
for (auto iCmds = 0, iPts = 0; iCmds < path.cmdsCnt; ++iCmds) {
|
||||||
|
switch (path.cmds[iCmds]) {
|
||||||
|
case PathCommand::MoveTo: {
|
||||||
|
startIndex = pts.count;
|
||||||
|
cmds.push(PathCommand::MoveTo);
|
||||||
|
pts.push(path.pts[iPts++]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PathCommand::CubicTo: {
|
||||||
|
auto& prev = path.pts[iPts - 1];
|
||||||
|
auto& curr = path.pts[iPts + 2];
|
||||||
|
if (iCmds < path.cmdsCnt - 1 &&
|
||||||
|
mathZero(path.pts[iPts - 1] - path.pts[iPts]) &&
|
||||||
|
mathZero(path.pts[iPts + 1] - path.pts[iPts + 2])) {
|
||||||
|
if (path.cmds[iCmds + 1] == PathCommand::CubicTo &&
|
||||||
|
mathZero(path.pts[iPts + 2] - path.pts[iPts + 3]) &&
|
||||||
|
mathZero(path.pts[iPts + 4] - path.pts[iPts + 5])) {
|
||||||
|
_roundCorner(cmds, pts, prev, curr, path.pts[iPts + 5], roundness);
|
||||||
|
iPts += 3;
|
||||||
|
break;
|
||||||
|
} else if (path.cmds[iCmds + 1] == PathCommand::Close) {
|
||||||
|
_roundCorner(cmds, pts, prev, curr, path.pts[2], roundness);
|
||||||
|
pts[startIndex] = pts.last();
|
||||||
|
iPts += 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmds.push(PathCommand::CubicTo);
|
||||||
|
pts.push(path.pts[iPts++]);
|
||||||
|
pts.push(path.pts[iPts++]);
|
||||||
|
pts.push(path.pts[iPts++]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PathCommand::Close: {
|
||||||
|
cmds.push(PathCommand::Close);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (transform) {
|
||||||
|
for (auto i = ptsCnt; i < pts.count; ++i)
|
||||||
|
mathTransform(transform, &pts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
uint32_t _bsearch(T* frames, float frameNo)
|
uint32_t _bsearch(T* frames, float frameNo)
|
||||||
{
|
{
|
||||||
|
@ -456,21 +529,33 @@ struct LottiePathSet : LottieProperty
|
||||||
return (*frames)[frames->count];
|
return (*frames)[frames->count];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform)
|
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness)
|
||||||
{
|
{
|
||||||
if (!frames) {
|
if (!frames) {
|
||||||
|
if (roundness > ROUNDNESS_EPSILON) {
|
||||||
|
_handleCorners(value, cmds, pts, transform, roundness);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
_copy(value, cmds);
|
_copy(value, cmds);
|
||||||
_copy(value, pts, transform);
|
_copy(value, pts, transform);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frames->count == 1 || frameNo <= frames->first().no) {
|
if (frames->count == 1 || frameNo <= frames->first().no) {
|
||||||
|
if (roundness > ROUNDNESS_EPSILON) {
|
||||||
|
_handleCorners(frames->first().value, cmds, pts, transform, roundness);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
_copy(frames->first().value, cmds);
|
_copy(frames->first().value, cmds);
|
||||||
_copy(frames->first().value, pts, transform);
|
_copy(frames->first().value, pts, transform);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frameNo >= frames->last().no) {
|
if (frameNo >= frames->last().no) {
|
||||||
|
if (roundness > ROUNDNESS_EPSILON) {
|
||||||
|
_handleCorners(frames->last().value, cmds, pts, transform, roundness);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
_copy(frames->last().value, cmds);
|
_copy(frames->last().value, cmds);
|
||||||
_copy(frames->last().value, pts, transform);
|
_copy(frames->last().value, pts, transform);
|
||||||
return true;
|
return true;
|
||||||
|
@ -478,43 +563,66 @@ struct LottiePathSet : LottieProperty
|
||||||
|
|
||||||
auto frame = frames->data + _bsearch(frames, frameNo);
|
auto frame = frames->data + _bsearch(frames, frameNo);
|
||||||
|
|
||||||
if (frame->no == frameNo) {
|
if (mathEqual(frame->no, frameNo)) {
|
||||||
|
if (roundness > ROUNDNESS_EPSILON) {
|
||||||
|
_handleCorners(frame->value, cmds, pts, transform, roundness);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
_copy(frame->value, cmds);
|
_copy(frame->value, cmds);
|
||||||
_copy(frame->value, pts, transform);
|
_copy(frame->value, pts, transform);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//interpolate
|
//interpolate
|
||||||
_copy(frame->value, cmds);
|
|
||||||
|
|
||||||
auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
|
auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
|
||||||
if (frame->interpolator) t = frame->interpolator->progress(t);
|
if (frame->interpolator) t = frame->interpolator->progress(t);
|
||||||
|
|
||||||
if (frame->hold) {
|
if (frame->hold) {
|
||||||
if (t < 1.0f) _copy(frame->value, pts, transform);
|
if (roundness > ROUNDNESS_EPSILON) {
|
||||||
else _copy((frame + 1)->value, pts, transform);
|
_handleCorners((frame + ((t < 1.0f) ? 0 : 1))->value, cmds, pts, transform, roundness);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_copy((frame + ((t < 1.0f) ? 0 : 1))->value, cmds);
|
||||||
|
_copy((frame)->value, pts, transform);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto s = frame->value.pts;
|
auto s = frame->value.pts;
|
||||||
auto e = (frame + 1)->value.pts;
|
auto e = (frame + 1)->value.pts;
|
||||||
|
|
||||||
|
if (roundness > ROUNDNESS_EPSILON) {
|
||||||
|
PathSet interpPathSet = frame->value;
|
||||||
|
interpPathSet.pts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point));
|
||||||
|
|
||||||
|
auto p = interpPathSet.pts;
|
||||||
|
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) {
|
||||||
|
auto pt = mathLerp(*s, *e, t);
|
||||||
|
if (transform) mathMultiply(&pt, transform);
|
||||||
|
*p = pt;
|
||||||
|
}
|
||||||
|
_handleCorners(interpPathSet, cmds, pts, nullptr, roundness);
|
||||||
|
free(interpPathSet.pts);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) {
|
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) {
|
||||||
auto pt = mathLerp(*s, *e, t);
|
auto pt = mathLerp(*s, *e, t);
|
||||||
if (transform) mathMultiply(&pt,transform);
|
if (transform) mathMultiply(&pt, transform);
|
||||||
pts.push(pt);
|
pts.push(pt);
|
||||||
}
|
}
|
||||||
|
_copy(frame->value, cmds);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, LottieExpressions* exps)
|
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness, LottieExpressions* exps)
|
||||||
{
|
{
|
||||||
if (exps && (exp && exp->enabled)) {
|
if (exps && (exp && exp->enabled)) {
|
||||||
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
|
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
|
||||||
if (exps->result<LottiePathSet>(frameNo, cmds, pts, transform, exp)) return true;
|
if (exps->result<LottiePathSet>(frameNo, cmds, pts, transform, roundness, exp)) return true;
|
||||||
}
|
}
|
||||||
return operator()(frameNo, cmds, pts, transform);
|
return operator()(frameNo, cmds, pts, transform, roundness);
|
||||||
}
|
}
|
||||||
|
|
||||||
void prepare() {}
|
void prepare() {}
|
||||||
|
|
Loading…
Add table
Reference in a new issue