#ifndef Keyframe_hpp #define Keyframe_hpp #include "Lottie/Public/Primitives/AnimationTime.hpp" #include "Lottie/Public/Primitives/Vectors.hpp" #include "Lottie/Public/Keyframes/Interpolatable.hpp" #include "Lottie/Public/Keyframes/ValueInterpolators.hpp" #include namespace lottie { /// A keyframe with a single value, and timing information /// about when the value should be displayed and how it /// should be interpolated. template class Keyframe { public: /// Initialize a value-only keyframe with no time data. Keyframe( T const &value_, std::optional spatialInTangent_, std::optional spatialOutTangent_ ) : value(value_), time(0), isHold(true), inTangent(std::nullopt), outTangent(std::nullopt), spatialInTangent(spatialInTangent_), spatialOutTangent(spatialOutTangent_) { } /// Initialize a keyframe Keyframe( T value_, AnimationFrameTime time_, bool isHold_, std::optional inTangent_, std::optional outTangent_, std::optional spatialInTangent_, std::optional spatialOutTangent_ ) : value(value_), time(time_), isHold(isHold_), inTangent(inTangent_), outTangent(outTangent_), spatialInTangent(spatialInTangent_), spatialOutTangent(spatialOutTangent_) { } bool operator==(Keyframe const &rhs) { return value == rhs.value && time == rhs.time && isHold == rhs.isHold && inTangent == rhs.inTangent && outTangent == rhs.outTangent && spatialInTangent == rhs.spatialInTangent && spatialOutTangent == rhs.spatialOutTangent; } bool operator!=(Keyframe const &rhs) { return !(*this == rhs); } public: T interpolate(Keyframe const &to, double progress) { std::optional spatialOutTangent2d; if (spatialOutTangent) { spatialOutTangent2d = Vector2D(spatialOutTangent->x, spatialOutTangent->y); } std::optional spatialInTangent2d; if (to.spatialInTangent) { spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y); } return ValueInterpolator::interpolate(value, to.value, progress, spatialOutTangent2d, spatialInTangent2d); } /// Interpolates the keyTime into a value from 0-1 double interpolatedProgress(Keyframe const &to, double keyTime) { double startTime = time; double endTime = to.time; if (keyTime <= startTime) { return 0.0; } if (endTime <= keyTime) { return 1.0; } if (isHold) { return 0.0; } Vector2D outTanPoint = Vector2D::Zero(); if (outTangent.has_value()) { outTanPoint = outTangent.value(); } Vector2D inTanPoint = Vector2D(1.0, 1.0); if (to.inTangent.has_value()) { inTanPoint = to.inTangent.value(); } double progress = remapDouble(keyTime, startTime, endTime, 0.0, 1.0); if (!outTanPoint.isZero() || inTanPoint != Vector2D(1.0, 1.0)) { /// Cubic interpolation progress = cubicBezierInterpolate(progress, Vector2D::Zero(), outTanPoint, inTanPoint, Vector2D(1.0, 1.0)); } return progress; } public: /// The value of the keyframe T value; /// The time in frames of the keyframe. AnimationFrameTime time; /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. bool isHold; /// The in tangent for the time interpolation curve. std::optional inTangent; /// The out tangent for the time interpolation curve. std::optional outTangent; /// The spatial in tangent of the vector. std::optional spatialInTangent; /// The spatial out tangent of the vector. std::optional spatialOutTangent; }; template class KeyframeData { public: KeyframeData( std::optional startValue_, std::optional endValue_, std::optional time_, std::optional hold_, std::optional inTangent_, std::optional outTangent_, std::optional spatialInTangent_, std::optional spatialOutTangent_ ) : startValue(startValue_), endValue(endValue_), time(time_), hold(hold_), inTangent(inTangent_), outTangent(outTangent_), spatialInTangent(spatialInTangent_), spatialOutTangent(spatialOutTangent_) { } explicit KeyframeData(lottiejson11::Json const &json) noexcept(false) { if (!json.is_object()) { throw LottieParsingException(); } if (const auto startValueData = getOptionalAny(json.object_items(), "s")) { startValue = T(startValueData.value()); } if (const auto endValueData = getOptionalAny(json.object_items(), "e")) { endValue = T(endValueData.value()); } time = getOptionalDouble(json.object_items(), "t"); hold = getOptionalInt(json.object_items(), "h"); if (const auto inTangentData = getOptionalObject(json.object_items(), "i")) { inTangent = Vector2D(inTangentData.value()); } if (const auto outTangentData = getOptionalObject(json.object_items(), "o")) { outTangent = Vector2D(outTangentData.value()); } if (const auto spatialInTangentData = getOptionalAny(json.object_items(), "ti")) { spatialInTangent = Vector3D(spatialInTangentData.value()); } if (const auto spatialOutTangentData = getOptionalAny(json.object_items(), "to")) { spatialOutTangent = Vector3D(spatialOutTangentData.value()); } if (const auto nDataValue = getOptionalAny(json.object_items(), "n")) { nData = nDataValue.value(); } } lottiejson11::Json::object toJson() const { lottiejson11::Json::object result; if (startValue.has_value()) { result.insert(std::make_pair("s", startValue->toJson())); } if (endValue.has_value()) { result.insert(std::make_pair("e", endValue->toJson())); } if (time.has_value()) { result.insert(std::make_pair("t", time.value())); } if (hold.has_value()) { result.insert(std::make_pair("h", hold.value())); } if (inTangent.has_value()) { result.insert(std::make_pair("i", inTangent->toJson())); } if (outTangent.has_value()) { result.insert(std::make_pair("o", outTangent->toJson())); } if (spatialInTangent.has_value()) { result.insert(std::make_pair("ti", spatialInTangent->toJson())); } if (spatialOutTangent.has_value()) { result.insert(std::make_pair("to", spatialOutTangent->toJson())); } if (nData.has_value()) { result.insert(std::make_pair("n", nData.value())); } return result; } public: /// The start value of the keyframe std::optional startValue; /// The End value of the keyframe. Note: Newer versions animation json do not have this field. std::optional endValue; /// The time in frames of the keyframe. std::optional time; /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. std::optional hold; /// The in tangent for the time interpolation curve. std::optional inTangent; /// The out tangent for the time interpolation curve. std::optional outTangent; /// The spacial in tangent of the vector. std::optional spatialInTangent; /// The spacial out tangent of the vector. std::optional spatialOutTangent; std::optional nData; bool isHold() const { if (hold.has_value()) { return hold.value() > 0; } else { return false; } } }; } #endif /* Keyframe_hpp */