diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt
index 7581daa25..a39950a51 100644
--- a/app/node/CMakeLists.txt
+++ b/app/node/CMakeLists.txt
@@ -16,6 +16,7 @@
add_subdirectory(audio)
add_subdirectory(block)
+add_subdirectory(distort)
add_subdirectory(filter)
add_subdirectory(generator)
add_subdirectory(input)
diff --git a/app/node/distort/CMakeLists.txt b/app/node/distort/CMakeLists.txt
new file mode 100644
index 000000000..0c704b458
--- /dev/null
+++ b/app/node/distort/CMakeLists.txt
@@ -0,0 +1,23 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2020 Olive Team
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+add_subdirectory(crop)
+add_subdirectory(transform)
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ PARENT_SCOPE
+)
diff --git a/app/node/distort/crop/CMakeLists.txt b/app/node/distort/crop/CMakeLists.txt
new file mode 100644
index 000000000..e42a61e81
--- /dev/null
+++ b/app/node/distort/crop/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2020 Olive Team
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ node/distort/crop/cropdistortnode.cpp
+ node/distort/crop/cropdistortnode.h
+ PARENT_SCOPE
+)
diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp
new file mode 100644
index 000000000..e5041992f
--- /dev/null
+++ b/app/node/distort/crop/cropdistortnode.cpp
@@ -0,0 +1,274 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2020 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
+
+#include "cropdistortnode.h"
+
+#include "common/lerp.h"
+
+namespace olive {
+
+CropDistortNode::CropDistortNode()
+{
+ texture_input_ = new NodeInput(QStringLiteral("tex_in"), NodeParam::kTexture);
+ AddInput(texture_input_);
+
+ left_input_ = new NodeInput(QStringLiteral("left_in"), NodeParam::kFloat, 0.0);
+ left_input_->set_property(QStringLiteral("min"), 0.0);
+ left_input_->set_property(QStringLiteral("max"), 1.0);
+ left_input_->set_property(QStringLiteral("view"), QStringLiteral("percent"));
+ AddInput(left_input_);
+
+ top_input_ = new NodeInput(QStringLiteral("top_in"), NodeParam::kFloat, 0.0);
+ top_input_->set_property(QStringLiteral("min"), 0.0);
+ top_input_->set_property(QStringLiteral("max"), 1.0);
+ top_input_->set_property(QStringLiteral("view"), QStringLiteral("percent"));
+ AddInput(top_input_);
+
+ right_input_ = new NodeInput(QStringLiteral("right_in"), NodeParam::kFloat, 0.0);
+ right_input_->set_property(QStringLiteral("min"), 0.0);
+ right_input_->set_property(QStringLiteral("max"), 1.0);
+ right_input_->set_property(QStringLiteral("view"), QStringLiteral("percent"));
+ AddInput(right_input_);
+
+ bottom_input_ = new NodeInput(QStringLiteral("bottom_in"), NodeParam::kFloat, 0.0);
+ bottom_input_->set_property(QStringLiteral("min"), 0.0);
+ bottom_input_->set_property(QStringLiteral("max"), 1.0);
+ bottom_input_->set_property(QStringLiteral("view"), QStringLiteral("percent"));
+ AddInput(bottom_input_);
+
+ feather_input_ = new NodeInput(QStringLiteral("feather_in"), NodeParam::kFloat, 0.0);
+ feather_input_->set_property(QStringLiteral("min"), 0.0);
+ AddInput(feather_input_);
+}
+
+void CropDistortNode::Retranslate()
+{
+ texture_input_->set_name(tr("Texture"));
+ left_input_->set_name(tr("Left"));
+ top_input_->set_name(tr("Top"));
+ right_input_->set_name(tr("Right"));
+ bottom_input_->set_name(tr("Bottom"));
+ feather_input_->set_name(tr("Feather"));
+}
+
+NodeValueTable CropDistortNode::Value(NodeValueDatabase &value) const
+{
+ ShaderJob job;
+ job.InsertValue(texture_input_, value);
+ job.InsertValue(left_input_, value);
+ job.InsertValue(top_input_, value);
+ job.InsertValue(right_input_, value);
+ job.InsertValue(bottom_input_, value);
+ job.InsertValue(feather_input_, value);
+ job.InsertValue(QStringLiteral("resolution_in"),
+ ShaderValue(value[QStringLiteral("global")].Get(NodeParam::kVec2, QStringLiteral("resolution")), NodeParam::kVec2));
+ job.SetAlphaChannelRequired(true);
+
+ NodeValueTable table = value.Merge();
+
+ if (!job.GetValue(texture_input_).data.isNull()) {
+ if (!qIsNull(job.GetValue(left_input_).data.toDouble())
+ || !qIsNull(job.GetValue(right_input_).data.toDouble())
+ || !qIsNull(job.GetValue(top_input_).data.toDouble())
+ || !qIsNull(job.GetValue(bottom_input_).data.toDouble())) {
+ table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this);
+ } else {
+ table.Push(NodeParam::kTexture, job.GetValue(texture_input_).data, this);
+ }
+ }
+
+ return table;
+}
+
+ShaderCode CropDistortNode::GetShaderCode(const QString &shader_id) const
+{
+ Q_UNUSED(shader_id)
+ return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/crop.frag")));
+}
+
+void CropDistortNode::DrawGizmos(NodeValueDatabase &db, QPainter *p)
+{
+ QVector2D resolution = db[QStringLiteral("global")].Get(NodeParam::kVec2, QStringLiteral("resolution")).value();
+
+ const int handle_radius = GetGizmoHandleRadius();
+
+ p->setPen(QPen(Qt::white, 0));
+
+ double left_pt = resolution.x() * db[left_input_].Get(NodeParam::kFloat).toDouble();
+ double top_pt = resolution.y() * db[top_input_].Get(NodeParam::kFloat).toDouble();
+ double right_pt = resolution.x() * (1.0 - db[right_input_].Get(NodeParam::kFloat).toDouble());
+ double bottom_pt = resolution.y() * (1.0 - db[bottom_input_].Get(NodeParam::kFloat).toDouble());
+ double center_x_pt = lerp(left_pt, right_pt, 0.5);
+ double center_y_pt = lerp(top_pt, bottom_pt, 0.5);
+
+ gizmo_whole_rect_ = QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt);
+ p->drawRect(gizmo_whole_rect_);
+
+ gizmo_resize_handle_[kGizmoScaleTopLeft] = CreateGizmoHandleRect(QPointF(left_pt, top_pt), handle_radius);
+ gizmo_resize_handle_[kGizmoScaleTopCenter] = CreateGizmoHandleRect(QPointF(center_x_pt, top_pt), handle_radius);
+ gizmo_resize_handle_[kGizmoScaleTopRight] = CreateGizmoHandleRect(QPointF(right_pt, top_pt), handle_radius);
+ gizmo_resize_handle_[kGizmoScaleBottomLeft] = CreateGizmoHandleRect(QPointF(left_pt, bottom_pt), handle_radius);
+ gizmo_resize_handle_[kGizmoScaleBottomCenter] = CreateGizmoHandleRect(QPointF(center_x_pt, bottom_pt), handle_radius);
+ gizmo_resize_handle_[kGizmoScaleBottomRight] = CreateGizmoHandleRect(QPointF(right_pt, bottom_pt), handle_radius);
+ gizmo_resize_handle_[kGizmoScaleCenterLeft] = CreateGizmoHandleRect(QPointF(left_pt, center_y_pt), handle_radius);
+ gizmo_resize_handle_[kGizmoScaleCenterRight] = CreateGizmoHandleRect(QPointF(right_pt, center_y_pt), handle_radius);
+
+ p->setPen(Qt::NoPen);
+ p->setBrush(Qt::white);
+ DrawAndExpandGizmoHandles(p, handle_radius, gizmo_resize_handle_, kGizmoScaleCount);
+}
+
+bool CropDistortNode::GizmoPress(NodeValueDatabase &db, const QPointF &p)
+{
+ bool found_handle = false;
+
+ bool gizmo_active[kGizmoScaleCount] = {false};
+
+ for (int i=0; i kGizmoNone) {
+ gizmo_res_ = db[QStringLiteral("global")].Get(NodeParam::kVec2, QStringLiteral("resolution")).value();
+ gizmo_drag_start_ = p;
+
+ return true;
+ }
+
+ return false;
+}
+
+void CropDistortNode::GizmoMove(const QPointF &p, const rational &time)
+{
+ if (gizmo_dragger_.isEmpty()) {
+ gizmo_dragger_.resize(gizmo_start_.size());
+
+ int counter = 0;
+
+ if (gizmo_drag_ & kGizmoLeft) {
+ gizmo_dragger_[counter].Start(left_input_, time, 0);
+ counter++;
+ }
+
+ if (gizmo_drag_ & kGizmoTop) {
+ gizmo_dragger_[counter].Start(top_input_, time, 0);
+ counter++;
+ }
+
+ if (gizmo_drag_ & kGizmoRight) {
+ gizmo_dragger_[counter].Start(right_input_, time, 0);
+ counter++;
+ }
+
+ if (gizmo_drag_ & kGizmoBottom) {
+ gizmo_dragger_[counter].Start(bottom_input_, time, 0);
+ counter++;
+ }
+ }
+
+ int counter = 0;
+
+ double x_diff = (p.x() - gizmo_drag_start_.x()) / gizmo_res_.x();
+ double y_diff = (p.y() - gizmo_drag_start_.y()) / gizmo_res_.y();
+
+ if (gizmo_drag_ & kGizmoLeft) {
+ gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() + x_diff);
+ counter++;
+ }
+
+ if (gizmo_drag_ & kGizmoTop) {
+ gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() + y_diff);
+ counter++;
+ }
+
+ if (gizmo_drag_ & kGizmoRight) {
+ gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() - x_diff);
+ counter++;
+ }
+
+ if (gizmo_drag_ & kGizmoBottom) {
+ gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() - y_diff);
+ counter++;
+ }
+}
+
+void CropDistortNode::GizmoRelease()
+{
+ for (NodeInputDragger& i : gizmo_dragger_) {
+ i.End();
+ }
+ gizmo_dragger_.clear();
+
+ gizmo_start_.clear();
+}
+
+}
diff --git a/app/node/distort/crop/cropdistortnode.h b/app/node/distort/crop/cropdistortnode.h
new file mode 100644
index 000000000..92ece059f
--- /dev/null
+++ b/app/node/distort/crop/cropdistortnode.h
@@ -0,0 +1,111 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2020 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
+
+#ifndef CROPDISTORTNODE_H
+#define CROPDISTORTNODE_H
+
+#include
+
+#include "node/inputdragger.h"
+#include "node/node.h"
+
+namespace olive {
+
+class CropDistortNode : public Node
+{
+public:
+ CropDistortNode();
+
+ virtual Node* copy() const override
+ {
+ return new CropDistortNode();
+ }
+
+ virtual QString Name() const override
+ {
+ return tr("Crop");
+ }
+
+ virtual QString id() const override
+ {
+ return QStringLiteral("org.olivevideoeditor.Olive.crop");
+ }
+
+ virtual QVector Category() const override
+ {
+ return {kCategoryDistort};
+ }
+
+ virtual QString Description() const override
+ {
+ return tr("Crop the edges of an image.");
+ }
+
+ virtual void Retranslate() override;
+
+ virtual NodeValueTable Value(NodeValueDatabase& value) const override;
+
+ virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
+
+ virtual bool HasGizmos() const override
+ {
+ return true;
+ }
+
+ virtual void DrawGizmos(NodeValueDatabase& db, QPainter *p) override;
+
+ virtual bool GizmoPress(NodeValueDatabase& db, const QPointF &p) override;
+ virtual void GizmoMove(const QPointF &p, const rational &time) override;
+ virtual void GizmoRelease() override;
+
+private:
+ NodeInput* texture_input_;
+
+ NodeInput* left_input_;
+ NodeInput* top_input_;
+ NodeInput* right_input_;
+ NodeInput* bottom_input_;
+
+ NodeInput* feather_input_;
+
+ // Gizmo variables
+ QRectF gizmo_resize_handle_[kGizmoScaleCount];
+ QRectF gizmo_whole_rect_;
+
+ enum GizmoDragDirection {
+ kGizmoNone = 0x0,
+ kGizmoLeft = 0x1,
+ kGizmoTop = 0x2,
+ kGizmoRight = 0x4,
+ kGizmoBottom = 0x8,
+ kGizmoRectangle = 0xFF
+ };
+
+ int gizmo_drag_;
+ QVector gizmo_dragger_;
+ QVector gizmo_start_;
+ QPointF gizmo_drag_start_;
+ QVector2D gizmo_res_;
+
+};
+
+}
+
+#endif // CROPDISTORTNODE_H
diff --git a/app/node/distort/transform/CMakeLists.txt b/app/node/distort/transform/CMakeLists.txt
new file mode 100644
index 000000000..bee2f4649
--- /dev/null
+++ b/app/node/distort/transform/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2020 Olive Team
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ node/distort/transform/transformdistortnode.cpp
+ node/distort/transform/transformdistortnode.h
+ PARENT_SCOPE
+)
diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp
new file mode 100644
index 000000000..34430916e
--- /dev/null
+++ b/app/node/distort/transform/transformdistortnode.cpp
@@ -0,0 +1,414 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2020 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
+
+#include "transformdistortnode.h"
+
+#include
+
+#include "common/range.h"
+
+namespace olive {
+
+TransformDistortNode::TransformDistortNode()
+{
+ autoscale_input_ = new NodeInput(QStringLiteral("autoscale_in"), NodeParam::kBoolean, false);
+ AddInput(autoscale_input_);
+
+ texture_input_ = new NodeInput(QStringLiteral("tex_in"), NodeParam::kTexture);
+ AddInput(texture_input_);
+}
+
+void TransformDistortNode::Retranslate()
+{
+ MatrixGenerator::Retranslate();
+
+ autoscale_input_->set_name(tr("Auto-Scale"));
+ texture_input_->set_name(tr("Texture"));
+}
+
+NodeValueTable TransformDistortNode::Value(NodeValueDatabase &value) const
+{
+ // Generate matrix
+ QMatrix4x4 generated_matrix = GenerateMatrix(value, true, false, false);
+
+ // Pop texture
+ TexturePtr texture = value[texture_input_].Take(NodeParam::kTexture).value();
+
+ // Merge table
+ NodeValueTable table = value.Merge();
+
+ // If we have a texture, generate a matrix and make it happen
+ if (texture) {
+ // Adjust our matrix by the resolutions involved
+ QVector2D sequence_res = value[QStringLiteral("global")].Get(NodeParam::kVec2, QStringLiteral("resolution")).value();
+ QVector2D texture_res(texture->params().width() * texture->pixel_aspect_ratio().toDouble(), texture->params().height());
+ bool autoscale = value[autoscale_input_].Get(NodeParam::kBoolean).toBool();
+
+ QMatrix4x4 real_matrix = AdjustMatrixByResolutions(generated_matrix,
+ sequence_res,
+ texture_res,
+ autoscale);
+
+ if (real_matrix.isIdentity()) {
+ // We don't expect any changes, just push as normal
+ table.Push(NodeParam::kTexture, QVariant::fromValue(texture), this);
+ } else {
+ // The matrix will transform things
+ ShaderJob job;
+ job.InsertValue(QStringLiteral("ove_maintex"), ShaderValue(QVariant::fromValue(texture), NodeParam::kTexture));
+ job.InsertValue(QStringLiteral("ove_mvpmat"), ShaderValue(real_matrix, NodeParam::kMatrix));
+ table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this);
+ }
+ }
+
+ return table;
+}
+
+ShaderCode TransformDistortNode::GetShaderCode(const QString &shader_id) const
+{
+ Q_UNUSED(shader_id);
+
+ // Returns default frag and vert shader
+ return ShaderCode();
+}
+
+bool TransformDistortNode::GizmoPress(NodeValueDatabase &db, const QPointF &p)
+{
+ // Store cursor position
+ gizmo_drag_pos_ = p;
+
+ // Check scaling
+ bool gizmo_scale_active[kGizmoScaleCount] = {false};
+ bool scaling = false;
+
+ for (int i=0; i();
+ gizmo_scale_anchor_ = db[anchor_input()].Get(NodeParam::kVec2).value() + texture_sz/2;
+
+ if (gizmo_scale_active[kGizmoScaleTopRight]
+ || gizmo_scale_active[kGizmoScaleBottomRight]
+ || gizmo_scale_active[kGizmoScaleCenterRight]) {
+ // Right handles, flip X axis
+ gizmo_scale_anchor_.setX(texture_sz.x() - gizmo_scale_anchor_.x());
+ }
+
+ if (gizmo_scale_active[kGizmoScaleBottomLeft]
+ || gizmo_scale_active[kGizmoScaleBottomRight]
+ || gizmo_scale_active[kGizmoScaleBottomCenter]) {
+ // Bottom handles, flip Y axis
+ gizmo_scale_anchor_.setY(texture_sz.y() - gizmo_scale_anchor_.y());
+ }
+
+ return true;
+
+ } else if (gizmo_anchor_pt_.contains(p)) {
+
+ // Dragging the anchor point specifically
+ gizmo_start_ = {db[anchor_input()].Get(NodeParam::kVec2),
+ db[position_input()].Get(NodeParam::kVec2)};
+ gizmo_drag_ = anchor_input();
+
+ // Store current matrix
+ gizmo_matrix_ = GenerateMatrix(db, false, true, true);
+
+ return true;
+
+ } else if (gizmo_rect_.containsPoint(p, Qt::OddEvenFill)) {
+
+ // Dragging the main rectangle
+ gizmo_start_ = {db[position_input()].Get(NodeParam::kVec2)};
+ gizmo_drag_ = position_input();
+
+ return true;
+
+ } else {
+
+ // Dragging rotation
+ gizmo_start_ = {db[rotation_input()].Get(NodeParam::kFloat)};
+ gizmo_drag_ = rotation_input();
+ gizmo_start_angle_ = qAtan2(gizmo_drag_pos_.y() - gizmo_anchor_pt_.center().y(),
+ gizmo_drag_pos_.x() - gizmo_anchor_pt_.center().x());
+
+ return true;
+
+ }
+
+ return false;
+}
+
+void TransformDistortNode::GizmoMove(const QPointF &p, const rational &time)
+{
+ QPointF movement = (p - gizmo_drag_pos_);
+ QVector2D vec_movement(movement);
+
+ if (gizmo_drag_ == anchor_input()) {
+
+ // Dragging the anchor point around
+ if (gizmo_dragger_.isEmpty()) {
+ gizmo_dragger_.resize(4);
+ gizmo_dragger_[0].Start(anchor_input(), time, 0);
+ gizmo_dragger_[1].Start(anchor_input(), time, 1);
+ gizmo_dragger_[2].Start(position_input(), time, 0);
+ gizmo_dragger_[3].Start(position_input(), time, 1);
+ }
+
+ QVector2D inverted_movement(gizmo_matrix_.toTransform().inverted().map(movement));
+ QVector2D anchor_cont = gizmo_start_[0].value() + inverted_movement;
+ QVector2D position_cont = gizmo_start_[1].value() + vec_movement;
+
+ gizmo_dragger_[0].Drag(anchor_cont.x());
+ gizmo_dragger_[1].Drag(anchor_cont.y());
+ gizmo_dragger_[2].Drag(position_cont.x());
+ gizmo_dragger_[3].Drag(position_cont.y());
+
+ } else if (gizmo_drag_ == position_input()) {
+
+ // Dragging the main rectangle around
+ if (gizmo_dragger_.isEmpty()) {
+ gizmo_dragger_.resize(2);
+ gizmo_dragger_[0].Start(position_input(), time, 0);
+ gizmo_dragger_[1].Start(position_input(), time, 1);
+ }
+
+ QVector2D position_cont = gizmo_start_[0].value() + vec_movement;
+
+ gizmo_dragger_[0].Drag(position_cont.x());
+ gizmo_dragger_[1].Drag(position_cont.y());
+
+ } else if (gizmo_drag_ == scale_input()) {
+
+ // Dragging a resize handle
+ if (gizmo_dragger_.isEmpty()) {
+ if (gizmo_scale_uniform_ || gizmo_scale_axes_ == kGizmoScaleXOnly) {
+ gizmo_dragger_.resize(1);
+ gizmo_dragger_[0].Start(scale_input(), time, 0);
+ } else if (gizmo_scale_axes_ == kGizmoScaleYOnly) {
+ gizmo_dragger_.resize(1);
+ gizmo_dragger_[0].Start(scale_input(), time, 1);
+ } else {
+ gizmo_dragger_.resize(2);
+ gizmo_dragger_[0].Start(scale_input(), time, 0);
+ gizmo_dragger_[1].Start(scale_input(), time, 1);
+ }
+ }
+
+
+
+ switch (gizmo_scale_axes_) {
+ case kGizmoScaleXOnly:
+ gizmo_dragger_[0].Drag(qAbs((p.x() - gizmo_anchor_pt_.center().x()) / gizmo_scale_anchor_.x()));
+ break;
+ case kGizmoScaleYOnly:
+ gizmo_dragger_[0].Drag(qAbs((p.y() - gizmo_anchor_pt_.center().y()) / gizmo_scale_anchor_.y()));
+ break;
+ case kGizmoScaleBoth:
+ if (gizmo_scale_uniform_) {
+ double distance = std::hypot(p.x() - gizmo_anchor_pt_.center().x(), p.y() - gizmo_anchor_pt_.center().y());
+ double texture_diag = std::hypot(gizmo_scale_anchor_.x(), gizmo_scale_anchor_.y());
+
+ gizmo_dragger_[0].Drag(qAbs(distance / texture_diag));
+ } else {
+ gizmo_dragger_[0].Drag(qAbs((p.x() - gizmo_anchor_pt_.center().x()) / gizmo_scale_anchor_.x()));
+ gizmo_dragger_[1].Drag(qAbs((p.y() - gizmo_anchor_pt_.center().y()) / gizmo_scale_anchor_.y()));
+ }
+ break;
+ }
+
+ } else if (gizmo_drag_ == rotation_input()) {
+
+ // Dragging outside the rectangle to rotate
+ if (gizmo_dragger_.isEmpty()) {
+ gizmo_dragger_.resize(1);
+ gizmo_dragger_[0].Start(rotation_input(), time, 0);
+ }
+
+ double current_angle = qAtan2(p.y() - gizmo_anchor_pt_.center().y(),
+ p.x() - gizmo_anchor_pt_.center().x());
+
+ double rotation_difference = (current_angle - gizmo_start_angle_) * 57.2958;
+
+ double rotation_cont = gizmo_start_[0].toDouble() + rotation_difference;
+
+ gizmo_dragger_[0].Drag(rotation_cont);
+
+ }
+}
+
+void TransformDistortNode::GizmoRelease()
+{
+ for (NodeInputDragger& i : gizmo_dragger_) {
+ i.End();
+ }
+ gizmo_dragger_.clear();
+
+ gizmo_start_.clear();
+
+ gizmo_drag_ = nullptr;
+}
+
+QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat, const QVector2D &sequence_res, const QVector2D &texture_res, bool auto_sz)
+{
+ // First, create an identity matrix
+ QMatrix4x4 adjusted_matrix;
+
+ // Scale it to a square based on the sequence's resolution
+ adjusted_matrix.scale(2.0 / sequence_res.x(), 2.0 / sequence_res.y(), 1.0);
+
+ // Adjust by the matrix we generated earlier
+ adjusted_matrix *= mat;
+
+ // Scale back out to texture size (adjusted by pixel aspect)
+ adjusted_matrix.scale(texture_res.x() * 0.5, texture_res.y() * 0.5, 1.0);
+
+ // If auto-scale is enabled, fit the texture to the sequence (without cropping)
+ if (auto_sz) {
+ double footage_real_ar = texture_res.x() / texture_res.y();
+ double sequence_real_ar = sequence_res.x() / sequence_res.y();
+
+ double autoscale_val;
+
+ if (sequence_real_ar > footage_real_ar) {
+ // Sequence is wider than footage, scale by height
+ autoscale_val = sequence_res.y() / texture_res.y();
+ } else {
+ // Footage is wider than sequence, scale by width
+ autoscale_val = sequence_res.x() / texture_res.x();
+ }
+
+ adjusted_matrix.scale(autoscale_val, autoscale_val, 1.0);
+ }
+
+ return adjusted_matrix;
+}
+
+QPointF TransformDistortNode::CreateScalePoint(double x, double y, const QPointF &half_res, const QMatrix4x4 &mat)
+{
+ return mat.map(QPointF(x, y)) + half_res;
+}
+
+void TransformDistortNode::DrawGizmos(NodeValueDatabase &db, QPainter *p)
+{
+ // 0 pen width is always 1px wide despite any transform
+ p->setPen(QPen(Qt::white, 0));
+
+ // Get the sequence resolution
+ QVector2D sequence_res = db[QStringLiteral("global")].Get(NodeParam::kVec2, QStringLiteral("resolution")).value();
+ QVector2D sequence_half_res = sequence_res/2;
+ QPointF sequence_half_res_pt = sequence_half_res.toPointF();
+
+ // GizmoTraverser just returns the sizes of the textures and no other data
+ QVector2D tex_sz = db[texture_input_].Get(NodeParam::kTexture).value();
+
+ // Retrieve autoscale value
+ bool autoscale = db[autoscale_input_].Get(NodeParam::kBoolean).toBool();
+
+ // Fold values into a matrix for the rectangle
+ QMatrix4x4 rectangle_matrix;
+ rectangle_matrix.scale(sequence_half_res);
+ rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(db, false, false, false),
+ sequence_res,
+ tex_sz,
+ autoscale);
+
+ // Create rect and transform it
+ const QVector points = {QPointF(-1, -1),
+ QPointF(1, -1),
+ QPointF(1, 1),
+ QPointF(-1, 1),
+ QPointF(-1, -1)};
+ QTransform rectangle_transform = rectangle_matrix.toTransform();
+ gizmo_rect_ = rectangle_transform.map(points);
+ gizmo_rect_.translate(sequence_half_res_pt);
+
+ // Draw rectangle
+ p->drawPolyline(gizmo_rect_);
+
+ // Draw anchor point
+ QMatrix4x4 anchor_matrix;
+ anchor_matrix.scale(sequence_half_res);
+ anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(db, false, true, false),
+ sequence_res,
+ tex_sz,
+ autoscale);
+ QPointF anchor_pt = anchor_matrix.toTransform().map(QPointF(0, 0)) + sequence_half_res_pt;
+ const int anchor_pt_radius = QFontMetrics(qApp->font()).height() / 2 * 3;
+
+ gizmo_anchor_pt_ = QRectF(anchor_pt.x() - anchor_pt_radius,
+ anchor_pt.y() - anchor_pt_radius,
+ anchor_pt_radius*2,
+ anchor_pt_radius*2);
+
+ p->drawEllipse(anchor_pt, anchor_pt_radius, anchor_pt_radius);
+
+ p->drawLines({QLineF(anchor_pt.x() - anchor_pt_radius, anchor_pt.y(),
+ anchor_pt.x() + anchor_pt_radius, anchor_pt.y()),
+ QLineF(anchor_pt.x(), anchor_pt.y() - anchor_pt_radius,
+ anchor_pt.x(), anchor_pt.y() + anchor_pt_radius)});
+
+ // Draw scale handles
+ p->setPen(Qt::NoPen);
+ p->setBrush(Qt::white);
+
+ const int resize_handle_rad = GetGizmoHandleRadius();
+
+ gizmo_resize_handle_[kGizmoScaleTopLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
+ gizmo_resize_handle_[kGizmoScaleTopCenter] = CreateGizmoHandleRect(CreateScalePoint( 0, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
+ gizmo_resize_handle_[kGizmoScaleTopRight] = CreateGizmoHandleRect(CreateScalePoint( 1, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
+ gizmo_resize_handle_[kGizmoScaleBottomLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, 1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
+ gizmo_resize_handle_[kGizmoScaleBottomCenter] = CreateGizmoHandleRect(CreateScalePoint( 0, 1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
+ gizmo_resize_handle_[kGizmoScaleBottomRight] = CreateGizmoHandleRect(CreateScalePoint( 1, 1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
+ gizmo_resize_handle_[kGizmoScaleCenterLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, 0, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
+ gizmo_resize_handle_[kGizmoScaleCenterRight] = CreateGizmoHandleRect(CreateScalePoint( 1, 0, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
+
+ DrawAndExpandGizmoHandles(p, resize_handle_rad, gizmo_resize_handle_, kGizmoScaleCount);
+
+ // Use offsets to make the appearance of values that start in the top left, even though we
+ // really anchor around the center
+ position_input()->set_property(QStringLiteral("offset"), sequence_res * 0.5);
+ anchor_input()->set_property(QStringLiteral("offset"), tex_sz * 0.5);
+}
+
+}
diff --git a/app/node/distort/transform/transformdistortnode.h b/app/node/distort/transform/transformdistortnode.h
new file mode 100644
index 000000000..d5a0aaf0f
--- /dev/null
+++ b/app/node/distort/transform/transformdistortnode.h
@@ -0,0 +1,124 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2020 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
+
+#ifndef TRANSFORMDISTORTNODE_H
+#define TRANSFORMDISTORTNODE_H
+
+#include "node/generator/matrix/matrix.h"
+
+namespace olive {
+
+class TransformDistortNode : public MatrixGenerator
+{
+ Q_OBJECT
+public:
+ TransformDistortNode();
+
+ virtual Node* copy() const override
+ {
+ return new TransformDistortNode();
+ }
+
+ virtual QString Name() const override
+ {
+ return tr("Transform");
+ }
+
+ virtual QString ShortName() const override
+ {
+ return Node::ShortName();
+ }
+
+ virtual QString id() const override
+ {
+ return QStringLiteral("org.olivevideoeditor.Olive.transform");
+ }
+
+ virtual QVector Category() const override
+ {
+ return {kCategoryDistort};
+ }
+
+ virtual QString Description() const override
+ {
+ return tr("Transform an image in 2D space. Equivalent to multiplying by an orthographic matrix.");
+ }
+
+ virtual void Retranslate() override;
+
+ virtual NodeValueTable Value(NodeValueDatabase& value) const override;
+
+ virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
+
+ virtual bool HasGizmos() const override
+ {
+ return true;
+ }
+
+ virtual void DrawGizmos(NodeValueDatabase& db, QPainter *p) override;
+
+ virtual bool GizmoPress(NodeValueDatabase& db, const QPointF &p) override;
+ virtual void GizmoMove(const QPointF &p, const rational &time) override;
+ virtual void GizmoRelease() override;
+
+ NodeInput* texture_input() const
+ {
+ return texture_input_;
+ }
+
+ static QMatrix4x4 AdjustMatrixByResolutions(const QMatrix4x4& mat,
+ const QVector2D& sequence_res,
+ const QVector2D& texture_res,
+ bool auto_sz);
+
+private:
+ static QPointF CreateScalePoint(double x, double y, const QPointF& half_res, const QMatrix4x4& mat);
+
+ NodeInput* texture_input_;
+ NodeInput* autoscale_input_;
+
+ // Gizmo variables
+ NodeInput* gizmo_drag_;
+ QVector gizmo_start_;
+ QVector gizmo_dragger_;
+ QPointF gizmo_drag_pos_;
+ double gizmo_start_angle_;
+ bool gizmo_scale_uniform_;
+
+ enum GizmoScaleType {
+ kGizmoScaleXOnly,
+ kGizmoScaleYOnly,
+ kGizmoScaleBoth
+ };
+
+ GizmoScaleType gizmo_scale_axes_;
+ QMatrix4x4 gizmo_matrix_;
+ QVector2D gizmo_scale_anchor_;
+
+ // Gizmo on screen object storage
+ QPolygonF gizmo_rect_;
+ QRectF gizmo_anchor_pt_;
+ QRectF gizmo_resize_handle_[kGizmoScaleCount];
+
+};
+
+}
+
+#endif // TRANSFORMDISTORTNODE_H
diff --git a/app/node/factory.cpp b/app/node/factory.cpp
index eb0eca531..1d29bf6a1 100644
--- a/app/node/factory.cpp
+++ b/app/node/factory.cpp
@@ -26,6 +26,8 @@
#include "block/gap/gap.h"
#include "block/transition/crossdissolve/crossdissolvetransition.h"
#include "block/transition/diptocolor/diptocolortransition.h"
+#include "distort/crop/cropdistortnode.h"
+#include "distort/transform/transformdistortnode.h"
#include "generator/matrix/matrix.h"
#include "generator/polygon/polygon.h"
#include "generator/solid/solid.h"
@@ -183,6 +185,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new PolygonGenerator();
case kMatrixGenerator:
return new MatrixGenerator();
+ case kTransformDistort:
+ return new TransformDistortNode();
case kFootageInput:
return new MediaInput();
case kTrackOutput:
@@ -215,6 +219,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new DipToColorTransition();
case kMosaicFilter:
return new MosaicFilterNode();
+ case kCropDistort:
+ return new CropDistortNode();
case kInternalNodeCount:
break;
diff --git a/app/node/factory.h b/app/node/factory.h
index 4b5942119..4ab3c43d6 100644
--- a/app/node/factory.h
+++ b/app/node/factory.h
@@ -37,6 +37,7 @@ public:
kGapBlock,
kPolygonGenerator,
kMatrixGenerator,
+ kTransformDistort,
kFootageInput,
kTrackOutput,
kAudioVolume,
@@ -52,6 +53,7 @@ public:
kCrossDissolveTransition,
kDipToColorTransition,
kMosaicFilter,
+ kCropDistort,
// Count value
kInternalNodeCount
diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp
index 466819fe1..34022dd76 100644
--- a/app/node/generator/matrix/matrix.cpp
+++ b/app/node/generator/matrix/matrix.cpp
@@ -20,12 +20,9 @@
#include "matrix.h"
-#include
#include
#include
-#include "common/range.h"
-
namespace olive {
MatrixGenerator::MatrixGenerator()
@@ -94,144 +91,54 @@ void MatrixGenerator::Retranslate()
NodeValueTable MatrixGenerator::Value(NodeValueDatabase &value) const
{
// Push matrix output
- QMatrix4x4 mat = GenerateMatrix(value);
+ QMatrix4x4 mat = GenerateMatrix(value, true, false, false);
NodeValueTable output = value.Merge();
output.Push(NodeParam::kMatrix, mat, this);
return output;
}
-bool MatrixGenerator::GizmoPress(NodeValueDatabase &db, const QPointF &p, const QVector2D &scale, const QSize &viewport)
-{
- GizmoSharedData gizmo_data(viewport, scale);
-
- QPointF anchor_pt = GetGizmoAnchorPoint(db, gizmo_data);
- int anchor_radius = GetGizmoAnchorPointRadius();
-
- if (InRange(p.x(), anchor_pt.x(), anchor_radius * 2.0)
- && InRange(p.y(), anchor_pt.y(), anchor_radius * 2.0)) {
- gizmo_drag_ = anchor_input_;
- gizmo_start2_ = db[position_input_].Get(NodeParam::kVec2).value();
- } else {
- gizmo_drag_ = position_input_;
- }
-
- gizmo_start_ = db[gizmo_drag_].Get(NodeParam::kVec2).value();
- gizmo_mouse_start_ = p;
-
- return true;
-}
-
-void MatrixGenerator::GizmoMove(const QPointF &p, const QVector2D &scale, const rational &time)
-{
- QVector2D movement = 2.0 * QVector2D(p - gizmo_mouse_start_) / scale;
- QVector2D new_pos = gizmo_start_ + movement;
-
- if (!gizmo_x_dragger_.IsStarted()) {
- gizmo_x_dragger_.Start(gizmo_drag_, time, 0);
- }
-
- if (!gizmo_y_dragger_.IsStarted()) {
- gizmo_y_dragger_.Start(gizmo_drag_, time, 1);
- }
-
- gizmo_x_dragger_.Drag(new_pos.x());
- gizmo_y_dragger_.Drag(new_pos.y());
-
- if (gizmo_drag_ == anchor_input_) {
- // If we're dragging the anchor, counter the position at the same time
- QVector2D new_pos2 = gizmo_start2_ + movement;
-
- if (!gizmo_x2_dragger_.IsStarted()) {
- gizmo_x2_dragger_.Start(position_input_, time, 0);
- }
-
- if (!gizmo_y2_dragger_.IsStarted()) {
- gizmo_y2_dragger_.Start(position_input_, time, 1);
- }
-
- gizmo_x2_dragger_.Drag(new_pos2.x());
- gizmo_y2_dragger_.Drag(new_pos2.y());
- }
-}
-
-void MatrixGenerator::GizmoRelease()
-{
- gizmo_x_dragger_.End();
- gizmo_y_dragger_.End();
- gizmo_x2_dragger_.End();
- gizmo_y2_dragger_.End();
-}
-
-bool MatrixGenerator::HasGizmos() const
-{
- return true;
-}
-
-void MatrixGenerator::DrawGizmos(NodeValueDatabase &db, QPainter *p, const QVector2D &scale, const QSize& viewport) const
-{
- p->setPen(Qt::white);
-
- GizmoSharedData gizmo_data(viewport, scale);
-
- {
- // Fold values into a matrix
- QMatrix4x4 matrix;
- matrix.scale(gizmo_data.half_scale);
- matrix *= GenerateMatrix(db, false);
- matrix.scale(gizmo_data.inverted_half_scale);
-
- // Create rect and transform it
- QVector points = {QPointF(-100, -100),
- QPointF(100, -100),
- QPointF(100, 100),
- QPointF(-100, 100),
- QPointF(-100, -100)};
- QPolygonF poly(points);
- poly = matrix.toTransform().map(poly);
- poly.translate(gizmo_data.half_viewport);
-
- // Draw square
- p->drawPolyline(poly);
- }
-
- {
- // Draw anchor point marker (with no anchor point translation)
- QPointF pt = GetGizmoAnchorPoint(db, gizmo_data);
-
- // Draw anchor point
- int anchor_pt_radius = GetGizmoAnchorPointRadius();
-
- p->drawEllipse(pt, anchor_pt_radius, anchor_pt_radius);
-
- p->drawLines({QLineF(pt.x() - anchor_pt_radius, pt.y(),
- pt.x() + anchor_pt_radius, pt.y()),
- QLineF(pt.x(), pt.y() - anchor_pt_radius,
- pt.x(), pt.y() + anchor_pt_radius)});
- }
-}
-
-QMatrix4x4 MatrixGenerator::GenerateMatrix(NodeValueDatabase &value) const
-{
- return GenerateMatrix(value[position_input_].Take(NodeParam::kVec2).value(),
- value[rotation_input_].Take(NodeParam::kFloat).toFloat(),
- value[scale_input_].Take(NodeParam::kVec2).value(),
- value[uniform_scale_input_].Take(NodeParam::kBoolean).toBool(),
- value[anchor_input_].Take(NodeParam::kVec2).value());
-}
-
-QMatrix4x4 MatrixGenerator::GenerateMatrix(NodeValueDatabase &value, bool ignore_anchor) const
+QMatrix4x4 MatrixGenerator::GenerateMatrix(NodeValueDatabase &value, bool take, bool ignore_anchor, bool ignore_position) const
{
QVector2D anchor;
+ QVector2D position;
if (!ignore_anchor) {
- anchor = value[anchor_input_].Get(NodeParam::kVec2).value();
+ if (take) {
+ // Take and store
+ anchor = value[anchor_input_].Take(NodeParam::kVec2).value();
+ } else {
+ // Get and store
+ anchor = value[anchor_input_].Get(NodeParam::kVec2).value();
+ }
+ } else if (take) {
+ // Just take
+ value[anchor_input_].Take(NodeParam::kVec2).value();
}
- return GenerateMatrix(value[position_input_].Get(NodeParam::kVec2).value(),
- value[rotation_input_].Get(NodeParam::kFloat).toFloat(),
- value[scale_input_].Get(NodeParam::kVec2).value(),
- value[uniform_scale_input_].Get(NodeParam::kBoolean).toBool(),
- anchor);
+ if (!ignore_position) {
+ if (take) {
+ position = value[position_input_].Take(NodeParam::kVec2).value();
+ } else {
+ position = value[position_input_].Get(NodeParam::kVec2).value();
+ }
+ } else if (take) {
+ value[position_input_].Take(NodeParam::kVec2).value();
+ }
+
+ if (take) {
+ return GenerateMatrix(position,
+ value[rotation_input_].Take(NodeParam::kFloat).toFloat(),
+ value[scale_input_].Take(NodeParam::kVec2).value(),
+ value[uniform_scale_input_].Take(NodeParam::kBoolean).toBool(),
+ anchor);
+ } else {
+ return GenerateMatrix(position,
+ value[rotation_input_].Get(NodeParam::kFloat).toFloat(),
+ value[scale_input_].Get(NodeParam::kVec2).value(),
+ value[uniform_scale_input_].Get(NodeParam::kBoolean).toBool(),
+ anchor);
+
+ }
}
QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos,
@@ -263,35 +170,9 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos,
return mat;
}
-QPointF MatrixGenerator::GetGizmoAnchorPoint(NodeValueDatabase &db,
- const GizmoSharedData& gizmo_data) const
-{
- QMatrix4x4 matrix;
- matrix.scale(gizmo_data.half_scale);
- matrix *= GenerateMatrix(db, true);
- matrix.scale(gizmo_data.inverted_half_scale);
-
- QPointF pt = matrix.toTransform().map(QPointF());
- pt += gizmo_data.half_viewport;
-
- return pt;
-}
-
-int MatrixGenerator::GetGizmoAnchorPointRadius()
-{
- return QFontMetrics(qApp->font()).height() / 2;
-}
-
void MatrixGenerator::UniformScaleChanged()
{
scale_input_->set_property("disabley", uniform_scale_input_->get_standard_value().toBool());
}
-MatrixGenerator::GizmoSharedData::GizmoSharedData(const QSize &viewport, const QVector2D &scale)
-{
- half_viewport = QPointF(viewport.width() / 2, viewport.height() / 2);
- half_scale = scale * 0.5;
- inverted_half_scale = QVector2D(1.0f / half_scale.x(), 1.0f / half_scale.y());
-}
-
}
diff --git a/app/node/generator/matrix/matrix.h b/app/node/generator/matrix/matrix.h
index e782ff02c..457f6e0c0 100644
--- a/app/node/generator/matrix/matrix.h
+++ b/app/node/generator/matrix/matrix.h
@@ -46,34 +46,40 @@ public:
virtual NodeValueTable Value(NodeValueDatabase& value) const override;
- virtual bool HasGizmos() const override;
- virtual void DrawGizmos(NodeValueDatabase& db, QPainter *p, const QVector2D &scale, const QSize& viewport) const override;
-
- virtual bool GizmoPress(NodeValueDatabase& db, const QPointF &p, const QVector2D &scale, const QSize& viewport) override;
- virtual void GizmoMove(const QPointF &p, const QVector2D &scale, const rational &time) override;
- virtual void GizmoRelease() override;
-
-private:
- struct GizmoSharedData {
- GizmoSharedData(const QSize& viewport, const QVector2D& scale);
-
- QPointF half_viewport;
- QVector2D half_scale;
- QVector2D inverted_half_scale;
- };
-
- QMatrix4x4 GenerateMatrix(NodeValueDatabase& value) const;
- QMatrix4x4 GenerateMatrix(NodeValueDatabase &value, bool ignore_anchor) const;
+protected:
+ QMatrix4x4 GenerateMatrix(NodeValueDatabase &value, bool take, bool ignore_anchor, bool ignore_position) const;
static QMatrix4x4 GenerateMatrix(const QVector2D &pos,
const float &rot,
const QVector2D &scale,
bool uniform_scale,
const QVector2D &anchor);
- QPointF GetGizmoAnchorPoint(NodeValueDatabase &db, const GizmoSharedData &gizmo_data) const;
- static int GetGizmoAnchorPointRadius();
- NodeInput* gizmo_drag_;
+ NodeInput* position_input() const
+ {
+ return position_input_;
+ }
+ NodeInput* rotation_input() const
+ {
+ return rotation_input_;
+ }
+
+ NodeInput* scale_input() const
+ {
+ return scale_input_;
+ }
+
+ NodeInput* uniform_scale_input() const
+ {
+ return uniform_scale_input_;
+ }
+
+ NodeInput* anchor_input() const
+ {
+ return anchor_input_;
+ }
+
+private:
NodeInput* position_input_;
NodeInput* rotation_input_;
@@ -84,14 +90,6 @@ private:
NodeInput* anchor_input_;
- QVector2D gizmo_start_;
- QVector2D gizmo_start2_;
- QPointF gizmo_mouse_start_;
- NodeInputDragger gizmo_x_dragger_;
- NodeInputDragger gizmo_y_dragger_;
- NodeInputDragger gizmo_x2_dragger_;
- NodeInputDragger gizmo_y2_dragger_;
-
private slots:
void UniformScaleChanged();
diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp
index aa60ee396..aa1afee4c 100644
--- a/app/node/generator/polygon/polygon.cpp
+++ b/app/node/generator/polygon/polygon.cpp
@@ -89,7 +89,7 @@ ShaderCode PolygonGenerator::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
- return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/polygon.frag"), QString());
+ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/polygon.frag"));
}
NodeValueTable PolygonGenerator::Value(NodeValueDatabase &value) const
@@ -98,6 +98,7 @@ NodeValueTable PolygonGenerator::Value(NodeValueDatabase &value) const
job.InsertValue(points_input_, value);
job.InsertValue(color_input_, value);
+ job.InsertValue(QStringLiteral("resolution_in"), ShaderValue(value[QStringLiteral("global")].Get(NodeParam::kVec2, QStringLiteral("resolution")), NodeParam::kVec2));
job.SetAlphaChannelRequired(true);
NodeValueTable table = value.Merge();
@@ -110,7 +111,7 @@ bool PolygonGenerator::HasGizmos() const
return true;
}
-void PolygonGenerator::DrawGizmos(NodeValueDatabase &db, QPainter *p, const QVector2D &scale, const QSize &viewport) const
+/*void PolygonGenerator::DrawGizmos(NodeValueDatabase &db, QPainter *p) const
{
Q_UNUSED(viewport)
@@ -170,7 +171,7 @@ void PolygonGenerator::GizmoRelease()
{
gizmo_x_dragger_.End();
gizmo_y_dragger_.End();
-}
+}*/
QVector PolygonGenerator::GetGizmoCoordinates(NodeValueDatabase &db, const QVector2D& scale) const
{
diff --git a/app/node/generator/polygon/polygon.h b/app/node/generator/polygon/polygon.h
index 645e8c5de..e14519d58 100644
--- a/app/node/generator/polygon/polygon.h
+++ b/app/node/generator/polygon/polygon.h
@@ -45,11 +45,11 @@ public:
virtual NodeValueTable Value(NodeValueDatabase &value) const override;
virtual bool HasGizmos() const override;
- virtual void DrawGizmos(NodeValueDatabase& db, QPainter *p, const QVector2D &scale, const QSize& viewport) const override;
+ //virtual void DrawGizmos(NodeValueDatabase& db, QPainter *p) const override;
- virtual bool GizmoPress(NodeValueDatabase &db, const QPointF &p, const QVector2D &scale, const QSize& viewport) override;
- virtual void GizmoMove(const QPointF &p, const QVector2D &scale, const rational &time) override;
- virtual void GizmoRelease() override;
+ //virtual bool GizmoPress(NodeValueDatabase &db, const QPointF &p) override;
+ //virtual void GizmoMove(const QPointF &p, const QVector2D &scale, const rational &time) override;
+ //virtual void GizmoRelease() override;
private:
QVector GetGizmoCoordinates(NodeValueDatabase &db, const QVector2D &scale) const;
diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp
index e86d316d2..ea4ddb6d6 100644
--- a/app/node/math/math/mathbase.cpp
+++ b/app/node/math/math/mathbase.cpp
@@ -24,6 +24,7 @@
#include
#include "common/tohex.h"
+#include "node/distort/transform/transformdistortnode.h"
#include "render/color.h"
namespace olive {
@@ -48,7 +49,17 @@ ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, NodeInp
// No-op frag shader (can we return QString() instead?)
operation = QStringLiteral("texture(%1, ove_texcoord)").arg(tex_in->id());
- vert = FileFunctions::ReadFileAsString(":/shaders/matrix.vert").arg(mat_in->id(), tex_in->id());
+ vert = QStringLiteral("uniform mat4 %1;\n"
+ "\n"
+ "in vec4 a_position;\n"
+ "in vec2 a_texcoord;\n"
+ "\n"
+ "out vec2 ove_texcoord;\n"
+ "\n"
+ "void main() {\n"
+ " gl_Position = %1 * a_position;\n"
+ " ove_texcoord = a_texcoord;\n"
+ "}\n").arg(mat_in->id());
} else {
switch (op) {
@@ -287,22 +298,32 @@ NodeValueTable MathNodeBase::ValueInternal(NodeValueDatabase &value, Operation o
bool operation_is_noop = false;
const NodeValue& number_val = val_a.type() == NodeParam::kTexture ? val_b : val_a;
+ const NodeValue& texture_val = val_a.type() == NodeParam::kTexture ? val_a : val_b;
+ TexturePtr texture = texture_val.data().value();
- if (pairing == kPairTextureNumber) {
+ if (!texture) {
+ operation_is_noop = true;
+ } else if (pairing == kPairTextureNumber) {
if (NumberIsNoOp(operation, RetrieveNumber(number_val))) {
operation_is_noop = true;
}
} else if (pairing == kPairTextureMatrix) {
// Only allow matrix multiplication
- bool matrix_is_identity = false;
+ QVector2D sequence_res = value[QStringLiteral("global")].Get(NodeParam::kVec2, QStringLiteral("resolution")).value();
+ QVector2D texture_res(texture->params().width() * texture->pixel_aspect_ratio().toDouble(), texture->params().height());
- // FIXME: The matrix in the shader is transformed around footage+sequence resolution so we
- // need to do that here to determine if the matrix is truly identity. But to do that,
- // we need access to the texture parameters which is currently not possible.
+ QMatrix4x4 adjusted_matrix = TransformDistortNode::AdjustMatrixByResolutions(number_val.data().value(),
+ sequence_res,
+ texture_res,
+ false);
- if (operation != kOpMultiply || matrix_is_identity) {
+ if (operation != kOpMultiply || adjusted_matrix.isIdentity()) {
operation_is_noop = true;
} else {
+ // Replace with adjusted matrix
+ job.InsertValue(val_a.type() == NodeParam::kTexture ? param_b_in : param_a_in,
+ ShaderValue(adjusted_matrix, NodeParam::kMatrix));
+
// It's likely an alpha channel will result from this operation
job.SetAlphaChannelRequired(true);
}
@@ -310,7 +331,7 @@ NodeValueTable MathNodeBase::ValueInternal(NodeValueDatabase &value, Operation o
if (operation_is_noop) {
// Just push texture as-is
- output.Push(val_a.type() == NodeParam::kTexture ? val_a : val_b);
+ output.Push(texture_val);
} else {
// Push shader job
output.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this);
diff --git a/app/node/node.cpp b/app/node/node.cpp
index c596e1f9d..99c5da2d8 100644
--- a/app/node/node.cpp
+++ b/app/node/node.cpp
@@ -21,6 +21,7 @@
#include "node.h"
#include
+#include
#include
#include
@@ -935,4 +936,30 @@ void Node::InputConnectionChanged(NodeEdgePtr edge)
InvalidateCache(TimeRange(RATIONAL_MIN, RATIONAL_MAX), edge->input(), edge->input());
}
+QRectF Node::CreateGizmoHandleRect(const QPointF &pt, int radius)
+{
+ return QRectF(pt.x() - radius,
+ pt.y() - radius,
+ 2*radius,
+ 2*radius);;
+}
+
+int Node::GetGizmoHandleRadius()
+{
+ return QFontMetrics(qApp->font()).height() / 2;
+}
+
+void Node::DrawAndExpandGizmoHandles(QPainter *p, int handle_radius, QRectF *rects, int count)
+{
+ for (int i=0; idrawRect(r);
+
+ // Extend rect so it's easier to drag with handle
+ r.adjust(-handle_radius, -handle_radius, handle_radius, handle_radius);
+ }
+}
+
}
diff --git a/app/node/node.h b/app/node/node.h
index c846e0b4e..78810e23b 100644
--- a/app/node/node.h
+++ b/app/node/node.h
@@ -444,6 +444,24 @@ protected:
virtual QVector GetInputsToHash() const;
+ enum GizmoScaleHandles {
+ kGizmoScaleTopLeft,
+ kGizmoScaleTopCenter,
+ kGizmoScaleTopRight,
+ kGizmoScaleBottomLeft,
+ kGizmoScaleBottomCenter,
+ kGizmoScaleBottomRight,
+ kGizmoScaleCenterLeft,
+ kGizmoScaleCenterRight,
+ kGizmoScaleCount,
+ };
+
+ static QRectF CreateGizmoHandleRect(const QPointF& pt, int radius);
+
+ static int GetGizmoHandleRadius();
+
+ static void DrawAndExpandGizmoHandles(QPainter* p, int handle_radius, QRectF* rects, int count);
+
protected slots:
void InputChanged(const olive::TimeRange &range);
diff --git a/app/shaders/matrix.vert b/app/shaders/matrix.vert
deleted file mode 100644
index d7305b7eb..000000000
--- a/app/shaders/matrix.vert
+++ /dev/null
@@ -1,35 +0,0 @@
-uniform mat4 %1;
-
-uniform vec2 %2_resolution;
-uniform vec2 ove_resolution;
-
-in vec4 a_position;
-in vec2 a_texcoord;
-
-out vec2 ove_texcoord;
-
-mat4 scale_mat4(vec3 scale) {
- return mat4(
- scale.x, 0.0, 0.0, 0.0,
- 0.0, scale.y, 0.0, 0.0,
- 0.0, 0.0, scale.z, 0.0,
- 0.0, 0.0, 0.0, 1.0
- );
-}
-
-void main() {
- // Create identity matrix
- mat4 transform = mat4(1.0);
-
- // Scale to square
- transform *= scale_mat4(vec3(1.0 / ove_resolution, 1.0));
-
- // Multiply by received matrix
- transform *= %1;
-
- // Scale back out to footage size
- transform *= scale_mat4(vec3(%2_resolution, 1.0));
-
- gl_Position = transform * a_position;
- ove_texcoord = a_texcoord;
-}
diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp
index 61c07ed35..930d7f71d 100644
--- a/app/widget/timelinewidget/tool/import.cpp
+++ b/app/widget/timelinewidget/tool/import.cpp
@@ -30,6 +30,7 @@
#include "core.h"
#include "dialog/sequence/sequence.h"
#include "node/audio/volume/volume.h"
+#include "node/distort/transform/transformdistortnode.h"
#include "node/generator/matrix/matrix.h"
#include "node/input/media/media.h"
#include "node/math/math/math.h"
@@ -419,7 +420,12 @@ void ImportTool::DropGhosts(bool insert)
video_input->SetStream(footage_stream);
new NodeAddCommand(dst_graph, video_input, command);
- new NodeEdgeAddCommand(video_input->output(), clip->texture_input(), command);
+
+ TransformDistortNode* transform = new TransformDistortNode();
+ new NodeAddCommand(dst_graph, transform, command);
+
+ new NodeEdgeAddCommand(video_input->output(), transform->texture_input(), command);
+ new NodeEdgeAddCommand(transform->output(), clip->texture_input(), command);
/*
MatrixGenerator* matrix = new MatrixGenerator();