OpenShot Library | libopenshot 1.1.1
Loading...
Searching...
No Matches
AnimatedCurve.cpp
Go to the documentation of this file.
1
9// Copyright (c) 2008-2026 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include "AnimatedCurve.h"
14#include "Exceptions.h"
15
16#include <algorithm>
17#include <cmath>
18#include <sstream>
19
20using namespace openshot;
21
22namespace {
23constexpr double kCurveDomainMax = 255.0;
24
25static double clamp01(const double value) {
26 return std::max(0.0, std::min(1.0, value));
27}
28}
29
32
33AnimatedCurveNode::AnimatedCurveNode(int node_id, double node_x, double node_y, InterpolationType node_interpolation)
34 : id(node_id),
35 x(clamp01(node_x)),
36 y(clamp01(node_y)),
37 left_handle_x(0.5),
38 left_handle_y(1.0),
39 right_handle_x(0.5),
40 right_handle_y(0.0),
41 interpolation(node_interpolation),
42 handle_type(AUTO) {}
43
44Point AnimatedCurveNode::Evaluate(int64_t frame_number, double x_scale) const {
45 Point point(static_cast<float>(clamp01(x.GetValue(frame_number)) * x_scale),
46 static_cast<float>(clamp01(y.GetValue(frame_number))),
48 point.handle_left = Coordinate(clamp01(left_handle_x.GetValue(frame_number)),
49 clamp01(left_handle_y.GetValue(frame_number)));
50 point.handle_right = Coordinate(clamp01(right_handle_x.GetValue(frame_number)),
51 clamp01(right_handle_y.GetValue(frame_number)));
53 return point;
54}
55
56std::string AnimatedCurveNode::Json() const {
57 return JsonValue().toStyledString();
58}
59
60Json::Value AnimatedCurveNode::JsonValue() const {
61 Json::Value root(Json::objectValue);
62 root["id"] = id;
63 root["x"] = x.JsonValue();
64 root["y"] = y.JsonValue();
65 root["left_handle_x"] = left_handle_x.JsonValue();
66 root["left_handle_y"] = left_handle_y.JsonValue();
67 root["right_handle_x"] = right_handle_x.JsonValue();
68 root["right_handle_y"] = right_handle_y.JsonValue();
69 root["interpolation"] = interpolation;
70 root["handle_type"] = handle_type;
71 return root;
72}
73
74void AnimatedCurveNode::SetJson(const std::string value) {
75 try {
77 } catch (const std::exception&) {
78 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
79 }
80}
81
82void AnimatedCurveNode::SetJsonValue(const Json::Value& root) {
83 if (!root["id"].isNull())
84 id = root["id"].asInt();
85 if (!root["x"].isNull())
86 x.SetJsonValue(root["x"]);
87 if (!root["y"].isNull())
88 y.SetJsonValue(root["y"]);
89 if (!root["left_handle_x"].isNull())
90 left_handle_x.SetJsonValue(root["left_handle_x"]);
91 if (!root["left_handle_y"].isNull())
92 left_handle_y.SetJsonValue(root["left_handle_y"]);
93 if (!root["right_handle_x"].isNull())
94 right_handle_x.SetJsonValue(root["right_handle_x"]);
95 if (!root["right_handle_y"].isNull())
96 right_handle_y.SetJsonValue(root["right_handle_y"]);
97 if (!root["interpolation"].isNull())
98 interpolation = static_cast<InterpolationType>(root["interpolation"].asInt());
99 if (!root["handle_type"].isNull())
100 handle_type = static_cast<HandleType>(root["handle_type"].asInt());
101}
102
104 : enabled(1.0) {
105 nodes.emplace_back(0, 0.0, 0.0, LINEAR);
106 nodes.emplace_back(1, 1.0, 1.0, LINEAR);
107}
108
109Keyframe AnimatedCurve::BuildCurve(int64_t frame_number, double x_scale) const {
110 Keyframe curve;
111 std::vector<AnimatedCurveNode> ordered_nodes = nodes;
112
113 std::sort(ordered_nodes.begin(), ordered_nodes.end(),
114 [frame_number](const AnimatedCurveNode& lhs, const AnimatedCurveNode& rhs) {
115 const double lhs_x = lhs.x.GetValue(frame_number);
116 const double rhs_x = rhs.x.GetValue(frame_number);
117 if (lhs_x == rhs_x)
118 return lhs.id < rhs.id;
119 return lhs_x < rhs_x;
120 });
121
122 for (const auto& node : ordered_nodes) {
123 curve.AddPoint(node.Evaluate(frame_number, x_scale));
124 }
125 return curve;
126}
127
128float AnimatedCurve::Sample(float input, int64_t frame_number) const {
129 if (enabled.GetValue(frame_number) < 0.5)
130 return static_cast<float>(clamp01(input));
131
132 const Keyframe curve = BuildCurve(frame_number, kCurveDomainMax);
133 return static_cast<float>(clamp01(curve.GetValue(std::lround(clamp01(input) * kCurveDomainMax))));
134}
135
136std::string AnimatedCurve::Summary(int64_t frame_number) const {
137 std::ostringstream ss;
138 if (enabled.GetValue(frame_number) < 0.5)
139 ss << "Disabled, ";
140 ss << nodes.size() << " nodes";
141 return ss.str();
142}
143
144std::string AnimatedCurve::Json() const {
145 return JsonValue().toStyledString();
146}
147
148Json::Value AnimatedCurve::JsonValue() const {
149 Json::Value root(Json::objectValue);
150 root["enabled"] = enabled.JsonValue();
151 root["nodes"] = Json::Value(Json::arrayValue);
152 for (const auto& node : nodes)
153 root["nodes"].append(node.JsonValue());
154 return root;
155}
156
157void AnimatedCurve::SetJson(const std::string value) {
158 try {
160 } catch (const std::exception&) {
161 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
162 }
163}
164
165void AnimatedCurve::SetJsonValue(const Json::Value& root) {
166 if (!root["enabled"].isNull())
167 enabled.SetJsonValue(root["enabled"]);
168
169 if (root["nodes"].isArray()) {
170 std::vector<AnimatedCurveNode> parsed_nodes;
171 for (const auto& item : root["nodes"]) {
173 node.SetJsonValue(item);
174 parsed_nodes.push_back(node);
175 }
176 if (!parsed_nodes.empty())
177 nodes.swap(parsed_nodes);
178 }
179}
Header file for AnimatedCurve classes.
Header file for all Exception classes.
void SetJsonValue(const Json::Value &root)
Json::Value JsonValue() const
Point Evaluate(int64_t frame_number, double x_scale=1.0) const
InterpolationType interpolation
void SetJson(const std::string value)
void SetJsonValue(const Json::Value &root)
void SetJson(const std::string value)
float Sample(float input, int64_t frame_number) const
std::string Summary(int64_t frame_number) const
std::string Json() const
Keyframe BuildCurve(int64_t frame_number, double x_scale=1.0) const
Json::Value JsonValue() const
A Cartesian coordinate (X, Y) used in the Keyframe animation system.
Definition Coordinate.h:38
Exception for invalid JSON.
Definition Exceptions.h:224
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition KeyFrame.h:53
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition KeyFrame.cpp:372
void AddPoint(Point p)
Add a new point on the key-frame. Each point has a primary coordinate, a left handle,...
Definition KeyFrame.cpp:131
double GetValue(int64_t index) const
Get the value at a specific index.
Definition KeyFrame.cpp:258
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition KeyFrame.cpp:339
A Point is the basic building block of a key-frame curve.
Definition Point.h:64
Coordinate handle_left
This is the left handle coordinate (in percentages from 0 to 1)
Definition Point.h:67
HandleType handle_type
This is the handle mode.
Definition Point.h:70
Coordinate handle_right
This is the right handle coordinate (in percentages from 0 to 1)
Definition Point.h:68
This namespace is the default namespace for all code in the openshot library.
HandleType
When BEZIER interpolation is used, the point's left and right handles are used to influence the direc...
Definition Point.h:41
@ AUTO
Automatically adjust the handles to achieve the smoothest curve.
Definition Point.h:42
const Json::Value stringToJson(const std::string value)
Definition Json.cpp:16
InterpolationType
This controls how a Keyframe uses this point to interpolate between two points.
Definition Point.h:28
@ LINEAR
Linear curves are angular, straight lines between two points.
Definition Point.h:30