diff --git a/CMakeLists.txt b/CMakeLists.txt index bfaf3af8..413da321 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,15 @@ Generating build files for OpenShot with CMake ${CMAKE_VERSION} # in order to properly configure CMAKE_INSTALL_LIBDIR path include(GNUInstallDirs) +# Collect and display summary of options/dependencies +include(FeatureSummary) + +################ OPTIONS ################## +# Optional build settings for libopenshot +option(USE_SYSTEM_JSONCPP "Use system installed JsonCpp, if found" ON) +option(DISABLE_BUNDLED_JSONCPP "Don't fall back to bundled JsonCpp" OFF) +option(ENABLE_IWYU "Enable 'Include What You Use' scanner (CMake 3.3+)" OFF) + ########## Configure Version.h header ############## configure_file(include/OpenShotVersion.h.in include/OpenShotVersion.h @ONLY) # We'll want that installed later @@ -106,17 +115,23 @@ if (TARGET doc) message(STATUS "Doxygen found, documentation target enabled") message("\nTo compile documentation in doc/html, run: 'make doc'") -# Install docs, if the user builds them with `make doc` -install(CODE "MESSAGE(\"Checking for documentation files to install...\")") -install(CODE "MESSAGE(\"(Compile with 'make doc' command, requires Doxygen)\")") + # Install docs, if the user builds them with `make doc` + install(CODE "MESSAGE(\"Checking for documentation files to install...\")") + install(CODE "MESSAGE(\"(Compile with 'make doc' command, requires Doxygen)\")") -install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/html/ - DESTINATION ${CMAKE_INSTALL_DOCDIR}/API - MESSAGE_NEVER # Don't spew about file copies - OPTIONAL ) # No error if the docs aren't found + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/html/ + DESTINATION ${CMAKE_INSTALL_DOCDIR}/API + MESSAGE_NEVER # Don't spew about file copies + OPTIONAL ) # No error if the docs aren't found endif() ############# PROCESS tests/ DIRECTORY ############## if(NOT DISABLE_TESTS) add_subdirectory(tests) endif() + +########### PRINT FEATURE SUMMARY ############## +feature_summary(WHAT ALL + INCLUDE_QUIET_PACKAGES + FATAL_ON_MISSING_REQUIRED_PACKAGES + DESCRIPTION "Displaying feature summary\n\nBuild configuration:") diff --git a/Doxyfile.in b/Doxyfile.in index 7b54a9c0..c47d6e65 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -340,7 +340,7 @@ AUTOLINK_SUPPORT = YES # diagrams that involve STL classes more complete and accurate. # The default value is: NO. -BUILTIN_STL_SUPPORT = NO +BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. @@ -2096,7 +2096,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = USE_BLACKMAGIC USE_IMAGEMAGICK # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/include/KeyFrame.h b/include/KeyFrame.h index a60958a6..6ed39553 100644 --- a/include/KeyFrame.h +++ b/include/KeyFrame.h @@ -63,37 +63,12 @@ namespace openshot { */ class Keyframe { private: - bool needs_update; - double FactorialLookup[4]; - - /* - * Because points can be added in any order, we need to reorder them - * in ascending order based on the point.co.X value. This simplifies - * processing the curve, due to all the points going from left to right. - */ - void ReorderPoints(); - - // Process an individual segment - void ProcessSegment(int Segment, Point p1, Point p2); - - // create lookup table for fast factorial calculation - void CreateFactorialTable(); - - // Get a factorial for a coordinate - double Factorial(int64_t n); - - // Calculate the factorial function for Bernstein basis - double Ni(int64_t n, int64_t i); - - // Calculate Bernstein Basis - double Bernstein(int64_t n, int64_t i, double t); + std::vector Points; ///< Vector of all Points public: - std::vector Points; ///< Vector of all Points - std::vector Values; ///< Vector of all Values (i.e. the processed coordinates from the curve) /// Default constructor for the Keyframe class - Keyframe(); + Keyframe() = default; /// Constructor which sets the default point & coordinate at X=1 Keyframe(double value); @@ -108,68 +83,60 @@ namespace openshot { void AddPoint(double x, double y, InterpolationType interpolate); /// Does this keyframe contain a specific point - bool Contains(Point p); + bool Contains(Point p) const; /// Flip all the points in this openshot::Keyframe (useful for reversing an effect or transition, etc...) void FlipPoints(); /// Get the index of a point by matching a coordinate - int64_t FindIndex(Point p); + int64_t FindIndex(Point p) const; /// Get the value at a specific index - double GetValue(int64_t index); + double GetValue(int64_t index) const; /// Get the rounded INT value at a specific index - int GetInt(int64_t index); + int GetInt(int64_t index) const; /// Get the rounded LONG value at a specific index - int64_t GetLong(int64_t index); + int64_t GetLong(int64_t index) const; /// Get the fraction that represents how many times this value is repeated in the curve - Fraction GetRepeatFraction(int64_t index); + Fraction GetRepeatFraction(int64_t index) const; /// Get the change in Y value (from the previous Y value) - double GetDelta(int64_t index); + double GetDelta(int64_t index) const; /// Get a point at a specific index - Point& GetPoint(int64_t index); + Point const & GetPoint(int64_t index) const; /// Get current point (or closest point to the right) from the X coordinate (i.e. the frame number) - Point GetClosestPoint(Point p); + Point GetClosestPoint(Point p) const; /// Get current point (or closest point) from the X coordinate (i.e. the frame number) /// Either use the closest left point, or right point - Point GetClosestPoint(Point p, bool useLeft); + Point GetClosestPoint(Point p, bool useLeft) const; /// Get previous point ( - Point GetPreviousPoint(Point p); + Point GetPreviousPoint(Point p) const; /// Get max point (by Y coordinate) - Point GetMaxPoint(); + Point GetMaxPoint() const; // Get the number of values (i.e. coordinates on the X axis) - int64_t GetLength(); + int64_t GetLength() const; /// Get the number of points (i.e. # of points) - int64_t GetCount(); + int64_t GetCount() const; /// Get the direction of the curve at a specific index (increasing or decreasing) - bool IsIncreasing(int index); + bool IsIncreasing(int index) const; /// Get and Set JSON methods - std::string Json(); ///< Generate JSON string of this object - Json::Value JsonValue(); ///< Generate Json::JsonValue for this object + std::string Json() const; ///< Generate JSON string of this object + Json::Value JsonValue() const; ///< Generate Json::JsonValue for this object void SetJson(std::string value); ///< Load JSON string into this object void SetJsonValue(Json::Value root); ///< Load Json::JsonValue into this object - /** - * @brief Calculate all of the values for this keyframe. - * - * This clears any existing data in the "values" vector. This method is automatically called - * by AddPoint(), so you don't typically need to call this method. - */ - void Process(); - /// Remove a point by matching a coordinate void RemovePoint(Point p); @@ -184,10 +151,10 @@ namespace openshot { void UpdatePoint(int64_t index, Point p); /// Print a list of points - void PrintPoints(); + void PrintPoints() const; /// Print just the Y value of the point's primary coordinate - void PrintValues(); + void PrintValues() const; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd61ef50..b684aaec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,12 +27,6 @@ # Collect and display summary of options/dependencies include(FeatureSummary) -################ OPTIONS ################## -# Optional build settings for libopenshot -option(USE_SYSTEM_JSONCPP "Use system installed JsonCpp, if found" ON) -option(DISABLE_BUNDLED_JSONCPP "Don't fall back to bundled JsonCpp" OFF) -option(ENABLE_IWYU "Enable 'Include What You Use' scanner (CMake 3.3+)" OFF) - # Automatically process Qt classes with meta-object compiler set(CMAKE_AUTOMOC True) @@ -426,12 +420,6 @@ ENDIF (BLACKMAGIC_FOUND) ############### INCLUDE SWIG BINDINGS ################ add_subdirectory(bindings) -########### PRINT FEATURE SUMMARY ############## -feature_summary(WHAT ALL - INCLUDE_QUIET_PACKAGES - FATAL_ON_MISSING_REQUIRED_PACKAGES - DESCRIPTION "Displaying feature summary\n\nBuild configuration:") - ############### INSTALL HEADERS & LIBRARY ################ set(LIB_INSTALL_DIR lib${LIB_SUFFIX}) # determine correct lib folder diff --git a/src/Clip.cpp b/src/Clip.cpp index e3f33a18..45afd2aa 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -109,10 +109,10 @@ void Clip::init_settings() // Init reader's rotation (if any) void Clip::init_reader_rotation() { // Only init rotation from reader when needed - if (rotation.Points.size() > 1) + if (rotation.GetCount() > 1) // Do nothing if more than 1 rotation Point return; - else if (rotation.Points.size() == 1 && rotation.GetValue(1) != 0.0) + else if (rotation.GetCount() == 1 && rotation.GetValue(1) != 0.0) // Do nothing if 1 Point, and it's not the default value return; @@ -273,7 +273,7 @@ void Clip::Close() float Clip::End() { // if a time curve is present, use its length - if (time.Points.size() > 1) + if (time.GetCount() > 1) { // Determine the FPS fo this clip float fps = 24.0; @@ -314,8 +314,8 @@ std::shared_ptr Clip::GetFrame(int64_t requested_frame) // Is a time map detected int64_t new_frame_number = requested_frame; int64_t time_mapped_number = adjust_frame_number_minimum(time.GetLong(requested_frame)); - if (time.Values.size() > 1) - new_frame_number = time_mapped_number; + if (time.GetLength() > 1) + new_frame_number = time_mapped_number; // Now that we have re-mapped what frame number is needed, go and get the frame pointer std::shared_ptr original_frame; @@ -397,7 +397,7 @@ void Clip::get_time_mapped_frame(std::shared_ptr frame, int64_t frame_num throw ReaderClosed("No Reader has been initialized for this Clip. Call Reader(*reader) before calling this method."); // Check for a valid time map curve - if (time.Values.size() > 1) + if (time.GetLength() > 1) { const GenericScopedLock lock(getFrameCriticalSection); @@ -914,7 +914,7 @@ void Clip::SetJsonValue(Json::Value root) { if (!existing_effect["type"].isNull()) { // Create instance of effect - if (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) { + if ( (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) ) { // Load Json into Effect e->SetJsonValue(existing_effect); diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 6af07d92..645c0cca 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -341,7 +341,7 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va // Was option found? if (option || (name == "g" || name == "qmin" || name == "qmax" || name == "max_b_frames" || name == "mb_decision" || name == "level" || name == "profile" || name == "slices" || name == "rc_min_rate" || name == "rc_max_rate" || - name == "crf" || name == "cqp")) { + name == "rc_buffer_size" || name == "crf" || name == "cqp")) { // Check for specific named options if (name == "g") // Set gop_size diff --git a/src/Frame.cpp b/src/Frame.cpp index 475d5c28..40183422 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -280,7 +280,7 @@ const unsigned char* Frame::GetWaveformPixels(int width, int height, int Red, in wave_image = GetWaveform(width, height, Red, Green, Blue, Alpha); // Return array of pixel packets - return wave_image->bits(); + return wave_image->constBits(); } // Display the wave form @@ -473,14 +473,14 @@ const unsigned char* Frame::GetPixels() AddColor(width, height, color); // Return array of pixel packets - return image->bits(); + return image->constBits(); } // Get pixel data (for only a single scan-line) const unsigned char* Frame::GetPixels(int row) { // Return array of pixel packets - return image->scanLine(row); + return image->constScanLine(row); } // Check a specific pixel color value (returns True/False) @@ -692,7 +692,7 @@ void Frame::Thumbnail(std::string path, int new_width, int new_height, std::stri // Get pixels unsigned char *pixels = (unsigned char *) thumbnail->bits(); - unsigned char *mask_pixels = (unsigned char *) mask->bits(); + const unsigned char *mask_pixels = (const unsigned char *) mask->constBits(); // Convert the mask image to grayscale // Loop through pixels @@ -826,8 +826,8 @@ void Frame::AddImage(std::shared_ptr new_image, bool only_odd_lines) const GenericScopedLock lock(addingImageSection); #pragma omp critical (AddImage) { - const unsigned char *pixels = image->bits(); - const unsigned char *new_pixels = new_image->bits(); + const unsigned char *pixels = image->constBits(); + const unsigned char *new_pixels = new_image->constBits(); // Loop through the scanlines of the image (even or odd) int start = 0; @@ -922,7 +922,7 @@ std::shared_ptr Frame::GetMagickImage() AddColor(width, height, "#000000"); // Get the pixels from the frame image - QRgb const *tmpBits = (const QRgb*)image->bits(); + const QRgb *tmpBits = (const QRgb*)image->constBits(); // Create new image object, and fill with pixel data std::shared_ptr magick_image = std::shared_ptr(new Magick::Image(image->width(), image->height(),"RGBA", Magick::CharPixel, tmpBits)); diff --git a/src/KeyFrame.cpp b/src/KeyFrame.cpp index d86ebc41..d16348a2 100644 --- a/src/KeyFrame.cpp +++ b/src/KeyFrame.cpp @@ -29,64 +29,121 @@ */ #include "../include/KeyFrame.h" +#include +#include +#include using namespace std; using namespace openshot; -// Because points can be added in any order, we need to reorder them -// in ascending order based on the point.co.X value. This simplifies -// processing the curve, due to all the points going from left to right. -void Keyframe::ReorderPoints() { - // Loop through all coordinates, and sort them by the X attribute - for (int64_t x = 0; x < Points.size(); x++) { - int64_t compare_index = x; - int64_t smallest_index = x; +namespace { + bool IsPointBeforeX(Point const & p, double const x) { + return p.co.X < x; + } - for (int64_t compare_index = x + 1; compare_index < Points.size(); compare_index++) { - if (Points[compare_index].co.X < Points[smallest_index].co.X) { - smallest_index = compare_index; + 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) { + 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]; + if (abs(target - x) < allowed_error) { + return y; + } + if (x > target) { + t -= t_step; + } + else { + t += t_step; + } + t_step /= 2; + } while (true); + } + + + 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); + } + } + + + template + 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; } } - - // swap items - if (smallest_index != compare_index) { - swap(Points[compare_index], Points[smallest_index]); - } + return start; } } -// Constructor which sets the default point & coordinate at X=1 -Keyframe::Keyframe(double value) : needs_update(true) { - // Init the factorial table, needed by bezier curves - CreateFactorialTable(); +// Constructor which sets the default point & coordinate at X=1 +Keyframe::Keyframe(double value) { // Add initial point AddPoint(Point(value)); } -// Keyframe constructor -Keyframe::Keyframe() : needs_update(true) { - // Init the factorial table, needed by bezier curves - CreateFactorialTable(); -} - // Add a new point on the key-frame. Each point has a primary coordinate, // a left handle, and a right handle. void Keyframe::AddPoint(Point p) { - // mark as dirty - needs_update = true; - - // Check for duplicate point (and remove it) - Point closest = GetClosestPoint(p); - if (closest.co.X == p.co.X) - // Remove existing point - RemovePoint(closest); - - // Add point at correct spot - Points.push_back(p); - - // Sort / Re-order points based on X coordinate - ReorderPoints(); + // candidate is not less (greater or equal) than the new point in + // the X coordinate. + std::vector::iterator candidate = + std::lower_bound(begin(Points), end(Points), p.co.X, IsPointBeforeX); + 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. + 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; + } } // Add a new point on the key-frame, with some defaults set (BEZIER) @@ -110,7 +167,7 @@ void Keyframe::AddPoint(double x, double y, InterpolationType interpolate) } // Get the index of a point by matching a coordinate -int64_t Keyframe::FindIndex(Point p) { +int64_t Keyframe::FindIndex(Point p) const { // loop through points, and find a matching coordinate for (int64_t x = 0; x < Points.size(); x++) { // Get each point @@ -128,67 +185,49 @@ int64_t Keyframe::FindIndex(Point p) { } // Determine if point already exists -bool Keyframe::Contains(Point p) { - // loop through points, and find a matching coordinate - for (int64_t x = 0; x < Points.size(); x++) { - // Get each point - Point existing_point = Points[x]; - - // find a match - if (p.co.X == existing_point.co.X) { - // Remove the matching point, and break out of loop - return true; - } - } - - // no matching point found - return false; +bool Keyframe::Contains(Point p) const { + std::vector::const_iterator i = + std::lower_bound(begin(Points), end(Points), p.co.X, IsPointBeforeX); + return i != end(Points) && i->co.X == p.co.X; } // Get current point (or closest point) from the X coordinate (i.e. the frame number) -Point Keyframe::GetClosestPoint(Point p, bool useLeft) { - Point closest(-1, -1); - - // loop through points, and find a matching coordinate - for (int64_t x = 0; x < Points.size(); x++) { - // Get each point - Point existing_point = Points[x]; - - // find a match - if (existing_point.co.X >= p.co.X && !useLeft) { - // New closest point found (to the Right) - closest = existing_point; - break; - } else if (existing_point.co.X < p.co.X && useLeft) { - // New closest point found (to the Left) - closest = existing_point; - } else if (existing_point.co.X >= p.co.X && useLeft) { - // We've gone past the left point... so break - break; - } +Point Keyframe::GetClosestPoint(Point p, bool useLeft) const { + if (Points.size() == 0) { + return Point(-1, -1); } - // Handle edge cases (if no point was found) - if (closest.co.X == -1) { - if (p.co.X <= 1 && Points.size() > 0) - // Assign 1st point - closest = Points[0]; - else if (Points.size() > 0) - // Assign last point - closest = Points[Points.size() - 1]; - } + // Finds a point with an X coordinate which is "not less" (greater + // or equal) than the queried X coordinate. + std::vector::const_iterator candidate = + std::lower_bound(begin(Points), end(Points), p.co.X, IsPointBeforeX); - // no matching point found - return closest; + 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; + } } // Get current point (or closest point to the right) from the X coordinate (i.e. the frame number) -Point Keyframe::GetClosestPoint(Point p) { +Point Keyframe::GetClosestPoint(Point p) const { return GetClosestPoint(p, false); } // Get previous point (if any) -Point Keyframe::GetPreviousPoint(Point p) { +Point Keyframe::GetPreviousPoint(Point p) const { // Lookup the index of this point try { @@ -207,17 +246,11 @@ Point Keyframe::GetPreviousPoint(Point p) { } // Get max point (by Y coordinate) -Point Keyframe::GetMaxPoint() { +Point Keyframe::GetMaxPoint() const { Point maxPoint(-1, -1); - // loop through points, and find the largest Y value - for (int64_t x = 0; x < Points.size(); x++) { - // Get each point - Point existing_point = Points[x]; - - // Is point larger than max point + for (Point const & existing_point: Points) { if (existing_point.co.Y >= maxPoint.co.Y) { - // New max point found maxPoint = existing_point; } } @@ -226,128 +259,74 @@ Point Keyframe::GetMaxPoint() { } // Get the value at a specific index -double Keyframe::GetValue(int64_t index) -{ - // Check if it needs to be processed - if (needs_update) - Process(); +double Keyframe::GetValue(int64_t index) const { + if (Points.empty()) { + return 0; + } + std::vector::const_iterator candidate = + std::lower_bound(begin(Points), end(Points), static_cast(index), IsPointBeforeX); - // Is index a valid point? - if (index >= 0 && index < Values.size()) - // Return value - return Values[index].Y; - else if (index < 0 && Values.size() > 0) - // Return the minimum value - return Values[0].Y; - else if (index >= Values.size() && Values.size() > 0) - // return the maximum value - return Values[Values.size() - 1].Y; - else - // return a blank coordinate (0,0) - return 0.0; + 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; + } + std::vector::const_iterator predecessor = candidate - 1; + return InterpolateBetween(*predecessor, *candidate, index, 0.01); } // Get the rounded INT value at a specific index -int Keyframe::GetInt(int64_t index) -{ - // Check if it needs to be processed - if (needs_update) - Process(); - - // Is index a valid point? - if (index >= 0 && index < Values.size()) - // Return value - return int(round(Values[index].Y)); - else if (index < 0 && Values.size() > 0) - // Return the minimum value - return int(round(Values[0].Y)); - else if (index >= Values.size() && Values.size() > 0) - // return the maximum value - return int(round(Values[Values.size() - 1].Y)); - else - // return a blank coordinate (0,0) - return 0; +int Keyframe::GetInt(int64_t index) const { + return int(round(GetValue(index))); } // Get the rounded INT value at a specific index -int64_t Keyframe::GetLong(int64_t index) -{ - // Check if it needs to be processed - if (needs_update) - Process(); - - // Is index a valid point? - if (index >= 0 && index < Values.size()) - // Return value - return long(round(Values[index].Y)); - else if (index < 0 && Values.size() > 0) - // Return the minimum value - return long(round(Values[0].Y)); - else if (index >= Values.size() && Values.size() > 0) - // return the maximum value - return long(round(Values[Values.size() - 1].Y)); - else - // return a blank coordinate (0,0) - return 0; +int64_t Keyframe::GetLong(int64_t index) const { + return long(round(GetValue(index))); } // Get the direction of the curve at a specific index (increasing or decreasing) -bool Keyframe::IsIncreasing(int index) +bool Keyframe::IsIncreasing(int index) const { - // Check if it needs to be processed - if (needs_update) - Process(); - - // Is index a valid point? - if (index >= 1 && (index + 1) < Values.size()) { - int64_t current_value = GetLong(index); - int64_t previous_value = 0; - int64_t next_value = 0; - int64_t previous_repeats = 0; - int64_t next_repeats = 0; - - // Loop backwards and look for the next unique value - for (std::vector::iterator backwards_it = Values.begin() + index; backwards_it != Values.begin(); backwards_it--) { - previous_value = long(round((*backwards_it).Y)); - if (previous_value == current_value) { - // Found same value - previous_repeats++; - } else { - // Found non repeating value, no more repeats found - break; - } - } - - // Loop forwards and look for the next unique value - for (std::vector::iterator forwards_it = Values.begin() + (index + 1); forwards_it != Values.end(); forwards_it++) { - next_value = long(round((*forwards_it).Y)); - if (next_value == current_value) { - // Found same value - next_repeats++; - } else { - // Found non repeating value, no more repeats found - break; - } - } - - if (current_value >= next_value) { - // Decreasing + if (index < 1 || (index + 1) >= GetLength()) { + return true; + } + std::vector::const_iterator candidate = + std::lower_bound(begin(Points), end(Points), static_cast(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); + do { + if (value < round(candidate->co.Y)) { + return true; + } else if (value > round(candidate->co.Y)) { return false; } - } - // return default true (since most curves increase) - return true; + ++candidate; + } while (candidate != end(Points)); + return false; } // Generate JSON string of this object -std::string Keyframe::Json() { +std::string Keyframe::Json() const { // Return formatted string return JsonValue().toStyledString(); } // Generate Json::JsonValue for this object -Json::Value Keyframe::JsonValue() { +Json::Value Keyframe::JsonValue() const { // Create root json object Json::Value root; @@ -395,10 +374,6 @@ void Keyframe::SetJson(std::string value) { // Load Json::JsonValue into this object void Keyframe::SetJsonValue(Json::Value root) { - - // mark as dirty - needs_update = true; - // Clear existing points Points.clear(); @@ -421,107 +396,118 @@ void Keyframe::SetJsonValue(Json::Value root) { // Get the fraction that represents how many times this value is repeated in the curve // This is depreciated and will be removed soon. -Fraction Keyframe::GetRepeatFraction(int64_t index) -{ - // Check if it needs to be processed - if (needs_update) - Process(); - - // Is index a valid point? - if (index >= 1 && (index + 1) < Values.size()) { - int64_t current_value = GetLong(index); - int64_t previous_value = 0; - int64_t next_value = 0; - int64_t previous_repeats = 0; - int64_t next_repeats = 0; - - // Loop backwards and look for the next unique value - for (std::vector::iterator backwards_it = Values.begin() + index; backwards_it != Values.begin(); backwards_it--) { - previous_value = long(round((*backwards_it).Y)); - if (previous_value == current_value) { - // Found same value - previous_repeats++; - } else { - // Found non repeating value, no more repeats found - break; - } - } - - // Loop forwards and look for the next unique value - for (std::vector::iterator forwards_it = Values.begin() + (index + 1); forwards_it != Values.end(); forwards_it++) { - next_value = long(round((*forwards_it).Y)); - if (next_value == current_value) { - // Found same value - next_repeats++; - } else { - // Found non repeating value, no more repeats found - break; - } - } - - int64_t total_repeats = previous_repeats + next_repeats; - return Fraction(previous_repeats, total_repeats); - } - else - // return a blank coordinate +Fraction Keyframe::GetRepeatFraction(int64_t index) const { + // Frame numbers (index) outside of the "defined" range of this + // keyframe result in a 1/1 default value. + if (index < 1 || (index + 1) >= GetLength()) { return Fraction(1,1); + } + 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::const_iterator const candidate = + std::lower_bound(begin(Points), end(Points), static_cast(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::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; + int64_t change_at; + if (current_value < round(i->co.Y)) { + change_at = SearchBetweenPoints(left, right, current_value, std::less_equal{}); + } else { + assert(current_value > round(i->co.Y)); + change_at = SearchBetweenPoints(left, right, current_value, std::greater_equal{}); + } + next_repeats = change_at - index; + } 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); + int64_t change_at; + if (current_value > round(left.co.Y)) { + change_at = SearchBetweenPoints(left, right, current_value, std::less{}); + } else { + assert(current_value < round(left.co.Y)); + change_at = SearchBetweenPoints(left, right, current_value, std::greater{}); + } + previous_repeats = index - change_at; + } 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); } // Get the change in Y value (from the previous Y value) -double Keyframe::GetDelta(int64_t index) -{ - // Check if it needs to be processed - if (needs_update) - Process(); - - // Is index a valid point? - if (index >= 1 && (index + 1) < Values.size()) { - int64_t current_value = GetLong(index); - int64_t previous_value = 0; - int64_t next_value = 0; - int64_t previous_repeats = 0; - int64_t next_repeats = 0; - - // Loop backwards and look for the next unique value - for (std::vector::iterator backwards_it = Values.begin() + index; backwards_it != Values.begin(); backwards_it--) { - previous_value = long(round((*backwards_it).Y)); - if (previous_value == current_value) { - // Found same value - previous_repeats++; - } else { - // Found non repeating value, no more repeats found - break; - } - } - - // Loop forwards and look for the next unique value - for (std::vector::iterator forwards_it = Values.begin() + (index + 1); forwards_it != Values.end(); forwards_it++) { - next_value = long(round((*forwards_it).Y)); - if (next_value == current_value) { - // Found same value - next_repeats++; - } else { - // Found non repeating value, no more repeats found - break; - } - } - - // Check for matching previous value (special case for 1st element) - if (current_value == previous_value) - previous_value = 0; - - if (previous_repeats == 1) - return current_value - previous_value; - else - return 0.0; - } - else - // return a blank coordinate - return 0.0; +double Keyframe::GetDelta(int64_t index) const { + 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); } // Get a point at a specific index -Point& Keyframe::GetPoint(int64_t index) { +Point const & Keyframe::GetPoint(int64_t index) const { // Is index a valid point? if (index >= 0 && index < Points.size()) return Points[index]; @@ -531,27 +517,20 @@ Point& Keyframe::GetPoint(int64_t index) { } // Get the number of values (i.e. coordinates on the X axis) -int64_t Keyframe::GetLength() { - // Check if it needs to be processed - if (needs_update) - Process(); - - // return the size of the Values vector - return Values.size(); +int64_t Keyframe::GetLength() const { + if (Points.empty()) return 0; + if (Points.size() == 1) return 1; + return round(Points.back().co.X) + 1; } // Get the number of points (i.e. # of points) -int64_t Keyframe::GetCount() { +int64_t Keyframe::GetCount() const { - // return the size of the Values vector return Points.size(); } // Remove a point by matching a coordinate void Keyframe::RemovePoint(Point p) { - // mark as dirty - needs_update = true; - // loop through points, and find a matching coordinate for (int64_t x = 0; x < Points.size(); x++) { // Get each point @@ -571,9 +550,6 @@ void Keyframe::RemovePoint(Point p) { // Remove a point by index void Keyframe::RemovePoint(int64_t index) { - // mark as dirty - needs_update = true; - // Is index a valid point? if (index >= 0 && index < Points.size()) { @@ -586,335 +562,54 @@ void Keyframe::RemovePoint(int64_t index) { } void Keyframe::UpdatePoint(int64_t index, Point p) { - // mark as dirty - needs_update = true; - // Remove matching point RemovePoint(index); // Add new point AddPoint(p); - - // Reorder points - ReorderPoints(); } -void Keyframe::PrintPoints() { - // Check if it needs to be processed - if (needs_update) - Process(); - +void Keyframe::PrintPoints() const { cout << fixed << setprecision(4); - for (std::vector::iterator it = Points.begin(); it != Points.end(); it++) { + for (std::vector::const_iterator it = Points.begin(); it != Points.end(); it++) { Point p = *it; cout << p.co.X << "\t" << p.co.Y << endl; } } -void Keyframe::PrintValues() { - // Check if it needs to be processed - if (needs_update) - Process(); - +void Keyframe::PrintValues() const { cout << fixed << setprecision(4); - cout << "Frame Number (X)\tValue (Y)\tIs Increasing\tRepeat Numerator\tRepeat Denominator\tDelta (Y Difference)" << endl; + cout << "Frame Number (X)\tValue (Y)\tIs Increasing\tRepeat Numerator\tRepeat Denominator\tDelta (Y Difference)\n"; - for (std::vector::iterator it = Values.begin() + 1; it != Values.end(); it++) { - Coordinate c = *it; - cout << long(round(c.X)) << "\t" << c.Y << "\t" << IsIncreasing(c.X) << "\t" << GetRepeatFraction(c.X).num << "\t" << GetRepeatFraction(c.X).den << "\t" << GetDelta(c.X) << endl; + for (uint64_t i = 1; i < GetLength(); ++i) { + cout << i << "\t" << GetValue(i) << "\t" << IsIncreasing(i) << "\t" ; + cout << GetRepeatFraction(i).num << "\t" << GetRepeatFraction(i).den << "\t" << GetDelta(i) << "\n"; } } -void Keyframe::Process() { - - #pragma omp critical (keyframe_process) - { - // only process if needed - if (needs_update && Points.size() == 0) { - // Clear all values - Values.clear(); - } - else if (needs_update && Points.size() > 0) - { - // Clear all values - Values.clear(); - - // fill in all values between 1 and 1st point's co.X - Point p1 = Points[0]; - if (Points.size() > 1) - // Fill in previous X values (before 1st point) - for (int64_t x = 0; x < p1.co.X; x++) - Values.push_back(Coordinate(Values.size(), p1.co.Y)); - else - // Add a single value (since we only have 1 point) - Values.push_back(Coordinate(Values.size(), p1.co.Y)); - - // Loop through each pair of points (1 less than the max points). Each - // pair of points is used to process a segment of the keyframe. - Point p2(0, 0); - for (int64_t x = 0; x < Points.size() - 1; x++) { - p1 = Points[x]; - p2 = Points[x + 1]; - - // process segment p1,p2 - ProcessSegment(x, p1, p2); - } - } - - // reset flag - needs_update = false; - } -} - -void Keyframe::ProcessSegment(int Segment, Point p1, Point p2) { - // Determine the number of values for this segment - int64_t number_of_values = round(p2.co.X) - round(p1.co.X); - - // Exit function if no values - if (number_of_values == 0) - return; - - // Based on the interpolation mode, fill the "values" vector with the coordinates - // for this segment - switch (p2.interpolation) { - - // Calculate the "values" for this segment in with a LINEAR equation, effectively - // creating a straight line with coordinates. - case LINEAR: { - // Get the difference in value - double current_value = p1.co.Y; - double value_difference = p2.co.Y - p1.co.Y; - double value_increment = 0.0f; - - // Get the increment value, but take into account the - // first segment has 1 extra value - value_increment = value_difference / (double) (number_of_values); - - if (Segment == 0) - // Add an extra value to the first segment - number_of_values++; - else - // If not 1st segment, skip the first value - current_value += value_increment; - - // Add each increment to the values vector - for (int64_t x = 0; x < number_of_values; x++) { - // add value as a coordinate to the "values" vector - Values.push_back(Coordinate(Values.size(), current_value)); - - // increment value - current_value += value_increment; - } - - break; - } - - // Calculate the "values" for this segment using a quadratic Bezier curve. This creates a - // smooth curve. - case BEZIER: { - - // Always increase the number of points by 1 (need all possible points - // to correctly calculate the curve). - number_of_values++; - number_of_values *= 4; // We need a higher resolution curve (4X) - - // Diff between points - double X_diff = p2.co.X - p1.co.X; - double Y_diff = p2.co.Y - p1.co.Y; - - std::vector segment_coordinates; - segment_coordinates.push_back(p1.co); - segment_coordinates.push_back(Coordinate(p1.co.X + (p1.handle_right.X * X_diff), p1.co.Y + (p1.handle_right.Y * Y_diff))); - segment_coordinates.push_back(Coordinate(p1.co.X + (p2.handle_left.X * X_diff), p1.co.Y + (p2.handle_left.Y * Y_diff))); - segment_coordinates.push_back(p2.co); - - std::vector raw_coordinates; - int64_t npts = segment_coordinates.size(); - int64_t icount, jcount; - double step, t; - double last_x = -1; // small number init, to track the last used x - - // Calculate points on curve - icount = 0; - t = 0; - - step = (double) 1.0 / (number_of_values - 1); - - for (int64_t i1 = 0; i1 < number_of_values; i1++) { - if ((1.0 - t) < 5e-6) - t = 1.0; - - jcount = 0; - - double new_x = 0.0f; - double new_y = 0.0f; - - for (int64_t i = 0; i < npts; i++) { - Coordinate co = segment_coordinates[i]; - double basis = Bernstein(npts - 1, i, t); - new_x += basis * co.X; - new_y += basis * co.Y; - } - - // Add new value to the vector - Coordinate current_value(new_x, new_y); - - // Add all values for 1st segment - raw_coordinates.push_back(current_value); - - // increment counters - icount += 2; - t += step; - } - - // Loop through the raw coordinates, and map them correctly to frame numbers. For example, - // we can't have duplicate X values, since X represents our frame numbers. - int64_t current_frame = p1.co.X; - double current_value = p1.co.Y; - for (int64_t i = 0; i < raw_coordinates.size(); i++) - { - // Get the raw coordinate - Coordinate raw = raw_coordinates[i]; - - if (current_frame == round(raw.X)) - // get value of raw coordinate - current_value = raw.Y; - else - { - // Missing X values (use last known Y values) - int64_t number_of_missing = round(raw.X) - current_frame; - for (int64_t missing = 0; missing < number_of_missing; missing++) - { - // Add new value to the vector - Coordinate new_coord(current_frame, current_value); - - if (Segment == 0 || (Segment > 0 && current_frame > p1.co.X)) - // Add to "values" vector - Values.push_back(new_coord); - - // Increment frame - current_frame++; - } - - // increment the current value - current_value = raw.Y; - } - } - - // Add final coordinate - Coordinate new_coord(current_frame, current_value); - Values.push_back(new_coord); - - break; - } - - // Calculate the "values" of this segment by maintaining the value of p1 until the - // last point, and then make the value jump to p2. This effectively just jumps - // the value, instead of ramping up or down the value. - case CONSTANT: { - - if (Segment == 0) - // first segment has 1 extra value - number_of_values++; - - // Add each increment to the values vector - for (int64_t x = 0; x < number_of_values; x++) { - if (x < (number_of_values - 1)) { - // Not the last value of this segment - // add coordinate to "values" - Values.push_back(Coordinate(Values.size(), p1.co.Y)); - } else { - // This is the last value of this segment - // add coordinate to "values" - Values.push_back(Coordinate(Values.size(), p2.co.Y)); - } - } - break; - } - - } -} - -// Create lookup table for fast factorial calculation -void Keyframe::CreateFactorialTable() { - // Only 4 lookups are needed, because we only support 4 coordinates per curve - FactorialLookup[0] = 1.0; - FactorialLookup[1] = 1.0; - FactorialLookup[2] = 2.0; - FactorialLookup[3] = 6.0; -} - -// Get a factorial for a coordinate -double Keyframe::Factorial(int64_t n) { - assert(n >= 0 && n <= 3); - return FactorialLookup[n]; /* returns the value n! as a SUMORealing point number */ -} - -// Calculate the factorial function for Bernstein basis -double Keyframe::Ni(int64_t n, int64_t i) { - double ni; - double a1 = Factorial(n); - double a2 = Factorial(i); - double a3 = Factorial(n - i); - ni = a1 / (a2 * a3); - return ni; -} - -// Calculate Bernstein basis -double Keyframe::Bernstein(int64_t n, int64_t i, double t) { - double basis; - double ti; /* t^i */ - double tni; /* (1 - t)^i */ - - /* Prevent problems with pow */ - if (t == 0.0 && i == 0) - ti = 1.0; - else - ti = pow(t, i); - - if (n == i && t == 1.0) - tni = 1.0; - else - tni = pow((1 - t), (n - i)); - - // Bernstein basis - basis = Ni(n, i) * ti * tni; - return basis; -} // Scale all points by a percentage (good for evenly lengthening or shortening an openshot::Keyframe) // 1.0 = same size, 1.05 = 5% increase, etc... void Keyframe::ScalePoints(double scale) { - // Loop through each point (skipping the 1st point) - for (int64_t point_index = 0; point_index < Points.size(); point_index++) { - // Skip the 1st point - if (point_index == 0) - continue; + // TODO: What if scale is small so that two points land on the + // same X coordinate? + // TODO: What if scale < 0? + // Loop through each point (skipping the 1st point) + for (int64_t point_index = 1; point_index < Points.size(); point_index++) { // Scale X value Points[point_index].co.X = round(Points[point_index].co.X * scale); - - // Mark for re-processing - needs_update = true; } } // Flip all the points in this openshot::Keyframe (useful for reversing an effect or transition, etc...) -void Keyframe::FlipPoints() -{ - // Loop through each point - std::vector FlippedPoints; - for (int64_t point_index = 0, reverse_index = Points.size() - 1; point_index < Points.size(); point_index++, reverse_index--) { +void Keyframe::FlipPoints() { + for (int64_t point_index = 0, reverse_index = Points.size() - 1; point_index < reverse_index; point_index++, reverse_index--) { // Flip the points - Point p = Points[point_index]; - p.co.Y = Points[reverse_index].co.Y; - FlippedPoints.push_back(p); + using std::swap; + swap(Points[point_index].co.Y, Points[reverse_index].co.Y); + // TODO: check that this has the desired effect even with + // regards to handles! } - - // Swap vectors - Points.swap(FlippedPoints); - - // Mark for re-processing - needs_update = true; } diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 51868157..86ef2911 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -824,7 +824,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Adding solid color)", "frame_number", frame_number, "info.width", info.width, "info.height", info.height); // Add Background Color to 1st layer (if animated or not black) - if ((color.red.Points.size() > 1 || color.green.Points.size() > 1 || color.blue.Points.size() > 1) || + if ((color.red.GetCount() > 1 || color.green.GetCount() > 1 || color.blue.GetCount() > 1) || (color.red.GetValue(frame_number) != 0.0 || color.green.GetValue(frame_number) != 0.0 || color.blue.GetValue(frame_number) != 0.0)) new_frame->AddColor(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, color.GetColorHex(frame_number)); @@ -1098,7 +1098,7 @@ void Timeline::SetJsonValue(Json::Value root) { if (!existing_effect["type"].isNull()) { // Create instance of effect - if (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) { + if ( (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) ) { // Load Json into Effect e->SetJsonValue(existing_effect); @@ -1364,7 +1364,7 @@ void Timeline::apply_json_to_effects(Json::Value change, EffectBase* existing_ef EffectBase *e = NULL; // Init the matching effect object - if (e = EffectInfo().CreateEffect(effect_type)) { + if ( (e = EffectInfo().CreateEffect(effect_type)) ) { // Load Json into Effect e->SetJsonValue(change["value"]); diff --git a/src/bindings/python/openshot.i b/src/bindings/python/openshot.i index a2228d75..44b1953e 100644 --- a/src/bindings/python/openshot.i +++ b/src/bindings/python/openshot.i @@ -153,10 +153,16 @@ } %extend openshot::OpenShotVersion { - // Give the struct a string representation + // Give the struct a string representation const std::string __str__() { return std::string(OPENSHOT_VERSION_FULL); } + // And a repr for interactive use + const std::string __repr__() { + std::ostringstream result; + result << "OpenShotVersion('" << OPENSHOT_VERSION_FULL << "')"; + return result.str(); + } } %include "OpenShotVersion.h" diff --git a/tests/Color_Tests.cpp b/tests/Color_Tests.cpp index 210cca59..82c7d180 100644 --- a/tests/Color_Tests.cpp +++ b/tests/Color_Tests.cpp @@ -79,7 +79,7 @@ TEST(Color_HEX_Value) c.blue.AddPoint(100, 255); CHECK_EQUAL("#000000", c.GetColorHex(1)); - CHECK_EQUAL("#7f7f7f", c.GetColorHex(50)); + CHECK_EQUAL("#7d7d7d", c.GetColorHex(50)); CHECK_EQUAL("#ffffff", c.GetColorHex(100)); } @@ -93,7 +93,7 @@ TEST(Color_HEX_Constructor) c.blue.AddPoint(100, 255); CHECK_EQUAL("#4586db", c.GetColorHex(1)); - CHECK_EQUAL("#a2c2ed", c.GetColorHex(50)); + CHECK_EQUAL("#a0c1ed", c.GetColorHex(50)); CHECK_EQUAL("#ffffff", c.GetColorHex(100)); } @@ -118,7 +118,7 @@ TEST(Color_RGBA_Constructor) c.blue.AddPoint(100, 255); CHECK_EQUAL("#4586db", c.GetColorHex(1)); - CHECK_EQUAL("#a2c2ed", c.GetColorHex(50)); + CHECK_EQUAL("#a0c1ed", c.GetColorHex(50)); CHECK_EQUAL("#ffffff", c.GetColorHex(100)); // Color with alpha diff --git a/tests/KeyFrame_Tests.cpp b/tests/KeyFrame_Tests.cpp index 3c94fa22..dbd81500 100644 --- a/tests/KeyFrame_Tests.cpp +++ b/tests/KeyFrame_Tests.cpp @@ -51,7 +51,7 @@ TEST(Keyframe_GetPoint_With_1_Points) k1.AddPoint(openshot::Point(2,3)); CHECK_THROW(k1.GetPoint(-1), OutOfBoundsPoint); - CHECK_EQUAL(1, k1.Points.size()); + CHECK_EQUAL(1, k1.GetCount()); CHECK_CLOSE(2.0f, k1.GetPoint(0).co.X, 0.00001); CHECK_CLOSE(3.0f, k1.GetPoint(0).co.Y, 0.00001); CHECK_THROW(k1.GetPoint(1), OutOfBoundsPoint); @@ -92,13 +92,13 @@ TEST(Keyframe_GetValue_For_Bezier_Curve_2_Points) // Spot check values from the curve CHECK_CLOSE(1.0f, kf.GetValue(-1), 0.0001); CHECK_CLOSE(1.0f, kf.GetValue(0), 0.0001); - CHECK_CLOSE(1.00023f, kf.GetValue(1), 0.0001); - CHECK_CLOSE(1.14025f, kf.GetValue(9), 0.0001); - CHECK_CLOSE(1.91492f, kf.GetValue(20), 0.0001); - CHECK_CLOSE(3.81602f, kf.GetValue(40), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.0001); + CHECK_CLOSE(1.12414f, kf.GetValue(9), 0.0001); + CHECK_CLOSE(1.86370f, kf.GetValue(20), 0.0001); + CHECK_CLOSE(3.79733f, kf.GetValue(40), 0.0001); CHECK_CLOSE(4.0f, kf.GetValue(50), 0.0001); // Check the expected number of values - CHECK_EQUAL(kf.Values.size(), 51); + CHECK_EQUAL(51, kf.GetLength()); } TEST(Keyframe_GetValue_For_Bezier_Curve_5_Points_40_Percent_Handle) @@ -114,14 +114,14 @@ TEST(Keyframe_GetValue_For_Bezier_Curve_5_Points_40_Percent_Handle) // Spot check values from the curve CHECK_CLOSE(kf.GetValue(-1), 1.0f, 0.0001); CHECK_CLOSE(1.0f, kf.GetValue(0), 0.0001); - CHECK_CLOSE(1.00023f, kf.GetValue(1), 0.0001); - CHECK_CLOSE(2.73656f, kf.GetValue(27), 0.0001); - CHECK_CLOSE(7.55139f, kf.GetValue(77), 0.0001); - CHECK_CLOSE(4.08102f, kf.GetValue(127), 0.0001); - CHECK_CLOSE(1.77569f, kf.GetValue(177), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.0001); + CHECK_CLOSE(2.68197f, kf.GetValue(27), 0.0001); + CHECK_CLOSE(7.47719f, kf.GetValue(77), 0.0001); + CHECK_CLOSE(4.20468f, kf.GetValue(127), 0.0001); + CHECK_CLOSE(1.73860f, kf.GetValue(177), 0.0001); CHECK_CLOSE(3.0f, kf.GetValue(200), 0.0001); // Check the expected number of values - CHECK_EQUAL(kf.Values.size(), 201); + CHECK_EQUAL(201, kf.GetLength()); } TEST(Keyframe_GetValue_For_Bezier_Curve_5_Points_25_Percent_Handle) @@ -137,14 +137,14 @@ TEST(Keyframe_GetValue_For_Bezier_Curve_5_Points_25_Percent_Handle) // Spot check values from the curve CHECK_CLOSE(1.0f, kf.GetValue(-1), 0.0001); CHECK_CLOSE(1.0f, kf.GetValue(0), 0.0001); - CHECK_CLOSE(1.00023f, kf.GetValue(1), 0.0001); - CHECK_CLOSE(2.73656f, kf.GetValue(27), 0.0001); - CHECK_CLOSE(7.55139f, kf.GetValue(77), 0.0001); - CHECK_CLOSE(4.08102f, kf.GetValue(127), 0.0001); - CHECK_CLOSE(1.77569f, kf.GetValue(177), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.0001); + CHECK_CLOSE(2.68197f, kf.GetValue(27), 0.0001); + CHECK_CLOSE(7.47719f, kf.GetValue(77), 0.0001); + CHECK_CLOSE(4.20468f, kf.GetValue(127), 0.0001); + CHECK_CLOSE(1.73860f, kf.GetValue(177), 0.0001); CHECK_CLOSE(3.0f, kf.GetValue(200), 0.0001); // Check the expected number of values - CHECK_EQUAL(kf.Values.size(), 201); + CHECK_EQUAL(201, kf.GetLength()); } TEST(Keyframe_GetValue_For_Linear_Curve_3_Points) @@ -164,7 +164,7 @@ TEST(Keyframe_GetValue_For_Linear_Curve_3_Points) CHECK_CLOSE(4.4f, kf.GetValue(40), 0.0001); CHECK_CLOSE(2.0f, kf.GetValue(50), 0.0001); // Check the expected number of values - CHECK_EQUAL(kf.Values.size(), 51); + CHECK_EQUAL(51, kf.GetLength()); } TEST(Keyframe_GetValue_For_Constant_Curve_3_Points) @@ -185,7 +185,7 @@ TEST(Keyframe_GetValue_For_Constant_Curve_3_Points) CHECK_CLOSE(8.0f, kf.GetValue(49), 0.0001); CHECK_CLOSE(2.0f, kf.GetValue(50), 0.0001); // Check the expected number of values - CHECK_EQUAL(kf.Values.size(), 51); + CHECK_EQUAL(51, kf.GetLength()); } TEST(Keyframe_Check_Direction_and_Repeat_Fractions) @@ -197,29 +197,29 @@ TEST(Keyframe_Check_Direction_and_Repeat_Fractions) kf.AddPoint(500, 500); // Spot check values from the curve - CHECK_EQUAL(kf.GetInt(1), 500); - CHECK_EQUAL(kf.IsIncreasing(1), false); - CHECK_EQUAL(kf.GetRepeatFraction(1).num, 1); - CHECK_EQUAL(kf.GetRepeatFraction(1).den, 12); - CHECK_EQUAL(kf.GetDelta(1), 500); + CHECK_EQUAL(500, kf.GetInt(1)); + CHECK_EQUAL(false, kf.IsIncreasing(1)); + CHECK_EQUAL(1, kf.GetRepeatFraction(1).num); + CHECK_EQUAL(13, kf.GetRepeatFraction(1).den); + CHECK_EQUAL(500, kf.GetDelta(1)); - CHECK_EQUAL(kf.GetInt(24), 498); - CHECK_EQUAL(kf.IsIncreasing(24), false); - CHECK_EQUAL(kf.GetRepeatFraction(24).num, 3); - CHECK_EQUAL(kf.GetRepeatFraction(24).den, 6); - CHECK_EQUAL(kf.GetDelta(24), 0); + CHECK_EQUAL(498, kf.GetInt(24)); + CHECK_EQUAL(false, kf.IsIncreasing(24)); + CHECK_EQUAL(3, kf.GetRepeatFraction(24).num); + CHECK_EQUAL(6, kf.GetRepeatFraction(24).den); + CHECK_EQUAL(0, kf.GetDelta(24)); - CHECK_EQUAL(kf.GetLong(390), 100); - CHECK_EQUAL(kf.IsIncreasing(390), true); - CHECK_EQUAL(kf.GetRepeatFraction(390).num, 3); - CHECK_EQUAL(kf.GetRepeatFraction(390).den, 15); - CHECK_EQUAL(kf.GetDelta(390), 0); + CHECK_EQUAL(100, kf.GetLong(390)); + CHECK_EQUAL(true, kf.IsIncreasing(390)); + CHECK_EQUAL(3, kf.GetRepeatFraction(390).num); + CHECK_EQUAL(16, kf.GetRepeatFraction(390).den); + CHECK_EQUAL(0, kf.GetDelta(390)); - CHECK_EQUAL(kf.GetLong(391), 100); - CHECK_EQUAL(kf.IsIncreasing(391), true); - CHECK_EQUAL(kf.GetRepeatFraction(391).num, 4); - CHECK_EQUAL(kf.GetRepeatFraction(391).den, 15); - CHECK_EQUAL(kf.GetDelta(388), -1); + CHECK_EQUAL(100, kf.GetLong(391)); + CHECK_EQUAL(true, kf.IsIncreasing(391)); + CHECK_EQUAL(4, kf.GetRepeatFraction(391).num); + CHECK_EQUAL(16, kf.GetRepeatFraction(391).den); + CHECK_EQUAL(-1, kf.GetDelta(388)); } @@ -232,22 +232,22 @@ TEST(Keyframe_Get_Closest_Point) kf.AddPoint(2500, 0.0); // Spot check values from the curve (to the right) - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(900, 900)).co.X, 1000); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(1, 1)).co.X, 1); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(5, 5)).co.X, 1000); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(1000, 1000)).co.X, 1000); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(1001, 1001)).co.X, 2500); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(2500, 2500)).co.X, 2500); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(3000, 3000)).co.X, 2500); + CHECK_EQUAL(1000, kf.GetClosestPoint(openshot::Point(900, 900)).co.X); + CHECK_EQUAL(1, kf.GetClosestPoint(openshot::Point(1, 1)).co.X); + CHECK_EQUAL(1000, kf.GetClosestPoint(openshot::Point(5, 5)).co.X); + CHECK_EQUAL(1000, kf.GetClosestPoint(openshot::Point(1000, 1000)).co.X); + CHECK_EQUAL(2500, kf.GetClosestPoint(openshot::Point(1001, 1001)).co.X); + CHECK_EQUAL(2500, kf.GetClosestPoint(openshot::Point(2500, 2500)).co.X); + CHECK_EQUAL(2500, kf.GetClosestPoint(openshot::Point(3000, 3000)).co.X); // Spot check values from the curve (to the left) - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(900, 900), true).co.X, 1); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(1, 1), true).co.X, 1); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(5, 5), true).co.X, 1); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(1000, 1000), true).co.X, 1); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(1001, 1001), true).co.X, 1000); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(2500, 2500), true).co.X, 1000); - CHECK_EQUAL(kf.GetClosestPoint(openshot::Point(3000, 3000), true).co.X, 2500); + CHECK_EQUAL(1, kf.GetClosestPoint(openshot::Point(900, 900), true).co.X); + CHECK_EQUAL(1, kf.GetClosestPoint(openshot::Point(1, 1), true).co.X); + CHECK_EQUAL(1, kf.GetClosestPoint(openshot::Point(5, 5), true).co.X); + CHECK_EQUAL(1, kf.GetClosestPoint(openshot::Point(1000, 1000), true).co.X); + CHECK_EQUAL(1000, kf.GetClosestPoint(openshot::Point(1001, 1001), true).co.X); + CHECK_EQUAL(1000, kf.GetClosestPoint(openshot::Point(2500, 2500), true).co.X); + CHECK_EQUAL(2500, kf.GetClosestPoint(openshot::Point(3000, 3000), true).co.X); } @@ -260,13 +260,13 @@ TEST(Keyframe_Get_Previous_Point) kf.AddPoint(2500, 0.0); // Spot check values from the curve - CHECK_EQUAL(kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(900, 900))).co.X, 1); - CHECK_EQUAL(kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(1, 1))).co.X, 1); - CHECK_EQUAL(kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(5, 5))).co.X, 1); - CHECK_EQUAL(kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(1000, 1000))).co.X, 1); - CHECK_EQUAL(kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(1001, 1001))).co.X, 1000); - CHECK_EQUAL(kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(2500, 2500))).co.X, 1000); - CHECK_EQUAL(kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(3000, 3000))).co.X, 1000); + CHECK_EQUAL(1, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(900, 900))).co.X); + CHECK_EQUAL(1, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(1, 1))).co.X); + CHECK_EQUAL(1, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(5, 5))).co.X); + CHECK_EQUAL(1, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(1000, 1000))).co.X); + CHECK_EQUAL(1000, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(1001, 1001))).co.X); + CHECK_EQUAL(1000, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(2500, 2500))).co.X); + CHECK_EQUAL(1000, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(3000, 3000))).co.X); } @@ -277,22 +277,22 @@ TEST(Keyframe_Get_Max_Point) kf.AddPoint(1, 1.0); // Spot check values from the curve - CHECK_EQUAL(kf.GetMaxPoint().co.Y, 1.0); + CHECK_EQUAL(1.0, kf.GetMaxPoint().co.Y); kf.AddPoint(2, 0.0); // Spot check values from the curve - CHECK_EQUAL(kf.GetMaxPoint().co.Y, 1.0); + CHECK_EQUAL(1.0, kf.GetMaxPoint().co.Y); kf.AddPoint(3, 2.0); // Spot check values from the curve - CHECK_EQUAL(kf.GetMaxPoint().co.Y, 2.0); + CHECK_EQUAL(2.0, kf.GetMaxPoint().co.Y); kf.AddPoint(4, 1.0); // Spot check values from the curve - CHECK_EQUAL(kf.GetMaxPoint().co.Y, 2.0); + CHECK_EQUAL(2.0, kf.GetMaxPoint().co.Y); } TEST(Keyframe_Scale_Keyframe) @@ -307,8 +307,8 @@ TEST(Keyframe_Scale_Keyframe) CHECK_CLOSE(1.0f, kf.GetValue(1), 0.01); CHECK_CLOSE(7.99f, kf.GetValue(24), 0.01); CHECK_CLOSE(8.0f, kf.GetValue(25), 0.01); - CHECK_CLOSE(3.68f, kf.GetValue(40), 0.01); - CHECK_CLOSE(2.0f, kf.GetValue(49), 0.01); + CHECK_CLOSE(3.85f, kf.GetValue(40), 0.01); + CHECK_CLOSE(2.01f, kf.GetValue(49), 0.01); CHECK_CLOSE(2.0f, kf.GetValue(50), 0.01); // Resize / Scale the keyframe @@ -316,12 +316,12 @@ TEST(Keyframe_Scale_Keyframe) // Spot check values from the curve CHECK_CLOSE(1.0f, kf.GetValue(1), 0.01); - CHECK_CLOSE(4.21f, kf.GetValue(24), 0.01); - CHECK_CLOSE(4.47f, kf.GetValue(25), 0.01); - CHECK_CLOSE(7.57f, kf.GetValue(40), 0.01); + CHECK_CLOSE(4.08f, kf.GetValue(24), 0.01); + CHECK_CLOSE(4.36f, kf.GetValue(25), 0.01); + CHECK_CLOSE(7.53f, kf.GetValue(40), 0.01); CHECK_CLOSE(7.99f, kf.GetValue(49), 0.01); CHECK_CLOSE(8.0f, kf.GetValue(50), 0.01); - CHECK_CLOSE(2.35f, kf.GetValue(90), 0.01); + CHECK_CLOSE(2.39f, kf.GetValue(90), 0.01); CHECK_CLOSE(2.0f, kf.GetValue(100), 0.01); // Resize / Scale the keyframe @@ -331,8 +331,8 @@ TEST(Keyframe_Scale_Keyframe) CHECK_CLOSE(1.0f, kf.GetValue(1), 0.01); CHECK_CLOSE(7.99f, kf.GetValue(24), 0.01); CHECK_CLOSE(8.0f, kf.GetValue(25), 0.01); - CHECK_CLOSE(3.68f, kf.GetValue(40), 0.01); - CHECK_CLOSE(2.0f, kf.GetValue(49), 0.01); + CHECK_CLOSE(3.85f, kf.GetValue(40), 0.01); + CHECK_CLOSE(2.01f, kf.GetValue(49), 0.01); CHECK_CLOSE(2.0f, kf.GetValue(50), 0.01); } @@ -380,14 +380,14 @@ TEST(Keyframe_Remove_Duplicate_Point) kf.AddPoint(1, 2.0); // Spot check values from the curve - CHECK_EQUAL(kf.GetLength(), 1); - CHECK_CLOSE(kf.GetPoint(0).co.Y, 2.0, 0.01); + CHECK_EQUAL(1, kf.GetLength()); + CHECK_CLOSE(2.0, kf.GetPoint(0).co.Y, 0.01); } TEST(Keyframe_Large_Number_Values) { // Large value - int64_t large_value = 30 * 60 * 90; + int64_t const large_value = 30 * 60 * 90; // Create a keyframe curve with 2 points Keyframe kf; @@ -395,7 +395,102 @@ TEST(Keyframe_Large_Number_Values) kf.AddPoint(large_value, 100.0); // 90 minutes long // Spot check values from the curve - CHECK_EQUAL(kf.GetLength(), large_value + 1); - CHECK_CLOSE(kf.GetPoint(0).co.Y, 1.0, 0.01); - CHECK_CLOSE(kf.GetPoint(1).co.Y, 100.0, 0.01); + CHECK_EQUAL(large_value + 1, kf.GetLength()); + CHECK_CLOSE(1.0, kf.GetPoint(0).co.Y, 0.01); + CHECK_CLOSE(100.0, kf.GetPoint(1).co.Y, 0.01); +} + +TEST(Keyframe_Remove_Point) +{ + Keyframe kf; + kf.AddPoint(openshot::Point(Coordinate(1, 1), CONSTANT)); + kf.AddPoint(openshot::Point(Coordinate(3, 100), CONSTANT)); + CHECK_EQUAL(1, kf.GetInt(2)); + kf.AddPoint(openshot::Point(Coordinate(2, 50), CONSTANT)); + CHECK_EQUAL(50, kf.GetInt(2)); + kf.RemovePoint(1); // This is the index of point with X == 2 + CHECK_EQUAL(1, kf.GetInt(2)); + CHECK_THROW(kf.RemovePoint(100), OutOfBoundsPoint); +} + +TEST(Keyframe_Constant_Interpolation_First_Segment) +{ + Keyframe kf; + kf.AddPoint(Point(Coordinate(1, 1), CONSTANT)); + kf.AddPoint(Point(Coordinate(2, 50), CONSTANT)); + kf.AddPoint(Point(Coordinate(3, 100), CONSTANT)); + CHECK_EQUAL(1, kf.GetInt(0)); + CHECK_EQUAL(1, kf.GetInt(1)); + CHECK_EQUAL(50, kf.GetInt(2)); + CHECK_EQUAL(100, kf.GetInt(3)); + CHECK_EQUAL(100, kf.GetInt(4)); +} + +TEST(Keyframe_isIncreasing) +{ + // Which cases need to be tested to keep same behaviour as + // previously? + // + // - "invalid point" => true + // - point where all next values are equal => false + // - point where first non-eq next value is smaller => false + // - point where first non-eq next value is larger => true + Keyframe kf; + kf.AddPoint(1, 1, LINEAR); // testing with linear + kf.AddPoint(3, 5, BEZIER); // testing with bezier + kf.AddPoint(6, 10, CONSTANT); // first non-eq is smaller + kf.AddPoint(8, 8, CONSTANT); // first non-eq is larger + kf.AddPoint(10, 10, CONSTANT); // all next values are equal + kf.AddPoint(15, 10, CONSTANT); + + // "invalid points" + CHECK_EQUAL(true, kf.IsIncreasing(0)); + CHECK_EQUAL(true, kf.IsIncreasing(15)); + // all next equal + CHECK_EQUAL(false, kf.IsIncreasing(12)); + // first non-eq is larger + CHECK_EQUAL(true, kf.IsIncreasing(8)); + // first non-eq is smaller + CHECK_EQUAL(false, kf.IsIncreasing(6)); + // bezier and linear + CHECK_EQUAL(true, kf.IsIncreasing(4)); + CHECK_EQUAL(true, kf.IsIncreasing(2)); +} + +TEST(Keyframe_GetLength) +{ + Keyframe f; + CHECK_EQUAL(0, f.GetLength()); + f.AddPoint(1, 1); + CHECK_EQUAL(1, f.GetLength()); + f.AddPoint(2, 1); + CHECK_EQUAL(3, f.GetLength()); + f.AddPoint(200, 1); + CHECK_EQUAL(201, f.GetLength()); + + Keyframe g; + g.AddPoint(200, 1); + CHECK_EQUAL(1, g.GetLength()); + g.AddPoint(1,1); + CHECK_EQUAL(201, g.GetLength()); +} + +TEST(Keyframe_Use_Interpolation_of_Segment_End_Point) +{ + Keyframe f; + f.AddPoint(1,0, CONSTANT); + f.AddPoint(100,155, BEZIER); + CHECK_CLOSE(75.9, f.GetValue(50), 0.1); +} + +TEST(Keyframe_Handle_Large_Segment) +{ + Keyframe kf; + kf.AddPoint(1, 0, CONSTANT); + kf.AddPoint(1000000, 1, LINEAR); + UNITTEST_TIME_CONSTRAINT(10); // 10 milliseconds would still be relatively slow, but need to think about slower build machines! + CHECK_CLOSE(0.5, kf.GetValue(500000), 0.01); + CHECK_EQUAL(true, kf.IsIncreasing(10)); + Fraction fr = kf.GetRepeatFraction(250000); + CHECK_CLOSE(0.5, (double)fr.num / fr.den, 0.01); }