diff --git a/src/common/tvgMath.h b/src/common/tvgMath.h index 60ea1d21b0..62a85261eb 100644 --- a/src/common/tvgMath.h +++ b/src/common/tvgMath.h @@ -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); @@ -116,6 +122,17 @@ static inline void mathTransform(Matrix* transform, Point* coord) } +static inline Point mathTransform(Matrix* transform, const Point& coord) +{ + if (!transform) return coord; + + auto x = coord.x * transform->e11 + coord.y * transform->e12 + transform->e13; + auto y = coord.x * transform->e21 + coord.y * transform->e22 + transform->e23; + + return {x, y}; +} + + static inline void mathScale(Matrix* m, float sx, float sy) { m->e11 *= sx; @@ -169,6 +186,12 @@ static inline float mathLength(const Point* a, const Point* b) } +static inline float mathLength(const Point& a) +{ + return sqrtf(a.x * a.x + a.y * a.y); +} + + static inline Point operator-(const Point& lhs, const Point& rhs) { return {lhs.x - rhs.x, lhs.y - rhs.y}; diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index 32d40a2b4c..a6337df3c5 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -520,15 +520,14 @@ 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!"); + if (ctx->roundness > ROUNDNESS_EPSILON && P(merging)->rs.stroke) { P(merging)->rs.stroke->join = StrokeJoin::Round; } } @@ -587,7 +586,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(*g); for (auto p = group->children.begin(); p < group->children.end(); ++p) { - if (static_cast(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, exps)) { + if (static_cast(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, 0.0f, exps)) { P(shape)->update(RenderUpdateFlag::Path); } } @@ -1032,7 +1031,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; diff --git a/src/loaders/lottie/tvgLottieExpressions.h b/src/loaders/lottie/tvgLottieExpressions.h index 8dd6ca27cb..a88d771696 100644 --- a/src/loaders/lottie/tvgLottieExpressions.h +++ b/src/loaders/lottie/tvgLottieExpressions.h @@ -109,12 +109,12 @@ struct LottieExpressions } template - bool result(float frameNo, Array& cmds, Array& pts, Matrix* transform, LottieExpression* exp) + bool result(float frameNo, Array& cmds, Array& pts, Matrix* transform, float roundness, LottieExpression* exp) { auto bm_rt = evaluate(frameNo, exp); if (auto pathset = static_cast(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; diff --git a/src/loaders/lottie/tvgLottieProperty.h b/src/loaders/lottie/tvgLottieProperty.h index 34ac4310f6..dd80b6ea11 100644 --- a/src/loaders/lottie/tvgLottieProperty.h +++ b/src/loaders/lottie/tvgLottieProperty.h @@ -30,6 +30,7 @@ #include "tvgLottieInterpolator.h" #include "tvgLottieExpressions.h" +#define ROUNDNESS_EPSILON 1.0f struct LottieFont; struct LottieLayer; @@ -455,11 +456,14 @@ struct LottiePathSet : LottieProperty return (*frames)[frames->count]; } - bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform) + bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, float roundness) { if (!frames) { - _copy(value, cmds); - _copy(value, pts, transform); + if (roundness > ROUNDNESS_EPSILON) _handleCorners(cmds, pts, transform, roundness); + else { + _copy(value, cmds); + _copy(value, pts, transform); + } return true; } @@ -507,16 +511,88 @@ struct LottiePathSet : LottieProperty } - bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, LottieExpressions* exps) + bool operator()(float frameNo, Array& cmds, Array& 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(frameNo, cmds, pts, transform, exp)) return true; + if (exps->result(frameNo, cmds, pts, transform, roundness, exp)) return true; } - return operator()(frameNo, cmds, pts, transform); + return operator()(frameNo, cmds, pts, transform, roundness); } void prepare() {} + +private: + void _roundCorner(Array& cmds, Array& 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); + } + + void _handleCorners(Array& cmds, Array& pts, Matrix* transform, float roundness) + { + cmds.reserve(value.cmdsCnt * 2); + pts.reserve((uint16_t)(value.ptsCnt * 1.5)); + auto ptsCnt = pts.count; + + for (auto iCmds = 0, iPts = 0; iCmds < value.cmdsCnt; ++iCmds) { + auto startIndex = 0; + switch (value.cmds[iCmds]) { + case PathCommand::MoveTo: { + startIndex = pts.count; + cmds.push(PathCommand::MoveTo); + pts.push(value.pts[iPts++]); + break; + } + case PathCommand::CubicTo: { + auto& prev = value.pts[iPts - 1]; + auto& curr = value.pts[iPts + 2]; + if (iCmds < value.cmdsCnt - 1 && + (mathZero(value.pts[iPts - 1] - value.pts[iPts]) || + mathZero(value.pts[iPts + 1] - value.pts[iPts + 2]))) { + if (value.cmds[iCmds + 1] == PathCommand::CubicTo && + (mathZero(value.pts[iPts + 2] - value.pts[iPts + 3]) || + mathZero(value.pts[iPts + 4] - value.pts[iPts + 5]))) { + _roundCorner(cmds, pts, prev, curr, value.pts[iPts + 5], roundness); + iPts += 3; + break; + } else if (value.cmds[iCmds + 1] == PathCommand::Close) { + _roundCorner(cmds, pts, prev, curr, value.pts[2], roundness); + pts[startIndex] = pts.last(); + iPts += 3; + break; + } + } + cmds.push(PathCommand::CubicTo); + pts.push(value.pts[iPts++]); + pts.push(value.pts[iPts++]); + pts.push(value.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]); + } + } };