diff --git a/include/Clip.h b/include/Clip.h
index aa24d670..5178274e 100644
--- a/include/Clip.h
+++ b/include/Clip.h
@@ -151,6 +151,7 @@ namespace openshot {
GravityType gravity; ///< The gravity of a clip determines where it snaps to it's parent
ScaleType scale; ///< The scale determines how a clip should be resized to fit it's parent
AnchorType anchor; ///< The anchor determines what parent a clip should snap to
+ TransformHandleType handles; ///< The transform handle determines if selection handles are added to clip (to display the clips edges)
/// Default Constructor
Clip();
diff --git a/include/Enums.h b/include/Enums.h
index 972bd793..324c2b3e 100644
--- a/include/Enums.h
+++ b/include/Enums.h
@@ -61,5 +61,11 @@ namespace openshot
ANCHOR_VIEWPORT ///< Anchor the clip to the viewport (which can be moved / animated around the canvas)
};
+ /// This enumeration determines what parent a clip should be aligned to.
+ enum TransformHandleType
+ {
+ TRANSFORM_HANDLE_NONE, ///< Do not add any transform handles to clip
+ TRANSFORM_HANDLE_SELECTION ///< Add selection handles to clip (useful for editors to display edges of clip)
+ };
}
#endif
diff --git a/src/Clip.cpp b/src/Clip.cpp
index 7e5cc299..b00d4bb2 100644
--- a/src/Clip.cpp
+++ b/src/Clip.cpp
@@ -40,6 +40,7 @@ void Clip::init_settings()
gravity = GRAVITY_CENTER;
scale = SCALE_FIT;
anchor = ANCHOR_CANVAS;
+ handles = TRANSFORM_HANDLE_NONE;
waveform = false;
previous_properties = "";
@@ -652,6 +653,7 @@ string Clip::PropertiesJSON(long int requested_frame) {
root["gravity"] = add_property_json("Gravity", gravity, "int", "", false, 0, 0, 8, CONSTANT, -1, false);
root["scale"] = add_property_json("Scale", scale, "int", "", false, 0, 0, 3, CONSTANT, -1, false);
root["anchor"] = add_property_json("Anchor", anchor, "int", "", false, 0, 0, 1, CONSTANT, -1, false);
+ root["handles"] = add_property_json("Handles", handles, "int", "", false, 0, 0, 1, CONSTANT, -1, false);
root["waveform"] = add_property_json("Waveform", waveform, "int", "", false, 0, 0, 1, CONSTANT, -1, false);
// Add gravity choices (dropdown style)
@@ -675,6 +677,10 @@ string Clip::PropertiesJSON(long int requested_frame) {
root["anchor"]["choices"].append(add_property_choice_json("Canvas", ANCHOR_CANVAS, anchor));
root["anchor"]["choices"].append(add_property_choice_json("Viewport", ANCHOR_VIEWPORT, anchor));
+ // Add transform handles choices (dropdown style)
+ root["handles"]["choices"].append(add_property_choice_json("None", TRANSFORM_HANDLE_NONE, handles));
+ root["handles"]["choices"].append(add_property_choice_json("Selection", TRANSFORM_HANDLE_SELECTION, handles));
+
// Add waveform choices (dropdown style)
root["waveform"]["choices"].append(add_property_choice_json("Yes", true, waveform));
root["waveform"]["choices"].append(add_property_choice_json("No", false, waveform));
@@ -685,6 +691,8 @@ string Clip::PropertiesJSON(long int requested_frame) {
root["scale_x"] = add_property_json("Scale X", scale_x.GetValue(requested_frame), "float", "", scale_x.Contains(requested_point), scale_x.GetCount(), 0.0, 1.0, scale_x.GetClosestPoint(requested_point).interpolation, scale_x.GetClosestPoint(requested_point).co.X, false);
root["scale_y"] = add_property_json("Scale Y", scale_y.GetValue(requested_frame), "float", "", scale_y.Contains(requested_point), scale_y.GetCount(), 0.0, 1.0, scale_y.GetClosestPoint(requested_point).interpolation, scale_y.GetClosestPoint(requested_point).co.X, false);
root["alpha"] = add_property_json("Alpha", alpha.GetValue(requested_frame), "float", "", alpha.Contains(requested_point), alpha.GetCount(), 0.0, 1.0, alpha.GetClosestPoint(requested_point).interpolation, alpha.GetClosestPoint(requested_point).co.X, false);
+ root["shear_x"] = add_property_json("Shear X", shear_x.GetValue(requested_frame), "float", "", shear_x.Contains(requested_point), shear_x.GetCount(), -1.0, 1.0, shear_x.GetClosestPoint(requested_point).interpolation, shear_x.GetClosestPoint(requested_point).co.X, false);
+ root["shear_y"] = add_property_json("Shear Y", shear_y.GetValue(requested_frame), "float", "", shear_y.Contains(requested_point), shear_y.GetCount(), -1.0, 1.0, shear_y.GetClosestPoint(requested_point).interpolation, shear_y.GetClosestPoint(requested_point).co.X, false);
root["rotation"] = add_property_json("Rotation", rotation.GetValue(requested_frame), "float", "", rotation.Contains(requested_point), rotation.GetCount(), -360, 360, rotation.GetClosestPoint(requested_point).interpolation, rotation.GetClosestPoint(requested_point).co.X, false);
root["volume"] = add_property_json("Volume", volume.GetValue(requested_frame), "float", "", volume.Contains(requested_point), volume.GetCount(), 0.0, 1.0, volume.GetClosestPoint(requested_point).interpolation, volume.GetClosestPoint(requested_point).co.X, false);
root["time"] = add_property_json("Time", time.GetValue(requested_frame), "float", "", time.Contains(requested_point), time.GetCount(), 0.0, 30 * 60 * 60 * 48, time.GetClosestPoint(requested_point).interpolation, time.GetClosestPoint(requested_point).co.X, false);
@@ -711,6 +719,7 @@ Json::Value Clip::JsonValue() {
root["gravity"] = gravity;
root["scale"] = scale;
root["anchor"] = anchor;
+ root["handles"] = handles;
root["waveform"] = waveform;
root["scale_x"] = scale_x.JsonValue();
root["scale_y"] = scale_y.JsonValue();
@@ -795,6 +804,8 @@ void Clip::SetJsonValue(Json::Value root) {
scale = (ScaleType) root["scale"].asInt();
if (!root["anchor"].isNull())
anchor = (AnchorType) root["anchor"].asInt();
+ if (!root["handles"].isNull())
+ handles = (TransformHandleType) root["handles"].asInt();
if (!root["waveform"].isNull())
waveform = root["waveform"].asBool();
if (!root["scale_x"].isNull())
diff --git a/src/Timeline.cpp b/src/Timeline.cpp
index 61428a65..a5da5cd0 100644
--- a/src/Timeline.cpp
+++ b/src/Timeline.cpp
@@ -448,53 +448,43 @@ void Timeline::add_layer(tr1::shared_ptr new_frame, Clip* source_clip, lo
y += (info.height * source_clip->location_y.GetValue(clip_frame_number)); // move in percentage of final height
bool is_x_animated = source_clip->location_x.Points.size() > 1;
bool is_y_animated = source_clip->location_y.Points.size() > 1;
+ float shear_x = source_clip->shear_x.GetValue(clip_frame_number);
+ float shear_y = source_clip->shear_y.GetValue(clip_frame_number);
int offset_x = -1;
int offset_y = -1;
bool transformed = false;
QTransform transform;
- if ((!isEqual(x, 0) || !isEqual(y, 0)) && (isEqual(r, 0) && isEqual(sx, 1) && isEqual(sy, 1) && !is_x_animated && !is_y_animated))
- {
- // SIMPLE OFFSET
- ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Transform: SIMPLE)", "source_frame->number", source_frame->number, "x", x, "y", y, "r", r, "sx", sx, "sy", sy);
- // If only X and Y are different, and no animation is being used (just set the offset for speed)
+ // 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 + (source_width / 2.0);
+ float origin_y = y + (source_height / 2.0);
+ transform.translate(origin_x, origin_y);
+ transform.rotate(r);
+ transform.translate(-origin_x,-origin_y);
transformed = true;
+ }
- // Set QTransform
+ if (!isEqual(x, 0) || !isEqual(y, 0)) {
+ // TRANSLATE/MOVE CLIP
transform.translate(x, y);
-
- } else if (!isEqual(r, 0) || !isEqual(x, 0) || !isEqual(y, 0) || !isEqual(sx, 1) || !isEqual(sy, 1))
- {
- // COMPLEX DISTORTION
- ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Transform: COMPLEX)", "source_frame->number", source_frame->number, "x", x, "y", y, "r", r, "sx", sx, "sy", sy);
-
- // Use the QTransform object, which can be very CPU intensive
transformed = true;
+ }
- // Set QTransform
- if (!isEqual(r, 0)) {
- // ROTATE CLIP
- float origin_x = x + (source_width / 2.0);
- float origin_y = y + (source_height / 2.0);
- transform.translate(origin_x, origin_y);
- transform.rotate(r);
- transform.translate(-origin_x,-origin_y);
- }
+ if (!isEqual(sx, 0) || !isEqual(sy, 0)) {
+ // TRANSLATE/MOVE CLIP
+ transform.scale(sx, sy);
+ transformed = true;
+ }
- // Set QTransform
- if (!isEqual(x, 0) || !isEqual(y, 0)) {
- // TRANSLATE/MOVE CLIP
- transform.translate(x, y);
- }
-
- if (!isEqual(sx, 0) || !isEqual(sy, 0)) {
- // TRANSLATE/MOVE CLIP
- transform.scale(sx, sy);
- }
-
- // Debug output
- ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Transform: COMPLEX: Completed ScaleRotateTranslateDistortion)", "source_frame->number", source_frame->number, "x", x, "y", y, "r", r, "sx", sx, "sy", sy);
+ if (!isEqual(shear_x, 0) || !isEqual(shear_y, 0)) {
+ // SHEAR HEIGHT/WIDTH
+ transform.shear(shear_x, shear_y);
+ transformed = true;
}
// Debug output
@@ -514,6 +504,31 @@ void Timeline::add_layer(tr1::shared_ptr new_frame, Clip* source_clip, lo
// Composite a new layer onto the image
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.drawImage(0, 0, *source_image);
+
+ // Draw transform selection handles (if needed)
+ if (source_clip->handles == TRANSFORM_HANDLE_SELECTION) {
+ // Debug output
+ ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Add transform selection handles)", "source_frame->number", source_frame->number, "offset_x", offset_x, "offset_y", offset_y, "new_frame->GetImage()->width()", new_frame->GetImage()->width(), "transformed", transformed, "", -1);
+
+ // Draw 4 corners
+ painter.fillRect(0.0, 0.0, 12.0/sx, 12.0/sy, QBrush(QColor("#53a0ed"))); // top left
+ painter.fillRect(source_width - (12.0/sx), 0, 12.0/sx, 12.0/sy, QBrush(QColor("#53a0ed"))); // top right
+ painter.fillRect(0.0, source_height - (12.0/sy), 12.0/sx, 12.0/sy, QBrush(QColor("#53a0ed"))); // bottom left
+ painter.fillRect(source_width - (12.0/sx), source_height - (12.0/sy), 12.0/sx, 12.0/sy, QBrush(QColor("#53a0ed"))); // bottom right
+
+ // Draw 4 sides (centered)
+ painter.fillRect(0.0 + (source_width / 2.0) - (6.0/sx), 0, 12.0/sx, 12.0/sy, QBrush(QColor("#53a0ed"))); // top center
+ painter.fillRect(0.0 + (source_width / 2.0) - (6.0/sx), source_height - (6.0/sy), 12.0/sx, 12.0/sy, QBrush(QColor("#53a0ed"))); // bottom center
+ painter.fillRect(0.0, (source_height / 2.0) - (6.0/sy), 12.0/sx, 12.0/sy, QBrush(QColor("#53a0ed"))); // left center
+ painter.fillRect(source_width - (12.0/sx), (source_height / 2.0) - (6.0/sy), 12.0/sx, 12.0/sy, QBrush(QColor("#53a0ed"))); // right center
+
+
+ // Draw origin QPen(const QBrush &brush, qreal width, Qt::PenStyle style = Qt::SolidLine, Qt::PenCapStyle cap = Qt::SquareCap, Qt::PenJoinStyle join = Qt::BevelJoin)
+ painter.setBrush(QColor(83, 160, 237, 122));
+ painter.setPen(Qt::NoPen);
+ painter.drawEllipse((source_width / 2.0) - (25.0/sx), (source_height / 2.0) - (25.0/sy), 50.0/sx, 50.0/sy);
+ }
+
painter.end();
// Debug output