mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-26 18:02:14 +00:00
948 lines
27 KiB
C++
948 lines
27 KiB
C++
/*
|
|
* Copyright (c) 2023 - 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.
|
|
*/
|
|
|
|
#ifndef _TVG_LOTTIE_PROPERTY_H_
|
|
#define _TVG_LOTTIE_PROPERTY_H_
|
|
|
|
#include <algorithm>
|
|
#include "tvgMath.h"
|
|
#include "tvgLottieData.h"
|
|
#include "tvgLottieInterpolator.h"
|
|
#include "tvgLottieExpressions.h"
|
|
#include "tvgLottieModifier.h"
|
|
|
|
|
|
struct LottieFont;
|
|
struct LottieLayer;
|
|
struct LottieObject;
|
|
|
|
|
|
//default keyframe updates condition (no tweening)
|
|
#define DEFAULT_COND (!tween.active || !frames || (frames->count == 1))
|
|
|
|
|
|
template<typename T>
|
|
struct LottieScalarFrame
|
|
{
|
|
T value; //keyframe value
|
|
float no; //frame number
|
|
LottieInterpolator* interpolator;
|
|
bool hold = false; //do not interpolate.
|
|
|
|
T interpolate(LottieScalarFrame<T>* next, float frameNo)
|
|
{
|
|
auto t = (frameNo - no) / (next->no - no);
|
|
if (interpolator) t = interpolator->progress(t);
|
|
|
|
if (hold) {
|
|
if (t < 1.0f) return value;
|
|
else return next->value;
|
|
}
|
|
return lerp(value, next->value, t);
|
|
}
|
|
|
|
float angle(LottieScalarFrame* next, float frameNo)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
void prepare(TVG_UNUSED LottieScalarFrame* next)
|
|
{
|
|
}
|
|
};
|
|
|
|
|
|
template<typename T>
|
|
struct LottieVectorFrame
|
|
{
|
|
T value; //keyframe value
|
|
float no; //frame number
|
|
LottieInterpolator* interpolator;
|
|
T outTangent, inTangent;
|
|
float length;
|
|
bool hasTangent = false;
|
|
bool hold = false;
|
|
|
|
T interpolate(LottieVectorFrame* next, float frameNo)
|
|
{
|
|
auto t = (frameNo - no) / (next->no - no);
|
|
if (interpolator) t = interpolator->progress(t);
|
|
|
|
if (hold) {
|
|
if (t < 1.0f) return value;
|
|
else return next->value;
|
|
}
|
|
|
|
if (hasTangent) {
|
|
Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
|
|
return bz.at(bz.atApprox(t * length, length));
|
|
} else {
|
|
return lerp(value, next->value, t);
|
|
}
|
|
}
|
|
|
|
float angle(LottieVectorFrame* next, float frameNo)
|
|
{
|
|
if (!hasTangent) {
|
|
Point dp = next->value - value;
|
|
return rad2deg(tvg::atan2(dp.y, dp.x));
|
|
}
|
|
|
|
auto t = (frameNo - no) / (next->no - no);
|
|
if (interpolator) t = interpolator->progress(t);
|
|
Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
|
|
t = bz.atApprox(t * length, length);
|
|
return bz.angle(t >= 1.0f ? 0.99f : (t <= 0.0f ? 0.01f : t));
|
|
}
|
|
|
|
void prepare(LottieVectorFrame* next)
|
|
{
|
|
Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
|
|
length = bz.lengthApprox();
|
|
}
|
|
};
|
|
|
|
|
|
//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, Image, Invalid };
|
|
|
|
LottieExpression* exp = nullptr;
|
|
Type type;
|
|
uint8_t ix; //property index
|
|
|
|
//TODO: Apply common bodies?
|
|
virtual ~LottieProperty() {}
|
|
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;
|
|
bool disabled = false;
|
|
|
|
struct {
|
|
uint32_t key = 0; //the keyframe number repeating to
|
|
float in = FLT_MAX; //looping duration in frame number
|
|
LoopMode mode = None;
|
|
} loop;
|
|
|
|
~LottieExpression()
|
|
{
|
|
tvg::free(code);
|
|
}
|
|
};
|
|
|
|
|
|
static void _copy(PathSet* pathset, Array<Point>& out, Matrix* transform)
|
|
{
|
|
Array<Point> in;
|
|
|
|
if (transform) {
|
|
for (int i = 0; i < pathset->ptsCnt; ++i) {
|
|
Point pt = pathset->pts[i];
|
|
pt *= *transform;
|
|
out.push(pt);
|
|
}
|
|
} else {
|
|
in.data = pathset->pts;
|
|
in.count = pathset->ptsCnt;
|
|
out.push(in);
|
|
in.data = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
static void _copy(PathSet* pathset, Array<PathCommand>& out)
|
|
{
|
|
Array<PathCommand> in;
|
|
in.data = pathset->cmds;
|
|
in.count = pathset->cmdsCnt;
|
|
out.push(in);
|
|
in.data = nullptr;
|
|
}
|
|
|
|
|
|
template<typename T>
|
|
uint32_t _bsearch(T* frames, float frameNo)
|
|
{
|
|
int32_t low = 0;
|
|
int32_t high = int32_t(frames->count) - 1;
|
|
|
|
while (low <= high) {
|
|
auto mid = low + (high - low) / 2;
|
|
auto frame = frames->data + mid;
|
|
if (frameNo < frame->no) high = mid - 1;
|
|
else low = mid + 1;
|
|
}
|
|
if (high < low) low = high;
|
|
if (low < 0) low = 0;
|
|
return low;
|
|
}
|
|
|
|
|
|
template<typename T>
|
|
uint32_t _nearest(T* frames, float frameNo)
|
|
{
|
|
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 (exp->loop.mode == LottieExpression::LoopMode::None) return frameNo;
|
|
|
|
if (frameNo >= exp->loop.in || frameNo < frames->first().no || frameNo < frames->last().no) return frameNo;
|
|
|
|
frameNo -= frames->first().no;
|
|
|
|
switch (exp->loop.mode) {
|
|
case LottieExpression::LoopMode::InCycle: {
|
|
return fmodf(frameNo, frames->last().no - frames->first().no) + (*frames)[exp->loop.key].no;
|
|
}
|
|
case LottieExpression::LoopMode::InPingPong: {
|
|
auto range = frames->last().no - (*frames)[exp->loop.key].no;
|
|
auto forward = (static_cast<int>(frameNo / range) % 2) == 0 ? true : false;
|
|
frameNo = fmodf(frameNo, range);
|
|
return (forward ? frameNo : (range - frameNo)) + (*frames)[exp->loop.key].no;
|
|
}
|
|
case LottieExpression::LoopMode::OutCycle: {
|
|
return fmodf(frameNo, (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no) + frames->first().no;
|
|
}
|
|
case LottieExpression::LoopMode::OutPingPong: {
|
|
auto range = (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no;
|
|
auto forward = (static_cast<int>(frameNo / range) % 2) == 0 ? true : false;
|
|
frameNo = fmodf(frameNo, range);
|
|
return (forward ? frameNo : (range - frameNo)) + frames->first().no;
|
|
}
|
|
default: break;
|
|
}
|
|
return frameNo;
|
|
}
|
|
|
|
|
|
template<typename Frame, typename Value, bool Scalar = 1>
|
|
struct LottieGenericProperty : LottieProperty
|
|
{
|
|
//Property has an either keyframes or single value.
|
|
Array<Frame>* frames = nullptr;
|
|
Value value;
|
|
|
|
LottieGenericProperty(Value v) : value(v) {}
|
|
LottieGenericProperty() {}
|
|
|
|
LottieGenericProperty(const LottieGenericProperty<Frame, Value>& rhs)
|
|
{
|
|
copy(rhs);
|
|
type = rhs.type;
|
|
ix = rhs.ix;
|
|
}
|
|
|
|
~LottieGenericProperty()
|
|
{
|
|
release();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
Frame& newFrame()
|
|
{
|
|
if (!frames) frames = new Array<Frame>;
|
|
if (frames->count + 1 >= frames->reserved) {
|
|
auto old = frames->reserved;
|
|
frames->grow(frames->count + 2);
|
|
memset((void*)(frames->data + old), 0x00, sizeof(Frame) * (frames->reserved - old));
|
|
}
|
|
++frames->count;
|
|
return frames->last();
|
|
}
|
|
|
|
Frame& nextFrame()
|
|
{
|
|
return (*frames)[frames->count];
|
|
}
|
|
|
|
Value operator()(float frameNo, LottieExpressions* exps = nullptr)
|
|
{
|
|
//overriding with expressions
|
|
if (exps && exp) {
|
|
Value out{};
|
|
frameNo = _loop(frames, frameNo, exp);
|
|
if (exps->result<LottieGenericProperty<Frame, Value>>(frameNo, out, exp)) return out;
|
|
}
|
|
|
|
if (!frames) return value;
|
|
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);
|
|
if (tvg::equal(frame->no, frameNo)) return frame->value;
|
|
return frame->interpolate(frame + 1, frameNo);
|
|
}
|
|
|
|
Value operator()(float frameNo, Tween& tween, LottieExpressions* exps)
|
|
{
|
|
if (DEFAULT_COND) return operator()(frameNo, exps);
|
|
return lerp(operator()(frameNo, exps), operator()(tween.frameNo, exps), tween.progress);
|
|
}
|
|
|
|
void copy(const LottieGenericProperty<Frame, Value, Scalar>& rhs, bool shallow = true)
|
|
{
|
|
if (rhs.frames) {
|
|
if (shallow) {
|
|
frames = rhs.frames;
|
|
const_cast<LottieGenericProperty<Frame, Value, Scalar>&>(rhs).frames = nullptr;
|
|
} else {
|
|
frames = new Array<Frame>;
|
|
*frames = *rhs.frames;
|
|
}
|
|
} else value = rhs.value;
|
|
|
|
// TODO: 슬롯 데이터 이전 로직에 expression에 대한 정보 추가 필요
|
|
if (rhs.exp) {
|
|
exp = rhs.exp;
|
|
const_cast<LottieGenericProperty<Frame, Value, Scalar>&>(rhs).exp = nullptr;
|
|
}
|
|
}
|
|
|
|
float angle(float frameNo)
|
|
{
|
|
if (!frames || frames->count == 1) return 0;
|
|
|
|
if (frameNo <= frames->first().no) return frames->first().angle(frames->data + 1, frames->first().no);
|
|
if (frameNo >= frames->last().no) {
|
|
auto frame = frames->data + frames->count - 2;
|
|
return frame->angle(frame + 1, frames->last().no);
|
|
}
|
|
|
|
auto frame = frames->data + _bsearch(frames, frameNo);
|
|
return frame->angle(frame + 1, frameNo);
|
|
}
|
|
|
|
float angle(float frameNo, Tween& tween)
|
|
{
|
|
if (DEFAULT_COND) return angle(frameNo);
|
|
return lerp(angle(frameNo), angle(tween.frameNo), tween.progress);
|
|
}
|
|
|
|
void prepare()
|
|
{
|
|
if (Scalar) return;
|
|
if (!frames || frames->count < 2) return;
|
|
for (auto frame = frames->begin() + 1; frame < frames->end(); ++frame) {
|
|
(frame - 1)->prepare(frame);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
struct LottiePathSet : LottieProperty
|
|
{
|
|
Array<LottieScalarFrame<PathSet>>* frames = nullptr;
|
|
PathSet value;
|
|
|
|
~LottiePathSet()
|
|
{
|
|
release();
|
|
}
|
|
|
|
void release()
|
|
{
|
|
if (exp) {
|
|
delete(exp);
|
|
exp = nullptr;
|
|
}
|
|
|
|
tvg::free(value.cmds);
|
|
tvg::free(value.pts);
|
|
|
|
if (!frames) return;
|
|
|
|
ARRAY_FOREACH(p, *frames) {
|
|
tvg::free((*p).value.cmds);
|
|
tvg::free((*p).value.pts);
|
|
}
|
|
tvg::free(frames->data);
|
|
tvg::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) {
|
|
frames = tvg::calloc<Array<LottieScalarFrame<PathSet>>*>(1, sizeof(Array<LottieScalarFrame<PathSet>>));
|
|
}
|
|
if (frames->count + 1 >= frames->reserved) {
|
|
auto old = frames->reserved;
|
|
frames->grow(frames->count + 2);
|
|
memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame<PathSet>) * (frames->reserved - old));
|
|
}
|
|
++frames->count;
|
|
return frames->last();
|
|
}
|
|
|
|
LottieScalarFrame<PathSet>& nextFrame()
|
|
{
|
|
return (*frames)[frames->count];
|
|
}
|
|
|
|
//return false means requiring the interpolation
|
|
bool dispatch(float frameNo, PathSet*& path, LottieScalarFrame<PathSet>*& frame, float& t)
|
|
{
|
|
if (!frames) path = &value;
|
|
else if (frames->count == 1 || frameNo <= frames->first().no) path = &frames->first().value;
|
|
else if (frameNo >= frames->last().no) path = &frames->last().value;
|
|
else {
|
|
frame = frames->data + _bsearch(frames, frameNo);
|
|
if (tvg::equal(frame->no, frameNo)) path = &frame->value;
|
|
else if (frame->value.ptsCnt != (frame + 1)->value.ptsCnt) {
|
|
path = &frame->value;
|
|
} else {
|
|
t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
|
|
if (frame->interpolator) t = frame->interpolator->progress(t);
|
|
if (frame->hold) path = &(frame + ((t < 1.0f) ? 0 : 1))->value;
|
|
else return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool modifiedPath(float frameNo, RenderPath& out, Matrix* transform, LottieModifier* modifier)
|
|
{
|
|
PathSet* path;
|
|
LottieScalarFrame<PathSet>* frame;
|
|
RenderPath temp;
|
|
float t;
|
|
|
|
if (dispatch(frameNo, path, frame, t)) {
|
|
if (modifier) return modifier->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, transform, out);
|
|
_copy(path, out.cmds);
|
|
_copy(path, out.pts, transform);
|
|
return true;
|
|
}
|
|
|
|
//interpolate 2 frames
|
|
auto s = frame->value.pts;
|
|
auto e = (frame + 1)->value.pts;
|
|
auto interpPts = tvg::malloc<Point*>(frame->value.ptsCnt * sizeof(Point));
|
|
auto p = interpPts;
|
|
|
|
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) {
|
|
*p = lerp(*s, *e, t);
|
|
if (transform) *p *= *transform;
|
|
}
|
|
|
|
if (modifier) modifier->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, nullptr, out);
|
|
|
|
tvg::free(interpPts);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool defaultPath(float frameNo, RenderPath& out, Matrix* transform)
|
|
{
|
|
PathSet* path;
|
|
LottieScalarFrame<PathSet>* frame;
|
|
float t;
|
|
|
|
if (dispatch(frameNo, path, frame, t)) {
|
|
_copy(path, out.cmds);
|
|
_copy(path, out.pts, transform);
|
|
return true;
|
|
}
|
|
|
|
//interpolate 2 frames
|
|
auto s = frame->value.pts;
|
|
auto e = (frame + 1)->value.pts;
|
|
|
|
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) {
|
|
auto pt = lerp(*s, *e, t);
|
|
if (transform) pt *= *transform;
|
|
out.pts.push(pt);
|
|
}
|
|
_copy(&frame->value, out.cmds);
|
|
return true;
|
|
}
|
|
|
|
bool tweening(float frameNo, RenderPath& out, Matrix* transform, LottieModifier* modifier, Tween& tween, LottieExpressions* exps)
|
|
{
|
|
RenderPath to; //used as temp as well.
|
|
auto pivot = out.pts.count;
|
|
if (!operator()(frameNo, out, transform, exps)) return false;
|
|
if (!operator()(tween.frameNo, to, transform, exps)) return false;
|
|
|
|
auto from = out.pts.data + pivot;
|
|
if (to.pts.count != out.pts.count - pivot) TVGLOG("LOTTIE", "Tweening has different numbers of points in consecutive frames.");
|
|
|
|
for (uint32_t i = 0; i < std::min(to.pts.count, (out.pts.count - pivot)); ++i) {
|
|
from[i] = lerp(from[i], to.pts[i], tween.progress);
|
|
}
|
|
|
|
if (!modifier) return true;
|
|
|
|
//Apply modifiers
|
|
to.clear();
|
|
return modifier->modifyPath(to.cmds.data, to.cmds.count, to.pts.data, to.pts.count, transform, out);
|
|
}
|
|
|
|
bool operator()(float frameNo, RenderPath& out, Matrix* transform, LottieExpressions* exps, LottieModifier* modifier = nullptr)
|
|
{
|
|
//overriding with expressions
|
|
if (exps && exp) {
|
|
frameNo = _loop(frames, frameNo, exp);
|
|
if (exps->result<LottiePathSet>(frameNo, out, transform, modifier, exp)) return true;
|
|
}
|
|
|
|
if (modifier) return modifiedPath(frameNo, out, transform, modifier);
|
|
else return defaultPath(frameNo, out, transform);
|
|
}
|
|
|
|
bool operator()(float frameNo, RenderPath& out, Matrix* transform, Tween& tween, LottieExpressions* exps, LottieModifier* modifier = nullptr)
|
|
{
|
|
if (DEFAULT_COND) return operator()(frameNo, out, transform, exps, modifier);
|
|
return tweening(frameNo, out, transform, modifier, tween, exps);
|
|
}
|
|
};
|
|
|
|
|
|
struct LottieColorStop : LottieProperty
|
|
{
|
|
Array<LottieScalarFrame<ColorStop>>* frames = nullptr;
|
|
ColorStop value;
|
|
uint16_t count = 0; //colorstop count
|
|
bool populated = false;
|
|
|
|
LottieColorStop() {}
|
|
|
|
LottieColorStop(const LottieColorStop& rhs)
|
|
{
|
|
copy(rhs);
|
|
type = rhs.type;
|
|
ix = rhs.ix;
|
|
}
|
|
|
|
~LottieColorStop()
|
|
{
|
|
release();
|
|
}
|
|
|
|
void release()
|
|
{
|
|
if (exp) {
|
|
delete(exp);
|
|
exp = nullptr;
|
|
}
|
|
|
|
if (value.data) {
|
|
tvg::free(value.data);
|
|
value.data = nullptr;
|
|
}
|
|
|
|
if (!frames) return;
|
|
|
|
ARRAY_FOREACH(p, *frames) {
|
|
tvg::free((*p).value.data);
|
|
}
|
|
tvg::free(frames->data);
|
|
tvg::free(frames);
|
|
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) {
|
|
frames = tvg::calloc<Array<LottieScalarFrame<ColorStop>>*>(1, sizeof(Array<LottieScalarFrame<ColorStop>>));
|
|
}
|
|
if (frames->count + 1 >= frames->reserved) {
|
|
auto old = frames->reserved;
|
|
frames->grow(frames->count + 2);
|
|
memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame<ColorStop>) * (frames->reserved - old));
|
|
}
|
|
++frames->count;
|
|
return frames->last();
|
|
}
|
|
|
|
LottieScalarFrame<ColorStop>& nextFrame()
|
|
{
|
|
return (*frames)[frames->count];
|
|
}
|
|
|
|
Result tweening(float frameNo, Fill* fill, Tween& tween, LottieExpressions* exps)
|
|
{
|
|
auto frame = frames->data + _bsearch(frames, frameNo);
|
|
if (tvg::equal(frame->no, frameNo)) return fill->colorStops(frame->value.data, count);
|
|
|
|
//from
|
|
operator()(frameNo, fill, exps);
|
|
|
|
//to
|
|
auto dup = fill->duplicate();
|
|
operator()(tween.frameNo, dup, exps);
|
|
|
|
//interpolate
|
|
const Fill::ColorStop* from;
|
|
auto fromCnt = fill->colorStops(&from);
|
|
|
|
const Fill::ColorStop* to;
|
|
auto toCnt = fill->colorStops(&to);
|
|
|
|
if (fromCnt != toCnt) TVGLOG("LOTTIE", "Tweening has different numbers of color data in consecutive frames.");
|
|
|
|
for (uint32_t i = 0; i < std::min(fromCnt, toCnt); ++i) {
|
|
const_cast<Fill::ColorStop*>(from)->offset = lerp(from->offset, to->offset, tween.progress);
|
|
const_cast<Fill::ColorStop*>(from)->r = lerp(from->r, to->r, tween.progress);
|
|
const_cast<Fill::ColorStop*>(from)->g = lerp(from->g, to->g, tween.progress);
|
|
const_cast<Fill::ColorStop*>(from)->b = lerp(from->b, to->b, tween.progress);
|
|
const_cast<Fill::ColorStop*>(from)->a = lerp(from->a, to->a, tween.progress);
|
|
}
|
|
|
|
return Result::Success;
|
|
}
|
|
|
|
Result operator()(float frameNo, Fill* fill, LottieExpressions* exps = nullptr)
|
|
{
|
|
//overriding with expressions
|
|
if (exps && exp) {
|
|
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) {
|
|
return fill->colorStops(frames->first().value.data, count);
|
|
}
|
|
|
|
if (frameNo >= frames->last().no) return fill->colorStops(frames->last().value.data, count);
|
|
|
|
auto frame = frames->data + _bsearch(frames, frameNo);
|
|
if (tvg::equal(frame->no, frameNo)) return fill->colorStops(frame->value.data, count);
|
|
|
|
//interpolate
|
|
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) fill->colorStops(frame->value.data, count);
|
|
else fill->colorStops((frame + 1)->value.data, count);
|
|
}
|
|
|
|
auto s = frame->value.data;
|
|
auto e = (frame + 1)->value.data;
|
|
|
|
Array<Fill::ColorStop> result;
|
|
|
|
for (auto i = 0; i < count; ++i, ++s, ++e) {
|
|
auto offset = lerp(s->offset, e->offset, t);
|
|
auto r = lerp(s->r, e->r, t);
|
|
auto g = lerp(s->g, e->g, t);
|
|
auto b = lerp(s->b, e->b, t);
|
|
auto a = lerp(s->a, e->a, t);
|
|
result.push({offset, r, g, b, a});
|
|
}
|
|
return fill->colorStops(result.data, count);
|
|
}
|
|
|
|
Result operator()(float frameNo, Fill* fill, Tween& tween, LottieExpressions* exps)
|
|
{
|
|
if (DEFAULT_COND) return operator()(frameNo, fill, exps);
|
|
return tweening(frameNo, fill, tween, exps);
|
|
}
|
|
|
|
void copy(const LottieColorStop& rhs, bool shallow = true)
|
|
{
|
|
if (rhs.frames) {
|
|
if (shallow) {
|
|
frames = rhs.frames;
|
|
const_cast<LottieColorStop&>(rhs).frames = nullptr;
|
|
} else {
|
|
frames = new Array<LottieScalarFrame<ColorStop>>;
|
|
*frames = *rhs.frames;
|
|
}
|
|
} else {
|
|
value = rhs.value;
|
|
const_cast<LottieColorStop&>(rhs).value = ColorStop();
|
|
}
|
|
populated = rhs.populated;
|
|
count = rhs.count;
|
|
}
|
|
|
|
void prepare() {}
|
|
};
|
|
|
|
|
|
struct LottieTextDoc : LottieProperty
|
|
{
|
|
Array<LottieScalarFrame<TextDocument>>* frames = nullptr;
|
|
TextDocument value;
|
|
|
|
LottieTextDoc() {}
|
|
|
|
LottieTextDoc(const LottieTextDoc& rhs)
|
|
{
|
|
copy(rhs);
|
|
type = rhs.type;
|
|
ix = rhs.ix;
|
|
}
|
|
|
|
~LottieTextDoc()
|
|
{
|
|
release();
|
|
}
|
|
|
|
void release()
|
|
{
|
|
if (exp) {
|
|
delete(exp);
|
|
exp = nullptr;
|
|
}
|
|
|
|
if (value.text) {
|
|
tvg::free(value.text);
|
|
value.text = nullptr;
|
|
}
|
|
if (value.name) {
|
|
tvg::free(value.name);
|
|
value.name = nullptr;
|
|
}
|
|
|
|
if (!frames) return;
|
|
|
|
ARRAY_FOREACH(p, *frames) {
|
|
tvg::free((*p).value.text);
|
|
tvg::free((*p).value.name);
|
|
}
|
|
delete(frames);
|
|
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>>;
|
|
if (frames->count + 1 >= frames->reserved) {
|
|
auto old = frames->reserved;
|
|
frames->grow(frames->count + 2);
|
|
memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame<TextDocument>) * (frames->reserved - old));
|
|
}
|
|
++frames->count;
|
|
return frames->last();
|
|
}
|
|
|
|
LottieScalarFrame<TextDocument>& nextFrame()
|
|
{
|
|
return (*frames)[frames->count];
|
|
}
|
|
|
|
TextDocument& operator()(float frameNo, LottieExpressions* exps = nullptr)
|
|
{
|
|
//overriding with expressions
|
|
if (exps && exp) TVGERR("LOTTIE", "Not support TextDocument expressions?");
|
|
|
|
if (!frames) return value;
|
|
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);
|
|
return frame->value;
|
|
}
|
|
|
|
void copy(const LottieTextDoc& rhs, bool shallow = true)
|
|
{
|
|
if (rhs.frames) {
|
|
if (shallow) {
|
|
frames = rhs.frames;
|
|
const_cast<LottieTextDoc&>(rhs).frames = nullptr;
|
|
} else {
|
|
frames = new Array<LottieScalarFrame<TextDocument>>;
|
|
*frames = *rhs.frames;
|
|
}
|
|
} else {
|
|
value = rhs.value;
|
|
const_cast<LottieTextDoc&>(rhs).value.text = nullptr;
|
|
const_cast<LottieTextDoc&>(rhs).value.name = nullptr;
|
|
}
|
|
}
|
|
|
|
void prepare() {}
|
|
};
|
|
|
|
|
|
struct LottieBitmap : LottieProperty
|
|
{
|
|
union {
|
|
char* b64Data = nullptr;
|
|
char* path;
|
|
};
|
|
char* mimeType = nullptr;
|
|
uint32_t size = 0;
|
|
float width = 0.0f;
|
|
float height = 0.0f;
|
|
|
|
LottieBitmap() {}
|
|
|
|
LottieBitmap(const LottieBitmap& rhs)
|
|
{
|
|
copy(rhs);
|
|
type = rhs.type;
|
|
ix = rhs.ix;
|
|
}
|
|
|
|
~LottieBitmap()
|
|
{
|
|
release();
|
|
}
|
|
|
|
void release()
|
|
{
|
|
tvg::free(b64Data);
|
|
tvg::free(mimeType);
|
|
|
|
b64Data = nullptr;
|
|
mimeType = nullptr;
|
|
}
|
|
|
|
uint32_t frameCnt() override { return 0; }
|
|
uint32_t nearest(float time) override { return 0; }
|
|
float frameNo(int32_t key) override { return 0; }
|
|
|
|
void copy(const LottieBitmap& rhs, bool shallow = true)
|
|
{
|
|
if (shallow) {
|
|
b64Data = rhs.b64Data;
|
|
mimeType = rhs.mimeType;
|
|
} else {
|
|
//TODO: optimize here by avoiding data copy
|
|
TVGLOG("LOTTIE", "Shallow copy of the image data!");
|
|
b64Data = strdup(rhs.b64Data);
|
|
mimeType = strdup(rhs.mimeType);
|
|
}
|
|
size = rhs.size;
|
|
width = rhs.width;
|
|
height = rhs.height;
|
|
|
|
const_cast<LottieBitmap&>(rhs).b64Data = nullptr;
|
|
const_cast<LottieBitmap&>(rhs).mimeType = nullptr;
|
|
}
|
|
};
|
|
|
|
|
|
using LottieScalar = LottieGenericProperty<LottieScalarFrame<Point>, Point>;
|
|
using LottieFloat = LottieGenericProperty<LottieScalarFrame<float>, float>;
|
|
using LottieOpacity = LottieGenericProperty<LottieScalarFrame<uint8_t>, uint8_t>;
|
|
using LottieColor = LottieGenericProperty<LottieScalarFrame<RGB24>, RGB24>;
|
|
using LottieInteger = LottieGenericProperty<LottieScalarFrame<int8_t>, int8_t>;
|
|
using LottieVector = LottieGenericProperty<LottieVectorFrame<Point>, Point, 0>;
|
|
|
|
#endif //_TVG_LOTTIE_PROPERTY_H_
|