/* * Copyright (c) 2024 - 2025 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. */ #include "tvgMath.h" #include "tvgCompressor.h" #include "tvgLottieModel.h" #include "tvgLottieExpressions.h" #ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT /************************************************************************/ /* Internal Class Implementation */ /************************************************************************/ struct ExpContent { LottieExpression* exp; union { LottieObject* obj; LottieEffect* effect; }; float frameNo; size_t refCnt; }; static jerry_value_t _content(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt); //reserved expressions specifiers static const char* EXP_NAME = "name"; static const char* EXP_CONTENT = "content"; static const char* EXP_WIDTH = "width"; static const char* EXP_HEIGHT = "height"; static const char* EXP_CYCLE = "cycle"; static const char* EXP_PINGPONG = "pingpong"; static const char* EXP_OFFSET = "offset"; static const char* EXP_CONTINUE = "continue"; static const char* EXP_TIME = "time"; static const char* EXP_VALUE = "value"; static const char* EXP_INDEX = "index"; 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) { auto data = tvg::malloc(sizeof(ExpContent)); data->exp = exp; data->frameNo = frameNo; data->obj = (LottieObject*)obj; data->refCnt = refCnt; return data; } static inline ExpContent* _expcontent(ExpContent* data) { ++data->refCnt; return data; } static float _rand() { return (float)(rand() % 10000001) * 0.0000001f; } static jerry_value_t _point2d(const Point& pt) { auto obj = jerry_object(); auto v1 = jerry_number(pt.x); auto v2 = jerry_number(pt.y); jerry_object_set_index(obj, 0, v1); jerry_object_set_index(obj, 1, v2); jerry_value_free(v1); jerry_value_free(v2); return obj; } static jerry_value_t _color(RGB24 rgb) { auto value = jerry_object(); auto r = jerry_number((float)rgb.rgb[0]); auto g = jerry_number((float)rgb.rgb[1]); auto b = jerry_number((float)rgb.rgb[2]); jerry_object_set_index(value, 0, r); jerry_object_set_index(value, 1, g); jerry_object_set_index(value, 2, b); jerry_value_free(r); jerry_value_free(g); jerry_value_free(b); return value; } static Point _point2d(jerry_value_t obj) { auto v1 = jerry_object_get_index(obj, 0); auto v2 = jerry_object_get_index(obj, 1); Point pt = {jerry_value_as_number(v1), jerry_value_as_number(v2)}; jerry_value_free(v1); jerry_value_free(v2); return pt; } static RGB24 _color(jerry_value_t obj) { RGB24 out; auto r = jerry_object_get_index(obj, 0); auto g = jerry_object_get_index(obj, 1); auto b = jerry_object_get_index(obj, 2); out.rgb[0] = jerry_value_as_int32(r); out.rgb[1] = jerry_value_as_int32(g); out.rgb[2] = jerry_value_as_int32(b); jerry_value_free(r); jerry_value_free(g); jerry_value_free(b); return out; } static void contentFree(void *native_p, struct jerry_object_native_info_t *info_p) { if (--static_cast(native_p)->refCnt == 0) { tvg::free(native_p); } } static jerry_object_native_info_t freeCb {contentFree, 0, 0}; static uint32_t engineRefCnt = 0; //Expressions Engine reference count static char* _name(jerry_value_t args) { auto arg0 = jerry_value_to_string(args); auto len = jerry_string_length(arg0); auto name = tvg::malloc(len * sizeof(jerry_char_t) + 1); jerry_string_to_buffer(arg0, JERRY_ENCODING_UTF8, name, len); name[len] = '\0'; jerry_value_free(arg0); return (char*) name; } static unsigned long _idByName(jerry_value_t args) { auto name = _name(args); auto id = djb2Encode(name); tvg::free(name); return id; } static jerry_value_t _toComp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto layer = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); return _point2d(_point2d(args[0]) * layer->cache.matrix); } static jerry_value_t _value(float frameNo, LottieProperty* property) { switch (property->type) { case LottieProperty::Type::Integer: return jerry_number((*static_cast(property))(frameNo)); case LottieProperty::Type::Float: return jerry_number((*static_cast(property))(frameNo)); case LottieProperty::Type::Scalar: return _point2d((*static_cast(property))(frameNo)); case LottieProperty::Type::Vector: return _point2d((*static_cast(property))(frameNo)); case LottieProperty::Type::PathSet: { auto value = jerry_object(); jerry_object_set_native_ptr(value, nullptr, property); return value; } case LottieProperty::Type::Color: return _color((*static_cast(property))(frameNo)); case LottieProperty::Type::Opacity: return jerry_number((*static_cast(property))(frameNo)); default: TVGERR("LOTTIE", "Non supported type for value? = %d", (int) property->type); } return jerry_undefined(); } static void _buildTransform(jerry_value_t context, float frameNo, LottieTransform* transform) { if (!transform) return; auto obj = jerry_object(); jerry_object_set_sz(context, "transform", obj); auto anchorPoint = _value(frameNo, &transform->anchor); jerry_object_set_sz(obj, "anchorPoint", anchorPoint); jerry_value_free(anchorPoint); auto position = _value(frameNo, &transform->position); jerry_object_set_sz(obj, "position", position); jerry_value_free(position); auto scale = _value(frameNo, &transform->scale); jerry_object_set_sz(obj, "scale", scale); jerry_value_free(scale); auto rotation = _value(frameNo, &transform->rotation); jerry_object_set_sz(obj, "rotation", rotation); jerry_value_free(rotation); auto opacity = _value(frameNo, &transform->opacity); jerry_object_set_sz(obj, "opacity", opacity); jerry_value_free(opacity); jerry_value_free(obj); } static jerry_value_t _buildGroup(LottieGroup* group, float frameNo) { auto obj = jerry_function_external(_content); //attach a transform ARRAY_FOREACH(p, group->children) { if ((*p)->type == LottieObject::Type::Transform) { _buildTransform(obj, frameNo, static_cast(*p)); break; } } jerry_object_set_native_ptr(obj, &freeCb, _expcontent(nullptr, frameNo, group)); jerry_object_set_sz(obj, EXP_CONTENT, obj); return obj; } static jerry_value_t _buildPolystar(LottiePolyStar* polystar, float frameNo) { auto obj = jerry_object(); auto position = jerry_object(); jerry_object_set_native_ptr(position, nullptr, &polystar->position); jerry_object_set_sz(obj, "position", position); jerry_value_free(position); auto innerRadius = jerry_number(polystar->innerRadius(frameNo)); jerry_object_set_sz(obj, "innerRadius", innerRadius); jerry_value_free(innerRadius); auto outerRadius = jerry_number(polystar->outerRadius(frameNo)); jerry_object_set_sz(obj, "outerRadius", outerRadius); jerry_value_free(outerRadius); auto innerRoundness = jerry_number(polystar->innerRoundness(frameNo)); jerry_object_set_sz(obj, "innerRoundness", innerRoundness); jerry_value_free(innerRoundness); auto outerRoundness = jerry_number(polystar->outerRoundness(frameNo)); jerry_object_set_sz(obj, "outerRoundness", outerRoundness); jerry_value_free(outerRoundness); auto rotation = jerry_number(polystar->rotation(frameNo)); jerry_object_set_sz(obj, "rotation", rotation); jerry_value_free(rotation); auto ptsCnt = jerry_number(polystar->ptsCnt(frameNo)); jerry_object_set_sz(obj, "points", ptsCnt); jerry_value_free(ptsCnt); return obj; } static jerry_value_t _buildTrimpath(LottieTrimpath* trimpath, float frameNo) { jerry_value_t obj = jerry_object(); auto start = jerry_number(trimpath->start(frameNo)); jerry_object_set_sz(obj, "start", start); jerry_value_free(start); auto end = jerry_number(trimpath->end(frameNo)); jerry_object_set_sz(obj, "end", end); jerry_value_free(end); auto offset = jerry_number(trimpath->offset(frameNo)); jerry_object_set_sz(obj, EXP_OFFSET, offset); jerry_value_free(offset); return obj; } static jerry_value_t _effectProperty(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 name = _name(args[0]); auto property = static_cast(data->effect)->property(name); tvg::free(name); if (!property) return jerry_undefined(); return _value(data->frameNo, property); } static jerry_value_t _effect(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 layer = static_cast(data->obj); LottieEffect* effect = nullptr; //either name or index if (jerry_value_is_string(args[0])) { auto name = _name(args[0]); effect = layer->effectById(djb2Encode(name)); tvg::free(name); } else { effect = layer->effectByIdx((int16_t)jerry_value_as_int32(args[0])); } if (!effect) return jerry_undefined(); //find a effect property auto obj = jerry_function_external(_effectProperty); jerry_object_set_native_ptr(obj, &freeCb, _expcontent(data->exp, data->frameNo, effect)); jerry_object_set_sz(obj, "", obj); return obj; } static void _buildLayer(jerry_value_t context, float frameNo, LottieLayer* layer, LottieLayer* comp, LottieExpression* exp) { auto width = jerry_number(layer->w); jerry_object_set_sz(context, EXP_WIDTH, width); jerry_value_free(width); auto height = jerry_number(layer->h); jerry_object_set_sz(context, EXP_HEIGHT, height); jerry_value_free(height); auto index = jerry_number(layer->ix); jerry_object_set_sz(context, EXP_INDEX, index); jerry_value_free(index); auto parent = jerry_object(); jerry_object_set_native_ptr(parent, nullptr, layer->parent); jerry_object_set_sz(context, "parent", parent); jerry_value_free(parent); auto hasParent = jerry_boolean(layer->parent ? true : false); jerry_object_set_sz(context, "hasParent", hasParent); jerry_value_free(hasParent); auto inPoint = jerry_number(layer->inFrame); jerry_object_set_sz(context, "inPoint", inPoint); jerry_value_free(inPoint); auto outPoint = jerry_number(layer->outFrame); jerry_object_set_sz(context, "outPoint", outPoint); jerry_value_free(outPoint); //TODO: Confirm exp->layer->comp->timeAtFrame() ? auto startTime = jerry_number(exp->comp->timeAtFrame(layer->startFrame)); jerry_object_set_sz(context, "startTime", startTime); jerry_value_free(startTime); auto hasVideo = jerry_boolean(false); jerry_object_set_sz(context, "hasVideo", hasVideo); jerry_value_free(hasVideo); auto hasAudio = jerry_boolean(false); jerry_object_set_sz(context, "hasAudio", hasAudio); jerry_value_free(hasAudio); //active, #current in the animation range? auto enabled = jerry_boolean(!layer->hidden); jerry_object_set_sz(context, "enabled", enabled); jerry_value_free(enabled); auto audioActive = jerry_boolean(false); jerry_object_set_sz(context, "audioActive", audioActive); jerry_value_free(audioActive); //sampleImage(point, radius = [.5, .5], postEffect=true, t=time) _buildTransform(context, frameNo, layer->transform); //audioLevels, #the value of the Audio Levels property of the layer in decibels auto timeRemap = jerry_object(); jerry_object_set_native_ptr(timeRemap, nullptr, &layer->timeRemap); jerry_object_set_sz(context, "timeRemap", timeRemap); jerry_value_free(timeRemap); //marker.key(index) //marker.key(name) //marker.nearestKey(t) //marker.numKeys if (layer->name) { auto name = jerry_string_sz(layer->name); jerry_object_set_sz(context, EXP_NAME, name); jerry_value_free(name); } auto toComp = jerry_function_external(_toComp); jerry_object_set_sz(context, "toComp", toComp); jerry_object_set_native_ptr(toComp, nullptr, comp); jerry_value_free(toComp); //content("name"), #look for the named property from a layer auto data = _expcontent(exp, frameNo, layer, 2); auto content = jerry_function_external(_content); jerry_object_set_sz(context, EXP_CONTENT, content); jerry_object_set_native_ptr(content, &freeCb, data); jerry_value_free(content); auto effect = jerry_function_external(_effect); jerry_object_set_sz(context, EXP_EFFECT, effect); jerry_object_set_native_ptr(effect, &freeCb, data); jerry_value_free(effect); } static jerry_value_t _addsub(const jerry_value_t args[], float addsub) { //string + string if (jerry_value_is_string(args[0]) || jerry_value_is_string(args[1])) { auto a = _name(args[0]); auto b = _name(args[1]); auto ret = tvg::concat(a, b); auto val = jerry_string_sz(ret); tvg::free(ret); tvg::free(a); tvg::free(b); return val; } //number + number auto n1 = jerry_value_is_number(args[0]); auto n2 = jerry_value_is_number(args[1]); //1d + 1d if (n1 && n2) return jerry_number(jerry_value_as_number(args[0]) + addsub * jerry_value_as_number(args[1])); auto pt = _point2d(args[n1 ? 1 : 0]); //2d + 1d if (n1 || n2) { auto secondary = n1 ? 0 : 1; auto val3 = jerry_value_as_number(args[secondary]); if (secondary == 0) pt.x = (pt.x * addsub) + val3; else pt.x += (addsub * val3); //2d + 2d } else { pt += _point2d(args[1]) * addsub; } return _point2d(pt); } 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); } static jerry_value_t _add(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { return _addsub(args, 1.0f); } static jerry_value_t _sub(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { return _addsub(args, -1.0f); } static jerry_value_t _mul(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { return _muldiv(args[0], jerry_value_as_number(args[1])); } static jerry_value_t _div(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { return _muldiv(args[0], 1.0f / jerry_value_as_number(args[1])); } static jerry_value_t _interp(float t, const jerry_value_t args[], int argsCnt) { auto tMin = 0.0f; auto tMax = 1.0f; int idx = 0; tMin = jerry_value_as_number(args[1]); tMax = jerry_value_as_number(args[2]); idx += 2; t = (t - tMin) / (tMax - tMin); if (t < 0) t = 0.0f; else if (t > 1) t = 1.0f; //2d if (jerry_value_is_object(args[idx + 1]) && jerry_value_is_object(args[idx + 2])) { return _point2d(tvg::lerp(_point2d(args[idx + 1]), _point2d(args[idx + 2]), t)); } //1d return jerry_number(tvg::lerp(jerry_value_as_number(args[idx + 1]), jerry_value_as_number(args[idx + 2]), t)); } static jerry_value_t _linear(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto t = jerry_value_as_number(args[0]); return _interp(t, args, jerry_value_as_uint32(argsCnt)); } static jerry_value_t _ease(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto t = jerry_value_as_number(args[0]); t = (t < 0.5f) ? (4 * t * t * t) : (1.0f - powf(-2.0f * t + 2.0f, 3) * 0.5f); return _interp(t, args, jerry_value_as_uint32(argsCnt)); } static jerry_value_t _easeIn(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto t = jerry_value_as_number(args[0]); t = t * t * t; return _interp(t, args, jerry_value_as_uint32(argsCnt)); } static jerry_value_t _easeOut(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto t = jerry_value_as_number(args[0]); t = 1.0f - powf(1.0f - t, 3); return _interp(t, args, jerry_value_as_uint32(argsCnt)); } static jerry_value_t _clamp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto num = jerry_value_as_number(args[0]); auto limit1 = jerry_value_as_number(args[1]); auto limit2 = jerry_value_as_number(args[2]); //clamping if (num < limit1) num = limit1; if (num > limit2) num = limit2; return jerry_number(num); } static jerry_value_t _dot(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { return jerry_number(tvg::dot(_point2d(args[0]), _point2d(args[1]))); } static jerry_value_t _cross(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { return jerry_number(tvg::cross(_point2d(args[0]), _point2d(args[1]))); } static jerry_value_t _normalize(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto pt = _point2d(args[0]); return _point2d(pt / tvg::length(pt)); } static jerry_value_t _length(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { return jerry_number(tvg::length(_point2d(args[0]))); } static jerry_value_t _random(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { return jerry_number(_rand()); } static jerry_value_t _deg2rad(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { return jerry_number(deg2rad(jerry_value_as_number(args[0]))); } static jerry_value_t _rad2deg(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { return jerry_number(rad2deg(jerry_value_as_number(args[0]))); } static jerry_value_t _fromCompToSurface(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { TVGLOG("LOTTIE", "fromCompToSurface is not supported in expressions!"); return jerry_undefined(); } static jerry_value_t _content(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 group = static_cast(data->obj); auto target = group->content(_idByName(args[0])); if (!target) return jerry_undefined(); //find the a path property(sh) in the group layer? switch (target->type) { case LottieObject::Group: return _buildGroup(static_cast(target), data->frameNo); case LottieObject::Path: { auto obj = jerry_object(); jerry_object_set_native_ptr(obj, nullptr, &static_cast(target)->pathset); jerry_object_set_sz(obj, "path", obj); return obj; } case LottieObject::Polystar: return _buildPolystar(static_cast(target), data->frameNo); case LottieObject::Trimpath: return _buildTrimpath(static_cast(target), data->frameNo); default: break; } return jerry_undefined(); } 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])); } if (!layer) return jerry_undefined(); auto obj = jerry_object(); jerry_object_set_native_ptr(obj, nullptr, layer); _buildLayer(obj, data->frameNo, layer, comp, data->exp); return obj; } static jerry_value_t _nearestKey(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); auto time = jerry_value_as_number(args[0]); auto frameNo = exp->comp->frameAtTime(time); auto index = jerry_number((float)exp->property->nearest(frameNo)); auto obj = jerry_object(); jerry_object_set_sz(obj, EXP_INDEX, index); jerry_value_free(index); return obj; } static jerry_value_t _property(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 property = data->obj->property(jerry_value_as_int32(args[0])); if (!property) return jerry_undefined(); return _value(data->frameNo, property); } static jerry_value_t _propertyGroup(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 level = jerry_value_as_int32(args[0]); //intermediate group if (level == 1) { auto group = jerry_function_external(_property); jerry_object_set_native_ptr(group, &freeCb, _expcontent(data)); jerry_object_set_sz(group, "", group); return group; } TVGLOG("LOTTIE", "propertyGroup(%d)?", level); return jerry_undefined(); } static jerry_value_t _valueAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); auto time = jerry_value_as_number(args[0]); auto frameNo = exp->comp->frameAtTime(time); return _value(frameNo, exp->property); } static jerry_value_t _velocity(Point& prv, Point& cur, float elapsed) { return _point2d({(cur.x - prv.x) / elapsed, (cur.y - prv.y) / elapsed}); } static jerry_value_t _velocityAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); auto key = exp->property->nearest(exp->comp->frameAtTime(jerry_value_as_number(args[0]))); auto pframe = exp->property->frameNo(key - 1); auto cframe = exp->property->frameNo(key); auto elapsed = (cframe - pframe) / (exp->comp->frameRate); //compute the velocity switch (exp->property->type) { case LottieProperty::Type::Float: { auto prv = (*static_cast(exp->property))(pframe); auto cur = (*static_cast(exp->property))(cframe); return jerry_number((cur - prv) / elapsed); } case LottieProperty::Type::Scalar: { auto prv = (*static_cast(exp->property))(pframe); auto cur = (*static_cast(exp->property))(cframe); return _velocity(prv, cur, elapsed); } case LottieProperty::Type::Vector: { auto prv = (*static_cast(exp->property))(pframe); auto cur = (*static_cast(exp->property))(cframe); return _velocity(prv, cur, elapsed); } default: TVGLOG("LOTTIE", "Non supported type for velocityAtTime?"); } return jerry_undefined(); } static jerry_value_t _speedAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); auto key = exp->property->nearest(exp->comp->frameAtTime(jerry_value_as_number(args[0]))); auto pframe = exp->property->frameNo(key - 1); auto cframe = exp->property->frameNo(key); Point cur, prv; //compute the velocity switch (exp->property->type) { case LottieProperty::Type::Scalar: { prv = (*static_cast(exp->property))(pframe); cur = (*static_cast(exp->property))(cframe); break; } case LottieProperty::Type::Vector: { prv = (*static_cast(exp->property))(pframe); cur = (*static_cast(exp->property))(cframe); break; } default: { TVGLOG("LOTTIE", "Non supported type for speedAtTime?"); return jerry_undefined(); } } auto elapsed = (cframe - pframe) / (exp->comp->frameRate); auto speed = sqrtf(pow(cur.x - prv.x, 2) + pow(cur.y - prv.y, 2)) / elapsed; return jerry_number(speed); } static jerry_value_t _wiggle(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 freq = jerry_value_as_number(args[0]); auto amp = jerry_value_as_number(args[1]); auto octaves = (argsCnt > 2) ? jerry_value_as_int32(args[2]) : 1; auto ampm = (argsCnt > 3) ? jerry_value_as_number(args[3]) : 5.0f; auto time = (argsCnt > 4) ? jerry_value_as_number(args[4]) : data->exp->comp->timeAtFrame(data->frameNo); Point result = {100.0f, 100.0f}; for (int o = 0; o < octaves; ++o) { auto repeat = int(time * freq); auto frac = (time * freq - float(repeat)) * 1.25f; for (int i = 0; i < repeat; ++i) { result.x += (_rand() * 2.0f - 1.0f) * amp * frac; result.y += (_rand() * 2.0f - 1.0f) * amp * frac; } freq *= 2.0f; amp *= ampm; } return _point2d(result); } static jerry_value_t _temporalWiggle(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 freq = jerry_value_as_number(args[0]); auto amp = jerry_value_as_number(args[1]); auto octaves = (argsCnt > 2) ? jerry_value_as_int32(args[2]) : 1; auto ampm = (argsCnt > 3) ? jerry_value_as_number(args[3]) : 5.0f; auto time = (argsCnt > 4) ? jerry_value_as_number(args[4]) : data->exp->comp->timeAtFrame(data->frameNo); auto wiggleTime = time; for (int o = 0; o < octaves; ++o) { auto repeat = int(time * freq); auto frac = (time * freq - float(repeat)); for (int i = 0; i < repeat; ++i) { wiggleTime += (_rand() * 2.0f - 1.0f) * amp * frac; } freq *= 2.0f; amp *= ampm; } return _value(data->exp->comp->frameAtTime(wiggleTime), data->exp->property); } static bool _loopOutCommon(LottieExpression* exp, const jerry_value_t args[], const jerry_length_t argsCnt) { exp->loop.mode = LottieExpression::LoopMode::OutCycle; if (argsCnt > 0) { auto name = _name(args[0]); if (!strcmp(name, EXP_CYCLE)) exp->loop.mode = LottieExpression::LoopMode::OutCycle; else if (!strcmp(name, EXP_PINGPONG)) exp->loop.mode = LottieExpression::LoopMode::OutPingPong; else if (!strcmp(name, EXP_OFFSET)) exp->loop.mode = LottieExpression::LoopMode::OutOffset; else if (!strcmp(name, EXP_CONTINUE)) exp->loop.mode = LottieExpression::LoopMode::OutContinue; tvg::free(name); } if (exp->loop.mode != LottieExpression::LoopMode::OutCycle && exp->loop.mode != LottieExpression::LoopMode::OutPingPong) { TVGLOG("LOTTIE", "Not supported loopOut type = %d", exp->loop.mode); return false; } return true; } static jerry_value_t _loopOut(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); if (!_loopOutCommon(exp, args, argsCnt)) return jerry_undefined(); if (argsCnt > 1) exp->loop.key = jerry_value_as_int32(args[1]); auto obj = jerry_object(); jerry_object_set_native_ptr(obj, nullptr, exp->property); return obj; } static jerry_value_t _loopOutDuration(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); if (!_loopOutCommon(exp, args, argsCnt)) return jerry_undefined(); 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); return obj; } static bool _loopInCommon(LottieExpression* exp, const jerry_value_t args[], const jerry_length_t argsCnt) { exp->loop.mode = LottieExpression::LoopMode::InCycle; if (argsCnt > 0) { auto name = _name(args[0]); if (!strcmp(name, EXP_CYCLE)) exp->loop.mode = LottieExpression::LoopMode::InCycle; else if (!strcmp(name, EXP_PINGPONG)) exp->loop.mode = LottieExpression::LoopMode::InPingPong; else if (!strcmp(name, EXP_OFFSET)) exp->loop.mode = LottieExpression::LoopMode::InOffset; else if (!strcmp(name, EXP_CONTINUE)) exp->loop.mode = LottieExpression::LoopMode::InContinue; tvg::free(name); } if (exp->loop.mode != LottieExpression::LoopMode::InCycle && exp->loop.mode != LottieExpression::LoopMode::InPingPong) { TVGLOG("LOTTIE", "Not supported loopIn type = %d", exp->loop.mode); return false; } return true; } static jerry_value_t _loopIn(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); if (!_loopInCommon(exp, args, argsCnt)) return jerry_undefined(); if (argsCnt > 1) exp->loop.key = jerry_value_as_int32(args[1]); auto obj = jerry_object(); jerry_object_set_native_ptr(obj, nullptr, exp->property); return obj; } static jerry_value_t _loopInDuration(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); if (argsCnt > 1) { exp->loop.in = exp->comp->frameAtTime(jerry_value_as_number(args[1])); } if (!_loopInCommon(exp, args, argsCnt)) return jerry_undefined(); auto obj = jerry_object(); jerry_object_set_native_ptr(obj, nullptr, exp->property); return obj; } static jerry_value_t _key(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) { auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); auto frameNo = exp->property->frameNo(jerry_value_as_int32(args[0])); auto time = jerry_number(exp->comp->timeAtFrame(frameNo)); auto value = _value(frameNo, exp->property); auto obj = jerry_object(); jerry_object_set_sz(obj, EXP_TIME, time); jerry_object_set_sz(obj, EXP_INDEX, args[0]); jerry_object_set_sz(obj, EXP_VALUE, value); //direct access, key[0], key[1] if (exp->property->type == LottieProperty::Type::Float) { jerry_object_set_index(obj, 0, value); } else if (exp->property->type == LottieProperty::Type::Scalar || exp->property->type == LottieProperty::Type::Vector) { jerry_object_set_index(obj, 0, jerry_object_get_index(value, 0)); jerry_object_set_index(obj, 1, jerry_object_get_index(value, 1)); } jerry_value_free(time); jerry_value_free(value); return obj; } 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); jerry_object_set_sz(context, EXP_VALUE, value); jerry_value_free(value); auto valueAtTime = jerry_function_external(_valueAtTime); jerry_object_set_sz(context, "valueAtTime", valueAtTime); jerry_object_set_native_ptr(valueAtTime, nullptr, exp); jerry_value_free(valueAtTime); auto velocity = jerry_number(0.0f); jerry_object_set_sz(context, "velocity", velocity); jerry_value_free(velocity); auto velocityAtTime = jerry_function_external(_velocityAtTime); jerry_object_set_sz(context, "velocityAtTime", velocityAtTime); jerry_object_set_native_ptr(velocityAtTime, nullptr, exp); jerry_value_free(velocityAtTime); auto speed = jerry_number(0.0f); jerry_object_set_sz(context, "speed", speed); jerry_value_free(speed); auto speedAtTime = jerry_function_external(_speedAtTime); jerry_object_set_sz(context, "speedAtTime", speedAtTime); jerry_object_set_native_ptr(speedAtTime, nullptr, exp); jerry_value_free(speedAtTime); { auto data = _expcontent(exp, frameNo, exp->object, 3); auto wiggle = jerry_function_external(_wiggle); jerry_object_set_sz(context, "wiggle", wiggle); jerry_object_set_native_ptr(wiggle, &freeCb, data); jerry_value_free(wiggle); auto temporalWiggle = jerry_function_external(_temporalWiggle); jerry_object_set_sz(context, "temporalWiggle", temporalWiggle); jerry_object_set_native_ptr(temporalWiggle, &freeCb, data); jerry_value_free(temporalWiggle); auto propertyGroup = jerry_function_external(_propertyGroup); jerry_object_set_native_ptr(propertyGroup, &freeCb, data); jerry_object_set_sz(context, "propertyGroup", propertyGroup); jerry_value_free(propertyGroup); //propertyIndex } //smooth(width=.2, samples=5, t=time) auto loopIn = jerry_function_external(_loopIn); jerry_object_set_sz(context, "loopIn", loopIn); jerry_object_set_native_ptr(loopIn, nullptr, exp); jerry_value_free(loopIn); auto loopOut = jerry_function_external(_loopOut); jerry_object_set_sz(context, "loopOut", loopOut); jerry_object_set_native_ptr(loopOut, nullptr, exp); jerry_value_free(loopOut); auto loopInDuration = jerry_function_external(_loopInDuration); jerry_object_set_sz(context, "loopInDuration", loopInDuration); jerry_object_set_native_ptr(loopInDuration, nullptr, exp); jerry_value_free(loopInDuration); auto loopOutDuration = jerry_function_external(_loopOutDuration); jerry_object_set_sz(context, "loopOutDuration", loopOutDuration); jerry_object_set_native_ptr(loopOutDuration, nullptr, exp); jerry_value_free(loopOutDuration); auto key = jerry_function_external(_key); jerry_object_set_sz(context, "key", key); jerry_object_set_native_ptr(key, nullptr, exp); jerry_value_free(key); //key(markerName) auto nearestKey = jerry_function_external(_nearestKey); jerry_object_set_native_ptr(nearestKey, nullptr, exp); jerry_object_set_sz(context, "nearestKey", nearestKey); jerry_value_free(nearestKey); auto numKeys = jerry_number((float)exp->property->frameCnt()); jerry_object_set_sz(context, "numKeys", numKeys); jerry_value_free(numKeys); //name { auto data = _expcontent(exp, frameNo, exp->layer, 2); //content("name"), #look for the named property from a layer auto content = jerry_function_external(_content); jerry_object_set_sz(context, EXP_CONTENT, content); jerry_object_set_native_ptr(content, &freeCb, data); jerry_value_free(content); auto effect = jerry_function_external(_effect); jerry_object_set_sz(context, EXP_EFFECT, effect); jerry_object_set_native_ptr(effect, &freeCb, data); jerry_value_free(effect); } auto effect = jerry_function_external(_effect); jerry_object_set_sz(context, EXP_EFFECT, effect); jerry_object_set_native_ptr(effect, &freeCb, _expcontent(exp, frameNo, exp->layer)); jerry_value_free(effect); //expansions per types if (exp->property->type == LottieProperty::Type::PathSet) _buildPath(context, exp); } static jerry_value_t _comp(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); auto layer = comp->layerById(_idByName(args[0])); if (!layer) return jerry_undefined(); auto obj = jerry_object(); jerry_object_set_native_ptr(obj, nullptr, layer); _buildLayer(obj, data->frameNo, layer, comp, data->exp); return obj; } static void _buildMath(jerry_value_t context) { auto bm_mul = jerry_function_external(_mul); jerry_object_set_sz(context, "$bm_mul", bm_mul); jerry_value_free(bm_mul); auto bm_sum = jerry_function_external(_add); jerry_object_set_sz(context, "$bm_sum", bm_sum); jerry_value_free(bm_sum); auto bm_add = jerry_function_external(_add); jerry_object_set_sz(context, "$bm_add", bm_add); jerry_value_free(bm_add); auto bm_sub = jerry_function_external(_sub); jerry_object_set_sz(context, "$bm_sub", bm_sub); jerry_value_free(bm_sub); auto bm_div = jerry_function_external(_div); jerry_object_set_sz(context, "$bm_div", bm_div); jerry_value_free(bm_div); auto mul = jerry_function_external(_mul); jerry_object_set_sz(context, "mul", mul); jerry_value_free(mul); auto sum = jerry_function_external(_add); jerry_object_set_sz(context, "sum", sum); jerry_value_free(sum); auto add = jerry_function_external(_add); jerry_object_set_sz(context, "add", add); jerry_value_free(add); auto sub = jerry_function_external(_sub); jerry_object_set_sz(context, "sub", sub); jerry_value_free(sub); auto div = jerry_function_external(_div); jerry_object_set_sz(context, "div", div); jerry_value_free(div); auto clamp = jerry_function_external(_clamp); jerry_object_set_sz(context, "clamp", clamp); jerry_value_free(clamp); auto dot = jerry_function_external(_dot); jerry_object_set_sz(context, "dot", dot); jerry_value_free(dot); auto cross = jerry_function_external(_cross); jerry_object_set_sz(context, "cross", cross); jerry_value_free(cross); auto normalize = jerry_function_external(_normalize); jerry_object_set_sz(context, "normalize", normalize); jerry_value_free(normalize); auto length = jerry_function_external(_length); jerry_object_set_sz(context, "length", length); jerry_value_free(length); auto random = jerry_function_external(_random); jerry_object_set_sz(context, "random", random); jerry_value_free(random); auto deg2rad = jerry_function_external(_deg2rad); jerry_object_set_sz(context, "degreesToRadians", deg2rad); jerry_value_free(deg2rad); auto rad2deg = jerry_function_external(_rad2deg); jerry_object_set_sz(context, "radiansToDegrees", rad2deg); jerry_value_free(rad2deg); auto linear = jerry_function_external(_linear); jerry_object_set_sz(context, "linear", linear); jerry_value_free(linear); auto ease = jerry_function_external(_ease); jerry_object_set_sz(context, "ease", ease); jerry_value_free(ease); auto easeIn = jerry_function_external(_easeIn); jerry_object_set_sz(context, "easeIn", easeIn); jerry_value_free(easeIn); auto easeOut = jerry_function_external(_easeOut); jerry_object_set_sz(context, "easeOut", easeOut); jerry_value_free(easeOut); //lookAt } void LottieExpressions::buildGlobal(float frameNo, LottieExpression* exp) { tvg::free(static_cast(jerry_object_get_native_ptr(comp, &freeCb))); jerry_object_set_native_ptr(comp, &freeCb, _expcontent(exp, frameNo, exp->layer)); auto index = jerry_number(exp->layer->ix); jerry_object_set_sz(global, EXP_INDEX, index); jerry_value_free(index); } void LottieExpressions::buildComp(jerry_value_t context, float frameNo, LottieLayer* comp, LottieExpression* exp) { //layer(index) / layer(name) / layer(otherLayer, reIndex) auto layer = jerry_function_external(_layer); jerry_object_set_sz(context, "layer", layer); jerry_object_set_native_ptr(layer, &freeCb, _expcontent(exp, frameNo, comp)); jerry_value_free(layer); auto numLayers = jerry_number((float)comp->children.count); jerry_object_set_sz(context, "numLayers", numLayers); jerry_value_free(numLayers); } void LottieExpressions::buildComp(LottieComposition* comp, float frameNo, LottieExpression* exp) { buildComp(this->comp, frameNo, comp->root, exp); //marker //marker.key(index) //marker.key(name) //marker.nearestKey(t) //marker.numKeys //activeCamera auto width = jerry_number(comp->w); jerry_object_set_sz(thisComp, EXP_WIDTH, width); jerry_value_free(width); auto height = jerry_number(comp->h); jerry_object_set_sz(thisComp, EXP_HEIGHT, height); jerry_value_free(height); auto duration = jerry_number(comp->duration()); jerry_object_set_sz(thisComp, "duration", duration); jerry_value_free(duration); //ntscDropFrame //displayStartTime auto frameDuration = jerry_number(1.0f / comp->frameRate); jerry_object_set_sz(thisComp, "frameDuration", frameDuration); jerry_value_free(frameDuration); //shutterAngle //shutterPhase //bgColor //pixelAspect if (comp->name) { auto name = jerry_string((jerry_char_t*)comp->name, strlen(comp->name), JERRY_ENCODING_UTF8); jerry_object_set_sz(thisComp, EXP_NAME, name); jerry_value_free(name); } } jerry_value_t LottieExpressions::buildGlobal() { global = jerry_current_realm(); //comp(name) comp = jerry_function_external(_comp); jerry_object_set_sz(global, "comp", comp); //footage(name) thisComp = jerry_object(); jerry_object_set_sz(global, "thisComp", thisComp); thisLayer = jerry_object(); jerry_object_set_sz(global, "thisLayer", thisLayer); thisProperty = jerry_object(); jerry_object_set_sz(global, "thisProperty", thisProperty); auto fromCompToSurface = jerry_function_external(_fromCompToSurface); jerry_object_set_sz(global, "fromCompToSurface", fromCompToSurface); jerry_value_free(fromCompToSurface); auto createPath = jerry_function_external(_createPath); jerry_object_set_sz(global, "createPath", createPath); jerry_value_free(createPath); //posterizeTime(framesPerSecond) //value return global; } void LottieExpressions::buildWritables(LottieExpression* exp) { if (exp->writables.empty()) return; ARRAY_FOREACH(p, exp->writables) { auto writable = jerry_number(p->val); jerry_object_set_sz(global, p->var, writable); jerry_value_free(writable); } } jerry_value_t LottieExpressions::evaluate(float frameNo, LottieExpression* exp) { if (exp->disabled && exp->writables.empty()) return jerry_undefined(); buildGlobal(frameNo, exp); //main composition buildComp(exp->comp, frameNo, exp); //this composition buildComp(thisComp, frameNo, exp->layer->comp, exp); //update global context values _buildProperty(frameNo, global, exp); //this layer jerry_object_set_native_ptr(thisLayer, nullptr, exp->layer); _buildLayer(thisLayer, frameNo, exp->layer, exp->comp->root, exp); //this property jerry_object_set_native_ptr(thisProperty, nullptr, exp->property); _buildProperty(frameNo, thisProperty, exp); //expansions per object type if (exp->object->type == LottieObject::Transform) _buildTransform(global, frameNo, static_cast(exp->object)); //update writable values buildWritables(exp); //evaluate the code auto eval = jerry_eval((jerry_char_t *) exp->code, strlen(exp->code), JERRY_PARSE_NO_OPTS); if (jerry_value_is_exception(eval)) { TVGERR("LOTTIE", "Failed to dispatch the expressions!"); exp->disabled = true; return jerry_undefined(); } jerry_value_free(eval); return jerry_object_get_sz(global, "$bm_rt"); } /************************************************************************/ /* External Class Implementation */ /************************************************************************/ LottieExpressions::~LottieExpressions() { jerry_value_free(thisProperty); jerry_value_free(thisLayer); jerry_value_free(thisComp); jerry_value_free(comp); jerry_value_free(global); jerry_cleanup(); } LottieExpressions::LottieExpressions() { jerry_init(JERRY_INIT_EMPTY); _buildMath(buildGlobal()); } void LottieExpressions::update(float curTime) { //time, #current time in seconds auto time = jerry_number(curTime); jerry_object_set_sz(global, EXP_TIME, time); jerry_value_free(time); } //FIXME: Threads support #include "tvgTaskScheduler.h" LottieExpressions* LottieExpressions::instance() { //FIXME: Threads support if (TaskScheduler::threads() > 1) { TVGLOG("LOTTIE", "Lottie Expressions are not supported with tvg threads"); return nullptr; } if (!exps) exps = new LottieExpressions; ++engineRefCnt; return exps; } void LottieExpressions::retrieve(LottieExpressions* instance) { if (--engineRefCnt == 0) { delete(instance); exps = nullptr; } } Point LottieExpressions::toPoint2d(jerry_value_t obj) { return _point2d(obj); } RGB24 LottieExpressions::toColor(jerry_value_t obj) { return _color(obj); } #endif //THORVG_LOTTIE_EXPRESSIONS_SUPPORT