31 #include "../include/KeyFrame.h" 40 bool IsPointBeforeX(
Point const & p,
double const x) {
44 double InterpolateLinearCurve(
Point const & left,
Point const & right,
double const target) {
45 double const diff_Y = right.
co.
Y - left.
co.
Y;
46 double const diff_X = right.
co.
X - left.
co.
X;
47 double const slope = diff_Y / diff_X;
48 return left.
co.
Y + slope * (target - left.
co.
X);
51 double InterpolateBezierCurve(
Point const & left,
Point const & right,
double const target,
double const allowed_error) {
52 double const X_diff = right.
co.
X - left.
co.
X;
53 double const Y_diff = right.
co.
Y - left.
co.
Y;
63 double B[4] = {1, 3, 3, 1};
64 double oneMinTExp = 1;
66 for (
int i = 0; i < 4; ++i, tExp *= t) {
69 for (
int i = 0; i < 4; ++i, oneMinTExp *= 1 - t) {
70 B[4 - i - 1] *= oneMinTExp;
72 double const x = p0.
X * B[0] + p1.
X * B[1] + p2.
X * B[2] + p3.
X * B[3];
73 double const y = p0.
Y * B[0] + p1.
Y * B[1] + p2.
Y * B[2] + p3.
Y * B[3];
74 if (fabs(target - x) < allowed_error) {
88 double InterpolateBetween(
Point const & left,
Point const & right,
double target,
double allowed_error) {
89 assert(left.
co.
X < target);
90 assert(target <= right.
co.
X);
93 case LINEAR:
return InterpolateLinearCurve(left, right, target);
94 case BEZIER:
return InterpolateBezierCurve(left, right, target, allowed_error);
99 template<
typename Check>
100 int64_t SearchBetweenPoints(
Point const & left,
Point const & right, int64_t
const current, Check check) {
101 int64_t start = left.
co.
X;
102 int64_t stop = right.
co.
X;
103 while (start < stop) {
104 int64_t
const mid = (start + stop + 1) / 2;
105 double const value = InterpolateBetween(left, right, mid, 0.01);
106 if (check(round(value), current)) {
118 Keyframe::Keyframe(
double value) {
120 AddPoint(
Point(value));
128 std::vector<Point>::iterator candidate =
129 std::lower_bound(begin(Points), end(Points), p.
co.
X, IsPointBeforeX);
130 if (candidate == end(Points)) {
134 }
else if ((*candidate).co.X == p.
co.
X) {
142 size_t const candidate_index = candidate - begin(Points);
144 std::move_backward(begin(Points) + candidate_index, end(Points) - 1, end(Points));
145 Points[candidate_index] = p;
150 void Keyframe::AddPoint(
double x,
double y)
163 Point new_point(x, y, interpolate);
170 int64_t Keyframe::FindIndex(
Point p)
const {
172 for (std::vector<Point>::size_type x = 0; x < Points.size(); x++) {
174 Point existing_point = Points[x];
177 if (p.
co.
X == existing_point.
co.
X && p.
co.
Y == existing_point.
co.
Y) {
188 bool Keyframe::Contains(
Point p)
const {
189 std::vector<Point>::const_iterator i =
190 std::lower_bound(begin(Points), end(Points), p.
co.
X, IsPointBeforeX);
191 return i != end(Points) && i->co.X == p.
co.
X;
195 Point Keyframe::GetClosestPoint(
Point p,
bool useLeft)
const {
196 if (Points.size() == 0) {
197 return Point(-1, -1);
202 std::vector<Point>::const_iterator candidate =
203 std::lower_bound(begin(Points), end(Points), p.
co.
X, IsPointBeforeX);
205 if (candidate == end(Points)) {
209 return Points.back();
211 if (candidate == begin(Points)) {
215 return Points.front();
218 return *(candidate - 1);
226 return GetClosestPoint(p,
false);
234 int64_t index = FindIndex(p);
238 return Points[index - 1];
244 return Point(-1, -1);
249 Point Keyframe::GetMaxPoint()
const {
250 Point maxPoint(-1, -1);
252 for (
Point const & existing_point: Points) {
253 if (existing_point.co.Y >= maxPoint.
co.
Y) {
254 maxPoint = existing_point;
262 double Keyframe::GetValue(int64_t index)
const {
263 if (Points.empty()) {
266 std::vector<Point>::const_iterator candidate =
267 std::lower_bound(begin(Points), end(Points), static_cast<double>(index), IsPointBeforeX);
269 if (candidate == end(Points)) {
271 return Points.back().co.Y;
273 if (candidate == begin(Points)) {
275 return Points.front().co.Y;
277 if (candidate->co.X == index) {
279 return candidate->co.Y;
281 std::vector<Point>::const_iterator predecessor = candidate - 1;
282 return InterpolateBetween(*predecessor, *candidate, index, 0.01);
286 int Keyframe::GetInt(int64_t index)
const {
287 return int(round(GetValue(index)));
291 int64_t Keyframe::GetLong(int64_t index)
const {
292 return long(round(GetValue(index)));
296 bool Keyframe::IsIncreasing(
int index)
const 298 if (index < 1 || (index + 1) >= GetLength()) {
301 std::vector<Point>::const_iterator candidate =
302 std::lower_bound(begin(Points), end(Points), static_cast<double>(index), IsPointBeforeX);
303 if (candidate == end(Points)) {
306 if ((candidate->co.X == index) || (candidate == begin(Points))) {
309 int64_t
const value = GetLong(index);
311 if (value < round(candidate->co.Y)) {
313 }
else if (value > round(candidate->co.Y)) {
317 }
while (candidate != end(Points));
322 std::string Keyframe::Json()
const {
325 return JsonValue().toStyledString();
329 Json::Value Keyframe::JsonValue()
const {
333 root[
"Points"] = Json::Value(Json::arrayValue);
336 for (
const auto existing_point : Points) {
337 root[
"Points"].append(existing_point.JsonValue());
345 void Keyframe::SetJson(
const std::string value) {
354 catch (
const std::exception& e)
357 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
362 void Keyframe::SetJsonValue(
const Json::Value root) {
366 if (!root[
"Points"].isNull())
368 for (
const auto existing_point : root[
"Points"]) {
382 Fraction Keyframe::GetRepeatFraction(int64_t index)
const {
385 if (index < 1 || (index + 1) >= GetLength()) {
388 assert(Points.size() > 1);
392 int64_t
const current_value = GetLong(index);
393 std::vector<Point>::const_iterator
const candidate =
394 std::lower_bound(begin(Points), end(Points), static_cast<double>(index), IsPointBeforeX);
395 assert(candidate != end(Points));
398 int64_t next_repeats = 0;
399 std::vector<Point>::const_iterator i = candidate;
404 if (i->co.X == index) {
408 bool all_constant =
true;
409 for (; i != end(Points); ++i) {
410 if (current_value != round(i->co.Y)) {
411 all_constant =
false;
415 if (! all_constant) {
420 assert(i != begin(Points));
421 Point const left = *(i - 1);
422 Point const right = *i;
424 if (current_value < round(i->co.Y)) {
425 change_at = SearchBetweenPoints(left, right, current_value, std::less_equal<double>{});
427 assert(current_value > round(i->co.Y));
428 change_at = SearchBetweenPoints(left, right, current_value, std::greater_equal<double>{});
430 next_repeats = change_at - index;
433 next_repeats = Points.back().co.X - index;
439 if (i != begin(Points)) {
447 int64_t previous_repeats = 0;
449 for (; i != begin(Points); --i) {
450 if (current_value != round(i->co.Y)) {
451 all_constant =
false;
458 if (current_value != round(i->co.Y)) {
459 assert(i != candidate);
460 all_constant =
false;
462 if (! all_constant) {
465 Point const left = *i;
466 Point const right = *(i + 1);
468 if (current_value > round(left.
co.
Y)) {
469 change_at = SearchBetweenPoints(left, right, current_value, std::less<double>{});
471 assert(current_value < round(left.
co.
Y));
472 change_at = SearchBetweenPoints(left, right, current_value, std::greater<double>{});
474 previous_repeats = index - change_at;
478 previous_repeats = index;
480 int64_t total_repeats = previous_repeats + next_repeats;
481 return Fraction(previous_repeats, total_repeats);
485 double Keyframe::GetDelta(int64_t index)
const {
486 if (index < 1)
return 0;
487 if (index == 1 && ! Points.empty())
return Points[0].co.Y;
488 if (index >= GetLength())
return 0;
489 return GetLong(index) - GetLong(index - 1);
493 Point const & Keyframe::GetPoint(int64_t index)
const {
495 if (index >= 0 && index < (int64_t)Points.size())
496 return Points[index];
503 int64_t Keyframe::GetLength()
const {
504 if (Points.empty())
return 0;
505 if (Points.size() == 1)
return 1;
506 return round(Points.back().co.X) + 1;
510 int64_t Keyframe::GetCount()
const {
512 return Points.size();
516 void Keyframe::RemovePoint(
Point p) {
518 for (std::vector<Point>::size_type x = 0; x < Points.size(); x++) {
520 Point existing_point = Points[x];
523 if (p.
co.
X == existing_point.
co.
X && p.
co.
Y == existing_point.
co.
Y) {
525 Points.erase(Points.begin() + x);
535 void Keyframe::RemovePoint(int64_t index) {
537 if (index >= 0 && index < (int64_t)Points.size())
540 Points.erase(Points.begin() + index);
547 void Keyframe::UpdatePoint(int64_t index,
Point p) {
555 void Keyframe::PrintPoints()
const {
556 cout << fixed << setprecision(4);
557 for (std::vector<Point>::const_iterator it = Points.begin(); it != Points.end(); it++) {
559 cout << p.
co.
X <<
"\t" << p.
co.
Y << endl;
563 void Keyframe::PrintValues()
const {
564 cout << fixed << setprecision(4);
565 cout <<
"Frame Number (X)\tValue (Y)\tIs Increasing\tRepeat Numerator\tRepeat Denominator\tDelta (Y Difference)\n";
567 for (int64_t i = 1; i < GetLength(); ++i) {
568 cout << i <<
"\t" << GetValue(i) <<
"\t" << IsIncreasing(i) <<
"\t" ;
569 cout << GetRepeatFraction(i).num <<
"\t" << GetRepeatFraction(i).den <<
"\t" << GetDelta(i) <<
"\n";
576 void Keyframe::ScalePoints(
double scale)
583 for (std::vector<Point>::size_type point_index = 1; point_index < Points.size(); point_index++) {
585 Points[point_index].co.X = round(Points[point_index].co.X * scale);
590 void Keyframe::FlipPoints() {
591 for (std::vector<Point>::size_type point_index = 0, reverse_index = Points.size() - 1; point_index < reverse_index; point_index++, reverse_index--) {
594 swap(Points[point_index].co.Y, Points[reverse_index].co.Y);
This class represents a Cartesian coordinate (X, Y) used in the Keyframe animation system...
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Bezier curves are quadratic curves, which create a smooth curve.
InterpolationType interpolation
This is the interpolation mode.
Coordinate handle_right
This is the right handle coordinate (in percentages from 0 to 1)
Coordinate handle_left
This is the left handle coordinate (in percentages from 0 to 1)
A Point is the basic building block of a key-frame curve.
const Json::Value stringToJson(const std::string value)
double Y
The Y value of the coordinate (usually representing the value of the property being animated) ...
This class represents a fraction.
double X
The X value of the coordinate (usually representing the frame #)
InterpolationType
This controls how a Keyframe uses this point to interpolate between two points.
This namespace is the default namespace for all code in the openshot library.
Linear curves are angular, straight lines between two points.
Coordinate co
This is the primary coordinate.
Exception for invalid JSON.
Exception for an out of bounds key-frame point.
Constant curves jump from their previous position to a new one (with no interpolation).