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);