diff --git a/include/Clip.h b/include/Clip.h index e153acb4d..0fbed1599 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -139,11 +139,11 @@ namespace openshot { 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 - openshot::AnchorType anchor; ///< The anchor determines what parent a clip should snap to - openshot::FrameDisplayType display; ///< The format to display the frame number (if any) - openshot::VolumeMixType mixing; ///< What strategy should be followed when mixing audio with other clips + 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 + openshot::AnchorType anchor; ///< The anchor determines what parent a clip should snap to + openshot::FrameDisplayType display; ///< The format to display the frame number (if any) + openshot::VolumeMixType mixing; ///< What strategy should be followed when mixing audio with other clips /// Default Constructor Clip(); @@ -207,15 +207,19 @@ namespace openshot { bool Waveform() { return waveform; } ///< Get the waveform property of this clip void Waveform(bool value) { waveform = value; } ///< Set the waveform property of this clip - // Scale and Location curves + // Scale, Location, and Alpha curves openshot::Keyframe scale_x; ///< Curve representing the horizontal scaling in percent (0 to 1) openshot::Keyframe scale_y; ///< Curve representing the vertical scaling in percent (0 to 1) openshot::Keyframe location_x; ///< Curve representing the relative X position in percent based on the gravity (-1 to 1) openshot::Keyframe location_y; ///< Curve representing the relative Y position in percent based on the gravity (-1 to 1) - - // Alpha and Rotation curves openshot::Keyframe alpha; ///< Curve representing the alpha (1 to 0) + + // Rotation and Shear curves (origin point (x,y) is adjustable for both rotation and shear) openshot::Keyframe rotation; ///< Curve representing the rotation (0 to 360) + openshot::Keyframe shear_x; ///< Curve representing X shear angle in degrees (-45.0=left, 45.0=right) + openshot::Keyframe shear_y; ///< Curve representing Y shear angle in degrees (-45.0=down, 45.0=up) + openshot::Keyframe origin_x; ///< Curve representing X origin point (0.0=0% (left), 1.0=100% (right)) + openshot::Keyframe origin_y; ///< Curve representing Y origin point (0.0=0% (top), 1.0=100% (bottom)) // Time and Volume curves openshot::Keyframe time; ///< Curve representing the frames over time to play (used for speed and direction of video) @@ -231,9 +235,7 @@ namespace openshot { openshot::Keyframe crop_x; ///< Curve representing X offset in percent (-1.0=-100%, 0.0=0%, 1.0=100%) openshot::Keyframe crop_y; ///< Curve representing Y offset in percent (-1.0=-100%, 0.0=0%, 1.0=100%) - // Shear and perspective curves - openshot::Keyframe shear_x; ///< Curve representing X shear angle in degrees (-45.0=left, 45.0=right) - openshot::Keyframe shear_y; ///< Curve representing Y shear angle in degrees (-45.0=down, 45.0=up) + // Perspective curves openshot::Keyframe perspective_c1_x; ///< Curves representing X for coordinate 1 openshot::Keyframe perspective_c1_y; ///< Curves representing Y for coordinate 1 openshot::Keyframe perspective_c2_x; ///< Curves representing X for coordinate 2 diff --git a/src/Clip.cpp b/src/Clip.cpp index fa1bb7c7c..d9f694408 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -89,6 +89,8 @@ void Clip::init_settings() // Init shear and perspective curves shear_x = Keyframe(0.0); shear_y = Keyframe(0.0); + origin_x = Keyframe(0.5); + origin_y = Keyframe(0.5); perspective_c1_x = Keyframe(-1.0); perspective_c1_y = Keyframe(-1.0); perspective_c2_x = Keyframe(-1.0); @@ -716,6 +718,8 @@ std::string Clip::PropertiesJSON(int64_t requested_frame) const { root["shear_x"] = add_property_json("Shear X", shear_x.GetValue(requested_frame), "float", "", &shear_x, -1.0, 1.0, false, requested_frame); root["shear_y"] = add_property_json("Shear Y", shear_y.GetValue(requested_frame), "float", "", &shear_y, -1.0, 1.0, false, requested_frame); root["rotation"] = add_property_json("Rotation", rotation.GetValue(requested_frame), "float", "", &rotation, -360, 360, false, requested_frame); + root["origin_x"] = add_property_json("Origin X", origin_x.GetValue(requested_frame), "float", "", &origin_x, 0.0, 1.0, false, requested_frame); + root["origin_y"] = add_property_json("Origin Y", origin_y.GetValue(requested_frame), "float", "", &origin_y, 0.0, 1.0, false, requested_frame); root["volume"] = add_property_json("Volume", volume.GetValue(requested_frame), "float", "", &volume, 0.0, 1.0, false, requested_frame); root["time"] = add_property_json("Time", time.GetValue(requested_frame), "float", "", &time, 0.0, 30 * 60 * 60 * 48, false, requested_frame); root["channel_filter"] = add_property_json("Channel Filter", channel_filter.GetValue(requested_frame), "int", "", &channel_filter, -1, 10, false, requested_frame); @@ -772,6 +776,8 @@ Json::Value Clip::JsonValue() const { root["crop_y"] = crop_y.JsonValue(); root["shear_x"] = shear_x.JsonValue(); root["shear_y"] = shear_y.JsonValue(); + root["origin_x"] = origin_x.JsonValue(); + root["origin_y"] = origin_y.JsonValue(); root["channel_filter"] = channel_filter.JsonValue(); root["channel_mapping"] = channel_mapping.JsonValue(); root["has_audio"] = has_audio.JsonValue(); @@ -869,6 +875,10 @@ void Clip::SetJsonValue(const Json::Value root) { shear_x.SetJsonValue(root["shear_x"]); if (!root["shear_y"].isNull()) shear_y.SetJsonValue(root["shear_y"]); + if (!root["origin_x"].isNull()) + origin_x.SetJsonValue(root["origin_x"]); + if (!root["origin_y"].isNull()) + origin_y.SetJsonValue(root["origin_y"]); if (!root["channel_filter"].isNull()) channel_filter.SetJsonValue(root["channel_filter"]); if (!root["channel_mapping"].isNull()) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 0a0806b15..124058ac2 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -674,6 +674,8 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in y += (Settings::Instance()->MAX_HEIGHT * source_clip->location_y.GetValue(clip_frame_number)); // move in percentage of final height float shear_x = source_clip->shear_x.GetValue(clip_frame_number); float shear_y = source_clip->shear_y.GetValue(clip_frame_number); + float origin_x = source_clip->origin_x.GetValue(clip_frame_number); + float origin_y = source_clip->origin_y.GetValue(clip_frame_number); bool transformed = false; QTransform transform; @@ -681,21 +683,22 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in // Transform source image (if needed) ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Build QTransform - if needed)", "source_frame->number", source_frame->number, "x", x, "y", y, "r", r, "sx", sx, "sy", sy); - if (!isEqual(r, 0)) { - // ROTATE CLIP - float origin_x = x + (scaled_source_width / 2.0); - float origin_y = y + (scaled_source_height / 2.0); - transform.translate(origin_x, origin_y); - transform.rotate(r); - transform.translate(-origin_x,-origin_y); + if (!isEqual(x, 0) || !isEqual(y, 0)) { + // TRANSLATE/MOVE CLIP + transform.translate(x, y); transformed = true; } - if (!isEqual(x, 0) || !isEqual(y, 0)) { - // TRANSLATE/MOVE CLIP - transform.translate(x, y); - transformed = true; - } + if (!isEqual(r, 0) || !isEqual(shear_x, 0) || !isEqual(shear_y, 0)) { + // ROTATE CLIP (around origin_x, origin_y) + float origin_x_value = (scaled_source_width * origin_x); + float origin_y_value = (scaled_source_height * origin_y); + transform.translate(origin_x_value, origin_y_value); + transform.rotate(r); + transform.shear(shear_x, shear_y); + transform.translate(-origin_x_value,-origin_y_value); + transformed = true; + } // SCALE CLIP (if needed) float source_width_scale = (float(source_size.width()) / float(source_image->width())) * sx; @@ -706,12 +709,6 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in transformed = true; } - if (!isEqual(shear_x, 0) || !isEqual(shear_y, 0)) { - // SHEAR HEIGHT/WIDTH - transform.shear(shear_x, shear_y); - transformed = true; - } - // Debug output ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Transform: Composite Image Layer: Prepare)", "source_frame->number", source_frame->number, "new_frame->GetImage()->width()", new_frame->GetImage()->width(), "transformed", transformed);