mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-14 12:04:29 +00:00
lottie: add offsetPath support
Due to the lack of an analytical solution for Bezier curves offsetting, a simple and computationally cheap approximation has been implemented. The algorithm shifts the segments connecting control points and determines new points based on their intersections. @issue: https://github.com/thorvg/thorvg/issues/2230
This commit is contained in:
parent
448d84ffb7
commit
90e7a50c8e
10 changed files with 367 additions and 50 deletions
|
@ -233,6 +233,17 @@ static inline Point operator/(const Point& lhs, const float rhs)
|
|||
}
|
||||
|
||||
|
||||
static inline Point normal(const Point& p1, const Point& p2)
|
||||
{
|
||||
auto dir = p2 - p1;
|
||||
auto len = length(dir);
|
||||
if (tvg::zero(len)) return {};
|
||||
|
||||
auto unitDir = dir / len;
|
||||
return {-unitDir.y, unitDir.x};
|
||||
}
|
||||
|
||||
|
||||
static inline void log(const Point& pt)
|
||||
{
|
||||
TVGLOG("COMMON", "Point: [%f %f]", pt.x, pt.y);
|
||||
|
|
|
@ -381,7 +381,7 @@ static void _repeat(LottieGroup* parent, Shape* path, RenderContext* ctx)
|
|||
}
|
||||
|
||||
|
||||
static void _appendRect(Shape* shape, float x, float y, float w, float h, float r, Matrix* transform, bool clockwise)
|
||||
static void _appendRect(Shape* shape, float x, float y, float w, float h, float r, const LottieOffsetModifier* offsetPath, Matrix* transform, bool clockwise)
|
||||
{
|
||||
//sharp rect
|
||||
if (tvg::zero(r)) {
|
||||
|
@ -407,7 +407,9 @@ static void _appendRect(Shape* shape, float x, float y, float w, float h, float
|
|||
points[i] *= *transform;
|
||||
}
|
||||
}
|
||||
shape->appendPath(commands, 5, points, 4);
|
||||
|
||||
if (offsetPath) offsetPath->modifyRect(commands, 5, points, 4, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, clockwise);
|
||||
else shape->appendPath(commands, 5, points, 4);
|
||||
//round rect
|
||||
} else {
|
||||
constexpr int cmdCnt = 10;
|
||||
|
@ -458,7 +460,9 @@ static void _appendRect(Shape* shape, float x, float y, float w, float h, float
|
|||
points[i] *= *transform;
|
||||
}
|
||||
}
|
||||
shape->appendPath(commands, cmdCnt, points, ptsCnt);
|
||||
|
||||
if (offsetPath) offsetPath->modifyRect(commands, cmdCnt, points, ptsCnt, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, clockwise);
|
||||
else shape->appendPath(commands, cmdCnt, points, ptsCnt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -479,17 +483,21 @@ void LottieBuilder::updateRect(LottieGroup* parent, LottieObject** child, float
|
|||
if (!ctx->repeaters.empty()) {
|
||||
auto shape = rect->pooling();
|
||||
shape->reset();
|
||||
_appendRect(shape, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, r, ctx->transform, rect->clockwise);
|
||||
_appendRect(shape, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, r, ctx->offsetPath, ctx->transform, rect->clockwise);
|
||||
_repeat(parent, shape, ctx);
|
||||
} else {
|
||||
_draw(parent, rect, ctx);
|
||||
_appendRect(ctx->merging, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, r, ctx->transform, rect->clockwise);
|
||||
_appendRect(ctx->merging, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, r, ctx->offsetPath, ctx->transform, rect->clockwise);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _appendCircle(Shape* shape, float cx, float cy, float rx, float ry, Matrix* transform, bool clockwise)
|
||||
static void _appendCircle(Shape* shape, float cx, float cy, float rx, float ry, const LottieOffsetModifier* offsetPath, Matrix* transform, bool clockwise)
|
||||
{
|
||||
if (offsetPath) offsetPath->modifyEllipse(rx, ry);
|
||||
|
||||
if (rx == 0.0f || ry == 0.0f) return;
|
||||
|
||||
auto rxKappa = rx * PATH_KAPPA;
|
||||
auto ryKappa = ry * PATH_KAPPA;
|
||||
|
||||
|
@ -536,11 +544,11 @@ void LottieBuilder::updateEllipse(LottieGroup* parent, LottieObject** child, flo
|
|||
if (!ctx->repeaters.empty()) {
|
||||
auto shape = ellipse->pooling();
|
||||
shape->reset();
|
||||
_appendCircle(shape, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->transform, ellipse->clockwise);
|
||||
_appendCircle(shape, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->offsetPath, ctx->transform, ellipse->clockwise);
|
||||
_repeat(parent, shape, ctx);
|
||||
} else {
|
||||
_draw(parent, ellipse, ctx);
|
||||
_appendCircle(ctx->merging, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->transform, ellipse->clockwise);
|
||||
_appendCircle(ctx->merging, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->offsetPath, ctx->transform, ellipse->clockwise);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -548,22 +556,22 @@ void LottieBuilder::updateEllipse(LottieGroup* parent, LottieObject** child, flo
|
|||
void LottieBuilder::updatePath(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
|
||||
{
|
||||
auto path = static_cast<LottiePath*>(*child);
|
||||
|
||||
//TODO: use direction for paths' offsetPath
|
||||
if (!ctx->repeaters.empty()) {
|
||||
auto shape = path->pooling();
|
||||
shape->reset();
|
||||
path->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, ctx->transform, ctx->roundness, exps);
|
||||
path->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, ctx->transform, ctx->roundness, ctx->offsetPath, exps);
|
||||
_repeat(parent, shape, ctx);
|
||||
} else {
|
||||
_draw(parent, path, ctx);
|
||||
if (path->pathset(frameNo, P(ctx->merging)->rs.path.cmds, P(ctx->merging)->rs.path.pts, ctx->transform, ctx->roundness, exps)) {
|
||||
if (path->pathset(frameNo, P(ctx->merging)->rs.path.cmds, P(ctx->merging)->rs.path.pts, ctx->transform, ctx->roundness, ctx->offsetPath, exps)) {
|
||||
P(ctx->merging)->update(RenderUpdateFlag::Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _updateStar(TVG_UNUSED LottieGroup* parent, LottiePolyStar* star, Matrix* transform, const LottieRoundnessModifier* roundness, float frameNo, Shape* merging, LottieExpressions* exps)
|
||||
static void _updateStar(TVG_UNUSED LottieGroup* parent, LottiePolyStar* star, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, float frameNo, Shape* merging, LottieExpressions* exps)
|
||||
{
|
||||
static constexpr auto POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f;
|
||||
|
||||
|
@ -585,7 +593,7 @@ static void _updateStar(TVG_UNUSED LottieGroup* parent, LottiePolyStar* star, Ma
|
|||
bool roundedCorner = roundness && (tvg::zero(innerRoundness) || tvg::zero(outerRoundness));
|
||||
|
||||
Shape* shape;
|
||||
if (roundedCorner) {
|
||||
if (roundedCorner || offsetPath) {
|
||||
shape = star->pooling();
|
||||
shape->reset();
|
||||
} else {
|
||||
|
@ -679,11 +687,19 @@ static void _updateStar(TVG_UNUSED LottieGroup* parent, LottiePolyStar* star, Ma
|
|||
}
|
||||
shape->close();
|
||||
|
||||
if (roundedCorner) roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, outerRoundness, hasRoundness);
|
||||
if (roundedCorner) {
|
||||
if (offsetPath) {
|
||||
auto intermediate = Shape::gen();
|
||||
roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, outerRoundness, hasRoundness);
|
||||
offsetPath->modifyPolystar(P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, star->clockwise);
|
||||
} else {
|
||||
roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, outerRoundness, hasRoundness);
|
||||
}
|
||||
} else if (offsetPath) offsetPath->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, star->clockwise);
|
||||
}
|
||||
|
||||
|
||||
static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, const LottieRoundnessModifier* roundness, float frameNo, Shape* merging, LottieExpressions* exps)
|
||||
static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, float frameNo, Shape* merging, LottieExpressions* exps)
|
||||
{
|
||||
static constexpr auto POLYGON_MAGIC_NUMBER = 0.25f;
|
||||
|
||||
|
@ -702,7 +718,7 @@ static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* tr
|
|||
angle += anglePerPoint * direction;
|
||||
|
||||
Shape* shape;
|
||||
if (roundedCorner) {
|
||||
if (roundedCorner || offsetPath) {
|
||||
shape = star->pooling();
|
||||
shape->reset();
|
||||
} else {
|
||||
|
@ -757,7 +773,15 @@ static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* tr
|
|||
}
|
||||
shape->close();
|
||||
|
||||
if (roundedCorner) roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, 0.0f, false);
|
||||
if (roundedCorner) {
|
||||
if (offsetPath) {
|
||||
auto intermediate = Shape::gen();
|
||||
roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, 0.0f, false);
|
||||
offsetPath->modifyPolystar(P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, star->clockwise);
|
||||
} else {
|
||||
roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, 0.0f, false);
|
||||
}
|
||||
} else if (offsetPath) offsetPath->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, star->clockwise);
|
||||
}
|
||||
|
||||
|
||||
|
@ -779,13 +803,13 @@ void LottieBuilder::updatePolystar(LottieGroup* parent, LottieObject** child, fl
|
|||
if (!ctx->repeaters.empty()) {
|
||||
auto shape = star->pooling();
|
||||
shape->reset();
|
||||
if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, frameNo, shape, exps);
|
||||
else _updatePolygon(parent, star, identity ? nullptr : &matrix, ctx->roundness, frameNo, shape, exps);
|
||||
if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, shape, exps);
|
||||
else _updatePolygon(parent, star, identity ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, shape, exps);
|
||||
_repeat(parent, shape, ctx);
|
||||
} else {
|
||||
_draw(parent, star, ctx);
|
||||
if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, frameNo, ctx->merging, exps);
|
||||
else _updatePolygon(parent, star, identity ? nullptr : &matrix, ctx->roundness, frameNo, ctx->merging, exps);
|
||||
if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, ctx->merging, exps);
|
||||
else _updatePolygon(parent, star, identity ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, ctx->merging, exps);
|
||||
P(ctx->merging)->update(RenderUpdateFlag::Path);
|
||||
}
|
||||
}
|
||||
|
@ -802,6 +826,13 @@ void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieOb
|
|||
}
|
||||
|
||||
|
||||
void LottieBuilder::updateOffsetPath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
|
||||
{
|
||||
auto offsetPath = static_cast<LottieOffsetPath*>(*child);
|
||||
if (!ctx->offsetPath) ctx->offsetPath = new LottieOffsetModifier(offsetPath->offset(frameNo, exps), offsetPath->miterLimit(frameNo, exps), offsetPath->join);
|
||||
}
|
||||
|
||||
|
||||
void LottieBuilder::updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
|
||||
{
|
||||
auto repeater = static_cast<LottieRepeater*>(*child);
|
||||
|
@ -905,6 +936,10 @@ void LottieBuilder::updateChildren(LottieGroup* parent, float frameNo, Inlist<Re
|
|||
updateRoundedCorner(parent, child, frameNo, contexts, ctx);
|
||||
break;
|
||||
}
|
||||
case LottieObject::OffsetPath: {
|
||||
updateOffsetPath(parent, child, frameNo, contexts, ctx);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
if (ctx->propagator->opacity() == 0) break;
|
||||
|
@ -1008,7 +1043,7 @@ void LottieBuilder::updateText(LottieLayer* layer, 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, nullptr)) {
|
||||
if (static_cast<LottiePath*>(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, nullptr, nullptr)) {
|
||||
P(shape)->update(RenderUpdateFlag::Path);
|
||||
}
|
||||
}
|
||||
|
@ -1097,7 +1132,7 @@ void LottieBuilder::updateMaskings(LottieLayer* layer, float frameNo)
|
|||
pShape->reset();
|
||||
pShape->fill(255, 255, 255, pMask->opacity(frameNo));
|
||||
pShape->transform(layer->cache.matrix);
|
||||
pMask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, exps);
|
||||
pMask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, nullptr, exps);
|
||||
|
||||
if (pMethod == CompositeMethod::SubtractMask || pMethod == CompositeMethod::InvAlphaMask) {
|
||||
layer->scene->composite(tvg::cast(pShape), CompositeMethod::InvAlphaMask);
|
||||
|
@ -1113,14 +1148,14 @@ void LottieBuilder::updateMaskings(LottieLayer* layer, float frameNo)
|
|||
|
||||
//Append the mask shape
|
||||
if (pMethod == method && (method == CompositeMethod::SubtractMask || method == CompositeMethod::DifferenceMask)) {
|
||||
mask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, exps);
|
||||
mask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, nullptr, exps);
|
||||
//Chain composition
|
||||
} else {
|
||||
auto shape = layer->pooling();
|
||||
shape->reset();
|
||||
shape->fill(255, 255, 255, mask->opacity(frameNo));
|
||||
shape->transform(layer->cache.matrix);
|
||||
mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, nullptr, exps);
|
||||
mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, nullptr, nullptr, exps);
|
||||
pShape->composite(tvg::cast(shape), method);
|
||||
pShape = shape;
|
||||
pMethod = method;
|
||||
|
|
|
@ -57,6 +57,7 @@ struct RenderContext
|
|||
Array<RenderRepeater> repeaters;
|
||||
Matrix* transform = nullptr;
|
||||
LottieRoundnessModifier* roundness = nullptr;
|
||||
LottieOffsetModifier* offsetPath = nullptr;
|
||||
bool fragmenting = false; //render context has been fragmented by filling
|
||||
bool reqFragment = false; //requirement to fragment the render context
|
||||
|
||||
|
@ -72,6 +73,7 @@ struct RenderContext
|
|||
PP(propagator)->unref();
|
||||
free(transform);
|
||||
delete(roundness);
|
||||
delete(offsetPath);
|
||||
}
|
||||
|
||||
RenderContext(const RenderContext& rhs, Shape* propagator, bool mergeable = false)
|
||||
|
@ -81,6 +83,7 @@ struct RenderContext
|
|||
this->propagator = propagator;
|
||||
this->repeaters = rhs.repeaters;
|
||||
if (rhs.roundness) this->roundness = new LottieRoundnessModifier(rhs.roundness->r);
|
||||
if (rhs.offsetPath) this->offsetPath = new LottieOffsetModifier(rhs.offsetPath->offset, rhs.offsetPath->miterLimit, rhs.offsetPath->join);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -122,6 +125,7 @@ private:
|
|||
void updateTrimpath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
|
||||
void updateRepeater(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
|
||||
void updateRoundedCorner(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
|
||||
void updateOffsetPath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
|
||||
|
||||
LottieExpressions* exps;
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@ struct LottieExpression;
|
|||
struct LottieComposition;
|
||||
struct LottieLayer;
|
||||
struct LottieRoundnessModifier;
|
||||
struct LottieOffsetModifier;
|
||||
|
||||
#ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT
|
||||
|
||||
|
@ -111,13 +112,13 @@ public:
|
|||
}
|
||||
|
||||
template<typename Property>
|
||||
bool result(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness, LottieExpression* exp)
|
||||
bool result(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, LottieExpression* exp)
|
||||
{
|
||||
auto bm_rt = evaluate(frameNo, exp);
|
||||
if (jerry_value_is_undefined(bm_rt)) return false;
|
||||
|
||||
if (auto pathset = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
|
||||
(*pathset)(frameNo, cmds, pts, transform, roundness);
|
||||
(*pathset)(frameNo, cmds, pts, transform, roundness, offsetPath);
|
||||
}
|
||||
jerry_value_free(bm_rt);
|
||||
return true;
|
||||
|
@ -156,7 +157,7 @@ struct LottieExpressions
|
|||
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Point&, LottieExpression*) { return false; }
|
||||
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED RGB24&, TVG_UNUSED LottieExpression*) { return false; }
|
||||
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Fill*, TVG_UNUSED LottieExpression*) { return false; }
|
||||
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Array<PathCommand>&, TVG_UNUSED Array<Point>&, TVG_UNUSED Matrix* transform, TVG_UNUSED const LottieRoundnessModifier*, TVG_UNUSED LottieExpression*) { return false; }
|
||||
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Array<PathCommand>&, TVG_UNUSED Array<Point>&, TVG_UNUSED Matrix* transform, TVG_UNUSED const LottieRoundnessModifier*, TVG_UNUSED const LottieOffsetModifier*, TVG_UNUSED LottieExpression*) { return false; }
|
||||
void update(TVG_UNUSED float) {}
|
||||
static LottieExpressions* instance() { return nullptr; }
|
||||
static void retrieve(TVG_UNUSED LottieExpressions* instance) {}
|
||||
|
|
|
@ -106,7 +106,8 @@ struct LottieObject
|
|||
Trimpath,
|
||||
Text,
|
||||
Repeater,
|
||||
RoundedCorner
|
||||
RoundedCorner,
|
||||
OffsetPath
|
||||
};
|
||||
|
||||
virtual ~LottieObject()
|
||||
|
@ -659,6 +660,19 @@ struct LottieRepeater : LottieObject
|
|||
};
|
||||
|
||||
|
||||
struct LottieOffsetPath : LottieObject
|
||||
{
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::OffsetPath;
|
||||
}
|
||||
|
||||
LottieFloat offset = 0.0f;
|
||||
LottieFloat miterLimit = 4.0f;
|
||||
StrokeJoin join = StrokeJoin::Miter;
|
||||
};
|
||||
|
||||
|
||||
struct LottieGroup : LottieObject, LottieRenderPooler<tvg::Shape>
|
||||
{
|
||||
LottieGroup();
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
*/
|
||||
|
||||
#include "tvgLottieModifier.h"
|
||||
#include "tvgMath.h"
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
|
@ -46,6 +45,115 @@ static void _roundCorner(Array<PathCommand>& cmds, Array<Point>& pts, const Poin
|
|||
cmds.push(PathCommand::CubicTo);
|
||||
}
|
||||
|
||||
|
||||
static bool _zero(const Point& p1, const Point& p2)
|
||||
{
|
||||
constexpr float epsilon = 1e-3f;
|
||||
return fabsf(p1.x / p2.x - 1.0f) < epsilon && fabsf(p1.y / p2.y - 1.0f) < epsilon;
|
||||
}
|
||||
|
||||
|
||||
static bool _intersect(const Line& line1, const Line& line2, Point& intersection, bool& inside)
|
||||
{
|
||||
if (_zero(line1.pt2, line2.pt1)) {
|
||||
intersection = line1.pt2;
|
||||
inside = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr float epsilon = 1e-3f;
|
||||
float denom = (line1.pt2.x - line1.pt1.x) * (line2.pt2.y - line2.pt1.y) - (line1.pt2.y - line1.pt1.y) * (line2.pt2.x - line2.pt1.x);
|
||||
if (fabsf(denom) < epsilon) return false;
|
||||
|
||||
float t = ((line2.pt1.x - line1.pt1.x) * (line2.pt2.y - line2.pt1.y) - (line2.pt1.y - line1.pt1.y) * (line2.pt2.x - line2.pt1.x)) / denom;
|
||||
float u = ((line2.pt1.x - line1.pt1.x) * (line1.pt2.y - line1.pt1.y) - (line2.pt1.y - line1.pt1.y) * (line1.pt2.x - line1.pt1.x)) / denom;
|
||||
|
||||
intersection.x = line1.pt1.x + t * (line1.pt2.x - line1.pt1.x);
|
||||
intersection.y = line1.pt1.y + t * (line1.pt2.y - line1.pt1.y);
|
||||
inside = t >= -epsilon && t <= 1.0f + epsilon && u >= -epsilon && u <= 1.0f + epsilon;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static Line _offset(const Point& p1, const Point& p2, float offset)
|
||||
{
|
||||
auto scaledNormal = normal(p1, p2) * offset;
|
||||
return {p1 - scaledNormal, p2 - scaledNormal};
|
||||
}
|
||||
|
||||
|
||||
void LottieOffsetModifier::corner(const Line& line, const Line& nextLine, uint32_t movetoOutIndex, bool nextClose, Array<PathCommand>& outCmds, Array<Point>& outPts) const
|
||||
{
|
||||
bool inside{};
|
||||
Point intersect{};
|
||||
if (_intersect(line, nextLine, intersect, inside)) {
|
||||
if (inside) {
|
||||
if (nextClose) outPts[movetoOutIndex] = intersect;
|
||||
outPts.push(intersect);
|
||||
} else {
|
||||
outPts.push(line.pt2);
|
||||
if (join == StrokeJoin::Round) {
|
||||
outCmds.push(PathCommand::CubicTo);
|
||||
outPts.push((line.pt2 + intersect) * 0.5f);
|
||||
outPts.push((nextLine.pt1 + intersect) * 0.5f);
|
||||
outPts.push(nextLine.pt1);
|
||||
} else if (join == StrokeJoin::Miter) {
|
||||
auto norm = normal(line.pt1, line.pt2);
|
||||
auto nextNorm = normal(nextLine.pt1, nextLine.pt2);
|
||||
auto miterDirection = (norm + nextNorm) / length(norm + nextNorm);
|
||||
outCmds.push(PathCommand::LineTo);
|
||||
if (1.0f <= miterLimit * fabsf(miterDirection.x * norm.x + miterDirection.y * norm.y)) outPts.push(intersect);
|
||||
else outPts.push(nextLine.pt1);
|
||||
} else {
|
||||
outCmds.push(PathCommand::LineTo);
|
||||
outPts.push(nextLine.pt1);
|
||||
}
|
||||
}
|
||||
} else outPts.push(line.pt2);
|
||||
}
|
||||
|
||||
void LottieOffsetModifier::line(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t& currentPt, uint32_t currentCmd, State& state, bool degenerated, Array<PathCommand>& outCmds, Array<Point>& outPts, float offset) const
|
||||
{
|
||||
if (tvg::zero(inPts[currentPt - 1] - inPts[currentPt])) {
|
||||
++currentPt;
|
||||
return;
|
||||
}
|
||||
|
||||
if (inCmds[currentCmd - 1] != PathCommand::LineTo) state.line = _offset(inPts[currentPt - 1], inPts[currentPt], offset);
|
||||
|
||||
if (state.moveto) {
|
||||
outCmds.push(PathCommand::MoveTo);
|
||||
state.movetoOutIndex = outPts.count;
|
||||
outPts.push(state.line.pt1);
|
||||
state.firstLine = state.line;
|
||||
state.moveto = false;
|
||||
}
|
||||
|
||||
auto nonDegeneratedCubic = [&](uint32_t cmd, uint32_t pt) {
|
||||
return inCmds[cmd] == PathCommand::CubicTo && !tvg::zero(inPts[pt] - inPts[pt + 1]) && !tvg::zero(inPts[pt + 2] - inPts[pt + 3]);
|
||||
};
|
||||
|
||||
outCmds.push(PathCommand::LineTo);
|
||||
|
||||
if (currentCmd + 1 == inCmdsCnt || inCmds[currentCmd + 1] == PathCommand::MoveTo || nonDegeneratedCubic(currentCmd + 1, currentPt + degenerated)) {
|
||||
outPts.push(state.line.pt2);
|
||||
++currentPt;
|
||||
return;
|
||||
}
|
||||
|
||||
Line nextLine = state.firstLine;
|
||||
if (inCmds[currentCmd + 1] == PathCommand::LineTo) nextLine = _offset(inPts[currentPt + degenerated], inPts[currentPt + 1 + degenerated], offset);
|
||||
else if (inCmds[currentCmd + 1] == PathCommand::CubicTo) nextLine = _offset(inPts[currentPt + 1 + degenerated], inPts[currentPt + 2 + degenerated], offset);
|
||||
else if (inCmds[currentCmd + 1] == PathCommand::Close && !_zero(inPts[currentPt + degenerated], inPts[state.movetoInIndex + degenerated]))
|
||||
nextLine = _offset(inPts[currentPt + degenerated], inPts[state.movetoInIndex + degenerated], offset);
|
||||
|
||||
corner(state.line, nextLine, state.movetoOutIndex, inCmds[currentCmd + 1] == PathCommand::Close, outCmds, outPts);
|
||||
|
||||
state.line = nextLine;
|
||||
++currentPt;
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
@ -178,3 +286,80 @@ bool LottieRoundnessModifier::modifyRect(const Point& size, float& r) const
|
|||
r = std::min(this->r, std::max(size.x, size.y) * 0.5f);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool LottieOffsetModifier::modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array<PathCommand>& outCmds, Array<Point>& outPts, bool clockwise) const
|
||||
{
|
||||
outCmds.reserve(inCmdsCnt * 2);
|
||||
outPts.reserve(inPtsCnt * (join == StrokeJoin::Round ? 4 : 2));
|
||||
|
||||
State state;
|
||||
auto offset = clockwise ? this->offset : -this->offset;
|
||||
|
||||
for (uint32_t iCmd = 0, iPt = 0; iCmd < inCmdsCnt; ++iCmd) {
|
||||
if (inCmds[iCmd] == PathCommand::MoveTo) {
|
||||
state.moveto = true;
|
||||
state.movetoInIndex = iPt++;
|
||||
} else if (inCmds[iCmd] == PathCommand::LineTo) {
|
||||
line(inCmds, inCmdsCnt, inPts, iPt, iCmd, state, false, outCmds, outPts, offset);
|
||||
} else if (inCmds[iCmd] == PathCommand::CubicTo) {
|
||||
//cubic degenerated to a line
|
||||
if (tvg::zero(inPts[iPt - 1] - inPts[iPt]) || tvg::zero(inPts[iPt + 1] - inPts[iPt + 2])) {
|
||||
++iPt;
|
||||
line(inCmds, inCmdsCnt, inPts, iPt, iCmd, state, true, outCmds, outPts, offset);
|
||||
++iPt;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto line1 = _offset(inPts[iPt - 1], inPts[iPt], offset);
|
||||
auto line2 = _offset(inPts[iPt], inPts[iPt + 1], offset);
|
||||
auto line3 = _offset(inPts[iPt + 1], inPts[iPt + 2], offset);
|
||||
|
||||
if (state.moveto) {
|
||||
outCmds.push(PathCommand::MoveTo);
|
||||
state.movetoOutIndex = outPts.count;
|
||||
outPts.push(line1.pt1);
|
||||
state.firstLine = line1;
|
||||
state.moveto = false;
|
||||
}
|
||||
|
||||
bool inside{};
|
||||
Point intersect{};
|
||||
_intersect(line1, line2, intersect, inside);
|
||||
outPts.push(intersect);
|
||||
_intersect(line2, line3, intersect, inside);
|
||||
outPts.push(intersect);
|
||||
outPts.push(line3.pt2);
|
||||
outCmds.push(PathCommand::CubicTo);
|
||||
|
||||
iPt += 3;
|
||||
}
|
||||
else {
|
||||
if (!_zero(inPts[iPt - 1], inPts[state.movetoInIndex])) {
|
||||
outCmds.push(PathCommand::LineTo);
|
||||
corner(state.line, state.firstLine, state.movetoOutIndex, true, outCmds, outPts);
|
||||
}
|
||||
outCmds.push(PathCommand::Close);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool LottieOffsetModifier::modifyPolystar(const Array<PathCommand>& inCmds, const Array<Point>& inPts, Array<PathCommand>& outCmds, Array<Point>& outPts, bool clockwise) const {
|
||||
return modifyPath(inCmds.data, inCmds.count, inPts.data, inPts.count, outCmds, outPts, clockwise);
|
||||
}
|
||||
|
||||
|
||||
bool LottieOffsetModifier::modifyRect(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array<PathCommand>& outCmds, Array<Point>& outPts, bool clockwise) const
|
||||
{
|
||||
return modifyPath(inCmds, inCmdsCnt, inPts, inPtsCnt, outCmds, outPts, clockwise);
|
||||
}
|
||||
|
||||
|
||||
bool LottieOffsetModifier::modifyEllipse(float& rx, float& ry) const
|
||||
{
|
||||
rx += offset;
|
||||
ry += offset;
|
||||
return true;
|
||||
}
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include "tvgCommon.h"
|
||||
#include "tvgArray.h"
|
||||
#include "tvgMath.h"
|
||||
|
||||
|
||||
struct LottieRoundnessModifier
|
||||
|
@ -40,4 +41,31 @@ struct LottieRoundnessModifier
|
|||
};
|
||||
|
||||
|
||||
struct LottieOffsetModifier
|
||||
{
|
||||
float offset;
|
||||
float miterLimit;
|
||||
StrokeJoin join;
|
||||
|
||||
LottieOffsetModifier(float offset, float miter, StrokeJoin join) : offset(offset), miterLimit(miter), join(join) {};
|
||||
|
||||
bool modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array<PathCommand>& outCmds, Array<Point>& outPts, bool clockwise) const;
|
||||
bool modifyPolystar(const Array<PathCommand>& inCmds, const Array<Point>& inPts, Array<PathCommand>& outCmds, Array<Point>& outPts, bool clockwise) const;
|
||||
bool modifyRect(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array<PathCommand>& outCmds, Array<Point>& outPts, bool clockwise) const;
|
||||
bool modifyEllipse(float& rx, float& ry) const;
|
||||
|
||||
private:
|
||||
struct State
|
||||
{
|
||||
Line line{};
|
||||
Line firstLine{};
|
||||
bool moveto = false;
|
||||
uint32_t movetoOutIndex = 0;
|
||||
uint32_t movetoInIndex = 0;
|
||||
};
|
||||
|
||||
void line(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t& currentPt, uint32_t currentCmd, State& state, bool degenerated, Array<PathCommand>& cmds, Array<Point>& pts, float offset) const;
|
||||
void corner(const Line& line, const Line& nextLine, uint32_t movetoIndex, bool nextClose, Array<PathCommand>& cmds, Array<Point>& pts) const;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -866,6 +866,25 @@ LottieRepeater* LottieParser::parseRepeater()
|
|||
}
|
||||
|
||||
|
||||
LottieOffsetPath* LottieParser::parseOffsetPath()
|
||||
{
|
||||
auto offsetPath = new LottieOffsetPath;
|
||||
|
||||
context.parent = offsetPath;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (parseCommon(offsetPath, key)) continue;
|
||||
else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Float>(offsetPath->offset);
|
||||
else if (KEY_AS("lj")) offsetPath->join = getStrokeJoin();
|
||||
else if (KEY_AS("ml")) parseProperty<LottieProperty::Type::Float>(offsetPath->miterLimit);
|
||||
else skip(key);
|
||||
}
|
||||
offsetPath->prepare();
|
||||
|
||||
return offsetPath;
|
||||
}
|
||||
|
||||
|
||||
LottieObject* LottieParser::parseObject()
|
||||
{
|
||||
auto type = getString();
|
||||
|
@ -887,7 +906,7 @@ LottieObject* LottieParser::parseObject()
|
|||
else if (!strcmp(type, "mm")) TVGERR("LOTTIE", "MergePath(mm) is not supported yet");
|
||||
else if (!strcmp(type, "pb")) TVGERR("LOTTIE", "Puker/Bloat(pb) is not supported yet");
|
||||
else if (!strcmp(type, "tw")) TVGERR("LOTTIE", "Twist(tw) is not supported yet");
|
||||
else if (!strcmp(type, "op")) TVGERR("LOTTIE", "Offset Path(op) is not supported yet");
|
||||
else if (!strcmp(type, "op")) return parseOffsetPath();
|
||||
else if (!strcmp(type, "zz")) TVGERR("LOTTIE", "Zig Zag(zz) is not supported yet");
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ private:
|
|||
LottieMask* parseMask();
|
||||
LottieTrimpath* parseTrimpath();
|
||||
LottieRepeater* parseRepeater();
|
||||
LottieOffsetPath* parseOffsetPath();
|
||||
LottieFont* parseFont();
|
||||
LottieMarker* parseMarker();
|
||||
|
||||
|
|
|
@ -407,7 +407,7 @@ struct LottiePathSet : LottieProperty
|
|||
return (*frames)[frames->count];
|
||||
}
|
||||
|
||||
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness)
|
||||
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath)
|
||||
{
|
||||
PathSet* path = nullptr;
|
||||
LottieScalarFrame<PathSet>* frame = nullptr;
|
||||
|
@ -432,7 +432,16 @@ struct LottiePathSet : LottieProperty
|
|||
}
|
||||
|
||||
if (!interpolate) {
|
||||
if (roundness) return roundness->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds, pts, transform);
|
||||
if (roundness) {
|
||||
if (offsetPath) {
|
||||
Array<PathCommand> cmds1(path->cmdsCnt);
|
||||
Array<Point> pts1(path->ptsCnt);
|
||||
roundness->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds1, pts1, transform);
|
||||
return offsetPath->modifyPath(cmds1.data, cmds1.count, pts1.data, pts1.count, cmds, pts, true);
|
||||
}
|
||||
return roundness->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds, pts, transform);
|
||||
}
|
||||
if (offsetPath) return offsetPath->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds, pts, true);
|
||||
|
||||
_copy(path, cmds);
|
||||
_copy(path, pts, transform);
|
||||
|
@ -442,18 +451,7 @@ struct LottiePathSet : LottieProperty
|
|||
auto s = frame->value.pts;
|
||||
auto e = (frame + 1)->value.pts;
|
||||
|
||||
if (roundness) {
|
||||
auto interpPts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point));
|
||||
auto p = interpPts;
|
||||
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) {
|
||||
*p = lerp(*s, *e, t);
|
||||
if (transform) *p *= *transform;
|
||||
}
|
||||
roundness->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds, pts, nullptr);
|
||||
free(interpPts);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!roundness && !offsetPath) {
|
||||
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) {
|
||||
auto pt = lerp(*s, *e, t);
|
||||
if (transform) pt *= *transform;
|
||||
|
@ -463,14 +461,35 @@ struct LottiePathSet : LottieProperty
|
|||
return true;
|
||||
}
|
||||
|
||||
auto interpPts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point));
|
||||
auto p = interpPts;
|
||||
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) {
|
||||
*p = lerp(*s, *e, t);
|
||||
if (transform) *p *= *transform;
|
||||
}
|
||||
|
||||
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness, LottieExpressions* exps)
|
||||
if (roundness) {
|
||||
if (offsetPath) {
|
||||
Array<PathCommand> cmds1;
|
||||
Array<Point> pts1;
|
||||
roundness->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds1, pts1, nullptr);
|
||||
offsetPath->modifyPath(cmds1.data, cmds1.count, pts1.data, pts1.count, cmds, pts, true);
|
||||
} else roundness->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds, pts, nullptr);
|
||||
} else if (offsetPath) offsetPath->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds, pts, true);
|
||||
|
||||
free(interpPts);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, LottieExpressions* exps)
|
||||
{
|
||||
if (exps && exp) {
|
||||
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
|
||||
if (exps->result<LottiePathSet>(frameNo, cmds, pts, transform, roundness, exp)) return true;
|
||||
if (exps->result<LottiePathSet>(frameNo, cmds, pts, transform, roundness, offsetPath, exp)) return true;
|
||||
}
|
||||
return operator()(frameNo, cmds, pts, transform, roundness);
|
||||
return operator()(frameNo, cmds, pts, transform, roundness, offsetPath);
|
||||
}
|
||||
|
||||
void prepare() {}
|
||||
|
|
Loading…
Add table
Reference in a new issue