From 2264d0ca792f462eb4e573568719206415d4bf06 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Thu, 10 Apr 2025 15:02:27 +0900 Subject: [PATCH] lottie/expressions: spec coverage++ support pointOnPath() and layer/group child exploration {pathProperty}.pointOnPath(percentage = 0.5, t = time) get the x,y coordinates of an arbitrary point along a path. the point is expressed as a percentage of the arc-length of the path. 0% is the first point and 100% is the last point. when the path is closed, 0% and 100% will return the same coordinates. percentage of arc-length is used to ensure uniform speed along the path. other than 0% and 100%, percentages do not necessarily correlate with the Bezier points on the path - For a path with three points, the second point will not necessarily be at 50%. this also means that for an open path and closed path with identical points, the percentage along the open path will not return the same coordinates as the closed path due to the additional length of the closed path. optionally specify the time at which to sample the path. issue: https://github.com/thorvg/thorvg/issues/2233 --- src/loaders/lottie/tvgLottieExpressions.cpp | 201 +++++++++++--------- 1 file changed, 112 insertions(+), 89 deletions(-) diff --git a/src/loaders/lottie/tvgLottieExpressions.cpp b/src/loaders/lottie/tvgLottieExpressions.cpp index 9f907da2..4a97ca75 100644 --- a/src/loaders/lottie/tvgLottieExpressions.cpp +++ b/src/loaders/lottie/tvgLottieExpressions.cpp @@ -38,6 +38,8 @@ struct ExpContent union { LottieObject* obj; LottieEffect* effect; + LottieProperty* property; + void *data; }; float frameNo; size_t refCnt; @@ -62,14 +64,14 @@ static const char* EXP_EFFECT= "effect"; static LottieExpressions* exps = nullptr; //singleton instance engine -static ExpContent* _expcontent(LottieExpression* exp, float frameNo, void* obj, size_t refCnt = 1) +static ExpContent* _expcontent(LottieExpression* exp, float frameNo, void* data, size_t refCnt = 1) { - auto data = tvg::malloc(sizeof(ExpContent)); - data->exp = exp; - data->frameNo = frameNo; - data->obj = (LottieObject*)obj; - data->refCnt = refCnt; - return data; + auto ret = tvg::malloc(sizeof(ExpContent)); + ret->exp = exp; + ret->frameNo = frameNo; + ret->data = data; + ret->refCnt = refCnt; + return ret; } @@ -472,7 +474,6 @@ static jerry_value_t _muldiv(const jerry_value_t arg1, float arg2) { //1d if (jerry_value_is_number(arg1)) return jerry_number(jerry_value_as_number(arg1) * arg2); - //2d return _point2d(_point2d(arg1) * arg2); } @@ -647,26 +648,114 @@ static jerry_value_t _content(const jerry_call_info_t* info, const jerry_value_t } +static jerry_value_t _createPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + //TODO: arg1: points, arg2: inTangents, arg3: outTangents, arg4: isClosed + auto arg1 = jerry_value_to_object(args[0]); + auto pathset = jerry_object_get_native_ptr(arg1, nullptr); + if (!pathset) { + TVGERR("LOTTIE", "failed createPath()"); + return jerry_undefined(); + } + + jerry_value_free(arg1); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, pathset); + return obj; +} + + +static jerry_value_t _points(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + /* TODO: ThorVG prebuilds the path data for performance. + It actually need to constructs the Array for points, inTangents, outTangents and then return here... */ + auto data = static_cast(jerry_object_get_native_ptr(info->function, &freeCb)); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, data->property); + return obj; +} + + +static jerry_value_t _pointOnPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto data = static_cast(jerry_object_get_native_ptr(info->function, &freeCb)); + auto pathset = static_cast(data->property); + auto progress = jerry_value_as_number(args[0]); + RenderPath out; + (*pathset)(data->frameNo, out, nullptr, nullptr); + return _point2d(out.point(progress)); +} + + +static void _buildPath(jerry_value_t context, float frameNo, LottieProperty* pathset) +{ + auto data = _expcontent(nullptr, frameNo, pathset, 2); + + //Trick for fast building path. + auto points = jerry_function_external(_points); + jerry_object_set_native_ptr(points, &freeCb, data); + jerry_object_set_sz(context, "points", points); + jerry_value_free(points); + + auto pointOnPath = jerry_function_external(_pointOnPath); + jerry_object_set_native_ptr(pointOnPath, &freeCb, data); + jerry_object_set_sz(context, "pointOnPath", pointOnPath); + jerry_value_free(pointOnPath); + + //inTangents + //outTangents + //isClosed +} + + +static jerry_value_t _layerChild(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto data = static_cast(jerry_object_get_native_ptr(info->function, &freeCb)); + jerry_value_t obj = jerry_undefined(); + + //find a member by index + if (jerry_value_is_number(args[0])) { + auto idx = (uint32_t)jerry_value_as_int32(args[0]) - 1; + auto children = static_cast*>(data->data); + if (idx < children->count) { + obj = jerry_function_external(_layerChild); + jerry_object_set_native_ptr(obj, &freeCb, _expcontent(data->exp, data->frameNo, (*children)[idx])); + } + //find a member by name + } else { + auto name = _name(args[0]); + if (name) { + //for backward compatibility: reserved ADOBE keyword + if (!strcmp(name, "ADBE Root Vectors Group") || !strcmp(name, "ADBE Vectors Group")) { + auto group = static_cast(data->obj); + if (group->type == LottieObject::Type::Group || group->type == LottieObject::Type::Layer) { + obj = jerry_function_external(_layerChild); + jerry_object_set_native_ptr(obj, &freeCb, _expcontent(data->exp, data->frameNo, &group->children)); + } + } else if (!strcmp(name, "ADBE Vector Shape")) { + obj = jerry_object(); + _buildPath(obj, data->frameNo, &static_cast(data->obj)->pathset); + } + tvg::free(name); + } + } + return obj; +} + + static jerry_value_t _layer(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto data = static_cast(jerry_object_get_native_ptr(info->function, &freeCb)); auto comp = static_cast(data->obj); - LottieLayer* layer; - - //layer index - if (jerry_value_is_number(args[0])) { - auto idx = (uint16_t)jerry_value_as_int32(args[0]); - layer = comp->layerByIdx(idx); - jerry_value_free(idx); - //layer name - } else { - layer = comp->layerById(_idByName(args[0])); - } + //either index or name + auto layer = jerry_value_is_number(args[0]) ? comp->layerByIdx((uint16_t)jerry_value_as_int32(args[0])) : comp->layerById(_idByName(args[0])); if (!layer) return jerry_undefined(); - auto obj = jerry_object(); - jerry_object_set_native_ptr(obj, nullptr, layer); + auto obj = jerry_function_external(_layerChild); + jerry_object_set_native_ptr(obj, &freeCb, _expcontent(data->exp, data->frameNo, layer)); _buildLayer(obj, data->frameNo, layer, comp, data->exp); return obj; @@ -845,7 +934,6 @@ static jerry_value_t _temporalWiggle(const jerry_call_info_t* info, const jerry_ } - static bool _loopOutCommon(LottieExpression* exp, const jerry_value_t args[], const jerry_length_t argsCnt) { exp->loop.mode = LottieExpression::LoopMode::OutCycle; @@ -888,9 +976,7 @@ static jerry_value_t _loopOutDuration(const jerry_call_info_t* info, const jerry if (!_loopOutCommon(exp, args, argsCnt)) return jerry_undefined(); - if (argsCnt > 1) { - exp->loop.in = exp->comp->frameAtTime(jerry_value_as_number(args[1])); - } + if (argsCnt > 1) exp->loop.in = exp->comp->frameAtTime(jerry_value_as_number(args[1])); auto obj = jerry_object(); jerry_object_set_native_ptr(obj, nullptr, exp->property); @@ -976,69 +1062,6 @@ static jerry_value_t _key(const jerry_call_info_t* info, const jerry_value_t arg } -static jerry_value_t _createPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) -{ - //TODO: arg1: points, arg2: inTangents, arg3: outTangents, arg4: isClosed - auto arg1 = jerry_value_to_object(args[0]); - auto pathset = jerry_object_get_native_ptr(arg1, nullptr); - if (!pathset) { - TVGERR("LOTTIE", "failed createPath()"); - return jerry_undefined(); - } - - jerry_value_free(arg1); - - auto obj = jerry_object(); - jerry_object_set_native_ptr(obj, nullptr, pathset); - return obj; -} - - -static jerry_value_t _uniformPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) -{ - auto pathset = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); - - /* TODO: ThorVG prebuilds the path data for performance. - It actually need to constructs the Array for points, inTangents, outTangents and then return here... */ - auto obj = jerry_object(); - jerry_object_set_native_ptr(obj, nullptr, pathset); - return obj; -} - - -static jerry_value_t _isClosed(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) -{ - //TODO: Not used - return jerry_boolean(true); -} - - -static void _buildPath(jerry_value_t context, LottieExpression* exp) -{ - //Trick for fast building path. - auto points = jerry_function_external(_uniformPath); - jerry_object_set_native_ptr(points, nullptr, exp->property); - jerry_object_set_sz(context, "points", points); - jerry_value_free(points); - - auto inTangents = jerry_function_external(_uniformPath); - jerry_object_set_native_ptr(inTangents, nullptr, exp->property); - jerry_object_set_sz(context, "inTangents", inTangents); - jerry_value_free(inTangents); - - auto outTangents = jerry_function_external(_uniformPath); - jerry_object_set_native_ptr(outTangents, nullptr, exp->property); - jerry_object_set_sz(context, "outTangents", outTangents); - jerry_value_free(outTangents); - - auto isClosed = jerry_function_external(_isClosed); - jerry_object_set_native_ptr(isClosed, nullptr, exp->property); - jerry_object_set_sz(context, "isClosed", isClosed); - jerry_value_free(isClosed); - -} - - static void _buildProperty(float frameNo, jerry_value_t context, LottieExpression* exp) { auto value = _value(frameNo, exp->property); @@ -1150,7 +1173,7 @@ static void _buildProperty(float frameNo, jerry_value_t context, LottieExpressio jerry_value_free(effect); //expansions per types - if (exp->property->type == LottieProperty::Type::PathSet) _buildPath(context, exp); + if (exp->property->type == LottieProperty::Type::PathSet) _buildPath(context, frameNo, exp->property); }