diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index faa23d251..1d2a59067 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -86,6 +86,7 @@ set(OPENSHOT_SOURCES FrameMapper.cpp Json.cpp KeyFrame.cpp + KeyFrameBase.cpp KeyFrameBBox.cpp OpenShotVersion.cpp ZmqLogger.cpp diff --git a/src/Clip.cpp b/src/Clip.cpp index 79c9b75ec..de7a8c6f5 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -39,6 +39,7 @@ #include "ChunkReader.h" #include "DummyReader.h" #include "Timeline.h" +#include "effects/Tracker.h" using namespace openshot; @@ -57,6 +58,7 @@ void Clip::init_settings() mixing = VOLUME_MIX_NONE; waveform = false; previous_properties = ""; + attached_id = ""; // Init scale curves scale_x = Keyframe(1.0); @@ -242,6 +244,31 @@ Clip::~Clip() } } +// Attach clip to bounding box +void Clip::AttachToTracker(std::string tracked_id) +{ + // Search for the tracked object on the timeline + Timeline *parentTimeline = (Timeline *) ParentTimeline(); + + // Create a smart pointer to the tracked object from the timeline + std::shared_ptr trackedObject = parentTimeline->GetTrackedObject(tracked_id); + + // Check for valid tracked object + if (trackedObject){ + SetAttachedObject(trackedObject); + return; + } + else{ + return; + } +} + +// Set the pointer to the trackedObject this clip is attached to +void Clip::SetAttachedObject(std::shared_ptr trackedObject){ + attachedObject = trackedObject; + return; +} + /// Set the current reader void Clip::Reader(ReaderBase* new_reader) { @@ -762,7 +789,24 @@ std::string Clip::PropertiesJSON(int64_t requested_frame) const { root["display"] = add_property_json("Frame Number", display, "int", "", NULL, 0, 3, false, requested_frame); root["mixing"] = add_property_json("Volume Mixing", mixing, "int", "", NULL, 0, 2, false, requested_frame); root["waveform"] = add_property_json("Waveform", waveform, "int", "", NULL, 0, 1, false, requested_frame); - + root["attached_id"] = add_property_json("Attached ID", 0.0, "string", GetAttachedId(), NULL, -1, -1, false, requested_frame); + + // Add attached id choices (dropdown style) + if (timeline){ + Timeline* parentTimeline = (Timeline *) timeline; + std::vector tracked_ids = parentTimeline->GetTrackedObjectsIds(); + Json::Value temp; + temp["name"] = ""; + temp["value"] = ""; + temp["selected"] = true; + root["attached_id"]["choices"].append(temp); + for (auto it = tracked_ids.begin(); it != tracked_ids.end(); ++it){ + temp["name"] = *it; + temp["value"] = *it; + temp["selected"] = true; + root["attached_id"]["choices"].append(temp); + } + } // Add gravity choices (dropdown style) root["gravity"]["choices"].append(add_property_choice_json("Top Left", GRAVITY_TOP_LEFT, gravity)); root["gravity"]["choices"].append(add_property_choice_json("Top Center", GRAVITY_TOP, gravity)); @@ -836,6 +880,7 @@ Json::Value Clip::JsonValue() const { // Create root json object Json::Value root = ClipBase::JsonValue(); // get parent properties + root["attached_id"] = attached_id; root["gravity"] = gravity; root["scale"] = scale; root["anchor"] = anchor; @@ -913,6 +958,11 @@ void Clip::SetJsonValue(const Json::Value root) { cache.Clear(); // Set data from Json (if key is found) + if (!root["attached_id"].isNull()) + attached_id = root["attached_id"].asString(); + if (attached_id.size() > 0){ + AttachToTracker(attached_id); + } if (!root["gravity"].isNull()) gravity = (GravityType) root["gravity"].asInt(); if (!root["scale"].isNull()) @@ -1096,6 +1146,21 @@ void Clip::AddEffect(EffectBase* effect) // Sort effects sort_effects(); + // Add Tracker to Timeline + if (effect->info.class_name == "Tracker"){ + + Timeline* parentTimeline = (Timeline *) ParentTimeline(); + + // Downcast effect as Tracker + Tracker* tracker = (Tracker *) effect; + + // Get tracked data from the Tracker effect + std::shared_ptr trackedData = tracker->trackedData; + + // Add tracked data to the timeline + parentTimeline->AddTrackedObject(trackedData); + } + // Clear cache cache.Clear(); } @@ -1213,6 +1278,21 @@ void Clip::apply_keyframes(std::shared_ptr frame, int width, int height) } } + /* TRANSFORM CLIP TO ATTACHED OBJECT'S POSITION AND DIMENSION */ + if (attachedObject){ + + // Access the KeyframeBBox properties + std::map boxValues = attachedObject->GetBoxValues(frame->number); + + // Set the bounding box keyframes to this clip keyframes + location_x.AddPoint(frame->number, (boxValues["cx"]-0.5)); + location_y.AddPoint(frame->number, (boxValues["cy"]-0.5)); + scale_x.AddPoint(frame->number, boxValues["w"]*boxValues["sx"]*2.0); + scale_y.AddPoint(frame->number, boxValues["h"]*boxValues["sy"]); + rotation.AddPoint(frame->number, boxValues["r"]); + + } + /* GRAVITY LOCATION - Initialize X & Y to the correct values (before applying location curves) */ float x = 0.0; // left float y = 0.0; // top @@ -1261,6 +1341,8 @@ void Clip::apply_keyframes(std::shared_ptr frame, int width, int height) // Debug output ZmqLogger::Instance()->AppendDebugMethod("Clip::apply_keyframes (Gravity)", "frame->number", frame->number, "source_clip->gravity", gravity, "scaled_source_width", scaled_source_width, "scaled_source_height", scaled_source_height); + QTransform transform; + /* LOCATION, ROTATION, AND SCALE */ float r = rotation.GetValue(frame->number); // rotate in degrees x += (width * location_x.GetValue(frame->number)); // move in percentage of final width @@ -1270,16 +1352,13 @@ void Clip::apply_keyframes(std::shared_ptr frame, int width, int height) float origin_x_value = origin_x.GetValue(frame->number); float origin_y_value = origin_y.GetValue(frame->number); - QTransform transform; - // Transform source image (if needed) ZmqLogger::Instance()->AppendDebugMethod("Clip::apply_keyframes (Build QTransform - if needed)", "frame->number", frame->number, "x", x, "y", y, "r", r, "sx", sx, "sy", sy); if (!isEqual(x, 0) || !isEqual(y, 0)) { // TRANSLATE/MOVE CLIP transform.translate(x, y); - } - + } if (!isEqual(r, 0) || !isEqual(shear_x_value, 0) || !isEqual(shear_y_value, 0)) { // ROTATE CLIP (around origin_x, origin_y) float origin_x_offset = (scaled_source_width * origin_x_value); @@ -1289,11 +1368,9 @@ void Clip::apply_keyframes(std::shared_ptr frame, int width, int height) transform.shear(shear_x_value, shear_y_value); transform.translate(-origin_x_offset,-origin_y_offset); } - // SCALE CLIP (if needed) float source_width_scale = (float(source_size.width()) / float(source_image->width())) * sx; float source_height_scale = (float(source_size.height()) / float(source_image->height())) * sy; - if (!isEqual(source_width_scale, 1.0) || !isEqual(source_height_scale, 1.0)) { transform.scale(source_width_scale, source_height_scale); } diff --git a/src/Clip.h b/src/Clip.h index e2521532a..0886f7894 100644 --- a/src/Clip.h +++ b/src/Clip.h @@ -54,6 +54,7 @@ #include "Fraction.h" #include "Frame.h" #include "KeyFrame.h" +#include "KeyFrameBase.h" #include "ReaderBase.h" #include "JuceHeader.h" @@ -123,6 +124,8 @@ namespace openshot { bool waveform; ///< Should a waveform be used instead of the clip's image std::list effects; /// Is Reader opened + std::string attached_id; ///< Id of the bounding box that this clip is attached to + std::shared_ptr attachedObject; // Audio resampler (if time mapping) openshot::AudioResampler *resampler; @@ -161,8 +164,6 @@ namespace openshot { /// Reverse an audio buffer void reverse_buffer(juce::AudioSampleBuffer* buffer); - - public: openshot::GravityType gravity; ///< The gravity of a clip determines where it snaps to its parent openshot::ScaleType scale; ///< The scale determines how a clip should be resized to fit its parent @@ -196,6 +197,19 @@ namespace openshot { /// Determine if reader is open or closed bool IsOpen() override { return is_open; }; + /// Get and set the bounding box that this clip is attached to + std::string GetAttachedId() const { return attached_id; }; + /// Set id of the bounding box that this clip is attached to + void SetAttachedId(std::string value) { attached_id = value; }; + + /// Attach clip to bounding box + void AttachToTracker(std::string tracked_id); + + /// Set the pointer to the trackedObject this clip is attached to + void SetAttachedObject(std::shared_ptr trackedObject); + /// Return a pointer to the trackedObject this clip is attached to + std::shared_ptr GetAttachedObject() const { return attachedObject; }; + /// Return the type name of the class std::string Name() override { return "Clip"; }; diff --git a/src/KeyFrame.cpp b/src/KeyFrame.cpp index b113e6325..4441f6a9d 100644 --- a/src/KeyFrame.cpp +++ b/src/KeyFrame.cpp @@ -36,66 +36,7 @@ using namespace std; using namespace openshot; -namespace { - bool IsPointBeforeX(Point const & p, double const x) { - return p.co.X < x; - } - - 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 (fabs(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); - } - } - - +namespace{ template int64_t SearchBetweenPoints(Point const & left, Point const & right, int64_t const current, Check check) { int64_t start = left.co.X; @@ -114,6 +55,7 @@ namespace { } + // Constructor which sets the default point & coordinate at X=1 Keyframe::Keyframe(double value) { // Add initial point @@ -544,6 +486,7 @@ void Keyframe::RemovePoint(int64_t index) { throw OutOfBoundsPoint("Invalid point requested", index, Points.size()); } +// Replace an existing point with a new point void Keyframe::UpdatePoint(int64_t index, Point p) { // Remove matching point RemovePoint(index); diff --git a/src/KeyFrame.h b/src/KeyFrame.h index 6424473a9..5ff191e4f 100644 --- a/src/KeyFrame.h +++ b/src/KeyFrame.h @@ -41,6 +41,7 @@ #include "Coordinate.h" #include "Point.h" #include "Json.h" +#include "KeyFrameBase.h" namespace openshot { @@ -61,9 +62,11 @@ namespace openshot { * kf.PrintValues(); * \endcode */ - class Keyframe { + class Keyframe : public KeyframeBase { + + private: - std::vector Points; ///< Vector of all Points + std::vector Points; ///< Vector of all Points public: @@ -145,7 +148,7 @@ namespace openshot { /// 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 ScalePoints(double scale); + void ScalePoints(double scale) override; /// Replace an existing point with a new point void UpdatePoint(int64_t index, Point p); diff --git a/src/KeyFrameBBox.cpp b/src/KeyFrameBBox.cpp index 7109101df..def7c186b 100644 --- a/src/KeyFrameBBox.cpp +++ b/src/KeyFrameBBox.cpp @@ -34,105 +34,9 @@ #include #include -//#define PI 3.14159265 - using namespace std; using namespace openshot; -namespace openshot -{ - bool IsPointBeforeX(Point const &p, double const x) - { - return p.co.X < x; - } - - 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 (fabs(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; - } - } - return start; - } -} // namespace openshot // Default Constructor that sets the bounding-box displacement as 0 and the scales as 1 for the first frame KeyFrameBBox::KeyFrameBBox() : delta_x(0.0), delta_y(0.0), scale_x(1.0), scale_y(1.0), rotation(0.0) @@ -207,7 +111,7 @@ void KeyFrameBBox::RemoveBox(int64_t frame_number) } // Return a bounding-box from BoxVec with it's properties adjusted by the Keyframes -BBox KeyFrameBBox::GetValue(int64_t frame_number) +BBox KeyFrameBBox::GetBox(int64_t frame_number) { // Get the time position of the given frame. double time = this->FrameNToTime(frame_number, this->TimeScale); @@ -372,33 +276,32 @@ bool KeyFrameBBox::LoadBoxData(std::string inputFilePath) return true; } +// Clear the BoxVec map +void KeyFrameBBox::clear() +{ + BoxVec.clear(); +} + // Generate JSON string of this object -std::string KeyFrameBBox::Json() +std::string KeyFrameBBox::Json() const { // Return formatted string return JsonValue().toStyledString(); } // Generate Json::Value for this object -Json::Value KeyFrameBBox::JsonValue() +Json::Value KeyFrameBBox::JsonValue() const { // Create root json object Json::Value root; + // Object's properties + root["box_id"] = Id(); root["BaseFPS"]["num"] = BaseFps.num; root["BaseFPS"]["den"] = BaseFps.den; - root["TimeScale"] = this->TimeScale; - root["Boxes"] = Json::Value(Json::arrayValue); + root["TimeScale"] = TimeScale; - // Loop through the BoxVec map and save the BBox data - for (auto const &x : BoxVec) - { - Json::Value box; - box["time"] = x.first; - box["data"] = x.second.JsonValue(); - root["Boxes"].append(box); - } - // Get the Keyframe's Json strings + // Keyframe's properties root["delta_x"] = delta_x.JsonValue(); root["delta_y"] = delta_y.JsonValue(); root["scale_x"] = scale_x.JsonValue(); @@ -427,45 +330,32 @@ void KeyFrameBBox::SetJson(const std::string value) return; } -// Clear the BoxVec map -void KeyFrameBBox::clear() -{ - BoxVec.clear(); -} - // Load Json::Value into this object void KeyFrameBBox::SetJsonValue(const Json::Value root) -{ - // Clear BoxVec - this->clear(); +{ + // Set the Id + if (!root["box_id"].isNull()) + SetId(root["box_id"].asString()); + + // Set the BaseFps by the given JSON object if (!root["BaseFPS"].isNull() && root["BaseFPS"].isObject()) - { - // Set the BaseFps by the given JSON object + { if (!root["BaseFPS"]["num"].isNull()) BaseFps.num = (int)root["BaseFPS"]["num"].asInt(); if (!root["BaseFPS"]["den"].isNull()) BaseFps.den = (int)root["BaseFPS"]["den"].asInt(); } + // Set the TimeScale by the given JSON object if (!root["TimeScale"].isNull()) { - // Set the TimeScale by the given JSON object double scale = (double)root["TimeScale"].asDouble(); this->ScalePoints(scale); } - if (!root["Boxes"].isNull()) - { - // Loop through the BBoxes data - for (const auto existing_point : root["Boxes"]) - { - // Insert BBox into the BoxVec map - BBox box; - box.SetJsonValue(existing_point["data"]); - BoxVec[existing_point["time"].asDouble()] = box; - } - } + if (!root["protobuf_data_path"].isNull()) + protobufDataPath = root["protobuf_data_path"].asString(); // Set the Keyframes by the given JSON object if (!root["delta_x"].isNull()) @@ -481,3 +371,96 @@ void KeyFrameBBox::SetJsonValue(const Json::Value root) return; } + +// Get all properties for a specific frame (perfect for a UI to display the current state +// of all properties at any time) +Json::Value KeyFrameBBox::PropertiesJSON(int64_t requested_frame) const +{ + Json::Value root; + + BBox box = GetBox(requested_frame); + + // Id + root["box_id"] = add_property_json("Box ID", 0.0, "string", Id(), NULL, -1, -1, true, requested_frame); + + // Add the data of given frame bounding-box to the JSON object + root["x1"] = add_property_json("X1", box.cx-(box.width/2), "float", "", NULL, 0.0, 1.0, false, requested_frame); + root["y1"] = add_property_json("Y1", box.cy-(box.height/2), "float", "", NULL, 0.0, 1.0, false, requested_frame); + root["x2"] = add_property_json("X2", box.cx+(box.width/2), "float", "", NULL, 0.0, 1.0, false, requested_frame); + root["y2"] = add_property_json("Y2", box.cy+(box.height/2), "float", "", NULL, 0.0, 1.0, false, requested_frame); + + // Add the bounding-box Keyframes to the JSON object + root["delta_x"] = add_property_json("Displacement X-axis", delta_x.GetValue(requested_frame), "float", "", &delta_x, -1.0, 1.0, false, requested_frame); + root["delta_y"] = add_property_json("Displacement Y-axis", delta_y.GetValue(requested_frame), "float", "", &delta_y, -1.0, 1.0, false, requested_frame); + root["scale_x"] = add_property_json("Scale (Width)", scale_x.GetValue(requested_frame), "float", "", &scale_x, -1.0, 1.0, false, requested_frame); + root["scale_y"] = add_property_json("Scale (Height)", scale_y.GetValue(requested_frame), "float", "", &scale_y, -1.0, 1.0, false, requested_frame); + root["rotation"] = add_property_json("Rotation", rotation.GetValue(requested_frame), "float", "", &rotation, 0, 360, false, requested_frame); + + // Return formatted string + return root; +} + + +// Generate JSON for a property +Json::Value KeyFrameBBox::add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe* keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const { + + // Requested Point + const Point requested_point(requested_frame, requested_frame); + + // Create JSON Object + Json::Value prop = Json::Value(Json::objectValue); + prop["name"] = name; + prop["value"] = value; + prop["memo"] = memo; + prop["type"] = type; + prop["min"] = min_value; + prop["max"] = max_value; + if (keyframe) { + prop["keyframe"] = keyframe->Contains(requested_point); + prop["points"] = int(keyframe->GetCount()); + Point closest_point = keyframe->GetClosestPoint(requested_point); + prop["interpolation"] = closest_point.interpolation; + prop["closest_point_x"] = closest_point.co.X; + prop["previous_point_x"] = keyframe->GetPreviousPoint(closest_point).co.X; + } + else { + prop["keyframe"] = false; + prop["points"] = 0; + prop["interpolation"] = CONSTANT; + prop["closest_point_x"] = -1; + prop["previous_point_x"] = -1; + } + + prop["readonly"] = readonly; + prop["choices"] = Json::Value(Json::arrayValue); + + // return JsonValue + return prop; +} + +// Return the bounding box properties and it's keyframes indexed by their names +std::map KeyFrameBBox::GetBoxValues(int64_t frame_number){ + + // Create the map + std::map boxValues; + + // Get bounding box of the current frame + BBox box = GetBox(frame_number); + + // Save the bounding box properties + boxValues["cx"] = box.cx; + boxValues["cy"] = box.cy; + boxValues["w"] = box.width; + boxValues["h"] = box.height; + boxValues["ang"] = box.angle; + + // Save the keyframes values + boxValues["sx"] = this->scale_x.GetValue(frame_number); + boxValues["sy"] = this->scale_y.GetValue(frame_number); + boxValues["dx"] = this->delta_x.GetValue(frame_number); + boxValues["dy"] = this->delta_y.GetValue(frame_number); + boxValues["r"] = this->rotation.GetValue(frame_number); + + + return boxValues; +} \ No newline at end of file diff --git a/src/KeyFrameBBox.h b/src/KeyFrameBBox.h index 4480bddbf..16b0323d3 100644 --- a/src/KeyFrameBBox.h +++ b/src/KeyFrameBBox.h @@ -1,6 +1,6 @@ /** * @file - * @brief Header file for the IKeyframe class + * @brief Header file for the KeyFrameBBox class * @author Jonathan Thomas * * @ref License @@ -43,9 +43,11 @@ #include "Json.h" #include "IKeyFrame.h" #include "KeyFrame.h" +#include "KeyFrameBase.h" #include "trackerdata.pb.h" #include + using google::protobuf::util::TimeUtil; namespace openshot @@ -153,12 +155,12 @@ namespace openshot * object of this class. */ - class KeyFrameBBox + class KeyFrameBBox : public KeyframeBase { private: bool visible; Fraction BaseFps; - double TimeScale; + double TimeScale; public: std::map BoxVec; ///< Index the bounding-box by time of each frame @@ -182,7 +184,7 @@ namespace openshot Fraction GetBaseFPS(); /// Update the TimeScale member variable - void ScalePoints(double scale); + void ScalePoints(double scale) override; /// Check if there is a bounding-box in the given frame bool Contains(int64_t frame_number); @@ -194,11 +196,11 @@ namespace openshot void RemoveBox(int64_t frame_number); /// Return a bounding-box from BoxVec with it's properties adjusted by the Keyframes - BBox GetValue(int64_t frame_number) const + BBox GetBox(int64_t frame_number) const { - return const_cast(this)->GetValue(frame_number); + return const_cast(this)->GetBox(frame_number); } - BBox GetValue(int64_t frame_number); + BBox GetBox(int64_t frame_number); /// Load the bounding-boxes information from the protobuf file bool LoadBoxData(std::string inputFilePath); @@ -209,16 +211,26 @@ namespace openshot /// Interpolate the bouding-boxes properties BBox InterpolateBoxes(double t1, double t2, BBox left, BBox right, double target); + /// Clear the BoxVec map + void clear(); + /// Get and Set JSON methods - std::string Json(); ///< Generate JSON string of this object - Json::Value JsonValue(); ///< Generate Json::Value for this object + std::string Json() const; ///< Generate JSON string of this object + Json::Value JsonValue() const; ///< Generate Json::Value for this object void SetJson(const std::string value); ///< Load JSON string into this object void SetJsonValue(const Json::Value root); ///< Load Json::Value into this object - /// Clear the BoxVec map - void clear(); - }; + /// Get all properties for a specific frame (perfect for a UI to display the current state + /// of all properties at any time) + Json::Value PropertiesJSON(int64_t requested_frame) const; + // Generate JSON for a property + Json::Value add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe* keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const; + + /// Return the bounding box properties and it's keyframes indexed by their names + std::map GetBoxValues(int64_t frame_number) override; + + }; } // namespace openshot -#endif +#endif \ No newline at end of file diff --git a/src/KeyFrameBase.cpp b/src/KeyFrameBase.cpp new file mode 100644 index 000000000..acdf389fc --- /dev/null +++ b/src/KeyFrameBase.cpp @@ -0,0 +1,107 @@ +/** + * @file + * @brief Source file for the KeyframeBase class + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . 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 . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include "KeyFrameBase.h" +#include +#include +#include + + +namespace openshot{ + + // Check if the X coordinate of a given Point is lower than a given value + bool IsPointBeforeX(Point const & p, double const x) { + return p.co.X < x; + } + + // Linear interpolation between two points + 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); + } + + // Bezier interpolation between two points + 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 (fabs(target - x) < allowed_error) { + return y; + } + if (x > target) { + t -= t_step; + } + else { + t += t_step; + } + t_step /= 2; + } while (true); + } + + // Interpolate two points using the right Point's interpolation method + 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); + } + } + + KeyframeBase::KeyframeBase(){ + id = "TESTBASEID"; + } + + KeyframeBase::KeyframeBase(std::string _id){ + SetId(_id); + } +} \ No newline at end of file diff --git a/src/KeyFrameBase.h b/src/KeyFrameBase.h new file mode 100644 index 000000000..316ed83a4 --- /dev/null +++ b/src/KeyFrameBase.h @@ -0,0 +1,98 @@ +/** + * @file + * @brief Header file for the KeyframeBase class + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . 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 . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#ifndef OPENSHOT_KEYFRAMEBASE_H +#define OPENSHOT_KEYFRAMEBASE_H + +#include +#include +#include +#include +#include +#include +#include "Exceptions.h" +#include "Fraction.h" +#include "Coordinate.h" +#include "Point.h" +#include "Json.h" + +namespace openshot { + /** + * @brief This abstract class is the base class of all Keyframes. + * + * A Keyframe is a collection of Point instances, which is used to vary a number or property over time. + * + * Keyframes are used to animate and interpolate values of properties over time. For example, a single property + * can use a Keyframe instead of a constant value. Assume you want to slide an image (from left to right) over + * a video. You can create a Keyframe which will adjust the X value of the image over 100 frames (or however many + * frames the animation needs to last) from the value of 0 to 640. + */ + + /// Check if the X coordinate of a given Point is lower than a given value + bool IsPointBeforeX(Point const & p, double const x); + + /// Linear interpolation between two points + double InterpolateLinearCurve(Point const & left, Point const & right, double const target); + + /// Bezier interpolation between two points + double InterpolateBezierCurve(Point const & left, Point const & right, double const target, double const allowed_error); + + /// Interpolate two points using the right Point's interpolation method + double InterpolateBetween(Point const & left, Point const & right, double target, double allowed_error); + + // template + // int64_t SearchBetweenPoints(Point const & left, Point const & right, int64_t const current, Check check); + + class KeyframeBase{ + private: + std::string id; + + public: + + /// Blank constructor + KeyframeBase(); + + /// Default constructor + KeyframeBase(std::string _id); + + std::string Id() const { return id; } + void SetId(std::string _id) { id = _id; } + + /// Scale all points by a percentage (good for evenly lengthening or shortening an openshot::Keyframe) + /// 1.0 = same size, 1.05 = 5% increase, etc... + virtual void ScalePoints(double scale) { return; }; + + /// Return the main properties of a KeyframeBBox instance using a pointer to this base class + virtual std::map GetBoxValues(int64_t frame_number) { std::map ret; return ret; }; + + }; +} // Namespace openshot + +#endif diff --git a/src/Timeline.cpp b/src/Timeline.cpp index d7377a280..80a2170ab 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -240,6 +240,55 @@ Timeline::~Timeline() { } } +// Add to the tracked_objects map a pointer to a tracked object (KeyframeBBox) +void Timeline::AddTrackedObject(std::shared_ptr trackedObject){ + + // Search for the tracked object on the map + auto iterator = tracked_objects.find(trackedObject->Id()); + + if (iterator != tracked_objects.end()){ + // Tracked object's id already present on the map, overwrite it + iterator->second = trackedObject; + } + else{ + // Tracked object's id not present -> insert it on the map + tracked_objects[trackedObject->Id()] = trackedObject; + } + return; +} + +// Return tracked object pointer by it's id +std::shared_ptr Timeline::GetTrackedObject(std::string id) const{ + + // Search for the tracked object on the map + auto iterator = tracked_objects.find(id); + + if (iterator != tracked_objects.end()){ + // Id found, return the pointer to the tracked object + std::shared_ptr trackedObject = iterator->second; + return trackedObject; + } + else { + // Id not found, return a null pointer + return nullptr; + } +} + +// Return the ID's of the tracked objects as a vector of strings +std::vector Timeline::GetTrackedObjectsIds() const{ + + // Create a vector of strings + std::vector trackedObjects_ids; + + // Iterate through the tracked_objects map + for (auto const& it: tracked_objects){ + // Add the IDs to the vector + trackedObjects_ids.push_back(it.first); + } + + return trackedObjects_ids; +} + // Add an openshot::Clip to the timeline void Timeline::AddClip(Clip* clip) { @@ -1505,4 +1554,4 @@ void Timeline::SetMaxSize(int width, int height) { // Update preview settings preview_width = display_ratio_size.width(); preview_height = display_ratio_size.height(); -} +} \ No newline at end of file diff --git a/src/Timeline.h b/src/Timeline.h index e12cf3ee5..d5d1bda9b 100644 --- a/src/Timeline.h +++ b/src/Timeline.h @@ -52,6 +52,8 @@ #include "Frame.h" #include "FrameMapper.h" #include "KeyFrame.h" +#include "KeyFrameBBox.h" +#include "KeyFrameBase.h" #include "OpenMPUtilities.h" #include "ReaderBase.h" #include "Settings.h" @@ -177,7 +179,8 @@ namespace openshot { bool managed_cache; ///< Does this timeline instance manage the cache object std::string path; ///< Optional path of loaded UTF-8 OpenShot JSON project file std::mutex get_frame_mutex; ///< Mutex to protect GetFrame method from different threads calling it - + std::map> tracked_objects; ///< map of KeyframeBBoxes and their IDs + /// Process a new layer of video or audio void add_layer(std::shared_ptr new_frame, openshot::Clip* source_clip, int64_t clip_frame_number, int64_t timeline_frame_number, bool is_top_clip, float max_volume); @@ -241,6 +244,13 @@ namespace openshot { virtual ~Timeline(); + /// Add to the tracked_objects map a pointer to a tracked object (KeyframeBBox) + void AddTrackedObject(std::shared_ptr trackedObject); + /// Return tracked object pointer by it's id + std::shared_ptr GetTrackedObject(std::string id) const; + /// Return the ID's of the tracked objects as a vector of strings + std::vector GetTrackedObjectsIds() const; + /// @brief Add an openshot::Clip to the timeline /// @param clip Add an openshot::Clip to the timeline. A clip can contain any type of Reader. void AddClip(openshot::Clip* clip); diff --git a/src/TimelineBase.cpp b/src/TimelineBase.cpp index 4356167f5..6170bff2b 100644 --- a/src/TimelineBase.cpp +++ b/src/TimelineBase.cpp @@ -39,3 +39,12 @@ TimelineBase::TimelineBase() preview_width = 1920; preview_height = 1080; } + +/* This function will be overloaded in the Timeline class passing no arguments +* so we'll be able to access the Timeline::Clips() function from a pointer object of +* the TimelineBase class +*/ +void TimelineBase::Clips(int test){ + std::cout << test << std::endl; + return; +} \ No newline at end of file diff --git a/src/TimelineBase.h b/src/TimelineBase.h index af6a65a20..7bcd8f9a9 100644 --- a/src/TimelineBase.h +++ b/src/TimelineBase.h @@ -30,7 +30,7 @@ #ifndef OPENSHOT_TIMELINE_BASE_H #define OPENSHOT_TIMELINE_BASE_H - +#include namespace openshot { /** @@ -44,6 +44,11 @@ namespace openshot { /// Constructor for the base timeline TimelineBase(); + + /// This function will be overloaded in the Timeline class passing no arguments + /// so we'll be able to access the Timeline::Clips() function from a pointer object of + /// the TimelineBase class + virtual void Clips(int test); }; } diff --git a/src/effects/Tracker.cpp b/src/effects/Tracker.cpp index 6833b7f0e..b5ac70a84 100644 --- a/src/effects/Tracker.cpp +++ b/src/effects/Tracker.cpp @@ -29,6 +29,7 @@ */ #include "effects/Tracker.h" +#include "Timeline.h" using namespace openshot; @@ -37,8 +38,11 @@ Tracker::Tracker(std::string clipTrackerDataPath) { // Init effect properties init_effect_details(); - // Tries to load the tracked object's data from protobuf file - trackedData.LoadBoxData(clipTrackerDataPath); + // Instantiate a keyframebbox object and point to it + KeyFrameBBox trackedDataObject; + trackedData = std::make_shared(trackedDataObject); + // Tries to load the tracked object's data from protobuf file + trackedData->LoadBoxData(clipTrackerDataPath); } // Default constructor @@ -46,6 +50,9 @@ Tracker::Tracker() { // Init effect properties init_effect_details(); + // Instantiate a keyframebbox object and point to it + KeyFrameBBox trackedDataObject; + trackedData = std::make_shared(trackedDataObject); } @@ -76,14 +83,14 @@ std::shared_ptr Tracker::GetFrame(std::shared_ptr frame, int64_t f if(!frame_image.empty()) { // Check if track data exists for the requested frame - if (trackedData.Contains(frame_number)) + if (trackedData->Contains(frame_number)) { // Get the width and height of the image float fw = frame_image.size().width; float fh = frame_image.size().height; // Get the bounding-box of given frame - BBox fd = this->trackedData.GetValue(frame_number); + BBox fd = trackedData->GetBox(frame_number); // Create a rotated rectangle object that holds the bounding box cv::RotatedRect box ( cv::Point2f( (int)(fd.cx*fw), (int)(fd.cy*fh) ), @@ -119,11 +126,23 @@ Json::Value Tracker::JsonValue() const { // Create root json object Json::Value root = EffectBase::JsonValue(); // get parent properties + + // Save the effect's properties on root root["type"] = info.class_name; root["protobuf_data_path"] = protobuf_data_path; root["BaseFPS"]["num"] = BaseFPS.num; root["BaseFPS"]["den"] = BaseFPS.den; root["TimeScale"] = this->TimeScale; + + // Get trackedData JSON + Json::Value trackedDataJSON; + trackedDataJSON = trackedData->JsonValue(); + + // Save the trackedData properties on root + for (const auto& key : trackedDataJSON.getMemberNames()){ + root[key] = trackedDataJSON[key]; + } + // return JsonValue return root; } @@ -143,6 +162,7 @@ void Tracker::SetJson(const std::string value) { // Error parsing JSON (or missing keys) throw InvalidJSON("JSON is invalid (missing keys or invalid data types)"); } + return; } // Load Json::Value into this object @@ -150,7 +170,10 @@ void Tracker::SetJsonValue(const Json::Value root) { // Set parent data EffectBase::SetJsonValue(root); - + + if(!root["type"].isNull()) + info.class_name = root["type"].asString(); + if (!root["BaseFPS"].isNull() && root["BaseFPS"].isObject()) { if (!root["BaseFPS"]["num"].isNull()) @@ -166,19 +189,20 @@ void Tracker::SetJsonValue(const Json::Value root) { if (!root["TimeScale"].isNull()) TimeScale = (double) root["TimeScale"].asDouble(); - trackedData.SetBaseFPS(this->BaseFPS); - trackedData.ScalePoints(TimeScale); - // Set data from Json (if key is found) if (!root["protobuf_data_path"].isNull()) { protobuf_data_path = (root["protobuf_data_path"].asString()); - if(!trackedData.LoadBoxData(protobuf_data_path)) + if(!trackedData->LoadBoxData(protobuf_data_path)) { std::cout<<"Invalid protobuf data path"; protobuf_data_path = ""; } } + + trackedData->SetJsonValue(root); + + return; } @@ -186,9 +210,10 @@ void Tracker::SetJsonValue(const Json::Value root) { std::string Tracker::PropertiesJSON(int64_t requested_frame) const { // Generate JSON properties list - Json::Value root; + Json::Value root; + root = trackedData->PropertiesJSON(requested_frame); - // Effect's properties + // Append effect's properties root["name"] = add_property_json("Tracker", 0.0, "string", "", NULL, -1, -1, true, requested_frame); root["id"] = add_property_json("ID", 0.0, "string", Id(), NULL, -1, -1, true, requested_frame); root["position"] = add_property_json("Position", Position(), "float", "", NULL, 0, 1000 * 60 * 30, false, requested_frame); @@ -197,73 +222,6 @@ std::string Tracker::PropertiesJSON(int64_t requested_frame) const { root["end"] = add_property_json("End", End(), "float", "", NULL, 0, 1000 * 60 * 30, false, requested_frame); root["duration"] = add_property_json("Duration", Duration(), "float", "", NULL, 0, 1000 * 60 * 30, true, requested_frame); - // Get the bounding-box for the given-frame - BBox fd = trackedData.GetValue(requested_frame); - // Add the data of given frame bounding-box to the JSON object - root["x1"] = add_property_json("X1", fd.cx-(fd.width/2), "float", "", NULL, 0.0, 1.0, false, requested_frame); - root["y1"] = add_property_json("Y1", fd.cy-(fd.height/2), "float", "", NULL, 0.0, 1.0, false, requested_frame); - root["x2"] = add_property_json("X2", fd.cx+(fd.width/2), "float", "", NULL, 0.0, 1.0, false, requested_frame); - root["y2"] = add_property_json("Y2", fd.cy+(fd.height/2), "float", "", NULL, 0.0, 1.0, false, requested_frame); - - // Add the bounding-box Keyframes to the JSON object - root["delta_x"] = add_property_json("Displacement X-axis", trackedData.delta_x.GetValue(requested_frame), "float", "", &trackedData.delta_x, -1.0, 1.0, false, requested_frame); - root["delta_y"] = add_property_json("Displacement Y-axis", trackedData.delta_y.GetValue(requested_frame), "float", "", &trackedData.delta_y, -1.0, 1.0, false, requested_frame); - root["scale_x"] = add_property_json("Scale (Width)", trackedData.scale_x.GetValue(requested_frame), "float", "", &trackedData.scale_x, -1.0, 1.0, false, requested_frame); - root["scale_y"] = add_property_json("Scale (Height)", trackedData.scale_y.GetValue(requested_frame), "float", "", &trackedData.scale_y, -1.0, 1.0, false, requested_frame); - root["rotation"] = add_property_json("Rotation", trackedData.rotation.GetValue(requested_frame), "float", "", &trackedData.rotation, 0, 360, false, requested_frame); - // Return formatted string return root.toStyledString(); } - -// Generate JSON string of the trackedData object passing the frame number -std::string Tracker::Json(int64_t requested_frame) const { - - // Generate JSON properties list - Json::Value root; - - // Add the KeyframeBBox class properties to the JSON object - root["type"] = info.class_name; - root["protobuf_data_path"] = protobuf_data_path; - root["BaseFPS"]["num"] = BaseFPS.num; - root["BaseFPS"]["den"] = BaseFPS.den; - root["TimeScale"] = this->TimeScale; - - // Add the bounding-box Keyframes to the JSON object - root["delta_x"] = trackedData.delta_x.JsonValue(); - root["delta_y"] = trackedData.delta_y.JsonValue(); - root["scale_x"] = trackedData.scale_x.JsonValue(); - root["scale_y"] = trackedData.scale_y.JsonValue(); - root["rotation"] = trackedData.rotation.JsonValue(); - - return root.toStyledString(); -} - -// Set the tracketData object properties by a JSON string -void Tracker::SetJson(int64_t requested_frame, const std::string value) -{ - // Parse JSON string into JSON objects - try - { - const Json::Value root = openshot::stringToJson(value); - - // Set all values that match - if (!root["delta_x"].isNull()) - trackedData.delta_x.SetJsonValue(root["delta_x"]); - if (!root["delta_y"].isNull()) - trackedData.delta_y.SetJsonValue(root["delta_y"]); - if (!root["scale_x"].isNull()) - trackedData.scale_x.SetJsonValue(root["scale_x"]); - if (!root["scale_y"].isNull()) - trackedData.scale_y.SetJsonValue(root["scale_y"]); - if (!root["rotation"].isNull()) - trackedData.rotation.SetJsonValue(root["rotation"]); - } - - catch (const std::exception& e) - { - // Error parsing JSON (or missing keys) - throw InvalidJSON("JSON is invalid (missing keys or invalid data types)"); - } - return; -} diff --git a/src/effects/Tracker.h b/src/effects/Tracker.h index 57a8b8b10..0f3cb018d 100644 --- a/src/effects/Tracker.h +++ b/src/effects/Tracker.h @@ -67,7 +67,7 @@ namespace openshot public: std::string protobuf_data_path; ///< Path to the protobuf file that holds the bounding-box data - KeyFrameBBox trackedData; ///< Object that holds the bounding-box data and it's Keyframes + std::shared_ptr trackedData; ///< Pointer to an object that holds the bounding-box data and it's Keyframes /// Blank constructor, useful when using Json to load the effect properties Tracker(std::string clipTrackerDataPath); @@ -96,9 +96,6 @@ namespace openshot /// Get all properties for a specific frame (perfect for a UI to display the current state /// of all properties at any time) std::string PropertiesJSON(int64_t requested_frame) const override; - - std::string Json(int64_t requested_frame) const override; ///< Generate JSON string of the trackedData object passing the frame number - void SetJson(int64_t requested_frame, const std::string value) override; ///< Set the tracketData object properties by a JSON string }; } diff --git a/tests/KeyFrame_Tests.cpp b/tests/KeyFrame_Tests.cpp index 05cd5109f..6ca98e920 100644 --- a/tests/KeyFrame_Tests.cpp +++ b/tests/KeyFrame_Tests.cpp @@ -522,7 +522,7 @@ TEST(KeyFrameBBox_GetVal_test) { kfb.AddBox(1, 10.0, 10.0, 100.0, 100.0, 0.0); - BBox val = kfb.GetValue(1); + BBox val = kfb.GetBox(1); CHECK_EQUAL(10.0, val.cx); CHECK_EQUAL(10.0, val.cy); @@ -540,21 +540,21 @@ TEST(KeyFrameBBox_GetVal_Interpolation) { kfb.AddBox(21, 30.0, 30.0, 100.0, 100.0, 0.0); kfb.AddBox(31, 40.0, 40.0, 100.0, 100.0, 0.0); - BBox val = kfb.GetValue(5); + BBox val = kfb.GetBox(5); CHECK_EQUAL(14.0, val.cx); CHECK_EQUAL(14.0, val.cy); CHECK_EQUAL(100.0,val.width); CHECK_EQUAL(100.0,val.height); - val = kfb.GetValue(15); + val = kfb.GetBox(15); CHECK_EQUAL(24.0, val.cx); CHECK_EQUAL(24.0, val.cy); CHECK_EQUAL(100.0,val.width); CHECK_EQUAL(100.0, val.height); - val = kfb.GetValue(25); + val = kfb.GetBox(25); CHECK_EQUAL(34.0, val.cx); CHECK_EQUAL(34.0, val.cy); @@ -581,8 +581,6 @@ TEST(KeyFrameBBox_Json_set) { KeyFrameBBox fromJSON_kfb; fromJSON_kfb.SetJson(dataJSON); - std::cout << fromJSON_kfb.Json() << std::endl; - CHECK_EQUAL(kfb.GetBaseFPS().num, fromJSON_kfb.GetBaseFPS().num); double time_kfb = kfb.FrameNToTime(1, 1.0); @@ -606,8 +604,78 @@ TEST(KeyFrameBBox_Scale_test){ kfb.scale_x.AddPoint(1.0, 2.0); kfb.scale_y.AddPoint(1.0, 3.0); - BBox bbox = kfb.GetValue(1); + BBox bbox = kfb.GetBox(1); CHECK_EQUAL(20.0, bbox.width); CHECK_EQUAL(30.0, bbox.height); +} + + + +TEST(Attach_test){ + + // Create Timelime + Timeline t(1280, 720, Fraction(25,1), 44100, 2, ChannelLayout::LAYOUT_STEREO); + + // Create Clip and add it to the Timeline + Clip clip(new ImageReader("../../examples/front.png")); + clip.Id("AAAA1234"); + + // Create a child clip and add it to the Timeline + Clip childClip(new ImageReader("../../examples/mask2.png")); + childClip.Id("CHILD123"); + + // Add clips to timeline + t.AddClip(&childClip); + t.AddClip(&clip); + + // Create tracker and add it to clip + Tracker tracker; + clip.AddEffect(&tracker); + + // Save a pointer to trackedData + std::shared_ptr trackedData = tracker.trackedData; + + // Change trackedData scale + trackedData->scale_x.AddPoint(1, 2.0); + CHECK_EQUAL(2.0, trackedData->scale_x.GetValue(1)); + + // Tracked Data JSON + auto trackedDataJson = trackedData->JsonValue(); + + // Get and cast the trakcedObject + auto trackedObject_base = t.GetTrackedObject("TESTBASEID"); + std::shared_ptr trackedObject = std::static_pointer_cast(trackedObject_base); + CHECK_EQUAL(trackedData, trackedObject); + + // Set trackedObject Json Value + trackedObject->SetJsonValue(trackedDataJson); + + // Attach childClip to tracked object + std::string tracked_id = trackedData->Id(); + childClip.Open(); + childClip.AttachToTracker(tracked_id); + + std::shared_ptr trackedTest = std::static_pointer_cast(childClip.GetAttachedObject()); + + CHECK_EQUAL(trackedData->scale_x.GetValue(1), trackedTest->scale_x.GetValue(1)); + + auto frameTest = childClip.GetFrame(1); + childClip.Close(); +} + +TEST(GetBoxValues_test){ + + KeyFrameBBox trackedDataObject; + trackedDataObject.AddBox(1, 10.0, 10.0, 20.0, 20.0, 30.0); + + std::shared_ptr trackedData = std::make_shared(trackedDataObject); + + auto boxValues = trackedData->GetBoxValues(1); + + CHECK_EQUAL(10.0, boxValues["cx"]); + CHECK_EQUAL(10.0, boxValues["cy"]); + CHECK_EQUAL(20.0, boxValues["w"]); + CHECK_EQUAL(20.0, boxValues["h"]); + CHECK_EQUAL(30.0, boxValues["ang"]); } \ No newline at end of file