OpenShot Library | libopenshot 1.1.1
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
30AnimatedCurveNode::AnimatedCurveNode()
31 : AnimatedCurveNode(0, 0.0, 0.0, LINEAR) {}
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.
std::string Json() const
void SetJsonValue(const Json::Value &root)
Json::Value JsonValue() const
Point Evaluate(int64_t frame_number, double x_scale=1.0) const
InterpolationType interpolation
Definition: AnimatedCurve.h:35
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.
Definition: AnimatedCurve.h:24
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