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