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:
Mira Grudzinska 2024-05-13 17:07:39 +02:00 committed by Hermet Park
parent 0fb37ea195
commit 6dc8a3b093
4 changed files with 130 additions and 20 deletions

View file

@ -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)
{
return (fabsf(a - b) < FLT_EPSILON);

View file

@ -520,17 +520,13 @@ static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo
if (ctx->repeater) {
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);
} else {
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);
}
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) {
auto group = static_cast<LottieGroup*>(*g);
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);
}
}
@ -1095,7 +1091,7 @@ static void _updateMaskings(LottieLayer* layer, float frameNo, LottieExpressions
auto shape = Shape::gen().release();
shape->fill(255, 255, 255, mask->opacity(frameNo));
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);
}
auto method = mask->method;

View file

@ -109,12 +109,12 @@ public:
}
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);
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 {
TVGERR("LOTTIE", "Failed dispatching PathSet!");
return false;

View file

@ -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>
uint32_t _bsearch(T* frames, float frameNo)
{
@ -456,21 +529,33 @@ struct LottiePathSet : LottieProperty
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 (roundness > ROUNDNESS_EPSILON) {
_handleCorners(value, cmds, pts, transform, roundness);
return true;
}
_copy(value, cmds);
_copy(value, pts, transform);
return true;
}
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, pts, transform);
return true;
}
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, pts, transform);
return true;
@ -478,43 +563,66 @@ struct LottiePathSet : LottieProperty
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, pts, transform);
return true;
}
//interpolate
_copy(frame->value, cmds);
auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
if (frame->interpolator) t = frame->interpolator->progress(t);
if (frame->hold) {
if (t < 1.0f) _copy(frame->value, pts, transform);
else _copy((frame + 1)->value, pts, transform);
if (roundness > ROUNDNESS_EPSILON) {
_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;
}
auto s = frame->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) {
auto pt = mathLerp(*s, *e, t);
if (transform) mathMultiply(&pt,transform);
if (transform) mathMultiply(&pt, transform);
pts.push(pt);
}
_copy(frame->value, cmds);
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 (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() {}