diff --git a/src/lib/tvgCommon.h b/src/lib/tvgCommon.h index 22683a6a..23011ffe 100644 --- a/src/lib/tvgCommon.h +++ b/src/lib/tvgCommon.h @@ -64,6 +64,8 @@ using namespace tvg; enum class FileType { Tvg = 0, Svg, Lottie, Raw, Png, Jpg, Webp, Unknown }; +using Size = Point; + #ifdef THORVG_LOG_ENABLED constexpr auto ErrorColor = "\033[31m"; //red constexpr auto ErrorBgColor = "\033[41m";//bg red diff --git a/src/lib/tvgMath.h b/src/lib/tvgMath.h index c5613e08..b28e20e5 100644 --- a/src/lib/tvgMath.h +++ b/src/lib/tvgMath.h @@ -165,4 +165,29 @@ static inline Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs) } +static inline Point operator-(const Point& lhs, const Point& rhs) +{ + return {lhs.x - rhs.x, lhs.y - rhs.y}; +} + + +static inline Point operator+(const Point& lhs, const Point& rhs) +{ + return {lhs.x + rhs.x, lhs.y + rhs.y}; +} + + +static inline Point operator*(const Point& lhs, float rhs) +{ + return {lhs.x * rhs, lhs.y * rhs}; +} + + +template +static inline T mathLerp(const T &start, const T &end, float t) +{ + return static_cast(start + (end - start) * t); +} + + #endif //_TVG_MATH_H_ diff --git a/src/loaders/lottie/meson.build b/src/loaders/lottie/meson.build index 29f37bb7..c7fb601e 100644 --- a/src/loaders/lottie/meson.build +++ b/src/loaders/lottie/meson.build @@ -1,8 +1,14 @@ source_file = [ 'tvgLottieInterpolator.h', 'tvgLottieLoader.h', + 'tvgLottieModel.h', + 'tvgLottieParser.h', + 'tvgLottieParserHandler.h', + 'tvgLottieProperty.h', 'tvgLottieInterpolator.cpp', 'tvgLottieLoader.cpp', + 'tvgLottieParserHandler.cpp', + 'tvgLottieParser.cpp' ] subloader_dep += [declare_dependency( diff --git a/src/loaders/lottie/tvgLottieLoader.cpp b/src/loaders/lottie/tvgLottieLoader.cpp index bfe1494b..e84f7322 100644 --- a/src/loaders/lottie/tvgLottieLoader.cpp +++ b/src/loaders/lottie/tvgLottieLoader.cpp @@ -23,6 +23,8 @@ #include #include "tvgLoader.h" #include "tvgLottieLoader.h" +#include "tvgLottieModel.h" +#include "tvgLottieParser.h" /************************************************************************/ /* Internal Class Implementation */ @@ -87,8 +89,8 @@ LottieLoader::~LottieLoader() void LottieLoader::run(unsigned tid) { - /* TODO: Compose current frame of Lottie Scene tree - The result should be assigned to "this->root" */ + LottieParser parser(content); + parser.parse(); } diff --git a/src/loaders/lottie/tvgLottieModel.h b/src/loaders/lottie/tvgLottieModel.h new file mode 100644 index 00000000..fb916ebc --- /dev/null +++ b/src/loaders/lottie/tvgLottieModel.h @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2023 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_MODEL_H_ +#define _TVG_LOTTIE_MODEL_H_ + +#include "tvgCommon.h" +#include "tvgRender.h" +#include "tvgLottieProperty.h" + + +struct LottieStroke +{ + bool dynamic() + { + if (dash.frames || width.frames) return true; + return false; + } + + LottieFloat dash = 0.0f; + LottieFloat width = 0.0f; + StrokeCap cap = StrokeCap::Butt; + StrokeJoin join = StrokeJoin::Miter; + float miterLimit = 0; +}; + + +struct LottieGradient +{ + bool dynamic() + { + if (start.frames || end.frames || opacity.frames || height.frames || angle.frames || colorStops.frames) return true; + return false; + } + + Fill* fill(int32_t frameNo) + { + Fill* fill = nullptr; + + //Linear Graident + if (id == 1) { + fill = LinearGradient::gen().release(); + static_cast(fill)->linear(start(frameNo).x, start(frameNo).y, end(frameNo).x, end(frameNo).y); + } + //Radial Gradient + if (id == 2) { + fill = RadialGradient::gen().release(); + TVGLOG("LOTTIE", "TODO: Missing Radial Gradient!"); + } + + if (!fill) return nullptr; + + colorStops(frameNo, fill); + + return fill; + } + + LottiePoint start = Point{0.0f, 0.0f}; + LottiePoint end = Point{0.0f, 0.0f}; + LottieOpacity opacity = 255; + LottieFloat height = 0.0f; //TODO: + LottieFloat angle = 0.0f; + LottieColorStop colorStops; + uint8_t id = 0; //1: linear, 2: radial +}; + + +struct LottieObject +{ + enum Type : uint8_t + { + Composition = 0, + Layer, + Group, + Transform, + SolidFill, + SolidStroke, + GradientFill, + GradientStroke, + Rect, + Ellipse, + Path, + Polystar, + Trim, + Repeater, + RoundedCorner, + Image + }; + + virtual ~LottieObject() + { + free(name); + } + + char* name = nullptr; + Type type; + bool statical = true; //no keyframes + bool hidden = false; +}; + + +struct LottieShape : LottieObject +{ + virtual ~LottieShape() {} + bool direction; //path direction (clock wise vs coutner clock wise) +}; + + +struct LottieRoundedCorner : LottieObject +{ + void prepare() + { + LottieObject::type = LottieObject::RoundedCorner; + if (radius.frames) statical = false; + } + + LottieFloat radius = 0.0f; +}; + + +struct LottiePath : LottieShape +{ + void prepare() + { + LottieObject::type = LottieObject::Path; + if (pathset.frames) statical = false; + } + + LottiePathSet pathset = PathSet{nullptr, nullptr, 0, 0}; +}; + + +struct LottieRect : LottieShape +{ + void prepare() + { + LottieObject::type = LottieObject::Rect; + if (position.frames || size.frames || round.frames) statical = false; + } + + float roundness(int32_t frameNo) + { + return roundedCorner ? roundedCorner->radius(frameNo) : round(frameNo); + } + + bool roundnessChanged(int prevFrame, int curFrame) + { + //return roundedCorner ? roundedCorner->radius.changed(prevFrame, curFrame) : round.changed(prevFrame, curFrame); + TVGERR("LOTTIE", "TODO: LottieRect::roundnessChanged()"); + return 0; + } + + LottieRoundedCorner* roundedCorner = nullptr; + LottiePosition position = Point{0.0f, 0.0f}; + LottiePoint size = Point{0.0f, 0.0f}; + LottieFloat round = 0.0f; +}; + + +struct LottiePolyStar : LottieShape +{ + enum Type : uint8_t {Star = 1, Polygon}; + + void prepare() + { + LottieObject::type = LottieObject::Polystar; + if (position.frames || innerRadius.frames || outerRadius.frames || innerRoundness.frames || outerRoundness.frames || rotation.frames || ptsCnt.frames) statical = false; + } + + LottiePosition position = Point{0.0f, 0.0f}; + LottieFloat innerRadius = 0.0f; + LottieFloat outerRadius = 0.0f; + LottieFloat innerRoundness = 0.0f; + LottieFloat outerRoundness = 0.0f; + LottieFloat rotation = 0.0f; + LottieFloat ptsCnt = 0.0f; + Type type = Polygon; +}; + + +struct LottieEllipse : LottieShape +{ + void prepare() + { + LottieObject::type = LottieObject::Ellipse; + if (position.frames || size.frames) statical = false; + } + + LottiePosition position = Point{0.0f, 0.0f}; + LottiePoint size = Point{0.0f, 0.0f}; +}; + + +struct LottieTransform : LottieObject +{ + struct SeparateCoord + { + LottieFloat x = 0.0f; + LottieFloat y = 0.0f; + }; + + ~LottieTransform() + { + delete(coords); + } + + void prepare() + { + LottieObject::type = LottieObject::Transform; + if (position.frames || rotation.frames || scale.frames || anchor.frames || opacity.frames) statical = false; + else if (coords && (coords->x.frames || coords->y.frames)) statical = false; + } + + LottiePosition position = Point{0.0f, 0.0f}; + LottieFloat rotation = 0.0f; + LottiePoint scale = Point{100.0f, 100.0f}; + LottiePoint anchor = Point{0.0f, 0.0f}; + LottieOpacity opacity = 255; + + //either a position or separate coordinates + SeparateCoord* coords = nullptr; +}; + + +struct LottieSolidStroke : LottieObject, LottieStroke +{ + void prepare() + { + LottieObject::type = LottieObject::SolidStroke; + if (color.frames || opacity.frames || LottieStroke::dynamic()) statical = false; + } + + LottieColor color = RGB24{255, 255, 255}; + LottieOpacity opacity = 255; + bool disabled = false; //TODO: can't replace with hidden? +}; + + +struct LottieSolidFill : LottieObject +{ + void prepare() + { + LottieObject::type = LottieObject::SolidFill; + if (color.frames || opacity.frames) statical = false; + } + + LottieColor color = RGB24{255, 255, 255}; + LottieOpacity opacity = 255; + FillRule rule = FillRule::Winding; + bool disabled = false; //TODO: can't replace with hidden? +}; + + +struct LottieGradientFill : LottieObject, LottieGradient +{ + void prepare() + { + LottieObject::type = LottieObject::GradientFill; + if (LottieGradient::dynamic()) statical = false; + } + + FillRule rule = FillRule::Winding; +}; + + +struct LottieGradientStroke : LottieObject, LottieStroke, LottieGradient +{ + void prepare() + { + LottieObject::type = LottieObject::GradientStroke; + if (LottieStroke::dynamic() || LottieGradient::dynamic()) statical = false; + } +}; + + +struct LottieImage : LottieObject +{ + Surface surface; + + void prepare() + { + LottieObject::type = LottieObject::Image; + } +}; + + +struct LottieGroup : LottieObject +{ + virtual ~LottieGroup() + { + for (auto p = children.data; p < children.end(); ++p) delete(*p); + delete(transform); + } + + void prepare(LottieObject::Type type = LottieObject::Group) + { + LottieObject::type = type; + if (transform) statical &= transform->statical; + for (auto child = children.data; child < children.end(); ++child) { + statical &= (*child)->statical; + if (!statical) break; + } + } + + virtual uint8_t opacity(int32_t frameNo) + { + if (children.empty()) return 0; + return (transform ? transform->opacity(frameNo) : 255); + } + + Scene* scene = nullptr; //tvg render data + + Array children; + LottieTransform* transform = nullptr; +}; + + +struct LottieLayer : LottieGroup +{ + enum Type : uint8_t {Precomp = 0, Solid, Image, Null, Shape, Text}; + + LottieLayer() + { + autoOrient = false; + mask = false; + } + + ~LottieLayer() + { + if (refId) { + //No need to free assets children because the Composition owns them. + children.clear(); + free(refId); + } + } + + void prepare() + { + LottieGroup::prepare(LottieObject::Layer); + + /* if layer is hidden, only useulf data is its transform matrix. + so force it to be a Null Layer and release all resource. */ + if (hidden) { + type = LottieLayer::Null; + children.reset(); + return; + } + } + + uint8_t opacity(int32_t frameNo) override + { + //return zero if the visibility is false. + if (frameNo < inFrame || frameNo > outFrame) return 0; + if (type == Null) return 255; + return LottieGroup::opacity(frameNo); + } + + /* frameRemap has the value in time domain(in sec) + To get the proper mapping first we get the mapped time at the current frame + Number then we need to convert mapped time to frame number using the + composition time line Ex: at frame 10 the mappend time is 0.5(500 ms) which + will be convert to frame number 30 if the frame rate is 60. or will result to + frame number 15 if the frame rate is 30. */ + int32_t remap(int32_t frameNo) + { + return frameNo; + //return (int32_t)((frameNo - startFrame) / timeStretch); + } + + RGB24 color = {255, 255, 255}; //Optimize: used for solidcolor + CompositeMethod matteType = CompositeMethod::None; + BlendMethod blendMethod = BlendMethod::Normal; + LottieLayer* parent = nullptr; + float timeStretch = 1.0f; + uint32_t w, h; + int32_t inFrame = 0; + int32_t outFrame = 0; + uint32_t startFrame = 0; + char* refId = nullptr; //pre-composition reference. + int16_t pid = -1; //id of the parent layer. + int16_t id = -1; //id of the current layer. + + //cached data + struct { + Matrix matrix; + int32_t frameNo; + uint8_t opacity; + } cache; + + Type type = Null; + + bool autoOrient : 1; + bool mask : 1; +}; + + +struct LottieComposition +{ + ~LottieComposition() + { + delete(root); + free(version); + free(name); + + //delete interpolators + for (auto i = interpolators.data; i < interpolators.end(); ++i) { + free((*i)->key); + free(*i); + } + + //delete assets + for (auto a = assets.data; a < assets.end(); ++a) { + delete(*a); + } + } + + float duration() const + { + return frameDuration() / frameRate; // in second + } + + uint32_t frameAtPos(float pos) const + { + if (pos < 0) pos = 0; + if (pos > 1) pos = 1; + return (uint32_t)lroundf(pos * frameDuration()); + } + + long frameAtTime(double timeInSec) const + { + return long(frameAtPos(timeInSec / duration())); + } + + uint32_t frameCnt() const + { + return endFrame - startFrame + 1; + } + + long frameDuration() const + { + return endFrame - startFrame; + } + + Scene* scene = nullptr; //tvg render data + + LottieLayer* root = nullptr; + char* version = nullptr; + char* name = nullptr; + uint32_t w, h; + long startFrame, endFrame; + float frameRate; + Array assets; + Array interpolators; +}; + +#endif //_TVG_LOTTIE_MODEL_H_ \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieParser.cpp b/src/loaders/lottie/tvgLottieParser.cpp new file mode 100644 index 00000000..4d7915bd --- /dev/null +++ b/src/loaders/lottie/tvgLottieParser.cpp @@ -0,0 +1,1010 @@ +/* + * Copyright (c) 2023 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 "tvgLottieModel.h" +#include "tvgLottieParser.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static char* _int2str(int num) +{ + char str[20]; + snprintf(str, 20, "%d", num); + return strdup(str); +} + + +BlendMethod LottieParser::getBlendMethod() +{ + switch (getInt()) { + case 1: return BlendMethod::Multiply; + case 2: return BlendMethod::Screen; + case 3: return BlendMethod::Overlay; + default: return BlendMethod::Normal; + } +} + + +RGB24 LottieParser::getColor(const char *str) +{ + RGB24 color; + + if (!str) return color; + + auto len = strlen(str); + + // some resource has empty color string, return a default color for those cases. + if (len != 7 || str[0] != '#') return color; + + char tmp[3] = {'\0', '\0', '\0'}; + tmp[0] = str[1]; + tmp[1] = str[2]; + color.rgb[0] = uint8_t(strtol(tmp, nullptr, 16)); + + tmp[0] = str[3]; + tmp[1] = str[4]; + color.rgb[1] = uint8_t(strtol(tmp, nullptr, 16)); + + tmp[0] = str[5]; + tmp[1] = str[6]; + color.rgb[2] = uint8_t(strtol(tmp, nullptr, 16)); + + return color; +} + + +FillRule LottieParser::getFillRule() +{ + switch (getInt()) { + case 2: return FillRule::EvenOdd; + default: return FillRule::Winding; + } +} + + +CompositeMethod LottieParser::getMatteType() +{ + switch (getInt()) { + case 1: return CompositeMethod::AlphaMask; + case 2: return CompositeMethod::InvAlphaMask; + case 3: return CompositeMethod::LumaMask; + case 4: return CompositeMethod::InvLumaMask; + default: return CompositeMethod::None; + } +} + + +StrokeCap LottieParser::getStrokeCap() +{ + switch (getInt()) { + case 1: return StrokeCap::Butt; + case 2: return StrokeCap::Round; + default: return StrokeCap::Square; + } +} + + +StrokeJoin LottieParser::getStrokeJoin() +{ + switch (getInt()) { + case 1: return StrokeJoin::Miter; + case 2: return StrokeJoin::Round; + default: return StrokeJoin::Bevel; + } +} + + +void LottieParser::getValue(PathSet& path) +{ + Array outs, ins, pts; + bool closed = false; + + /* The shape object could be wrapped by a array + if its part of the keyframe object */ + auto arrayWrapper = (peekType() == kArrayType) ? true : false; + if (arrayWrapper) enterArray(); + + enterObject(); + while (auto key = nextObjectKey()) { + if (!strcmp(key, "i")) { + getValue(ins); + } else if (!strcmp(key, "o")) { + getValue(outs); + } else if (!strcmp(key, "v")) { + getValue(pts); + } else if (!strcmp(key, "c")) { + closed = getBool(); + } else { + Error(); + skip(key); + } + } + + //exit properly from the array + if (arrayWrapper) nextArrayValue(); + + //valid path data? + if (ins.empty() || outs.empty() || pts.empty()) return; + if (ins.count != outs.count || outs.count != pts.count) return; + + //convert path + auto out = outs.data; + auto in = ins.data; + auto pt = pts.data; + + //Store manipulated results + Array outPts; + Array outCmds; + + //Resuse the buffers + outPts.data = path.pts; + outPts.reserved = path.ptsCnt; + outCmds.data = path.cmds; + outCmds.reserved = path.cmdsCnt; + + outPts.reserve(pts.count * 3 + 1); + outCmds.reserve(pts.count + 2); + + outCmds.push(PathCommand::MoveTo); + outPts.push(*pt); + + for (++pt, ++out, ++in; pt < pts.end(); ++pt, ++out, ++in) { + outCmds.push(PathCommand::CubicTo); + outPts.push(*(pt - 1) + *(out - 1)); + outPts.push(*pt + *in); + outPts.push(*pt); + } + + if (closed) { + outPts.push(pts.last() + outs.last()); + outPts.push(pts.first() + ins.first()); + outPts.push(pts.first()); + outCmds.push(PathCommand::CubicTo); + outCmds.push(PathCommand::Close); + } + + path.pts = outPts.data; + path.cmds = outCmds.data; + path.ptsCnt = outPts.count; + path.cmdsCnt = outCmds.count; + + outPts.data = nullptr; + outCmds.data = nullptr; +} + + +void LottieParser::getValue(ColorStop& color) +{ + if (peekType() == kArrayType) enterArray(); + + int idx = 0; + auto count = context->gradient->colorStops.count; + color.data = static_cast(malloc(sizeof(Fill::ColorStop) * count)); + + while (nextArrayValue()) { + auto remains = (idx % 4); + if (remains == 0) { + color.data[idx / 4].offset = getFloat(); + color.data[idx / 4].a = 255; //Not used. + } else if (remains == 1) { + color.data[idx / 4].r = lroundf(getFloat() * 255.0f); + } else if (remains == 2) { + color.data[idx / 4].g = lroundf(getFloat() * 255.0f); + } else if (remains == 3) { + color.data[idx / 4].b = lroundf(getFloat() * 255.0f); + } + ++idx; + } +} + + +void LottieParser::getValue(Array& pts) +{ + enterArray(); + while (nextArrayValue()) { + enterArray(); + Point pt; + getValue(pt); + pts.push(pt); + } +} + + +void LottieParser::getValue(uint8_t& val) +{ + if (peekType() == kArrayType) { + enterArray(); + if (nextArrayValue()) val = (uint8_t)(getFloat() * 2.55f); + //discard rest + while (nextArrayValue()) getFloat(); + } else if (peekType() == kNumberType) { + val = (uint8_t)(getFloat() * 2.55f); + } else { + Error(); + } +} + + +void LottieParser::getValue(float& val) +{ + if (peekType() == kArrayType) { + enterArray(); + if (nextArrayValue()) val = getFloat(); + //discard rest + while (nextArrayValue()) getFloat(); + } else if (peekType() == kNumberType) { + val = getFloat(); + } else { + Error(); + } +} + + +void LottieParser::getValue(Point& pt) +{ + int i = 0; + auto ptr = (float*)(&pt); + + if (peekType() == kArrayType) enterArray(); + + while (nextArrayValue()) { + auto val = getFloat(); + if (i < 2) ptr[i++] = val; + } +} + + +void LottieParser::getValue(RGB24& color) +{ + int i = 0; + + if (peekType() == kArrayType) enterArray(); + + while (nextArrayValue()) { + auto val = getFloat(); + if (i < 3) color.rgb[i++] = int32_t(lroundf(val * 255.0f)); + } + + //TODO: color filter? +} + + +void LottieParser::getInperpolatorPoint(Point& pt) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (!strcmp(key, "x")) getValue(pt.x); + else if (!strcmp(key, "y")) getValue(pt.y); + } +} + +template +bool LottieParser::parseTangent(const char *key, LottieVectorFrame& value) +{ + if (!strcmp(key, "ti")) { + value.hasTangent = true; + getValue(value.inTangent); + } else if (!strcmp(key, "to")) { + value.hasTangent = true; + getValue(value.outTangent); + } else return false; + + return true; +} + + +template +bool LottieParser::parseTangent(const char *key, LottieScalarFrame& value) +{ + return false; +} + + +LottieInterpolator* LottieParser::getInterpolator(const char* key, Point& in, Point& out) +{ + char buf[20]; + + if (!key) { + snprintf(buf, sizeof(buf), "%.2f_%.2f_%.2f_%.2f", in.x, in.y, out.x, out.y); + key = buf; + } + + LottieInterpolator* interpolator = nullptr; + + //get a cached interpolator if it has any. + for (auto i = comp->interpolators.data; i < comp->interpolators.end(); ++i) { + if (!strncmp((*i)->key, key, sizeof(buf))) interpolator = *i; + } + + //new interpolator + if (!interpolator) { + interpolator = static_cast(malloc(sizeof(LottieInterpolator))); + interpolator->set(key, in, out); + comp->interpolators.push(interpolator); + } + + return interpolator; +} + + +template +void LottieParser::parseKeyFrame(T& prop) +{ + Point inTangent, outTangent; + const char* interpolatorKey = nullptr; + auto& frame = prop.newFrame(); + bool interpolator = false; + + enterObject(); + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "i")) { + interpolator = true; + getInperpolatorPoint(inTangent); + } else if (!strcmp(key, "o")) { + getInperpolatorPoint(outTangent); + } else if (!strcmp(key, "n")) { + if (peekType() == kStringType) { + interpolatorKey = getString(); + } else { + enterArray(); + while (nextArrayValue()) { + if (!interpolatorKey) interpolatorKey = getString(); + else skip(nullptr); + } + } + } else if (!strcmp(key, "t")) { + frame.no = lroundf(getFloat()); + } else if (!strcmp(key, "s")) { + getValue(frame.value); + } else if (!strcmp(key, "e")) { + //current end frame and the next start frame is duplicated, + //We propagate the end value to the next frame to avoid having duplicated values. + auto& frame2 = prop.nextFrame(); + getValue(frame2.value); + } else if (parseTangent(key, frame)) { + continue; + } else skip(key); + } + + if (interpolator) { + frame.interpolator = getInterpolator(interpolatorKey, inTangent, outTangent); + } +} + +template +void LottieParser::parsePropertyInternal(T& prop) +{ + //single value property + if (peekType() == kNumberType) { + getValue(prop.value); + //multi value property + } else { + enterArray(); + while (nextArrayValue()) { + //keyframes value + if (peekType() == kObjectType) { + parseKeyFrame(prop); + //multi value property with no keyframes + } else { + getValue(prop.value); + break; + } + } + prop.prepare(); + } +} + + +template +void LottieParser::parseProperty(T& prop) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (!strcmp(key, "k")) parsePropertyInternal(prop); + else skip(key); + } +} + + +LottieRect* LottieParser::parseRect() +{ + auto rect = new LottieRect; + if (!rect) return nullptr; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "d")) rect->direction = getInt(); + else if (!strcmp(key, "s")) parseProperty(rect->size); + else if (!strcmp(key, "p")) parseProperty(rect->position); + else if (!strcmp(key, "r")) parseProperty(rect->round); + else if (!strcmp(key, "nm")) rect->name = getStringCopy(); + else if (!strcmp(key, "hd")) rect->hidden = getBool(); + else skip(key); + } + rect->prepare(); + return rect; +} + + +LottieEllipse* LottieParser::parseEllipse() +{ + auto ellipse = new LottieEllipse; + if (!ellipse) return nullptr; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "nm")) ellipse->name = getStringCopy(); + else if (!strcmp(key, "p")) parseProperty(ellipse->position); + else if (!strcmp(key, "s")) parseProperty(ellipse->size); + else if (!strcmp(key, "d")) ellipse->direction = getInt(); + else if (!strcmp(key, "hd")) ellipse->hidden = getBool(); + else skip(key); + } + ellipse->prepare(); + return ellipse; +} + + +LottieTransform* LottieParser::parseTransform(bool ddd) +{ + auto transform = new LottieTransform; + if (!transform) return nullptr; + + if (ddd) TVGLOG("LOTTIE", "3d transform(ddd) is not supported"); + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "p")) + { + enterObject(); + while (auto key = nextObjectKey()) { + if (!strcmp(key, "k")) parsePropertyInternal(transform->position); + else if (!strcmp(key, "s")) { + if (getBool()) transform->coords = new LottieTransform::SeparateCoord; + //check separateCoord to figure out whether "x(expression)" / "x(coord)" + } else if (transform->coords && !strcmp(key, "x")) { + parseProperty(transform->coords->x); + } else if (transform->coords && !strcmp(key, "y")) { + parseProperty(transform->coords->y); + } else skip(key); + } + } + else if (!strcmp(key, "a")) parseProperty(transform->anchor); + else if (!strcmp(key, "s")) parseProperty(transform->scale); + else if (!strcmp(key, "r")) parseProperty(transform->rotation); + else if (!strcmp(key, "o")) parseProperty(transform->opacity); + //else if (!strcmp(key, "sk")) //skew + //else if (!strcmp(key, "sa")) //skew axis + //else if (!strcmp(key, "rx") //3d rotation + //else if (!strcmp(key, "ry") //3d rotation + else if (!strcmp(key, "rz")) parseProperty(transform->rotation); + else if (!strcmp(key, "nm")) transform->name = getStringCopy(); + else skip(key); + } + transform->prepare(); + return transform; +} + + +LottieSolidFill* LottieParser::parseSolidFill() +{ + auto fill = new LottieSolidFill; + if (!fill) return nullptr; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "nm")) fill->name = getStringCopy(); + else if (!strcmp(key, "c")) parseProperty(fill->color); + else if (!strcmp(key, "o")) parseProperty(fill->opacity); + else if (!strcmp(key, "fillEnabled")) fill->disabled = !getBool(); + else if (!strcmp(key, "r")) fill->rule = getFillRule(); + else if (!strcmp(key, "hd")) fill->hidden = getBool(); + else skip(key); + } + fill->prepare(); + return fill; +} + + +void LottieParser::parseStrokeDash(LottieStroke* stroke) +{ + TVGLOG("LOTTIE", "StrokeDash(d) is not supported"); + + enterArray(); + while (nextArrayValue()) { + enterObject(); + while (auto key = nextObjectKey()) { + if (!strcmp(key, "v")) { + } else skip(key); + } + } +} + + +LottieSolidStroke* LottieParser::parseSolidStroke() +{ + auto stroke = new LottieSolidStroke; + if (!stroke) return nullptr; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "c")) parseProperty(stroke->color); + else if (!strcmp(key, "o")) parseProperty(stroke->opacity); + else if (!strcmp(key, "w")) parseProperty(stroke->width); + else if (!strcmp(key, "lc")) stroke->cap = getStrokeCap(); + else if (!strcmp(key, "lj")) stroke->join = getStrokeJoin(); + else if (!strcmp(key, "ml")) stroke->miterLimit = getFloat(); + else if (!strcmp(key, "nm")) stroke->name = getStringCopy(); + else if (!strcmp(key, "hd")) stroke->hidden = getBool(); + else if (!strcmp(key, "fillEnabled")) stroke->disabled = !getBool(); + else if (!strcmp(key, "d")) parseStrokeDash(stroke); + else skip(key); + } + stroke->prepare(); + return stroke; +} + + + void LottieParser::getPathSet(LottiePathSet& path) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (!strcmp(key, "k")) { + if (peekType() == kArrayType) { + enterArray(); + while (nextArrayValue()) parseKeyFrame(path); + } else { + if (path.frames) { + Error(); + return; + } + getValue(path.value); + } + } else skip(key); + } +} + + +LottiePath* LottieParser::parsePath() +{ + auto path = new LottiePath; + if (!path) return nullptr; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "nm")) path->name = getStringCopy(); + else if (!strcmp(key, "ks")) getPathSet(path->pathset); + else if (!strcmp(key, "d")) path->direction = getInt(); + else if (!strcmp(key, "hd")) path->hidden = getBool(); + else skip(key); + } + path->prepare(); + return path; +} + + +LottiePolyStar* LottieParser::parsePolyStar() +{ + auto star = new LottiePolyStar; + if (!star) return nullptr; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "nm")) star->name = getStringCopy(); + else if (!strcmp(key, "p")) parseProperty(star->position); + else if (!strcmp(key, "pt")) parseProperty(star->ptsCnt); + else if (!strcmp(key, "ir")) parseProperty(star->innerRadius); + else if (!strcmp(key, "is")) parseProperty(star->innerRoundness); + else if (!strcmp(key, "or")) parseProperty(star->outerRadius); + else if (!strcmp(key, "os")) parseProperty(star->outerRoundness); + else if (!strcmp(key, "r")) parseProperty(star->rotation); + else if (!strcmp(key, "sy")) star->type = (LottiePolyStar::Type) getInt(); + else if (!strcmp(key, "d")) star->direction = getInt(); + else if (!strcmp(key, "hd")) star->hidden = getBool(); + else skip(key); + } + star->prepare(); + return star; +} + + +LottieRoundedCorner* LottieParser::parseRoundedCorner() +{ + auto corner = new LottieRoundedCorner; + if (!corner) return nullptr; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "nm")) corner->name = getStringCopy(); + else if (!strcmp(key, "r")) parseProperty(corner->radius); + else if (!strcmp(key, "hd")) corner->hidden = getBool(); + else skip(key); + } + corner->prepare(); + return corner; +} + + +void LottieParser::parseGradient(LottieGradient* gradient, const char* key) +{ + if (!strcmp(key, "t")) gradient->id = getInt(); + else if (!strcmp(key, "o")) parseProperty(gradient->opacity); + else if (!strcmp(key, "g")) + { + enterObject(); + while (auto key = nextObjectKey()) { + if (!strcmp(key, "p")) gradient->colorStops.count = getInt(); + else if (!strcmp(key, "k")) parseProperty(gradient->colorStops); + else skip(key); + } + } + else if (!strcmp(key, "s")) parseProperty(gradient->start); + else if (!strcmp(key, "e")) parseProperty(gradient->end); + else if (!strcmp(key, "h")) parseProperty(gradient->height); + else if (!strcmp(key, "a")) parseProperty(gradient->angle); + else skip(key); +} + + +LottieGradientFill* LottieParser::parseGradientFill() +{ + auto fill = new LottieGradientFill; + if (!fill) return nullptr; + + context->gradient = fill; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "nm")) fill->name = getStringCopy(); + else if (!strcmp(key, "r")) fill->rule = getFillRule(); + else if (!strcmp(key, "hd")) fill->hidden = getBool(); + else parseGradient(fill, key); + } + + fill->prepare(); + + return fill; +} + + +LottieGradientStroke* LottieParser::parseGradientStroke() +{ + auto stroke = new LottieGradientStroke; + if (!stroke) return nullptr; + + context->gradient = stroke; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "nm")) stroke->name = getStringCopy(); + else if (!strcmp(key, "lc")) stroke->cap = getStrokeCap(); + else if (!strcmp(key, "lj")) stroke->join = getStrokeJoin(); + else if (!strcmp(key, "ml")) stroke->miterLimit = getFloat(); + else if (!strcmp(key, "hd")) stroke->hidden = getBool(); + else if (!strcmp(key, "w")) parseProperty(stroke->width); + else if (!strcmp(key, "d")) parseStrokeDash(stroke); + else parseGradient(stroke, key); + } + stroke->prepare(); + + return stroke; +} + + +LottieObject* LottieParser::parseObject() +{ + auto type = getString(); + if (!type) return nullptr; + + if (!strcmp(type, "gr")) { + return parseGroup(); + } else if (!strcmp(type, "rc")) { + return parseRect(); + } else if (!strcmp(type, "el")) { + return parseEllipse(); + } else if (!strcmp(type, "tr")) { + return parseTransform(); + } else if (!strcmp(type, "fl")) { + return parseSolidFill(); + } else if (!strcmp(type, "st")) { + return parseSolidStroke(); + } else if (!strcmp(type, "sh")) { + return parsePath(); + } else if (!strcmp(type, "sr")) { + TVGLOG("LOTTIE", "Polystar(sr) is not supported"); + return parsePolyStar(); + } else if (!strcmp(type, "rd")) { + TVGLOG("LOTTIE", "RoundedCorner(rd) is not supported"); + return parseRoundedCorner(); + } else if (!strcmp(type, "gf")) { + return parseGradientFill(); + } else if (!strcmp(type, "gs")) { + return parseGradientStroke(); + } else if (!strcmp(type, "tm")) { + TVGLOG("LOTTIE", "Trimpath(tm) is not supported"); + } else if (!strcmp(type, "rp")) { + TVGLOG("LOTTIE", "Repeater(rp) is not supported yet"); + } else if (!strcmp(type, "mm")) { + TVGLOG("LOTTIE", "MergePath(mm) is not supported yet"); + } else { + TVGERR("LOTTIE", "Unkown object type(%s) is given", type); + } + return nullptr; +} + + +void LottieParser::parseObject(LottieGroup* parent) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (!strcmp(key, "ty")) { + auto child = parseObject(); + if (child && !child->hidden) { + if (child->type == LottieObject::RoundedCorner) { + //TODO: + } + parent->children.push(child); + } else delete(child); + } else skip(key); + } +} + + +LottieImage* LottieParser::parseImage(const char* key) +{ + auto image = new LottieImage; + if (!image) return nullptr; + + //Used for Image Asset + const char* fileName = nullptr; + const char* relativePath = nullptr; + auto embedded = false; + + do { + if (!strcmp(key, "w")) { + image->surface.w = getInt(); + } else if (!strcmp(key, "h")) { + image->surface.h = getInt(); + } else if (!strcmp(key, "u")) { + relativePath = getString(); + } else if (!strcmp(key, "e")) { + embedded = getInt(); + } else if (!strcmp(key, "p")) { + fileName = getString(); + } else skip(key); + } while ((key = nextObjectKey())); + + image->prepare(); + + // embeded resource should start with "data:" + if (embedded && !strncmp(fileName, "data:", 5)) { + //TODO: + } else { + //TODO: + } + + TVGLOG("LOTTIE", "Image is not supported: (dirPath + %s + %s)", relativePath, fileName); + return image; +} + + +LottieObject* LottieParser::parseAsset() +{ + enterObject(); + + LottieObject* obj = nullptr; + char *id = nullptr; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "id")) { + if (peekType() == kStringType) { + id = getStringCopy(); + } else { + id = _int2str(getInt()); + } + //Precomposition asset + } else if (!strcmp(key, "layers")) { + obj = parseLayers(); + //Image asset + } else { + obj = parseImage(key); + break; + } + } + obj->name = id; + return obj; +} + + +void LottieParser::parseAssets() +{ + enterArray(); + while (nextArrayValue()) { + comp->assets.push(parseAsset()); + } +} + + +LottieObject* LottieParser::parseGroup() +{ + auto group = new LottieGroup; + if (!group) return nullptr; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "nm")) { + group->name = getStringCopy(); + } else if (!strcmp(key, "it")) { + enterArray(); + while (nextArrayValue()) parseObject(group); + } else skip(key); + } + if (group->children.empty()) { + delete(group); + return nullptr; + } + group->prepare(); + + return group; +} + + +void LottieParser::parseShapes(LottieLayer* layer) +{ + enterArray(); + while (nextArrayValue()) { + parseObject(layer); + } +} + + +LottieLayer* LottieParser::parseLayer() +{ + auto layer = new LottieLayer; + if (!layer) return nullptr; + + context->layer = layer; + + auto ddd = false; + + enterObject(); + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "ddd")) ddd = getInt(); //3d layer + else if (!strcmp(key, "ind")) layer->id = getInt(); + else if (!strcmp(key, "ty")) layer->type = (LottieLayer::Type) getInt(); + else if (!strcmp(key, "nm")) layer->name = getStringCopy(); + else if (!strcmp(key, "sr")) layer->timeStretch = getFloat(); + else if (!strcmp(key, "ks")) + { + enterObject(); + layer->transform = parseTransform(ddd); + } + else if (!strcmp(key, "ao")) layer->autoOrient = getInt(); + else if (!strcmp(key, "shapes")) parseShapes(layer); + else if (!strcmp(key, "ip")) layer->inFrame = lroundf(getFloat()); + else if (!strcmp(key, "op")) layer->outFrame = lroundf(getFloat()); + else if (!strcmp(key, "st")) layer->startFrame = lroundf(getFloat()); + else if (!strcmp(key, "bm")) layer->blendMethod = getBlendMethod(); + else if (!strcmp(key, "parent")) layer->pid = getInt(); + else if (!strcmp(key, "tm")) TVGLOG("LOTTIE", "Time Remap(tm) is not supported"); + else if (!strcmp(key, "w")) layer->w = getInt(); + else if (!strcmp(key, "h")) layer->h = getInt(); + else if (!strcmp(key, "sw")) layer->w = getInt(); + else if (!strcmp(key, "sh")) layer->h = getInt(); + else if (!strcmp(key, "sc")) layer->color = getColor(getString()); + else if (!strcmp(key, "tt")) layer->matteType = getMatteType(); + else if (!strcmp(key, "hasMask")) layer->mask = getBool(); + else if (!strcmp(key, "masksProperties")) TVGLOG("LOTTIE", "Masking(maskProperties) is not supported"); + else if (!strcmp(key, "hd")) layer->hidden = getBool(); + else if (!strcmp(key, "refId")) layer->refId = getStringCopy(); + else skip(key); + } + + //Not a valid layer + if (!layer->transform) { + TVGERR("LOTTIE", "Invalid Layer data, id(%d), transform(%p)", layer->id, layer->transform); + delete(layer); + return nullptr; + } + + layer->prepare(); + + return layer; +} + + +LottieLayer* LottieParser::parseLayers() +{ + auto root = new LottieLayer; + if (!root) return nullptr; + + root->type = LottieLayer::Precomp; + + enterArray(); + while (nextArrayValue()) { + if (auto layer = parseLayer()) { + root->statical &= layer->statical; + root->children.push(layer); + } + } + return root; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool LottieParser::parse() +{ + //verify json. + if (!parseNext()) return false; + + enterObject(); + + if (comp) delete(comp); + comp = new LottieComposition; + if (!comp) return false; + + //assign parsing context + LottieParser::Context context; + this->context = &context; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "v")) comp->version = getStringCopy(); + else if (!strcmp(key, "fr")) comp->frameRate = getFloat(); + else if (!strcmp(key, "ip")) comp->startFrame = lroundf(getFloat()); + else if (!strcmp(key, "op")) comp->endFrame = lroundf(getFloat()); + else if (!strcmp(key, "w")) comp->w = getInt(); + else if (!strcmp(key, "h")) comp->h = getInt(); + else if (!strcmp(key, "nm")) comp->name = getStringCopy(); + else if (!strcmp(key, "assets")) parseAssets(); + else if (!strcmp(key, "layers")) comp->root = parseLayers(); + else skip(key); + } + + if (Invalid() || !comp->root) return false; + + for (auto c = comp->root->children.data; c < comp->root->children.end(); ++c) { + auto child = static_cast(*c); + //Organize the parent-chlid layers. + if (child->pid != -1) { + for (auto p = comp->root->children.data; p < comp->root->children.end(); ++p) { + if (c == p) continue; + auto parent = static_cast(*p); + if (child->pid == parent->id) { + child->parent = parent; + break; + } + } + } + //Resolve Assets + if (child->refId) { + for (auto asset = comp->assets.data; asset < comp->assets.end(); ++asset) { + if (strcmp(child->refId, (*asset)->name)) continue; + if (child->type == LottieLayer::Precomp) { + child->children = static_cast(*asset)->children; + child->statical &= (*asset)->statical; + } else if (child->type == LottieLayer::Image) { + //TODO: + } + } + } + } + comp->root->inFrame = comp->startFrame; + comp->root->outFrame = comp->endFrame; + return true; +} \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieParser.h b/src/loaders/lottie/tvgLottieParser.h new file mode 100644 index 00000000..668c4e1d --- /dev/null +++ b/src/loaders/lottie/tvgLottieParser.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023 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_PARSER_H_ +#define _TVG_LOTTIE_PARSER_H_ + +#include "tvgCommon.h" +#include "tvgLottieParserHandler.h" + +struct LottieParser : LookaheadParserHandler +{ +public: + LottieParser(const char *str) : LookaheadParserHandler(str) {} + + bool parse(); + + LottieComposition* comp = nullptr; + +private: + BlendMethod getBlendMethod(); + RGB24 getColor(const char *str); + CompositeMethod getMatteType(); + FillRule getFillRule(); + StrokeCap getStrokeCap(); + StrokeJoin getStrokeJoin(); + LottieInterpolator* getInterpolator(const char* key, Point& in, Point& out); + + void getInperpolatorPoint(Point& pt); + void getPathSet(LottiePathSet& path); + void getValue(PathSet& path); + void getValue(Array& pts); + void getValue(ColorStop& color); + void getValue(float& val); + void getValue(uint8_t& val); + void getValue(Point& pt); + void getValue(RGB24& color); + + template bool parseTangent(const char *key, LottieVectorFrame& value); + template bool parseTangent(const char *key, LottieScalarFrame& value); + template void parseKeyFrame(T& prop); + template void parsePropertyInternal(T& prop); + template void parseProperty(T& prop); + + LottieObject* parseObject(); + LottieObject* parseAsset(); + LottieImage* parseImage(const char* key); + LottieLayer* parseLayer(); + LottieObject* parseGroup(); + LottieRect* parseRect(); + LottieEllipse* parseEllipse(); + LottieSolidFill* parseSolidFill(); + LottieTransform* parseTransform(bool ddd = false); + LottieSolidStroke* parseSolidStroke(); + LottieGradientStroke* parseGradientStroke(); + LottiePath* parsePath(); + LottiePolyStar* parsePolyStar(); + LottieRoundedCorner* parseRoundedCorner(); + LottieGradientFill* parseGradientFill(); + LottieLayer* parseLayers(); + + void parseObject(LottieGroup* parent); + void parseShapes(LottieLayer* layer); + void parseStrokeDash(LottieStroke* stroke); + void parseGradient(LottieGradient* gradient, const char* key); + void parseAssets(); + + //Current parsing context + struct Context { + LottieLayer* layer = nullptr; + LottieGradient* gradient = nullptr; + } *context; +}; + +#endif //_TVG_LOTTIE_PARSER_H_ \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieParserHandler.cpp b/src/loaders/lottie/tvgLottieParserHandler.cpp new file mode 100644 index 00000000..4335a0b5 --- /dev/null +++ b/src/loaders/lottie/tvgLottieParserHandler.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2023 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. + */ + +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgLottieParserHandler.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static const int PARSE_FLAGS = kParseDefaultFlags | kParseInsituFlag; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +bool LookaheadParserHandler::enterArray() +{ + if (state != kEnteringArray) { + Error(); + return false; + } + parseNext(); + return true; +} + + +bool LookaheadParserHandler::nextArrayValue() +{ + if (state == kExitingArray) { + parseNext(); + return false; + } + //SPECIAL CASE: same as nextObjectKey() + if (state == kExitingObject) return false; + if (state == kError || state == kHasKey) { + Error(); + return false; + } + return true; +} + + +int LookaheadParserHandler::getInt() +{ + if (state != kHasNumber || !val.IsInt()) { + Error(); + return 0; + } + auto result = val.GetInt(); + parseNext(); + return result; +} + + +float LookaheadParserHandler::getFloat() +{ + if (state != kHasNumber) { + Error(); + return 0; + } + auto result = val.GetFloat(); + parseNext(); + return result; +} + + +const char* LookaheadParserHandler::getString() +{ + if (state != kHasString) { + Error(); + return nullptr; + } + auto result = val.GetString(); + parseNext(); + return result; +} + + +char* LookaheadParserHandler::getStringCopy() +{ + auto str = getString(); + if (str) return strdup(str); + return nullptr; +} + + +bool LookaheadParserHandler::getBool() +{ + if (state != kHasBool) { + Error(); + return false; + } + auto result = val.GetBool(); + parseNext(); + return result; +} + + +void LookaheadParserHandler::getNull() +{ + if (state != kHasNull) { + Error(); + return; + } + parseNext(); +} + + +bool LookaheadParserHandler::parseNext() +{ + if (reader.HasParseError()) { + Error(); + return false; + } + if (!reader.IterativeParseNext(iss, *this)) { + Error(); + return false; + } + return true; +} + + +bool LookaheadParserHandler::enterObject() +{ + if (state != kEnteringObject) { + Error(); + return false; + } + parseNext(); + return true; +} + + +int LookaheadParserHandler::peekType() +{ + if (state >= kHasNull && state <= kHasKey) return val.GetType(); + if (state == kEnteringArray) return kArrayType; + if (state == kEnteringObject) return kObjectType; + return -1; +} + + +void LookaheadParserHandler::skipOut(int depth) +{ + do { + if (state == kEnteringArray || state == kEnteringObject) ++depth; + else if (state == kExitingArray || state == kExitingObject) --depth; + else if (state == kError) return; + parseNext(); + } while (depth > 0); +} + + +const char* LookaheadParserHandler::nextObjectKey() +{ + if (state == kHasKey) { + auto result = val.GetString(); + parseNext(); + return result; + } + + /* SPECIAL CASE: The parser works with a prdefined rule that it will be only + while (nextObjectKey()) for each object but in case of our nested group + object we can call multiple time nextObjectKey() while exiting the object + so ignore those and don't put parser in the error state. */ + if (state == kExitingArray || state == kEnteringObject) return nullptr; + + if (state != kExitingObject) { + Error(); + return nullptr; + } + + parseNext(); + return nullptr; +} + + +void LookaheadParserHandler::skip(const char* key) +{ + if (key) TVGLOG("LOTTIE", "Skipped parsing value = %s", key); + + if (peekType() == kArrayType) { + enterArray(); + skipOut(1); + } else if (peekType() == kObjectType) { + enterObject(); + skipOut(1); + } else { + skipOut(0); + } +} \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieParserHandler.h b/src/loaders/lottie/tvgLottieParserHandler.h new file mode 100644 index 00000000..5da6c443 --- /dev/null +++ b/src/loaders/lottie/tvgLottieParserHandler.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2023 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. + */ + +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_PARSER_HANDLER_H_ +#define _TVG_LOTTIE_PARSER_HANDLER_H_ + +#include "rapidjson/document.h" +#include "tvgCommon.h" + +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) + +using namespace rapidjson; + + +struct LookaheadParserHandler +{ + enum LookaheadParsingState { + kInit = 0, + kError, + kHasNull, + kHasBool, + kHasNumber, + kHasString, + kHasKey, + kEnteringObject, + kExitingObject, + kEnteringArray, + kExitingArray + }; + + Value val; + LookaheadParsingState state = kInit; + Reader reader; + InsituStringStream iss; + + LookaheadParserHandler(const char *str) : iss((char*)str) + { + reader.IterativeParseInit(); + } + + bool Null() + { + state = kHasNull; + val.SetNull(); + return true; + } + + bool Bool(bool b) + { + state = kHasBool; + val.SetBool(b); + return true; + } + + bool Int(int i) + { + state = kHasNumber; + val.SetInt(i); + return true; + } + + bool Uint(unsigned u) + { + state = kHasNumber; + val.SetUint(u); + return true; + } + + bool Int64(int64_t i) + { + state = kHasNumber; + val.SetInt64(i); + return true; + } + + bool Uint64(int64_t u) + { + state = kHasNumber; + val.SetUint64(u); + return true; + } + + bool Double(double d) + { + state = kHasNumber; + val.SetDouble(d); + return true; + } + + bool RawNumber(const char *, SizeType, TVG_UNUSED bool) + { + return false; + } + + bool String(const char *str, SizeType length, TVG_UNUSED bool) + { + state = kHasString; + val.SetString(str, length); + return true; + } + + bool StartObject() + { + state = kEnteringObject; + return true; + } + + bool Key(const char *str, SizeType length, TVG_UNUSED bool) + { + state = kHasKey; + val.SetString(str, length); + return true; + } + + bool EndObject(SizeType) + { + state = kExitingObject; + return true; + } + + bool StartArray() + { + state = kEnteringArray; + return true; + } + + bool EndArray(SizeType) + { + state = kExitingArray; + return true; + } + + void Error() + { + TVGERR("LOTTIE", "Parsing Error!"); + state = kError; + } + + bool Invalid() + { + return state == kError; + } + + bool enterObject(); + bool enterArray(); + bool nextArrayValue(); + int getInt(); + float getFloat(); + const char* getString(); + char* getStringCopy(); + bool getBool(); + void getNull(); + bool parseNext(); + const char* nextObjectKey(); + void skip(const char* key); + void skipOut(int depth); + int peekType(); +}; + +#endif //_TVG_LOTTIE_PARSER_HANDLER_H_ \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieProperty.h b/src/loaders/lottie/tvgLottieProperty.h new file mode 100644 index 00000000..9ad299be --- /dev/null +++ b/src/loaders/lottie/tvgLottieProperty.h @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2023 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 "tvgCommon.h" +#include "tvgArray.h" +#include "tvgMath.h" +#include "tvgBezier.h" +#include "tvgLottieInterpolator.h" + +struct PathSet +{ + Point* pts; + PathCommand* cmds; + uint16_t ptsCnt; + uint16_t cmdsCnt; +}; + + +struct RGB24 +{ + int32_t rgb[3]; +}; + + +struct ColorStop +{ + Fill::ColorStop* data; +}; + + +static inline RGB24 operator-(const RGB24& lhs, const RGB24& rhs) +{ + return {lhs.rgb[0] - rhs.rgb[0], lhs.rgb[1] - rhs.rgb[1], lhs.rgb[2] - rhs.rgb[2]}; +} + + +static inline RGB24 operator+(const RGB24& lhs, const RGB24& rhs) +{ + return {lhs.rgb[0] + rhs.rgb[0], lhs.rgb[1] + rhs.rgb[1], lhs.rgb[2] + rhs.rgb[2]}; +} + + +static inline RGB24 operator*(const RGB24& lhs, float rhs) +{ + return {(int32_t)lroundf(lhs.rgb[0] * rhs), (int32_t)lroundf(lhs.rgb[1] * rhs), (int32_t)lroundf(lhs.rgb[2] * rhs)}; +} + + +static void copy(PathSet& pathset, Array& outPts) +{ + Array inPts; + inPts.data = pathset.pts; + inPts.count = pathset.ptsCnt; + outPts.push(inPts); + inPts.data = nullptr; +} + + +static void copy(PathSet& pathset, Array& outCmds) +{ + Array inCmds; + inCmds.data = pathset.cmds; + inCmds.count = pathset.cmdsCnt; + outCmds.push(inCmds); + inCmds.data = nullptr; +} + + +template +struct LottieScalarFrame +{ + T value; //keyframe value + int32_t no; //frame number + LottieInterpolator* interpolator; + + T interpolate(LottieScalarFrame* next, int32_t frameNo) + { + auto t = float(frameNo - no) / float(next->no - no); + if (interpolator) t = interpolator->progress(t); + return mathLerp(value, next->value, t); + } +}; + + +template +struct LottieVectorFrame +{ + T value; //keyframe value + int32_t no; //frame number + LottieInterpolator* interpolator; + T outTangent, inTangent; + float length; + bool hasTangent = false; + + T interpolate(LottieVectorFrame* next, int32_t frameNo) + { + auto t = float(frameNo - no) / float(next->no - no); + if (interpolator) t = interpolator->progress(t); + + if (hasTangent) { + Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; + t = bezAt(bz, t * length, length); + return bezPointAt(bz, t); + } else { + return mathLerp(value, next->value, t); + } + } + + float angle(LottieVectorFrame* next, int32_t frameNo) + { + if (!hasTangent) return 0; + auto t = float(frameNo - no) / float(next->no - no); + if (interpolator) t = interpolator->progress(t); + Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; + t = bezAt(bz, t * length, length); + return -bezAngleAt(bz, t); + } + + void prepare(LottieVectorFrame* next) + { + Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; + length = bezLength(bz); + } +}; + + +template +struct LottieProperty +{ + //Property has an either keyframes or single value. + Array>* frames = nullptr; + T value; + + LottieProperty(T v) : value(v) {} + + ~LottieProperty() + { + delete(frames); + } + + LottieScalarFrame& newFrame() + { + if (!frames) frames = new Array>; + if (frames->count + 1 >= frames->reserved) { + auto old = frames->reserved; + frames->grow(frames->count + 2); + memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame) * (frames->reserved - old)); + } + ++frames->count; + return frames->last(); + } + + LottieScalarFrame& nextFrame() + { + return frames->data[frames->count]; + } + + T operator()(int32_t frameNo) + { + 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; + + for (auto frame = frames->data + 1; frame < frames->end(); ++frame) { + if (frameNo > frame->no) continue; + if (frameNo == frame->no) return frame->value; + return (frame - 1)->interpolate(frame, frameNo); + } + return value; + } + + float angle(int32_t frameNo) { return 0; } + void prepare() {} +}; + + +struct LottiePathSet +{ + Array>* frames = nullptr; + PathSet value; + + LottiePathSet(PathSet v) : value(v) + { + } + + ~LottiePathSet() + { + free(value.cmds); + free(value.pts); + + if (!frames) return; + for (auto p = frames->data; p < frames->end(); ++p) { + free((*p).value.cmds); + free((*p).value.pts); + } + free(frames->data); + free(frames); + } + + LottieScalarFrame& newFrame() + { + if (!frames) { + frames = static_cast>*>(calloc(1, sizeof(Array>))); + } + if (frames->count + 1 >= frames->reserved) { + auto old = frames->reserved; + frames->grow(frames->count + 2); + memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame) * (frames->reserved - old)); + } + ++frames->count; + return frames->last(); + } + + LottieScalarFrame& nextFrame() + { + return frames->data[frames->count]; + } + + bool operator()(int32_t frameNo, Array& cmds, Array& pts) + { + if (!frames) { + copy(value, cmds); + copy(value, pts); + return true; + } + + if (frames->count == 1 || frameNo <= frames->first().no) { + copy(frames->first().value, cmds); + copy(frames->first().value, pts); + return true; + } + + if (frameNo >= frames->last().no) { + copy(frames->last().value, cmds); + copy(frames->last().value, pts); + return true; + } + + for (auto frame = frames->data + 1; frame < frames->end(); ++frame) { + if (frameNo > frame->no) continue; + if (frameNo == frame->no) { + copy(frame->value, cmds); + copy(frame->value, pts); + return true; + } + //interpolate + auto pframe = frame - 1; + copy(pframe->value, cmds); + + auto t = float(frameNo - pframe->no) / float(frame->no - pframe->no); + if (pframe->interpolator) t = pframe->interpolator->progress(t); + + auto s = pframe->value.pts; + auto e = frame->value.pts; + + for (auto i = 0; i < pframe->value.ptsCnt; ++i, ++s, ++e) { + pts.push(mathLerp(*s, *e, t)); + } + return true; + } + return false; + } + + void prepare() {} +}; + + +struct LottieColorStop +{ + Array>* frames = nullptr; + ColorStop value; + uint16_t count = 0; //colorstop count + + ~LottieColorStop() + { + free(value.data); + if (!frames) return; + for (auto p = frames->data; p < frames->end(); ++p) { + free((*p).value.data); + } + free(frames->data); + free(frames); + } + + LottieScalarFrame& newFrame() + { + if (!frames) { + frames = static_cast>*>(calloc(1, sizeof(Array>))); + } + if (frames->count + 1 >= frames->reserved) { + auto old = frames->reserved; + frames->grow(frames->count + 2); + memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame) * (frames->reserved - old)); + } + ++frames->count; + return frames->last(); + } + + LottieScalarFrame& nextFrame() + { + return frames->data[frames->count]; + } + + void operator()(int32_t frameNo, Fill* fill) + { + if (!frames) { + fill->colorStops(value.data, count); + return; + } + + if (frames->count == 1 || frameNo <= frames->first().no) { + fill->colorStops(frames->first().value.data, count); + return; + } + + if (frameNo >= frames->last().no) { + fill->colorStops(frames->last().value.data, count); + return; + } + + for (auto frame = frames->data + 1; frame < frames->end(); ++frame) { + if (frameNo > frame->no) continue; + if (frameNo == frame->no) { + fill->colorStops(frame->value.data, count); + return; + } + + //interpolate + auto pframe = frame - 1; + auto t = float(frameNo - pframe->no) / float(frame->no - pframe->no); + if (pframe->interpolator) t = pframe->interpolator->progress(t); + + auto s = pframe->value.data; + auto e = frame->value.data; + + Array result; + + for (auto i = 0; i < count; ++i, ++s, ++e) { + auto offset = mathLerp(s->offset, e->offset, t); + auto r = mathLerp(s->r, e->r, t); + auto g = mathLerp(s->g, e->g, t); + auto b = mathLerp(s->b, e->b, t); + result.push({offset, r, g, b, 255}); + } + fill->colorStops(result.data, count); + return; + } + } + void prepare() {} +}; + + +struct LottiePosition +{ + Array>* frames = nullptr; + Point value; + + LottiePosition(Point v) : value(v) + { + } + + ~LottiePosition() + { + delete(frames); + } + + LottieVectorFrame& newFrame() + { + if (!frames) frames = new Array>; + if (frames->count + 1 >= frames->reserved) { + auto old = frames->reserved; + frames->grow(frames->count + 2); + memset((void*)(frames->data + old), 0x00, sizeof(LottieVectorFrame) * (frames->reserved - old)); + } + ++frames->count; + return frames->last(); + } + + LottieVectorFrame& nextFrame() + { + return frames->data[frames->count]; + } + + Point operator()(int32_t frameNo) + { + 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; + + for (auto frame = frames->data + 1; frame < frames->end(); ++frame) { + if (frameNo > frame->no) continue; + if (frameNo == frame->no) return frame->value; + return (frame - 1)->interpolate(frame, frameNo); + } + return value; + } + + float angle(int32_t frameNo) + { + if (!frames) return 0; + if (frames->count == 1 || frameNo <= frames->first().no) return 0; + if (frameNo >= frames->last().no) return 0; + + for (auto frame = frames->data + 1; frame < frames->end(); ++frame) { + if (frameNo > frame->no) continue; + return (frame - 1)->angle(frame, frameNo); + } + return 0; + } + + void prepare() + { + if (!frames || frames->count < 2) return; + for (auto frame = frames->data + 1; frame < frames->end(); ++frame) { + (frame - 1)->prepare(frame); + } + } + +}; + + +using LottiePoint = LottieProperty; +using LottieFloat = LottieProperty; +using LottieOpacity = LottieProperty; +using LottieColor = LottieProperty; + +#endif //_TVG_LOTTIE_PROPERTY_H_ \ No newline at end of file