lottie: support the expression feature

The current development of the expression engine is experimental.
It does not support multi-threading.

Therefore, when tvg::Initializer::init() is configured
with more than one thread, expressions will be automatically disabled.

issue: https://github.com/thorvg/thorvg/issues/1640
This commit is contained in:
Hermet Park 2024-04-18 23:02:18 +09:00 committed by Hermet Park
parent 4d02cda350
commit 324d0f8943
9 changed files with 1828 additions and 152 deletions

View file

@ -3,15 +3,17 @@ if lottie_expressions
endif
source_file = [
'tvgLottieAnimation.cpp',
'tvgLottieBuilder.h',
'tvgLottieExpressions.h',
'tvgLottieInterpolator.h',
'tvgLottieLoader.h',
'tvgLottieModel.h',
'tvgLottieParser.h',
'tvgLottieParserHandler.h',
'tvgLottieProperty.h',
'tvgLottieAnimation.cpp',
'tvgLottieBuilder.cpp',
'tvgLottieExpressions.cpp',
'tvgLottieInterpolator.cpp',
'tvgLottieLoader.cpp',
'tvgLottieModel.cpp',
@ -25,4 +27,4 @@ subloader_dep += [declare_dependency(
)]
install_headers('thorvg_lottie.h')
headers += include_directories('.')
headers += include_directories('.')

View file

@ -27,9 +27,10 @@
#include "tvgPaint.h"
#include "tvgShape.h"
#include "tvgInlist.h"
#include "tvgTaskScheduler.h"
#include "tvgLottieModel.h"
#include "tvgLottieBuilder.h"
#include "tvgTaskScheduler.h"
#include "tvgLottieExpressions.h"
/************************************************************************/
@ -96,8 +97,8 @@ struct RenderContext
};
static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderContext>& contexts);
static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo);
static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderContext>& contexts, LottieExpressions* exps);
static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo, LottieExpressions* exps);
static bool _buildComposition(LottieComposition* comp, LottieGroup* parent);
static Shape* _draw(LottieGroup* parent, RenderContext* ctx);
@ -128,7 +129,7 @@ static void _rotationZ(Matrix* m, float degree)
}
static bool _updateTransform(LottieTransform* transform, float frameNo, bool autoOrient, Matrix& matrix, uint8_t& opacity)
static bool _updateTransform(LottieTransform* transform, float frameNo, bool autoOrient, Matrix& matrix, uint8_t& opacity, LottieExpressions* exps)
{
mathIdentity(&matrix);
@ -140,46 +141,46 @@ static bool _updateTransform(LottieTransform* transform, float frameNo, bool aut
if (transform->coords) {
mathTranslate(&matrix, transform->coords->x(frameNo), transform->coords->y(frameNo));
} else {
auto position = transform->position(frameNo);
auto position = transform->position(frameNo, exps);
mathTranslate(&matrix, position.x, position.y);
}
auto angle = 0.0f;
if (autoOrient) angle = transform->position.angle(frameNo);
_rotationZ(&matrix, transform->rotation(frameNo) + angle);
_rotationZ(&matrix, transform->rotation(frameNo, exps) + angle);
if (transform->rotationEx) {
_rotateY(&matrix, transform->rotationEx->y(frameNo));
_rotateX(&matrix, transform->rotationEx->x(frameNo));
}
auto scale = transform->scale(frameNo);
auto scale = transform->scale(frameNo, exps);
mathScaleR(&matrix, scale.x * 0.01f, scale.y * 0.01f);
//Lottie specific anchor transform.
auto anchor = transform->anchor(frameNo);
auto anchor = transform->anchor(frameNo, exps);
mathTranslateR(&matrix, -anchor.x, -anchor.y);
//invisible just in case.
if (scale.x == 0.0f || scale.y == 0.0f) opacity = 0;
else opacity = transform->opacity(frameNo);
else opacity = transform->opacity(frameNo, exps);
return true;
}
static void _updateTransform(LottieLayer* layer, float frameNo)
static void _updateTransform(LottieLayer* layer, float frameNo, LottieExpressions* exps)
{
if (!layer || mathEqual(layer->cache.frameNo, frameNo)) return;
auto transform = layer->transform;
auto parent = layer->parent;
if (parent) _updateTransform(parent, frameNo);
if (parent) _updateTransform(parent, frameNo, exps);
auto& matrix = layer->cache.matrix;
_updateTransform(transform, frameNo, layer->autoOrient, matrix, layer->cache.opacity);
_updateTransform(transform, frameNo, layer->autoOrient, matrix, layer->cache.opacity, exps);
if (parent) {
if (!mathIdentity((const Matrix*) &parent->cache.matrix)) {
@ -191,7 +192,7 @@ static void _updateTransform(LottieLayer* layer, float frameNo)
}
static void _updateTransform(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
static void _updateTransform(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx, LottieExpressions* exps)
{
auto transform = static_cast<LottieTransform*>(*child);
if (!transform) return;
@ -200,14 +201,14 @@ static void _updateTransform(LottieGroup* parent, LottieObject** child, float fr
if (parent->mergeable()) {
if (!ctx->transform) ctx->transform = (Matrix*)malloc(sizeof(Matrix));
_updateTransform(transform, frameNo, false, *ctx->transform, opacity);
_updateTransform(transform, frameNo, false, *ctx->transform, opacity, exps);
return;
}
ctx->merging = nullptr;
Matrix matrix;
if (!_updateTransform(transform, frameNo, false, matrix, opacity)) return;
if (!_updateTransform(transform, frameNo, false, matrix, opacity, exps)) return;
auto pmatrix = PP(ctx->propagator)->transform();
ctx->propagator->transform(pmatrix ? mathMultiply(pmatrix, &matrix) : matrix);
@ -221,7 +222,7 @@ static void _updateTransform(LottieGroup* parent, LottieObject** child, float fr
}
static void _updateGroup(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& pcontexts, RenderContext* ctx)
static void _updateGroup(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& pcontexts, RenderContext* ctx, LottieExpressions* exps)
{
auto group = static_cast<LottieGroup*>(*child);
@ -237,13 +238,13 @@ static void _updateGroup(LottieGroup* parent, LottieObject** child, float frameN
Inlist<RenderContext> contexts;
contexts.back(new RenderContext(*ctx, group->mergeable()));
_updateChildren(group, frameNo, contexts);
_updateChildren(group, frameNo, contexts, exps);
contexts.free();
}
static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ctx)
static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ctx, LottieExpressions* exps)
{
ctx->propagator->strokeWidth(stroke->width(frameNo));
ctx->propagator->strokeCap(stroke->cap);
@ -252,9 +253,9 @@ static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ct
if (stroke->dashattr) {
float dashes[2];
dashes[0] = stroke->dashSize(frameNo);
dashes[1] = dashes[0] + stroke->dashGap(frameNo);
ctx->propagator->strokeDash(dashes, 2, stroke->dashOffset(frameNo));
dashes[0] = stroke->dashSize(frameNo, exps);
dashes[1] = dashes[0] + stroke->dashGap(frameNo, exps);
ctx->propagator->strokeDash(dashes, 2, stroke->dashOffset(frameNo, exps));
} else {
ctx->propagator->strokeDash(nullptr, 0);
}
@ -275,7 +276,7 @@ static bool _fragmented(LottieObject** child, Inlist<RenderContext>& contexts, R
}
static void _updateSolidStroke(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
static void _updateSolidStroke(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx, LottieExpressions* exps)
{
if (_fragmented(child, contexts, ctx)) return;
@ -284,19 +285,19 @@ static void _updateSolidStroke(TVG_UNUSED LottieGroup* parent, LottieObject** ch
ctx->merging = nullptr;
auto color = stroke->color(frameNo);
ctx->propagator->strokeFill(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo));
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx);
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, exps);
}
static void _updateGradientStroke(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
static void _updateGradientStroke(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx, LottieExpressions* exps)
{
if (_fragmented(child, contexts, ctx)) return;
auto stroke = static_cast<LottieGradientStroke*>(*child);
ctx->merging = nullptr;
ctx->propagator->strokeFill(unique_ptr<Fill>(stroke->fill(frameNo)));
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx);
ctx->propagator->strokeFill(unique_ptr<Fill>(stroke->fill(frameNo, exps)));
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, exps);
}
@ -315,7 +316,7 @@ static void _updateSolidFill(TVG_UNUSED LottieGroup* parent, LottieObject** chil
}
static Shape* _updateGradientFill(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
static Shape* _updateGradientFill(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx, LottieExpressions* exps)
{
if (_fragmented(child, contexts, ctx)) return nullptr;
@ -323,7 +324,7 @@ static Shape* _updateGradientFill(TVG_UNUSED LottieGroup* parent, LottieObject**
ctx->merging = nullptr;
//TODO: reuse the fill instance?
ctx->propagator->fill(unique_ptr<Fill>(fill->fill(frameNo)));
ctx->propagator->fill(unique_ptr<Fill>(fill->fill(frameNo, exps)));
ctx->propagator->fill(fill->rule);
ctx->propagator->opacity(MULTIPLY(fill->opacity(frameNo), PP(ctx->propagator)->opacity));
@ -513,17 +514,17 @@ static void _updateEllipse(LottieGroup* parent, LottieObject** child, float fram
}
static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx, LottieExpressions* exps)
{
auto path = static_cast<LottiePath*>(*child);
if (ctx->repeater) {
auto p = Shape::gen();
path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform);
path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform, 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)) {
if (path->pathset(frameNo, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, ctx->transform, exps)) {
P(merging)->update(RenderUpdateFlag::Path);
}
if (ctx->roundness > 1.0f && P(merging)->rs.stroke) {
@ -534,7 +535,7 @@ static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo
}
static void _updateText(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, TVG_UNUSED RenderContext* ctx)
static void _updateText(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, TVG_UNUSED RenderContext* ctx, LottieExpressions* exps)
{
auto text = static_cast<LottieText*>(*child);
auto& doc = text->doc(frameNo);
@ -586,7 +587,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)) {
if (static_cast<LottiePath*>(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, exps)) {
P(shape)->update(RenderUpdateFlag::Path);
}
}
@ -888,12 +889,12 @@ static void _updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject** child
}
static void _updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
static void _updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx, LottieExpressions* exps)
{
auto trimpath= static_cast<LottieTrimpath*>(*child);
float begin, end;
trimpath->segment(frameNo, begin, end);
trimpath->segment(frameNo, begin, end, exps);
if (P(ctx->propagator)->rs.stroke) {
auto pbegin = P(ctx->propagator)->rs.stroke->trim.begin;
@ -907,7 +908,7 @@ static void _updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject** child
}
static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderContext>& contexts)
static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderContext>& contexts, LottieExpressions* exps)
{
contexts.head->begin = parent->children.end() - 1;
@ -917,11 +918,11 @@ static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderCon
for (auto child = ctx->begin; child >= parent->children.data; --child) {
switch ((*child)->type) {
case LottieObject::Group: {
_updateGroup(parent, child, frameNo, contexts, ctx);
_updateGroup(parent, child, frameNo, contexts, ctx, exps);
break;
}
case LottieObject::Transform: {
_updateTransform(parent, child, frameNo, contexts, ctx);
_updateTransform(parent, child, frameNo, contexts, ctx, exps);
break;
}
case LottieObject::SolidFill: {
@ -929,15 +930,15 @@ static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderCon
break;
}
case LottieObject::SolidStroke: {
_updateSolidStroke(parent, child, frameNo, contexts, ctx);
_updateSolidStroke(parent, child, frameNo, contexts, ctx, exps);
break;
}
case LottieObject::GradientFill: {
_updateGradientFill(parent, child, frameNo, contexts, ctx);
_updateGradientFill(parent, child, frameNo, contexts, ctx, exps);
break;
}
case LottieObject::GradientStroke: {
_updateGradientStroke(parent, child, frameNo, contexts, ctx);
_updateGradientStroke(parent, child, frameNo, contexts, ctx, exps);
break;
}
case LottieObject::Rect: {
@ -949,7 +950,7 @@ static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderCon
break;
}
case LottieObject::Path: {
_updatePath(parent, child, frameNo, contexts, ctx);
_updatePath(parent, child, frameNo, contexts, ctx, exps);
break;
}
case LottieObject::Polystar: {
@ -961,11 +962,11 @@ static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderCon
break;
}
case LottieObject::Trimpath: {
_updateTrimpath(parent, child, frameNo, contexts, ctx);
_updateTrimpath(parent, child, frameNo, contexts, ctx, exps);
break;
}
case LottieObject::Text: {
_updateText(parent, child, frameNo, contexts, ctx);
_updateText(parent, child, frameNo, contexts, ctx, exps);
break;
}
case LottieObject::Repeater: {
@ -984,14 +985,14 @@ static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderCon
}
static void _updatePrecomp(LottieLayer* precomp, float frameNo)
static void _updatePrecomp(LottieLayer* precomp, float frameNo, LottieExpressions* exps)
{
if (precomp->children.empty()) return;
frameNo = precomp->remap(frameNo);
frameNo = precomp->remap(frameNo, exps);
for (auto child = precomp->children.end() - 1; child >= precomp->children.begin(); --child) {
_updateLayer(precomp, static_cast<LottieLayer*>(*child), frameNo);
_updateLayer(precomp, static_cast<LottieLayer*>(*child), frameNo, exps);
}
//clip the layer viewport
@ -1018,7 +1019,7 @@ static void _updateSolid(LottieLayer* layer)
}
static void _updateMaskings(LottieLayer* layer, float frameNo)
static void _updateMaskings(LottieLayer* layer, float frameNo, LottieExpressions* exps)
{
if (layer->masks.count == 0) return;
@ -1031,7 +1032,7 @@ static void _updateMaskings(LottieLayer* layer, float frameNo)
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)) {
if (mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, exps)) {
P(shape)->update(RenderUpdateFlag::Path);
}
auto method = mask->method;
@ -1056,12 +1057,12 @@ static void _updateMaskings(LottieLayer* layer, float frameNo)
}
static bool _updateMatte(LottieLayer* root, LottieLayer* layer, float frameNo)
static bool _updateMatte(LottieLayer* root, LottieLayer* layer, float frameNo, LottieExpressions* exps)
{
auto target = layer->matte.target;
if (!target) return true;
_updateLayer(root, target, frameNo);
_updateLayer(root, target, frameNo, exps);
if (target->scene) {
layer->scene->composite(cast(target->scene), layer->matte.type);
@ -1075,14 +1076,14 @@ static bool _updateMatte(LottieLayer* root, LottieLayer* layer, float frameNo)
}
static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo)
static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo, LottieExpressions* exps)
{
layer->scene = nullptr;
//visibility
if (frameNo < layer->inFrame || frameNo >= layer->outFrame) return;
_updateTransform(layer, frameNo);
_updateTransform(layer, frameNo, exps);
//full transparent scene. no need to perform
if (layer->type != LottieLayer::Null && layer->cache.opacity == 0) return;
@ -1097,13 +1098,13 @@ static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo)
if (layer->matte.target && layer->masks.count > 0) TVGERR("LOTTIE", "FIXME: Matte + Masking??");
if (!_updateMatte(root, layer, frameNo)) return;
if (!_updateMatte(root, layer, frameNo, exps)) return;
_updateMaskings(layer, frameNo);
_updateMaskings(layer, frameNo, exps);
switch (layer->type) {
case LottieLayer::Precomp: {
_updatePrecomp(layer, frameNo);
_updatePrecomp(layer, frameNo, exps);
break;
}
case LottieLayer::Solid: {
@ -1114,7 +1115,7 @@ static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo)
if (!layer->children.empty()) {
Inlist<RenderContext> contexts;
contexts.back(new RenderContext);
_updateChildren(layer, frameNo, contexts);
_updateChildren(layer, frameNo, contexts, exps);
contexts.free();
}
break;
@ -1231,9 +1232,12 @@ bool LottieBuilder::update(LottieComposition* comp, float frameNo)
auto root = comp->root;
root->scene->clear();
if (exps && comp->expressions) exps->update(frameNo, comp->timeAtFrame(frameNo));
for (auto child = root->children.end() - 1; child >= root->children.begin(); --child) {
_updateLayer(root, static_cast<LottieLayer*>(*child), frameNo);
_updateLayer(root, static_cast<LottieLayer*>(*child), frameNo, exps);
}
return true;
}
@ -1253,4 +1257,4 @@ void LottieBuilder::build(LottieComposition* comp)
auto clip = Shape::gen();
clip->appendRect(0, 0, static_cast<float>(comp->w), static_cast<float>(comp->h));
comp->root->scene->composite(std::move(clip), CompositeMethod::ClipPath);
}
}

View file

@ -24,11 +24,24 @@
#define _TVG_LOTTIE_BUILDER_H_
#include "tvgCommon.h"
#include "tvgLottieExpressions.h"
struct LottieComposition;
struct LottieBuilder
{
LottieExpressions* exps = nullptr;
LottieBuilder()
{
exps = LottieExpressions::instance();
}
~LottieBuilder()
{
LottieExpressions::retrieve(exps);
}
bool update(LottieComposition* comp, float progress);
void build(LottieComposition* comp);
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,170 @@
/*
* Copyright (c) 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_LOTTIE_EXPRESSIONS_H_
#define _TVG_LOTTIE_EXPRESSIONS_H_
#include "tvgCommon.h"
struct LottieExpression;
struct LottieComposition;
struct RGB24;
#ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT
#include "jerryscript.h"
struct LottieExpressions
{
public:
template<typename Property, typename NumType>
bool result(float frameNo, NumType& out, LottieExpression* exp)
{
auto success = true;
auto bm_rt = evaluate(frameNo, exp);
if (auto prop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
out = (*prop)(frameNo);
} else if (jerry_value_is_number(bm_rt)) {
out = (NumType) jerry_value_as_number(bm_rt);
} else {
TVGERR("LOTTIE", "Failed dispatching a Value!");
success = false;
}
jerry_value_free(bm_rt);
return success;
}
template<typename Property>
bool result(float frameNo, Point& out, LottieExpression* exp)
{
auto success = true;
auto bm_rt = evaluate(frameNo, exp);
if (jerry_value_is_object(bm_rt)) {
if (auto prop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
out = (*prop)(frameNo);
} else {
auto x = jerry_object_get_index(bm_rt, 0);
auto y = jerry_object_get_index(bm_rt, 1);
out.x = jerry_value_as_number(x);
out.y = jerry_value_as_number(y);
jerry_value_free(x);
jerry_value_free(y);
}
} else {
TVGERR("LOTTIE", "Failed dispatching Point!");
success = false;
}
jerry_value_free(bm_rt);
return success;
}
template<typename Property>
bool result(float frameNo, RGB24& out, LottieExpression* exp)
{
auto success = true;
auto bm_rt = evaluate(frameNo, exp);
if (auto color = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
out = (*color)(frameNo);
} else {
TVGERR("LOTTIE", "Failed dispatching Color!");
success = false;
}
jerry_value_free(bm_rt);
return success;
}
template<typename Property>
bool result(float frameNo, Fill* fill, LottieExpression* exp)
{
auto success = true;
auto bm_rt = evaluate(frameNo, exp);
if (auto colorStop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
(*colorStop)(frameNo, fill, this);
} else {
TVGERR("LOTTIE", "Failed dispatching ColorStop!");
success = false;
}
jerry_value_free(bm_rt);
return success;
}
template<typename Property>
bool result(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, LottieExpression* exp)
{
auto success = true;
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);
} else {
TVGERR("LOTTIE", "Failed dispatching PathSet!");
success = false;
}
jerry_value_free(bm_rt);
return success;
}
void update(float frameNo, float curTime);
//singleton (no thread safety)
static LottieExpressions* instance();
static void retrieve(LottieExpressions* instance);
private:
LottieExpressions();
~LottieExpressions();
jerry_value_t evaluate(float frameNo, LottieExpression* exp);
jerry_value_t buildGlobal();
void buildComp(LottieComposition* comp);
//global object, attributes, methods
jerry_value_t global;
jerry_value_t comp;
jerry_value_t layer;
jerry_value_t thisComp;
jerry_value_t thisLayer;
jerry_value_t thisProperty;
};
#else
struct LottieExpressions
{
template<typename Property, typename NumType> bool result(TVG_UNUSED float, TVG_UNUSED NumType&, TVG_UNUSED LottieExpression*) { return false; }
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 LottieExpression*) { return false; }
void update(TVG_UNUSED float, TVG_UNUSED float) {}
static LottieExpressions* instance() { return nullptr; }
static void retrieve(TVG_UNUSED LottieExpressions* instance) {}
};
#endif //THORVG_LOTTIE_EXPRESSIONS_SUPPORT
#endif //_TVG_LOTTIE_EXPRESSIONS_H_

View file

@ -46,11 +46,11 @@ LottieImage::~LottieImage()
}
void LottieTrimpath::segment(float frameNo, float& start, float& end)
void LottieTrimpath::segment(float frameNo, float& start, float& end, LottieExpressions* exps)
{
auto s = this->start(frameNo) * 0.01f;
auto e = this->end(frameNo) * 0.01f;
auto o = fmodf(this->offset(frameNo), 360.0f) / 360.0f; //0 ~ 1
auto s = this->start(frameNo, exps) * 0.01f;
auto e = this->end(frameNo, exps) * 0.01f;
auto o = fmodf(this->offset(frameNo, exps), 360.0f) / 360.0f; //0 ~ 1
auto diff = fabs(s - e);
if (mathZero(diff)) {
@ -89,7 +89,7 @@ void LottieTrimpath::segment(float frameNo, float& start, float& end)
}
Fill* LottieGradient::fill(float frameNo)
Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps)
{
Fill* fill = nullptr;
@ -126,7 +126,7 @@ Fill* LottieGradient::fill(float frameNo)
if (!fill) return nullptr;
colorStops(frameNo, fill);
colorStops(frameNo, fill, exps);
return fill;
}
@ -216,10 +216,10 @@ void LottieLayer::prepare()
}
float LottieLayer::remap(float frameNo)
float LottieLayer::remap(float frameNo, LottieExpressions* exp)
{
if (timeRemap.frames || timeRemap.value) {
frameNo = comp->frameAtTime(timeRemap(frameNo));
frameNo = comp->frameAtTime(timeRemap(frameNo, exp));
} else {
frameNo -= startFrame;
}

View file

@ -51,29 +51,23 @@ struct LottieStroke
return dashattr->value[no];
}
float dashOffset(float frameNo)
float dashOffset(float frameNo, LottieExpressions* exps)
{
return dash(0)(frameNo);
return dash(0)(frameNo, exps);
}
float dashGap(float frameNo)
float dashGap(float frameNo, LottieExpressions* exps)
{
return dash(2)(frameNo);
return dash(2)(frameNo, exps);
}
float dashSize(float frameNo)
float dashSize(float frameNo, LottieExpressions* exps)
{
auto d = dash(1)(frameNo);
auto d = dash(1)(frameNo, exps);
if (d == 0.0f) return 0.1f;
else return d;
}
bool dynamic()
{
if (width.frames || dashattr) return true;
return false;
}
LottieFloat width = 0.0f;
DashAttr* dashattr = nullptr;
float miterLimit = 0;
@ -221,7 +215,7 @@ struct LottieTrimpath : LottieObject
return false;
}
void segment(float frameNo, float& start, float& end);
void segment(float frameNo, float& start, float& end, LottieExpressions* exps);
LottieFloat start = 0.0f;
LottieFloat end = 100.0f;
@ -489,7 +483,7 @@ struct LottieGradient : LottieObject
return false;
}
Fill* fill(float frameNo);
Fill* fill(float frameNo, LottieExpressions* exps);
LottiePoint start = Point{0.0f, 0.0f};
LottiePoint end = Point{0.0f, 0.0f};
@ -626,7 +620,7 @@ struct LottieLayer : LottieGroup
bool mergeable() override { return false; }
void prepare();
float remap(float frameNo);
float remap(float frameNo, LottieExpressions* exp);
struct {
CompositeMethod type = CompositeMethod::None;
@ -823,6 +817,7 @@ struct LottieComposition
Array<LottieFont*> fonts;
Array<LottieSlot*> slots;
Array<LottieMarker*> markers;
bool expressions = false;
bool initiated = false;
};

View file

@ -24,6 +24,7 @@
#include "tvgCompressor.h"
#include "tvgLottieModel.h"
#include "tvgLottieParser.h"
#include "tvgLottieExpressions.h"
/************************************************************************/
@ -33,6 +34,22 @@
#define KEY_AS(name) !strcmp(key, name)
static LottieExpression* _expression(char* code, LottieComposition* comp, LottieLayer* layer, LottieObject* object, LottieProperty* property, LottieProperty::Type type)
{
if (!comp->expressions) comp->expressions = true;
auto inst = new LottieExpression;
inst->code = code;
inst->comp = comp;
inst->layer = layer;
inst->object = object;
inst->property = property;
inst->type = type;
return inst;
}
static char* _int2str(int num)
{
char str[20];
@ -486,7 +503,10 @@ void LottieParser::parseProperty(T& prop, LottieObject* obj)
return;
}
comp->slots.push(new LottieSlot(sid, obj, type));
} else skip(key);
} else if (!strcmp(key, "x")) {
prop.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &prop, type);
}
else skip(key);
}
}
@ -552,6 +572,7 @@ LottieTransform* LottieParser::parseTransform(bool ddd)
//check separateCoord to figure out whether "x(expression)" / "x(coord)"
else if (transform->coords && KEY_AS("x")) parseProperty<LottieProperty::Type::Float>(transform->coords->x);
else if (transform->coords && KEY_AS("y")) parseProperty<LottieProperty::Type::Float>(transform->coords->y);
else if (KEY_AS("x")) transform->position.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &transform->position, LottieProperty::Type::Position);
else skip(key);
}
}
@ -638,7 +659,7 @@ LottieSolidStroke* LottieParser::parseSolidStroke()
}
void LottieParser::getPathSet(LottiePathSet& path)
void LottieParser::getPathSet(LottiePathSet& path)
{
enterObject();
while (auto key = nextObjectKey()) {
@ -649,6 +670,8 @@ LottieSolidStroke* LottieParser::parseSolidStroke()
} else {
getValue(path.value);
}
} else if (!strcmp(key, "x")) {
path.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &path, LottieProperty::Type::PathSet);
} else skip(key);
}
}

View file

@ -28,6 +28,13 @@
#include "tvgMath.h"
#include "tvgLines.h"
#include "tvgLottieInterpolator.h"
#include "tvgLottieExpressions.h"
struct LottieFont;
struct LottieLayer;
struct LottieObject;
struct PathSet
{
@ -51,8 +58,6 @@ struct ColorStop
};
struct LottieFont;
struct TextDocument
{
char* text = nullptr;
@ -93,35 +98,6 @@ static inline RGB24 operator*(const RGB24& lhs, float rhs)
}
static void copy(PathSet& pathset, Array<Point>& outPts, Matrix* transform)
{
Array<Point> inPts;
if (transform) {
for (int i = 0; i < pathset.ptsCnt; ++i) {
Point pt = pathset.pts[i];
mathMultiply(&pt, transform);
outPts.push(pt);
}
} else {
inPts.data = pathset.pts;
inPts.count = pathset.ptsCnt;
outPts.push(inPts);
inPts.data = nullptr;
}
}
static void copy(PathSet& pathset, Array<PathCommand>& outCmds)
{
Array<PathCommand> inCmds;
inCmds.data = pathset.cmds;
inCmds.count = pathset.cmdsCnt;
outCmds.push(inCmds);
inCmds.data = nullptr;
}
template<typename T>
struct LottieScalarFrame
{
@ -192,8 +168,76 @@ struct LottieVectorFrame
};
//Property would have an either keyframes or single value.
struct LottieProperty
{
enum class Type : uint8_t { Point = 0, Float, Opacity, Color, PathSet, ColorStop, Position, TextDoc, Invalid };
virtual ~LottieProperty() {}
LottieExpression* exp = nullptr;
//TODO: Apply common bodies?
virtual uint32_t frameCnt() = 0;
virtual uint32_t nearest(float time) = 0;
virtual float frameNo(int32_t key) = 0;
};
struct LottieExpression
{
enum LoopMode : uint8_t { None = 0, InCycle = 1, InPingPong, InOffset, InContinue, OutCycle, OutPingPong, OutOffset, OutContinue };
char* code;
LottieComposition* comp;
LottieLayer* layer;
LottieObject* object;
LottieProperty* property;
LottieProperty::Type type;
struct {
uint32_t key = 0; //the keyframe number repeating to
float in = FLT_MAX; //looping duration in frame number
LoopMode mode = None;
} loop;
;
~LottieExpression()
{
free(code);
}
};
static void _copy(PathSet& pathset, Array<Point>& outPts, Matrix* transform)
{
Array<Point> inPts;
if (transform) {
for (int i = 0; i < pathset.ptsCnt; ++i) {
Point pt = pathset.pts[i];
mathMultiply(&pt, transform);
outPts.push(pt);
}
} else {
inPts.data = pathset.pts;
inPts.count = pathset.ptsCnt;
outPts.push(inPts);
inPts.data = nullptr;
}
}
static void _copy(PathSet& pathset, Array<PathCommand>& outCmds)
{
Array<PathCommand> inCmds;
inCmds.data = pathset.cmds;
inCmds.count = pathset.cmdsCnt;
outCmds.push(inCmds);
inCmds.data = nullptr;
}
template<typename T>
uint32_t bsearch(T* frames, float frameNo)
uint32_t _bsearch(T* frames, float frameNo)
{
int32_t low = 0;
int32_t high = int32_t(frames->count) - 1;
@ -206,16 +250,50 @@ uint32_t bsearch(T* frames, float frameNo)
}
if (high < low) low = high;
if (low < 0) low = 0;
return low;
}
struct LottieProperty
template<typename T>
uint32_t _nearest(T* frames, float frameNo)
{
enum class Type : uint8_t { Point = 0, Float, Opacity, Color, PathSet, ColorStop, Position, TextDoc, Invalid };
virtual ~LottieProperty() {}
};
if (frames) {
auto key = _bsearch(frames, frameNo);
if (key == frames->count - 1) return key;
return (fabsf(frames->data[key].no - frameNo) < fabsf(frames->data[key + 1].no - frameNo)) ? key : (key + 1);
}
return 0;
}
template<typename T>
float _frameNo(T* frames, int32_t key)
{
if (!frames) return 0.0f;
if (key < 0) key = 0;
if (key >= (int32_t) frames->count) key = (int32_t)(frames->count - 1);
return (*frames)[key].no;
}
template<typename T>
float _loop(T* frames, float frameNo, LottieExpression* exp)
{
if (frameNo >= exp->loop.in || frameNo < frames->first().no ||frameNo < frames->last().no) return frameNo;
switch (exp->loop.mode) {
case LottieExpression::LoopMode::InCycle: {
frameNo -= frames->first().no;
return fmodf(frameNo, frames->last().no - frames->first().no) + (*frames)[exp->loop.key].no;
}
case LottieExpression::LoopMode::OutCycle: {
frameNo -= frames->first().no;
return fmodf(frameNo, (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no) + frames->first().no;
}
default: break;
}
return frameNo;
}
template<typename T>
@ -236,6 +314,26 @@ struct LottieGenericProperty : LottieProperty
void release()
{
delete(frames);
frames = nullptr;
if (exp) {
delete(exp);
exp = nullptr;
}
}
uint32_t nearest(float frameNo) override
{
return _nearest(frames, frameNo);
}
uint32_t frameCnt() override
{
return frames ? frames->count : 1;
}
float frameNo(int32_t key) override
{
return _frameNo(frames, key);
}
LottieScalarFrame<T>& newFrame()
@ -261,11 +359,21 @@ struct LottieGenericProperty : LottieProperty
if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
if (frameNo >= frames->last().no) return frames->last().value;
auto frame = frames->data + bsearch(frames, frameNo);
auto frame = frames->data + _bsearch(frames, frameNo);
if (frame->no == frameNo) return frame->value;
return frame->interpolate(frame + 1, frameNo);
}
T operator()(float frameNo, LottieExpressions* exps)
{
T out{};
if (exps && exp) {
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
if (exps->result<LottieGenericProperty<T>>(frameNo, out, exp)) return out;
}
return operator()(frameNo);
}
T& operator=(const T& other)
{
//shallow copy, used for slot overriding
@ -293,6 +401,11 @@ struct LottiePathSet : LottieProperty
void release()
{
if (exp) {
delete(exp);
exp = nullptr;
}
free(value.cmds);
free(value.pts);
@ -306,6 +419,21 @@ struct LottiePathSet : LottieProperty
free(frames);
}
uint32_t nearest(float frameNo) override
{
return _nearest(frames, frameNo);
}
uint32_t frameCnt() override
{
return frames ? frames->count : 1;
}
float frameNo(int32_t key) override
{
return _frameNo(frames, key);
}
LottieScalarFrame<PathSet>& newFrame()
{
if (!frames) {
@ -325,43 +453,43 @@ struct LottiePathSet : LottieProperty
return (*frames)[frames->count];
}
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform = nullptr)
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform)
{
if (!frames) {
copy(value, cmds);
copy(value, pts, transform);
_copy(value, cmds);
_copy(value, pts, transform);
return true;
}
if (frames->count == 1 || frameNo <= frames->first().no) {
copy(frames->first().value, cmds);
copy(frames->first().value, pts, transform);
_copy(frames->first().value, cmds);
_copy(frames->first().value, pts, transform);
return true;
}
if (frameNo >= frames->last().no) {
copy(frames->last().value, cmds);
copy(frames->last().value, pts, transform);
_copy(frames->last().value, cmds);
_copy(frames->last().value, pts, transform);
return true;
}
auto frame = frames->data + bsearch(frames, frameNo);
auto frame = frames->data + _bsearch(frames, frameNo);
if (frame->no == frameNo) {
copy(frame->value, cmds);
copy(frame->value, pts, transform);
_copy(frame->value, cmds);
_copy(frame->value, pts, transform);
return true;
}
//interpolate
copy(frame->value, cmds);
_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 (t < 1.0f) _copy(frame->value, pts, transform);
else _copy((frame + 1)->value, pts, transform);
return true;
}
@ -376,6 +504,16 @@ struct LottiePathSet : LottieProperty
return true;
}
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, 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, exp)) return true;
}
return operator()(frameNo, cmds, pts, transform);
}
void prepare() {}
};
@ -394,6 +532,11 @@ struct LottieColorStop : LottieProperty
void release()
{
if (exp) {
delete(exp);
exp = nullptr;
}
if (value.data) {
free(value.data);
value.data = nullptr;
@ -409,6 +552,21 @@ struct LottieColorStop : LottieProperty
frames = nullptr;
}
uint32_t nearest(float frameNo) override
{
return _nearest(frames, frameNo);
}
uint32_t frameCnt() override
{
return frames ? frames->count : 1;
}
float frameNo(int32_t key) override
{
return _frameNo(frames, key);
}
LottieScalarFrame<ColorStop>& newFrame()
{
if (!frames) {
@ -428,27 +586,26 @@ struct LottieColorStop : LottieProperty
return (*frames)[frames->count];
}
void operator()(float frameNo, Fill* fill)
Result operator()(float frameNo, Fill* fill, LottieExpressions* exps)
{
if (!frames) {
fill->colorStops(value.data, count);
return;
if (exps && exp) {
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
if (exps->result<LottieColorStop>(frameNo, fill, exp)) return Result::Success;
}
if (!frames) return fill->colorStops(value.data, count);
if (frames->count == 1 || frameNo <= frames->first().no) {
fill->colorStops(frames->first().value.data, count);
return;
return fill->colorStops(frames->first().value.data, count);
}
if (frameNo >= frames->last().no) {
fill->colorStops(frames->last().value.data, count);
return;
return fill->colorStops(frames->last().value.data, count);
}
auto frame = frames->data + bsearch(frames, frameNo);
auto frame = frames->data + _bsearch(frames, frameNo);
if (frame->no == frameNo) {
fill->colorStops(frame->value.data, count);
return;
return fill->colorStops(frame->value.data, count);
}
//interpolate
@ -473,7 +630,7 @@ struct LottieColorStop : LottieProperty
auto a = mathLerp(s->a, e->a, t);
result.push({offset, r, g, b, a});
}
fill->colorStops(result.data, count);
return fill->colorStops(result.data, count);
}
LottieColorStop& operator=(const LottieColorStop& other)
@ -513,6 +670,27 @@ struct LottiePosition : LottieProperty
void release()
{
delete(frames);
frames = nullptr;
if (exp) {
delete(exp);
exp = nullptr;
}
}
uint32_t nearest(float frameNo) override
{
return _nearest(frames, frameNo);
}
uint32_t frameCnt() override
{
return frames ? frames->count : 1;
}
float frameNo(int32_t key) override
{
return _frameNo(frames, key);
}
LottieVectorFrame<Point>& newFrame()
@ -538,18 +716,28 @@ struct LottiePosition : LottieProperty
if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
if (frameNo >= frames->last().no) return frames->last().value;
auto frame = frames->data + bsearch(frames, frameNo);
auto frame = frames->data + _bsearch(frames, frameNo);
if (frame->no == frameNo) return frame->value;
return frame->interpolate(frame + 1, frameNo);
}
Point operator()(float frameNo, LottieExpressions* exps)
{
Point out{};
if (exps && exp) {
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
if (exps->result<LottiePosition>(frameNo, out, exp)) return out;
}
return operator()(frameNo);
}
float angle(float frameNo)
{
if (!frames) return 0;
if (frames->count == 1 || frameNo <= frames->first().no) return 0;
if (frameNo >= frames->last().no) return 0;
auto frame = frames->data + bsearch(frames, frameNo);
auto frame = frames->data + _bsearch(frames, frameNo);
return frame->angle(frame + 1, frameNo);
}
@ -575,6 +763,11 @@ struct LottieTextDoc : LottieProperty
void release()
{
if (exp) {
delete(exp);
exp = nullptr;
}
if (value.text) {
free(value.text);
value.text = nullptr;
@ -594,6 +787,21 @@ struct LottieTextDoc : LottieProperty
frames = nullptr;
}
uint32_t nearest(float frameNo) override
{
return _nearest(frames, frameNo);
}
uint32_t frameCnt() override
{
return frames ? frames->count : 1;
}
float frameNo(int32_t key) override
{
return _frameNo(frames, key);
}
LottieScalarFrame<TextDocument>& newFrame()
{
if (!frames) frames = new Array<LottieScalarFrame<TextDocument>>;
@ -617,7 +825,7 @@ struct LottieTextDoc : LottieProperty
if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
if (frameNo >= frames->last().no) return frames->last().value;
auto frame = frames->data + bsearch(frames, frameNo);
auto frame = frames->data + _bsearch(frames, frameNo);
return frame->value;
}