2011-10-11 08:44:27 -05:00
|
|
|
/**
|
2013-09-09 23:32:16 -05:00
|
|
|
* @file
|
2013-09-12 23:41:49 -05:00
|
|
|
* @brief Source file for the Keyframe class
|
2013-09-12 17:52:10 -05:00
|
|
|
* @author Jonathan Thomas <jonathan@openshot.org>
|
|
|
|
|
*
|
2019-06-09 08:31:04 -04:00
|
|
|
* @ref License
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* LICENSE
|
2013-09-12 17:52:10 -05:00
|
|
|
*
|
2019-06-11 06:48:32 -04:00
|
|
|
* Copyright (c) 2008-2019 OpenShot Studios, LLC
|
2014-03-29 18:49:22 -05:00
|
|
|
* <http://www.openshotstudios.com/>. This file is part of
|
|
|
|
|
* OpenShot Library (libopenshot), an open-source project dedicated to
|
|
|
|
|
* delivering high quality video editing and animation solutions to the
|
|
|
|
|
* world. For more information visit <http://www.openshot.org/>.
|
2013-09-12 17:52:10 -05:00
|
|
|
*
|
2014-03-29 18:49:22 -05:00
|
|
|
* OpenShot Library (libopenshot) is free software: you can redistribute it
|
2014-07-11 16:52:14 -05:00
|
|
|
* and/or modify it under the terms of the GNU Lesser General Public License
|
2014-03-29 18:49:22 -05:00
|
|
|
* as published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
* License, or (at your option) any later version.
|
2013-09-12 17:52:10 -05:00
|
|
|
*
|
2014-03-29 18:49:22 -05:00
|
|
|
* OpenShot Library (libopenshot) is distributed in the hope that it will be
|
|
|
|
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2014-07-11 16:52:14 -05:00
|
|
|
* GNU Lesser General Public License for more details.
|
2013-09-12 17:52:10 -05:00
|
|
|
*
|
2014-07-11 16:52:14 -05:00
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
2014-03-29 18:49:22 -05:00
|
|
|
* along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
|
2011-10-11 08:44:27 -05:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "../include/KeyFrame.h"
|
2019-11-19 22:01:47 +01:00
|
|
|
#include <algorithm>
|
2019-12-06 01:21:25 +01:00
|
|
|
#include <functional>
|
2019-11-19 20:37:22 +01:00
|
|
|
#include <utility>
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
using namespace openshot;
|
|
|
|
|
|
2019-11-25 10:34:14 +01:00
|
|
|
namespace {
|
|
|
|
|
bool IsPointBeforeX(Point const & p, double const x) {
|
|
|
|
|
return p.co.X < x;
|
|
|
|
|
}
|
2019-12-03 16:56:53 +01:00
|
|
|
|
2019-12-06 01:03:56 +01:00
|
|
|
double InterpolateLinearCurve(Point const & left, Point const & right, double const target) {
|
|
|
|
|
double const diff_Y = right.co.Y - left.co.Y;
|
|
|
|
|
double const diff_X = right.co.X - left.co.X;
|
|
|
|
|
double const slope = diff_Y / diff_X;
|
|
|
|
|
return left.co.Y + slope * (target - left.co.X);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double InterpolateBezierCurve(Point const & left, Point const & right, double const target, double const allowed_error) {
|
2019-12-03 16:56:53 +01:00
|
|
|
double const X_diff = right.co.X - left.co.X;
|
|
|
|
|
double const Y_diff = right.co.Y - left.co.Y;
|
|
|
|
|
Coordinate const p0 = left.co;
|
|
|
|
|
Coordinate const p1 = Coordinate(p0.X + left.handle_right.X * X_diff, p0.Y + left.handle_right.Y * Y_diff);
|
|
|
|
|
Coordinate const p2 = Coordinate(p0.X + right.handle_left.X * X_diff, p0.Y + right.handle_left.Y * Y_diff);
|
|
|
|
|
Coordinate const p3 = right.co;
|
|
|
|
|
|
|
|
|
|
double t = 0.5;
|
|
|
|
|
double t_step = 0.25;
|
|
|
|
|
do {
|
|
|
|
|
// Bernstein polynoms
|
|
|
|
|
double B[4] = {1, 3, 3, 1};
|
|
|
|
|
double oneMinTExp = 1;
|
|
|
|
|
double tExp = 1;
|
|
|
|
|
for (int i = 0; i < 4; ++i, tExp *= t) {
|
|
|
|
|
B[i] *= tExp;
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < 4; ++i, oneMinTExp *= 1 - t) {
|
|
|
|
|
B[4 - i - 1] *= oneMinTExp;
|
|
|
|
|
}
|
|
|
|
|
double const x = p0.X * B[0] + p1.X * B[1] + p2.X * B[2] + p3.X * B[3];
|
|
|
|
|
double const y = p0.Y * B[0] + p1.Y * B[1] + p2.Y * B[2] + p3.Y * B[3];
|
2019-12-06 01:03:56 +01:00
|
|
|
if (abs(target - x) < allowed_error) {
|
|
|
|
|
return y;
|
2019-12-03 16:56:53 +01:00
|
|
|
}
|
2019-12-06 01:03:56 +01:00
|
|
|
if (x > target) {
|
2019-12-03 16:56:53 +01:00
|
|
|
t -= t_step;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
t += t_step;
|
|
|
|
|
}
|
|
|
|
|
t_step /= 2;
|
|
|
|
|
} while (true);
|
|
|
|
|
}
|
2019-12-06 01:03:56 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
double InterpolateBetween(Point const & left, Point const & right, double target, double allowed_error) {
|
|
|
|
|
assert(left.co.X < target);
|
|
|
|
|
assert(target <= right.co.X);
|
|
|
|
|
switch (right.interpolation) {
|
|
|
|
|
case CONSTANT: return left.co.Y;
|
|
|
|
|
case LINEAR: return InterpolateLinearCurve(left, right, target);
|
|
|
|
|
case BEZIER: return InterpolateBezierCurve(left, right, target, allowed_error);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-06 01:21:25 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
template<typename Check>
|
|
|
|
|
int64_t SearchBetweenPoints(Point const & left, Point const & right, int64_t const current, Check check) {
|
|
|
|
|
int64_t start = left.co.X;
|
|
|
|
|
int64_t stop = right.co.X;
|
|
|
|
|
while (start < stop) {
|
|
|
|
|
int64_t const mid = (start + stop + 1) / 2;
|
|
|
|
|
double const value = InterpolateBetween(left, right, mid, 0.01);
|
|
|
|
|
if (check(round(value), current)) {
|
|
|
|
|
start = mid;
|
|
|
|
|
} else {
|
|
|
|
|
stop = mid - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return start;
|
|
|
|
|
}
|
2019-11-25 10:34:14 +01:00
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2019-11-02 18:01:59 +02:00
|
|
|
// Constructor which sets the default point & coordinate at X=1
|
2019-11-22 22:37:06 +01:00
|
|
|
Keyframe::Keyframe(double value) {
|
2012-10-03 01:55:24 -05:00
|
|
|
// Add initial point
|
|
|
|
|
AddPoint(Point(value));
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-04 15:07:29 -05:00
|
|
|
// Add a new point on the key-frame. Each point has a primary coordinate,
|
|
|
|
|
// a left handle, and a right handle.
|
2011-10-11 08:44:27 -05:00
|
|
|
void Keyframe::AddPoint(Point p) {
|
2019-11-19 22:27:29 +01:00
|
|
|
// candidate is not less (greater or equal) than the new point in
|
|
|
|
|
// the X coordinate.
|
|
|
|
|
std::vector<Point>::iterator candidate =
|
2019-11-25 10:34:14 +01:00
|
|
|
std::lower_bound(begin(Points), end(Points), p.co.X, IsPointBeforeX);
|
2019-11-19 22:27:29 +01:00
|
|
|
if (candidate == end(Points)) {
|
|
|
|
|
// New point X is greater than all other points' X, add to
|
|
|
|
|
// back.
|
|
|
|
|
Points.push_back(p);
|
|
|
|
|
} else if ((*candidate).co.X == p.co.X) {
|
|
|
|
|
// New point is at same X coordinate as some point, overwrite
|
|
|
|
|
// point.
|
|
|
|
|
*candidate = p;
|
|
|
|
|
} else {
|
|
|
|
|
// New point needs to be inserted before candidate; thus move
|
|
|
|
|
// candidate and all following one to the right and insert new
|
|
|
|
|
// point then where candidate was.
|
2019-11-21 11:35:23 +01:00
|
|
|
size_t const candidate_index = candidate - begin(Points);
|
|
|
|
|
Points.push_back(p); // Make space; could also be a dummy point. INVALIDATES candidate!
|
|
|
|
|
std::move_backward(begin(Points) + candidate_index, end(Points) - 1, end(Points));
|
|
|
|
|
Points[candidate_index] = p;
|
2019-11-19 22:27:29 +01:00
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2016-10-19 02:19:07 -05:00
|
|
|
// Add a new point on the key-frame, with some defaults set (BEZIER)
|
2017-01-24 18:39:17 -06:00
|
|
|
void Keyframe::AddPoint(double x, double y)
|
2012-10-04 15:07:29 -05:00
|
|
|
{
|
|
|
|
|
// Create a point
|
|
|
|
|
Point new_point(x, y, BEZIER);
|
|
|
|
|
|
|
|
|
|
// Add the point
|
|
|
|
|
AddPoint(new_point);
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-10 17:27:46 -05:00
|
|
|
// Add a new point on the key-frame, with a specific interpolation type
|
2017-01-24 18:39:17 -06:00
|
|
|
void Keyframe::AddPoint(double x, double y, InterpolationType interpolate)
|
2012-10-10 17:27:46 -05:00
|
|
|
{
|
|
|
|
|
// Create a point
|
|
|
|
|
Point new_point(x, y, interpolate);
|
|
|
|
|
|
|
|
|
|
// Add the point
|
|
|
|
|
AddPoint(new_point);
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Get the index of a point by matching a coordinate
|
2019-11-25 10:24:37 +01:00
|
|
|
int64_t Keyframe::FindIndex(Point p) const {
|
2011-10-11 08:44:27 -05:00
|
|
|
// loop through points, and find a matching coordinate
|
2019-12-15 14:22:59 -05:00
|
|
|
for (std::vector<Point>::size_type x = 0; x < Points.size(); x++) {
|
2011-10-11 08:44:27 -05:00
|
|
|
// Get each point
|
|
|
|
|
Point existing_point = Points[x];
|
|
|
|
|
|
|
|
|
|
// find a match
|
|
|
|
|
if (p.co.X == existing_point.co.X && p.co.Y == existing_point.co.Y) {
|
|
|
|
|
// Remove the matching point, and break out of loop
|
|
|
|
|
return x;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// no matching point found
|
|
|
|
|
throw OutOfBoundsPoint("Invalid point requested", -1, Points.size());
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-09 22:41:42 -06:00
|
|
|
// Determine if point already exists
|
2019-11-25 10:24:37 +01:00
|
|
|
bool Keyframe::Contains(Point p) const {
|
2019-11-30 11:32:52 +01:00
|
|
|
std::vector<Point>::const_iterator i =
|
|
|
|
|
std::lower_bound(begin(Points), end(Points), p.co.X, IsPointBeforeX);
|
|
|
|
|
return i != end(Points) && i->co.X == p.co.X;
|
2015-02-09 22:41:42 -06:00
|
|
|
}
|
|
|
|
|
|
2015-02-21 03:10:38 -06:00
|
|
|
// Get current point (or closest point) from the X coordinate (i.e. the frame number)
|
2019-11-25 10:24:37 +01:00
|
|
|
Point Keyframe::GetClosestPoint(Point p, bool useLeft) const {
|
2019-11-30 11:58:51 +01:00
|
|
|
if (Points.size() == 0) {
|
|
|
|
|
return Point(-1, -1);
|
2015-02-21 03:10:38 -06:00
|
|
|
}
|
|
|
|
|
|
2019-11-30 11:58:51 +01:00
|
|
|
// Finds a point with an X coordinate which is "not less" (greater
|
|
|
|
|
// or equal) than the queried X coordinate.
|
|
|
|
|
std::vector<Point>::const_iterator candidate =
|
|
|
|
|
std::lower_bound(begin(Points), end(Points), p.co.X, IsPointBeforeX);
|
2015-02-22 01:04:54 -06:00
|
|
|
|
2019-11-30 11:58:51 +01:00
|
|
|
if (candidate == end(Points)) {
|
|
|
|
|
// All points are before the queried point.
|
|
|
|
|
//
|
|
|
|
|
// Note: Behavior the same regardless of useLeft!
|
|
|
|
|
return Points.back();
|
|
|
|
|
}
|
|
|
|
|
if (candidate == begin(Points)) {
|
|
|
|
|
// First point is greater or equal to the queried point.
|
|
|
|
|
//
|
|
|
|
|
// Note: Behavior the same regardless of useLeft!
|
|
|
|
|
return Points.front();
|
|
|
|
|
}
|
|
|
|
|
if (useLeft) {
|
|
|
|
|
return *(candidate - 1);
|
|
|
|
|
} else {
|
|
|
|
|
return *candidate;
|
|
|
|
|
}
|
2015-02-21 03:10:38 -06:00
|
|
|
}
|
|
|
|
|
|
2016-10-19 02:19:07 -05:00
|
|
|
// Get current point (or closest point to the right) from the X coordinate (i.e. the frame number)
|
2019-11-25 10:24:37 +01:00
|
|
|
Point Keyframe::GetClosestPoint(Point p) const {
|
2016-10-19 02:19:07 -05:00
|
|
|
return GetClosestPoint(p, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get previous point (if any)
|
2019-11-25 10:24:37 +01:00
|
|
|
Point Keyframe::GetPreviousPoint(Point p) const {
|
2016-10-19 02:19:07 -05:00
|
|
|
|
|
|
|
|
// Lookup the index of this point
|
|
|
|
|
try {
|
2017-09-28 16:03:01 -05:00
|
|
|
int64_t index = FindIndex(p);
|
2016-10-19 02:19:07 -05:00
|
|
|
|
|
|
|
|
// If not the 1st point
|
|
|
|
|
if (index > 0)
|
|
|
|
|
return Points[index - 1];
|
|
|
|
|
else
|
|
|
|
|
return Points[0];
|
|
|
|
|
|
2019-07-03 12:58:02 -04:00
|
|
|
} catch (const OutOfBoundsPoint& e) {
|
2016-10-19 02:19:07 -05:00
|
|
|
// No previous point
|
|
|
|
|
return Point(-1, -1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-17 17:14:27 -05:00
|
|
|
// Get max point (by Y coordinate)
|
2019-11-25 10:24:37 +01:00
|
|
|
Point Keyframe::GetMaxPoint() const {
|
2016-09-17 17:14:27 -05:00
|
|
|
Point maxPoint(-1, -1);
|
|
|
|
|
|
2019-12-03 16:58:53 +01:00
|
|
|
for (Point const & existing_point: Points) {
|
2016-09-17 17:14:27 -05:00
|
|
|
if (existing_point.co.Y >= maxPoint.co.Y) {
|
|
|
|
|
maxPoint = existing_point;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return maxPoint;
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Get the value at a specific index
|
2019-11-25 10:24:37 +01:00
|
|
|
double Keyframe::GetValue(int64_t index) const {
|
2019-11-22 22:37:06 +01:00
|
|
|
if (Points.empty()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2019-11-25 10:24:37 +01:00
|
|
|
std::vector<Point>::const_iterator candidate =
|
2019-11-25 10:34:14 +01:00
|
|
|
std::lower_bound(begin(Points), end(Points), static_cast<double>(index), IsPointBeforeX);
|
2012-10-19 17:04:57 -05:00
|
|
|
|
2019-11-22 22:37:06 +01:00
|
|
|
if (candidate == end(Points)) {
|
|
|
|
|
// index is behind last point
|
|
|
|
|
return Points.back().co.Y;
|
|
|
|
|
}
|
|
|
|
|
if (candidate == begin(Points)) {
|
|
|
|
|
// index is at or before first point
|
|
|
|
|
return Points.front().co.Y;
|
|
|
|
|
}
|
|
|
|
|
if (candidate->co.X == index) {
|
|
|
|
|
// index is directly on a point
|
|
|
|
|
return candidate->co.Y;
|
|
|
|
|
}
|
2019-11-25 10:24:37 +01:00
|
|
|
std::vector<Point>::const_iterator predecessor = candidate - 1;
|
2019-12-06 01:03:56 +01:00
|
|
|
return InterpolateBetween(*predecessor, *candidate, index, 0.01);
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2012-10-19 16:53:18 -05:00
|
|
|
// Get the rounded INT value at a specific index
|
2019-11-25 10:24:37 +01:00
|
|
|
int Keyframe::GetInt(int64_t index) const {
|
2019-11-19 21:41:08 +01:00
|
|
|
return int(round(GetValue(index)));
|
2012-10-19 16:53:18 -05:00
|
|
|
}
|
|
|
|
|
|
2015-08-24 01:05:48 -05:00
|
|
|
// Get the rounded INT value at a specific index
|
2019-11-25 10:24:37 +01:00
|
|
|
int64_t Keyframe::GetLong(int64_t index) const {
|
2019-11-19 21:41:08 +01:00
|
|
|
return long(round(GetValue(index)));
|
2015-08-24 01:05:48 -05:00
|
|
|
}
|
|
|
|
|
|
2012-10-19 22:11:22 -05:00
|
|
|
// Get the direction of the curve at a specific index (increasing or decreasing)
|
2019-11-25 10:24:37 +01:00
|
|
|
bool Keyframe::IsIncreasing(int index) const
|
2012-10-19 22:11:22 -05:00
|
|
|
{
|
2019-11-22 22:37:06 +01:00
|
|
|
if (index < 1 || (index + 1) >= GetLength()) {
|
|
|
|
|
return true;
|
2019-03-06 15:35:03 -06:00
|
|
|
}
|
2019-12-03 17:27:28 +01:00
|
|
|
std::vector<Point>::const_iterator candidate =
|
|
|
|
|
std::lower_bound(begin(Points), end(Points), static_cast<double>(index), IsPointBeforeX);
|
|
|
|
|
if (candidate == end(Points)) {
|
|
|
|
|
return false; // After the last point, thus constant.
|
|
|
|
|
}
|
|
|
|
|
if ((candidate->co.X == index) || (candidate == begin(Points))) {
|
|
|
|
|
++candidate;
|
|
|
|
|
}
|
|
|
|
|
int64_t const value = GetLong(index);
|
2019-11-22 22:37:06 +01:00
|
|
|
do {
|
2019-12-03 17:27:28 +01:00
|
|
|
if (value < round(candidate->co.Y)) {
|
|
|
|
|
return true;
|
|
|
|
|
} else if (value > round(candidate->co.Y)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
++candidate;
|
|
|
|
|
} while (candidate != end(Points));
|
2019-11-22 22:37:06 +01:00
|
|
|
return false;
|
2012-10-19 22:11:22 -05:00
|
|
|
}
|
|
|
|
|
|
2013-12-06 00:40:26 -06:00
|
|
|
// Generate JSON string of this object
|
2019-11-25 10:24:37 +01:00
|
|
|
std::string Keyframe::Json() const {
|
2013-12-03 00:13:25 -06:00
|
|
|
|
2013-12-06 00:40:26 -06:00
|
|
|
// Return formatted string
|
|
|
|
|
return JsonValue().toStyledString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate Json::JsonValue for this object
|
2019-11-25 10:24:37 +01:00
|
|
|
Json::Value Keyframe::JsonValue() const {
|
2013-12-03 00:13:25 -06:00
|
|
|
|
|
|
|
|
// Create root json object
|
2013-12-06 00:40:26 -06:00
|
|
|
Json::Value root;
|
|
|
|
|
root["Points"] = Json::Value(Json::arrayValue);
|
2013-12-03 00:13:25 -06:00
|
|
|
|
|
|
|
|
// loop through points, and find a matching coordinate
|
2019-12-27 01:01:48 -05:00
|
|
|
for (auto existing_point : Points) {
|
2013-12-06 00:40:26 -06:00
|
|
|
root["Points"].append(existing_point.JsonValue());
|
2013-12-03 00:13:25 -06:00
|
|
|
}
|
|
|
|
|
|
2013-12-06 00:40:26 -06:00
|
|
|
// return JsonValue
|
|
|
|
|
return root;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load JSON string into this object
|
2019-08-04 22:54:06 -04:00
|
|
|
void Keyframe::SetJson(std::string value) {
|
2013-12-06 00:40:26 -06:00
|
|
|
|
|
|
|
|
// Parse JSON string into JSON objects
|
|
|
|
|
Json::Value root;
|
2019-06-19 21:20:04 -04:00
|
|
|
Json::CharReaderBuilder rbuilder;
|
|
|
|
|
Json::CharReader* reader(rbuilder.newCharReader());
|
|
|
|
|
|
2019-08-04 22:54:06 -04:00
|
|
|
std::string errors;
|
2019-07-11 05:00:47 -04:00
|
|
|
bool success = reader->parse( value.c_str(),
|
2019-06-19 21:20:04 -04:00
|
|
|
value.c_str() + value.size(), &root, &errors );
|
2019-07-11 05:00:47 -04:00
|
|
|
delete reader;
|
|
|
|
|
|
2013-12-06 00:40:26 -06:00
|
|
|
if (!success)
|
|
|
|
|
// Raise exception
|
2019-08-27 15:47:39 -04:00
|
|
|
throw InvalidJSON("JSON could not be parsed (or is invalid)");
|
2013-12-06 00:40:26 -06:00
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Set all values that match
|
2013-12-07 21:09:55 -06:00
|
|
|
SetJsonValue(root);
|
2013-12-06 00:40:26 -06:00
|
|
|
}
|
2019-07-03 12:58:02 -04:00
|
|
|
catch (const std::exception& e)
|
2013-12-06 00:40:26 -06:00
|
|
|
{
|
|
|
|
|
// Error parsing JSON (or missing keys)
|
2019-08-27 15:47:39 -04:00
|
|
|
throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
|
2013-12-06 00:40:26 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load Json::JsonValue into this object
|
2013-12-07 21:09:55 -06:00
|
|
|
void Keyframe::SetJsonValue(Json::Value root) {
|
2013-12-06 00:40:26 -06:00
|
|
|
// Clear existing points
|
|
|
|
|
Points.clear();
|
|
|
|
|
|
2014-01-08 01:43:58 -06:00
|
|
|
if (!root["Points"].isNull())
|
2014-01-05 22:37:11 -06:00
|
|
|
// loop through points
|
2019-12-27 01:01:48 -05:00
|
|
|
for (const auto existing_point : root["Points"]) {
|
2013-12-06 00:40:26 -06:00
|
|
|
// Create Point
|
|
|
|
|
Point p;
|
|
|
|
|
|
|
|
|
|
// Load Json into Point
|
2013-12-07 21:09:55 -06:00
|
|
|
p.SetJsonValue(existing_point);
|
2013-12-06 00:40:26 -06:00
|
|
|
|
|
|
|
|
// Add Point to Keyframe
|
|
|
|
|
AddPoint(p);
|
|
|
|
|
}
|
2013-12-03 00:13:25 -06:00
|
|
|
}
|
|
|
|
|
|
2012-10-19 22:11:22 -05:00
|
|
|
// Get the fraction that represents how many times this value is repeated in the curve
|
2019-03-06 15:35:03 -06:00
|
|
|
// This is depreciated and will be removed soon.
|
2019-11-25 10:24:37 +01:00
|
|
|
Fraction Keyframe::GetRepeatFraction(int64_t index) const {
|
2019-12-06 01:04:47 +01:00
|
|
|
// Frame numbers (index) outside of the "defined" range of this
|
|
|
|
|
// keyframe result in a 1/1 default value.
|
|
|
|
|
if (index < 1 || (index + 1) >= GetLength()) {
|
2012-10-19 22:11:22 -05:00
|
|
|
return Fraction(1,1);
|
2019-12-06 01:04:47 +01:00
|
|
|
}
|
|
|
|
|
assert(Points.size() > 1); // Due to ! ((index + 1) >= GetLength) there are at least two points!
|
|
|
|
|
|
|
|
|
|
// First, get the value at the given frame and the closest point
|
|
|
|
|
// to the right.
|
|
|
|
|
int64_t const current_value = GetLong(index);
|
|
|
|
|
std::vector<Point>::const_iterator const candidate =
|
|
|
|
|
std::lower_bound(begin(Points), end(Points), static_cast<double>(index), IsPointBeforeX);
|
|
|
|
|
assert(candidate != end(Points)); // Due to the (index + 1) >= GetLength check above!
|
|
|
|
|
|
|
|
|
|
// Calculate how many of the next values are going to be the same:
|
|
|
|
|
int64_t next_repeats = 0;
|
|
|
|
|
std::vector<Point>::const_iterator i = candidate;
|
|
|
|
|
// If the index (frame number) is the X coordinate of the closest
|
|
|
|
|
// point, then look at the segment to the right; the "current"
|
|
|
|
|
// segement is not interesting because we're already at the last
|
|
|
|
|
// value of it.
|
|
|
|
|
if (i->co.X == index) {
|
|
|
|
|
++i;
|
|
|
|
|
}
|
|
|
|
|
// Skip over "constant" (when rounded) segments.
|
|
|
|
|
bool all_constant = true;
|
|
|
|
|
for (; i != end(Points); ++i) {
|
|
|
|
|
if (current_value != round(i->co.Y)) {
|
|
|
|
|
all_constant = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (! all_constant) {
|
|
|
|
|
// Found a point which defines a segment which will give a
|
|
|
|
|
// different value than the current value. This means we
|
|
|
|
|
// moved at least one segment to the right, thus we cannot be
|
|
|
|
|
// at the first point.
|
|
|
|
|
assert(i != begin(Points));
|
|
|
|
|
Point const left = *(i - 1);
|
|
|
|
|
Point const right = *i;
|
2019-12-06 01:21:25 +01:00
|
|
|
int64_t change_at;
|
|
|
|
|
if (current_value < round(i->co.Y)) {
|
|
|
|
|
change_at = SearchBetweenPoints(left, right, current_value, std::less_equal<double>{});
|
|
|
|
|
} else {
|
|
|
|
|
assert(current_value > round(i->co.Y));
|
|
|
|
|
change_at = SearchBetweenPoints(left, right, current_value, std::greater_equal<double>{});
|
2019-12-06 01:04:47 +01:00
|
|
|
}
|
2019-12-06 01:21:25 +01:00
|
|
|
next_repeats = change_at - index;
|
2019-12-06 01:04:47 +01:00
|
|
|
} else {
|
|
|
|
|
// All values to the right are the same!
|
|
|
|
|
next_repeats = Points.back().co.X - index;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now look to the left, to the previous values.
|
|
|
|
|
all_constant = true;
|
|
|
|
|
i = candidate;
|
|
|
|
|
if (i != begin(Points)) {
|
|
|
|
|
// The binary search below assumes i to be the left point;
|
|
|
|
|
// candidate is the right point of the current segment
|
|
|
|
|
// though. So change this if possible. If this branch is NOT
|
|
|
|
|
// taken, then we're at/before the first point and all is
|
|
|
|
|
// constant!
|
|
|
|
|
--i;
|
|
|
|
|
}
|
|
|
|
|
int64_t previous_repeats = 0;
|
|
|
|
|
// Skip over constant (when rounded) segments!
|
|
|
|
|
for (; i != begin(Points); --i) {
|
|
|
|
|
if (current_value != round(i->co.Y)) {
|
|
|
|
|
all_constant = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Special case when skipped until the first point, but the first
|
|
|
|
|
// point is actually different. Will not happen if index is
|
|
|
|
|
// before the first point!
|
|
|
|
|
if (current_value != round(i->co.Y)) {
|
|
|
|
|
assert(i != candidate);
|
|
|
|
|
all_constant = false;
|
|
|
|
|
}
|
|
|
|
|
if (! all_constant) {
|
|
|
|
|
// There are at least two points, and we're not at the end,
|
|
|
|
|
// thus the following is safe!
|
|
|
|
|
Point const left = *i;
|
|
|
|
|
Point const right = *(i + 1);
|
2019-12-06 01:21:25 +01:00
|
|
|
int64_t change_at;
|
|
|
|
|
if (current_value > round(left.co.Y)) {
|
|
|
|
|
change_at = SearchBetweenPoints(left, right, current_value, std::less<double>{});
|
|
|
|
|
} else {
|
|
|
|
|
assert(current_value < round(left.co.Y));
|
|
|
|
|
change_at = SearchBetweenPoints(left, right, current_value, std::greater<double>{});
|
2019-12-06 01:04:47 +01:00
|
|
|
}
|
2019-12-06 01:21:25 +01:00
|
|
|
previous_repeats = index - change_at;
|
2019-12-06 01:04:47 +01:00
|
|
|
} else {
|
|
|
|
|
// Every previous value is the same (rounded) as the current
|
|
|
|
|
// value.
|
|
|
|
|
previous_repeats = index;
|
|
|
|
|
}
|
|
|
|
|
int64_t total_repeats = previous_repeats + next_repeats;
|
|
|
|
|
return Fraction(previous_repeats, total_repeats);
|
2012-10-19 22:11:22 -05:00
|
|
|
}
|
|
|
|
|
|
2012-10-19 22:24:54 -05:00
|
|
|
// Get the change in Y value (from the previous Y value)
|
2019-11-25 10:24:37 +01:00
|
|
|
double Keyframe::GetDelta(int64_t index) const {
|
2019-11-22 22:37:06 +01:00
|
|
|
if (index < 1) return 0;
|
|
|
|
|
if (index == 1 && ! Points.empty()) return Points[0].co.Y;
|
|
|
|
|
if (index >= GetLength()) return 0;
|
|
|
|
|
return GetLong(index) - GetLong(index - 1);
|
2012-10-19 22:24:54 -05:00
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Get a point at a specific index
|
2019-11-25 10:24:37 +01:00
|
|
|
Point const & Keyframe::GetPoint(int64_t index) const {
|
2011-10-11 08:44:27 -05:00
|
|
|
// Is index a valid point?
|
2019-12-15 14:22:59 -05:00
|
|
|
if (index >= 0 && index < (int64_t)Points.size())
|
2011-10-11 08:44:27 -05:00
|
|
|
return Points[index];
|
|
|
|
|
else
|
|
|
|
|
// Invalid index
|
|
|
|
|
throw OutOfBoundsPoint("Invalid point requested", index, Points.size());
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-21 05:29:29 -05:00
|
|
|
// Get the number of values (i.e. coordinates on the X axis)
|
2019-11-25 10:24:37 +01:00
|
|
|
int64_t Keyframe::GetLength() const {
|
2019-11-22 22:37:06 +01:00
|
|
|
if (Points.empty()) return 0;
|
|
|
|
|
if (Points.size() == 1) return 1;
|
|
|
|
|
return round(Points.back().co.X) + 1;
|
2012-10-21 05:29:29 -05:00
|
|
|
}
|
|
|
|
|
|
2015-02-22 01:04:54 -06:00
|
|
|
// Get the number of points (i.e. # of points)
|
2019-11-25 10:24:37 +01:00
|
|
|
int64_t Keyframe::GetCount() const {
|
2015-02-22 01:04:54 -06:00
|
|
|
|
|
|
|
|
return Points.size();
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Remove a point by matching a coordinate
|
2017-10-26 18:44:35 -05:00
|
|
|
void Keyframe::RemovePoint(Point p) {
|
2011-10-11 08:44:27 -05:00
|
|
|
// loop through points, and find a matching coordinate
|
2019-12-15 14:22:59 -05:00
|
|
|
for (std::vector<Point>::size_type x = 0; x < Points.size(); x++) {
|
2011-10-11 08:44:27 -05:00
|
|
|
// Get each point
|
|
|
|
|
Point existing_point = Points[x];
|
|
|
|
|
|
|
|
|
|
// find a match
|
|
|
|
|
if (p.co.X == existing_point.co.X && p.co.Y == existing_point.co.Y) {
|
|
|
|
|
// Remove the matching point, and break out of loop
|
|
|
|
|
Points.erase(Points.begin() + x);
|
2015-12-15 18:12:24 -06:00
|
|
|
return;
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// no matching point found
|
|
|
|
|
throw OutOfBoundsPoint("Invalid point requested", -1, Points.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove a point by index
|
2017-10-26 18:44:35 -05:00
|
|
|
void Keyframe::RemovePoint(int64_t index) {
|
2011-10-11 08:44:27 -05:00
|
|
|
// Is index a valid point?
|
2019-12-15 14:22:59 -05:00
|
|
|
if (index >= 0 && index < (int64_t)Points.size())
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
|
|
|
|
// Remove a specific point by index
|
|
|
|
|
Points.erase(Points.begin() + index);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
// Invalid index
|
|
|
|
|
throw OutOfBoundsPoint("Invalid point requested", index, Points.size());
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-28 16:03:01 -05:00
|
|
|
void Keyframe::UpdatePoint(int64_t index, Point p) {
|
2011-10-11 08:44:27 -05:00
|
|
|
// Remove matching point
|
|
|
|
|
RemovePoint(index);
|
|
|
|
|
|
|
|
|
|
// Add new point
|
|
|
|
|
AddPoint(p);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-25 10:24:37 +01:00
|
|
|
void Keyframe::PrintPoints() const {
|
2015-06-01 00:20:14 -07:00
|
|
|
cout << fixed << setprecision(4);
|
2019-11-25 10:24:37 +01:00
|
|
|
for (std::vector<Point>::const_iterator it = Points.begin(); it != Points.end(); it++) {
|
2012-10-19 21:50:50 -05:00
|
|
|
Point p = *it;
|
|
|
|
|
cout << p.co.X << "\t" << p.co.Y << endl;
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-25 10:24:37 +01:00
|
|
|
void Keyframe::PrintValues() const {
|
2015-06-01 00:20:14 -07:00
|
|
|
cout << fixed << setprecision(4);
|
2019-11-22 22:37:06 +01:00
|
|
|
cout << "Frame Number (X)\tValue (Y)\tIs Increasing\tRepeat Numerator\tRepeat Denominator\tDelta (Y Difference)\n";
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2019-12-15 14:22:59 -05:00
|
|
|
for (int64_t i = 1; i < GetLength(); ++i) {
|
2019-11-22 22:37:06 +01:00
|
|
|
cout << i << "\t" << GetValue(i) << "\t" << IsIncreasing(i) << "\t" ;
|
|
|
|
|
cout << GetRepeatFraction(i).num << "\t" << GetRepeatFraction(i).den << "\t" << GetDelta(i) << "\n";
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-03-13 23:19:55 -05:00
|
|
|
// Scale all points by a percentage (good for evenly lengthening or shortening an openshot::Keyframe)
|
|
|
|
|
// 1.0 = same size, 1.05 = 5% increase, etc...
|
2017-01-24 18:39:17 -06:00
|
|
|
void Keyframe::ScalePoints(double scale)
|
2015-03-13 23:19:55 -05:00
|
|
|
{
|
2019-11-22 22:37:06 +01:00
|
|
|
// TODO: What if scale is small so that two points land on the
|
|
|
|
|
// same X coordinate?
|
|
|
|
|
// TODO: What if scale < 0?
|
|
|
|
|
|
2015-03-13 23:19:55 -05:00
|
|
|
// Loop through each point (skipping the 1st point)
|
2019-12-15 14:22:59 -05:00
|
|
|
for (std::vector<Point>::size_type point_index = 1; point_index < Points.size(); point_index++) {
|
2015-03-13 23:19:55 -05:00
|
|
|
// Scale X value
|
|
|
|
|
Points[point_index].co.X = round(Points[point_index].co.X * scale);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flip all the points in this openshot::Keyframe (useful for reversing an effect or transition, etc...)
|
2019-11-25 10:24:37 +01:00
|
|
|
void Keyframe::FlipPoints() {
|
2019-12-15 14:22:59 -05:00
|
|
|
for (std::vector<Point>::size_type point_index = 0, reverse_index = Points.size() - 1; point_index < reverse_index; point_index++, reverse_index--) {
|
2015-03-13 23:19:55 -05:00
|
|
|
// Flip the points
|
2019-11-19 20:37:22 +01:00
|
|
|
using std::swap;
|
|
|
|
|
swap(Points[point_index].co.Y, Points[reverse_index].co.Y);
|
2019-11-22 22:37:06 +01:00
|
|
|
// TODO: check that this has the desired effect even with
|
|
|
|
|
// regards to handles!
|
2015-03-13 23:19:55 -05:00
|
|
|
}
|
|
|
|
|
}
|