From 5824b4873b07631aae7bbc3475ea6eb6b19a8b26 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 22 Sep 2022 10:56:35 -0700 Subject: [PATCH 01/19] colorlabelmenu: improve usability and code quality --- app/widget/colorlabelmenu/CMakeLists.txt | 2 - app/widget/colorlabelmenu/colorlabelmenu.cpp | 24 +++++--- app/widget/colorlabelmenu/colorlabelmenu.h | 3 +- .../colorlabelmenu/colorlabelmenuitem.cpp | 57 ------------------- .../colorlabelmenu/colorlabelmenuitem.h | 48 ---------------- 5 files changed, 16 insertions(+), 118 deletions(-) delete mode 100644 app/widget/colorlabelmenu/colorlabelmenuitem.cpp delete mode 100644 app/widget/colorlabelmenu/colorlabelmenuitem.h diff --git a/app/widget/colorlabelmenu/CMakeLists.txt b/app/widget/colorlabelmenu/CMakeLists.txt index 221b11e23..0879cb270 100644 --- a/app/widget/colorlabelmenu/CMakeLists.txt +++ b/app/widget/colorlabelmenu/CMakeLists.txt @@ -20,7 +20,5 @@ set(OLIVE_SOURCES widget/colorlabelmenu/colorcodingcombobox.h widget/colorlabelmenu/colorlabelmenu.cpp widget/colorlabelmenu/colorlabelmenu.h - widget/colorlabelmenu/colorlabelmenuitem.cpp - widget/colorlabelmenu/colorlabelmenuitem.h PARENT_SCOPE ) diff --git a/app/widget/colorlabelmenu/colorlabelmenu.cpp b/app/widget/colorlabelmenu/colorlabelmenu.cpp index 8dfbb0e42..cbd36186b 100644 --- a/app/widget/colorlabelmenu/colorlabelmenu.cpp +++ b/app/widget/colorlabelmenu/colorlabelmenu.cpp @@ -21,6 +21,7 @@ #include "colorlabelmenu.h" #include +#include #include #include "ui/colorcoding.h" @@ -30,17 +31,22 @@ namespace olive { ColorLabelMenu::ColorLabelMenu(QWidget *parent) : Menu(parent) { + // Used for size calculations + int box_size = fontMetrics().height(); + + color_items_.resize(ColorCoding::standard_colors().size()); for (int i=0; iSetColor(ColorCoding::standard_colors().at(i)); - color_items_.append(item); + QPixmap p(box_size, box_size); - QWidgetAction* a = new QWidgetAction(this); - Menu::ConformItem(a, QStringLiteral("colorlabel%1").arg(i), this, &ColorLabelMenu::ActionTriggered); + QPainter painter(&p); + painter.setPen(Qt::black); + painter.setBrush(ColorCoding::standard_colors().at(i).toQColor()); + painter.drawRect(p.rect().adjusted(0, 0, -1, -1)); + + QAction *a = AddItem(QStringLiteral("colorlabel%1").arg(i), this, &ColorLabelMenu::ActionTriggered); + a->setIcon(p); a->setData(i); - a->setDefaultWidget(item); - - this->addAction(a); + color_items_.replace(i, a); } Retranslate(); @@ -60,7 +66,7 @@ void ColorLabelMenu::Retranslate() this->setTitle(tr("Color")); for (int i=0; iSetText(ColorCoding::GetColorName(i)); + color_items_.at(i)->setText(ColorCoding::GetColorName(i)); } } diff --git a/app/widget/colorlabelmenu/colorlabelmenu.h b/app/widget/colorlabelmenu/colorlabelmenu.h index 86c5fe5cd..5a7ee83fb 100644 --- a/app/widget/colorlabelmenu/colorlabelmenu.h +++ b/app/widget/colorlabelmenu/colorlabelmenu.h @@ -21,7 +21,6 @@ #ifndef COLORLABELMENU_H #define COLORLABELMENU_H -#include "colorlabelmenuitem.h" #include "widget/menu/menu.h" namespace olive { @@ -40,7 +39,7 @@ signals: private: void Retranslate(); - QVector color_items_; + QVector color_items_; private slots: void ActionTriggered(); diff --git a/app/widget/colorlabelmenu/colorlabelmenuitem.cpp b/app/widget/colorlabelmenu/colorlabelmenuitem.cpp deleted file mode 100644 index 935427bac..000000000 --- a/app/widget/colorlabelmenu/colorlabelmenuitem.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 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 "colorlabelmenuitem.h" - -#include - -#include "ui/style/style.h" - -namespace olive { - -ColorLabelMenuItem::ColorLabelMenuItem(QWidget* parent) : - QWidget(parent) -{ - int text_height = fontMetrics().height(); - int padding = text_height/4; - - QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(padding); - layout->setSpacing(padding); - - box_ = new ColorPreviewBox(); - box_->setFixedSize(text_height, text_height); - layout->addWidget(box_); - - label_ = new QLabel(); - layout->addWidget(label_); -} - -void ColorLabelMenuItem::SetText(const QString &text) -{ - label_->setText(text); -} - -void ColorLabelMenuItem::SetColor(const Color &color) -{ - box_->SetColor(color); -} - -} diff --git a/app/widget/colorlabelmenu/colorlabelmenuitem.h b/app/widget/colorlabelmenu/colorlabelmenuitem.h deleted file mode 100644 index 29307d681..000000000 --- a/app/widget/colorlabelmenu/colorlabelmenuitem.h +++ /dev/null @@ -1,48 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 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 COLORLABELMENUITEM_H -#define COLORLABELMENUITEM_H - -#include -#include - -#include "widget/colorwheel/colorpreviewbox.h" - -namespace olive { - -class ColorLabelMenuItem : public QWidget -{ -public: - ColorLabelMenuItem(QWidget* parent = nullptr); - - void SetText(const QString& text); - - void SetColor(const Color& color); - -private: - ColorPreviewBox* box_; - QLabel* label_; - -}; - -} - -#endif // COLORLABELMENUITEM_H From f84cd8644d85792d032782147af71877c48c69ae Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 22 Sep 2022 10:56:52 -0700 Subject: [PATCH 02/19] exportformatcombobox: categorize formats where possible --- app/dialog/export/exportformatcombobox.cpp | 145 ++++++++++++++------- app/dialog/export/exportformatcombobox.h | 18 ++- 2 files changed, 111 insertions(+), 52 deletions(-) diff --git a/app/dialog/export/exportformatcombobox.cpp b/app/dialog/export/exportformatcombobox.cpp index dcbcdd01d..2c60cb1a2 100644 --- a/app/dialog/export/exportformatcombobox.cpp +++ b/app/dialog/export/exportformatcombobox.cpp @@ -20,75 +20,120 @@ #include "exportformatcombobox.h" +#include +#include + +#include "ui/icons/icons.h" + namespace olive { ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent) : QComboBox(parent) { + custom_menu_ = new Menu(this); + // Populate combobox formats - for (int i=0; i(i); + switch (mode) { + case kShowAllFormats: + custom_menu_->addAction(CreateHeader(icon::Video, tr("Video"))); + PopulateType(Track::kVideo); + custom_menu_->addSeparator(); - switch (mode) { - case kShowAllFormats: - break; - case kShowAudioOnly: - if (!ExportFormat::GetVideoCodecs(f).isEmpty() - || !ExportFormat::GetSubtitleCodecs(f).isEmpty() - || ExportFormat::GetAudioCodecs(f).isEmpty()) { - continue; - } - break; - case kShowVideoOnly: - if (ExportFormat::GetVideoCodecs(f).isEmpty() - || !ExportFormat::GetSubtitleCodecs(f).isEmpty() - || !ExportFormat::GetAudioCodecs(f).isEmpty()) { - continue; - } - break; - case kShowSubtitlesOnly: - if (!ExportFormat::GetVideoCodecs(f).isEmpty() - || ExportFormat::GetSubtitleCodecs(f).isEmpty() - || !ExportFormat::GetAudioCodecs(f).isEmpty()) { - continue; - } - break; - } + custom_menu_->addAction(CreateHeader(icon::Audio, tr("Audio"))); + PopulateType(Track::kAudio); + custom_menu_->addSeparator(); - QString format_name = ExportFormat::GetName(f); - - bool inserted = false; - - // Sort formats alphabetically - for (int j=0; j format_name) { - insertItem(j, format_name, i); - inserted = true; - break; - } - } - - if (!inserted) { - addItem(format_name, i); - } + custom_menu_->addAction(CreateHeader(icon::Subtitles, tr("Subtitle"))); + PopulateType(Track::kSubtitle); + break; + case kShowAudioOnly: + PopulateType(Track::kAudio); + break; + case kShowVideoOnly: + PopulateType(Track::kVideo); + break; + case kShowSubtitlesOnly: + PopulateType(Track::kSubtitle); + break; } - connect(this, static_cast(&QComboBox::currentIndexChanged), this, &ExportFormatComboBox::HandleIndexChange); + connect(custom_menu_, &Menu::triggered, this, &ExportFormatComboBox::HandleIndexChange); +} + +void ExportFormatComboBox::showPopup() +{ + custom_menu_->setMinimumWidth(this->width()); + custom_menu_->exec(mapToGlobal(QPoint(0, 0))); } void ExportFormatComboBox::SetFormat(ExportFormat::Format fmt) { - for (int i=0; i(a->data().toInt()); + SetFormat(f); + emit FormatChanged(f); +} + +void ExportFormatComboBox::PopulateType(Track::Type type) +{ + for (int i=0; i(i); + + if (type == Track::kVideo + && !ExportFormat::GetVideoCodecs(f).isEmpty()) { + // Do nothing + } else if (type == Track::kAudio + && ExportFormat::GetVideoCodecs(f).isEmpty() + && !ExportFormat::GetAudioCodecs(f).isEmpty()) { + // Do nothing + } else if (type == Track::kSubtitle + && ExportFormat::GetVideoCodecs(f).isEmpty() + && ExportFormat::GetAudioCodecs(f).isEmpty() + && !ExportFormat::GetSubtitleCodecs(f).isEmpty()) { + // Do nothing + } else { + continue; } + + QString format_name = ExportFormat::GetName(f); + + QAction *a = custom_menu_->addAction(format_name); + a->setData(i); + a->setIconVisibleInMenu(false); } } -void ExportFormatComboBox::HandleIndexChange(int index) +QWidgetAction *ExportFormatComboBox::CreateHeader(const QIcon &icon, const QString &title) { - emit FormatChanged(static_cast(itemData(index).toInt())); + QWidgetAction *a = new QWidgetAction(this); + + QWidget *w = new QWidget(); + QHBoxLayout *layout = new QHBoxLayout(w); + + QLabel *icon_lbl = new QLabel(); + + QLabel *text_lbl = new QLabel(title); + text_lbl->setAlignment(Qt::AlignCenter); + QFont f = text_lbl->font(); + f.setWeight(QFont::Bold); + text_lbl->setFont(f); + + icon_lbl->setPixmap(icon.pixmap(text_lbl->sizeHint())); + + layout->addStretch(); + layout->addWidget(icon_lbl); + layout->addWidget(text_lbl); + layout->addStretch(); + + a->setDefaultWidget(w); + a->setEnabled(false); + return a; } } diff --git a/app/dialog/export/exportformatcombobox.h b/app/dialog/export/exportformatcombobox.h index c90479e72..8cff5beec 100644 --- a/app/dialog/export/exportformatcombobox.h +++ b/app/dialog/export/exportformatcombobox.h @@ -22,8 +22,11 @@ #define EXPORTFORMATCOMBOBOX_H #include +#include #include "codec/exportformat.h" +#include "node/output/track/track.h" +#include "widget/menu/menu.h" namespace olive { @@ -45,9 +48,11 @@ public: ExportFormat::Format GetFormat() const { - return static_cast(currentData().toInt()); + return current_; } + void showPopup(); + signals: void FormatChanged(ExportFormat::Format fmt); @@ -55,7 +60,16 @@ public slots: void SetFormat(ExportFormat::Format fmt); private slots: - void HandleIndexChange(int index); + void HandleIndexChange(QAction *a); + +private: + void PopulateType(Track::Type type); + + QWidgetAction *CreateHeader(const QIcon &icon, const QString &title); + + Menu *custom_menu_; + + ExportFormat::Format current_; }; From b553d5afb7aeb67cba7cea1958fd8cfb92fd088b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 22 Sep 2022 18:12:03 -0700 Subject: [PATCH 03/19] nodes: implemented more distortion nodes --- app/node/distort/CMakeLists.txt | 4 + app/node/distort/flip/flipdistortnode.cpp | 5 +- app/node/distort/ripple/CMakeLists.txt | 22 +++ app/node/distort/ripple/rippledistortnode.cpp | 130 ++++++++++++++ app/node/distort/ripple/rippledistortnode.h | 66 +++++++ app/node/distort/swirl/CMakeLists.txt | 22 +++ app/node/distort/swirl/swirldistortnode.cpp | 125 +++++++++++++ app/node/distort/swirl/swirldistortnode.h | 64 +++++++ app/node/distort/tile/CMakeLists.txt | 22 +++ app/node/distort/tile/tiledistortnode.cpp | 165 ++++++++++++++++++ app/node/distort/tile/tiledistortnode.h | 78 +++++++++ app/node/distort/wave/CMakeLists.txt | 22 +++ app/node/distort/wave/wavedistortnode.cpp | 104 +++++++++++ app/node/distort/wave/wavedistortnode.h | 56 ++++++ app/node/factory.cpp | 12 ++ app/node/factory.h | 4 + app/shaders/ripple.frag | 38 ++++ app/shaders/swirl.frag | 29 +++ app/shaders/tile.frag | 61 +++++++ app/shaders/wave.frag | 25 +++ 20 files changed, 1051 insertions(+), 3 deletions(-) create mode 100644 app/node/distort/ripple/CMakeLists.txt create mode 100644 app/node/distort/ripple/rippledistortnode.cpp create mode 100644 app/node/distort/ripple/rippledistortnode.h create mode 100644 app/node/distort/swirl/CMakeLists.txt create mode 100644 app/node/distort/swirl/swirldistortnode.cpp create mode 100644 app/node/distort/swirl/swirldistortnode.h create mode 100644 app/node/distort/tile/CMakeLists.txt create mode 100644 app/node/distort/tile/tiledistortnode.cpp create mode 100644 app/node/distort/tile/tiledistortnode.h create mode 100644 app/node/distort/wave/CMakeLists.txt create mode 100644 app/node/distort/wave/wavedistortnode.cpp create mode 100644 app/node/distort/wave/wavedistortnode.h create mode 100644 app/shaders/ripple.frag create mode 100644 app/shaders/swirl.frag create mode 100644 app/shaders/tile.frag create mode 100644 app/shaders/wave.frag diff --git a/app/node/distort/CMakeLists.txt b/app/node/distort/CMakeLists.txt index 03ede0b9a..2feb4ee34 100644 --- a/app/node/distort/CMakeLists.txt +++ b/app/node/distort/CMakeLists.txt @@ -18,7 +18,11 @@ add_subdirectory(cornerpin) add_subdirectory(crop) add_subdirectory(flip) add_subdirectory(mask) +add_subdirectory(ripple) +add_subdirectory(swirl) +add_subdirectory(tile) add_subdirectory(transform) +add_subdirectory(wave) set(OLIVE_SOURCES ${OLIVE_SOURCES} diff --git a/app/node/distort/flip/flipdistortnode.cpp b/app/node/distort/flip/flipdistortnode.cpp index af14263a1..e2355ade7 100644 --- a/app/node/distort/flip/flipdistortnode.cpp +++ b/app/node/distort/flip/flipdistortnode.cpp @@ -87,11 +87,10 @@ void FlipDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global if (job.Get(kHorizontalInput).toBool() || job.Get(kVerticalInput).toBool()) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } else { - // If we're not flipping or flopping just push the texture - table->Push(job.Get(kTextureInput)); + // If we're not flipping or flopping just push the texture + table->Push(job.Get(kTextureInput)); } } - } } diff --git a/app/node/distort/ripple/CMakeLists.txt b/app/node/distort/ripple/CMakeLists.txt new file mode 100644 index 000000000..1860986c6 --- /dev/null +++ b/app/node/distort/ripple/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 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/ripple/rippledistortnode.cpp + node/distort/ripple/rippledistortnode.h + PARENT_SCOPE +) diff --git a/app/node/distort/ripple/rippledistortnode.cpp b/app/node/distort/ripple/rippledistortnode.cpp new file mode 100644 index 000000000..bddb679d4 --- /dev/null +++ b/app/node/distort/ripple/rippledistortnode.cpp @@ -0,0 +1,130 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 "rippledistortnode.h" + +namespace olive { + +const QString RippleDistortNode::kTextureInput = QStringLiteral("tex_in"); +const QString RippleDistortNode::kEvolutionInput = QStringLiteral("evolution_in"); +const QString RippleDistortNode::kIntensityInput = QStringLiteral("intensity_in"); +const QString RippleDistortNode::kFrequencyInput = QStringLiteral("frequency_in"); +const QString RippleDistortNode::kPositionInput = QStringLiteral("position_in"); +const QString RippleDistortNode::kStretchInput = QStringLiteral("stretch_in"); + +#define super Node + +RippleDistortNode::RippleDistortNode() +{ + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + AddInput(kEvolutionInput, NodeValue::kFloat, 0); + AddInput(kIntensityInput, NodeValue::kFloat, 100); + + AddInput(kFrequencyInput, NodeValue::kFloat, 1); + SetInputProperty(kFrequencyInput, QStringLiteral("base"), 0.01); + + AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0, 0)); + AddInput(kStretchInput, NodeValue::kBoolean, false); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); + + gizmo_ = AddDraggableGizmo({ + NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), + NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), + }); + gizmo_->SetShape(PointGizmo::kAnchorPoint); +} + +QString RippleDistortNode::Name() const +{ + return tr("Ripple"); +} + +QString RippleDistortNode::id() const +{ + return QStringLiteral("org.oliveeditor.Olive.ripple"); +} + +QVector RippleDistortNode::Category() const +{ + return {kCategoryDistort}; +} + +QString RippleDistortNode::Description() const +{ + return tr("Distorts an image with a ripple effect."); +} + +void RippleDistortNode::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTextureInput, tr("Input")); + SetInputName(kFrequencyInput, tr("Frequency")); + SetInputName(kIntensityInput, tr("Intensity")); + SetInputName(kEvolutionInput, tr("Evolution")); + SetInputName(kPositionInput, tr("Position")); + SetInputName(kStretchInput, tr("Stretch")); +} + +ShaderCode RippleDistortNode::GetShaderCode(const ShaderRequest &request) const +{ + Q_UNUSED(request) + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/ripple.frag")); +} + +void RippleDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + ShaderJob job; + + job.Insert(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + + // If there's no texture, no need to run an operation + if (job.Get(kTextureInput).toTexture()) { + // Only run shader if at least one of flip or flop are selected + if (!qIsNull(job.Get(kIntensityInput).toDouble())) { + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + } else { + // If we're not flipping or flopping just push the texture + table->Push(job.Get(kTextureInput)); + } + } +} + +void RippleDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +{ + QPointF half_res(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2); + + gizmo_->SetPoint(half_res + row[kPositionInput].toVec2().toPointF()); +} + +void RippleDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) +{ + NodeInputDragger &x_drag = gizmo_->GetDraggers()[0]; + NodeInputDragger &y_drag = gizmo_->GetDraggers()[1]; + + x_drag.Drag(x_drag.GetStartValue().toDouble() + x); + y_drag.Drag(y_drag.GetStartValue().toDouble() + y); +} + +} diff --git a/app/node/distort/ripple/rippledistortnode.h b/app/node/distort/ripple/rippledistortnode.h new file mode 100644 index 000000000..9f05e06a8 --- /dev/null +++ b/app/node/distort/ripple/rippledistortnode.h @@ -0,0 +1,66 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 RIPPLEDISTORTNODE_H +#define RIPPLEDISTORTNODE_H + +#include "node/gizmo/point.h" +#include "node/node.h" + +namespace olive { + +class RippleDistortNode : public Node +{ + Q_OBJECT +public: + RippleDistortNode(); + + NODE_DEFAULT_FUNCTIONS(RippleDistortNode) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + + static const QString kTextureInput; + static const QString kEvolutionInput; + static const QString kIntensityInput; + static const QString kFrequencyInput; + static const QString kPositionInput; + static const QString kStretchInput; + +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + +private: + PointGizmo *gizmo_; + +}; + +} + +#endif // RIPPLEDISTORTNODE_H diff --git a/app/node/distort/swirl/CMakeLists.txt b/app/node/distort/swirl/CMakeLists.txt new file mode 100644 index 000000000..e41d599c4 --- /dev/null +++ b/app/node/distort/swirl/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 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/swirl/swirldistortnode.cpp + node/distort/swirl/swirldistortnode.h + PARENT_SCOPE +) diff --git a/app/node/distort/swirl/swirldistortnode.cpp b/app/node/distort/swirl/swirldistortnode.cpp new file mode 100644 index 000000000..658feb134 --- /dev/null +++ b/app/node/distort/swirl/swirldistortnode.cpp @@ -0,0 +1,125 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 "swirldistortnode.h" + +namespace olive { + +const QString SwirlDistortNode::kTextureInput = QStringLiteral("tex_in"); +const QString SwirlDistortNode::kRadiusInput = QStringLiteral("radius_in"); +const QString SwirlDistortNode::kAngleInput = QStringLiteral("angle_in"); +const QString SwirlDistortNode::kPositionInput = QStringLiteral("pos_in"); + +#define super Node + +SwirlDistortNode::SwirlDistortNode() +{ + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + AddInput(kRadiusInput, NodeValue::kFloat, 200); + SetInputProperty(kRadiusInput, QStringLiteral("min"), 0); + + AddInput(kAngleInput, NodeValue::kFloat, 10); + SetInputProperty(kAngleInput, QStringLiteral("base"), 0.1); + + AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0, 0)); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); + + gizmo_ = AddDraggableGizmo({ + NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), + NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), + }); + gizmo_->SetShape(PointGizmo::kAnchorPoint); +} + +QString SwirlDistortNode::Name() const +{ + return tr("Swirl"); +} + +QString SwirlDistortNode::id() const +{ + return QStringLiteral("org.oliveeditor.Olive.swirl"); +} + +QVector SwirlDistortNode::Category() const +{ + return {kCategoryDistort}; +} + +QString SwirlDistortNode::Description() const +{ + return tr("Distorts an image along a sine wave."); +} + +void SwirlDistortNode::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTextureInput, tr("Input")); + SetInputName(kRadiusInput, tr("Radius")); + SetInputName(kAngleInput, tr("Angle")); + SetInputName(kPositionInput, tr("Position")); +} + +ShaderCode SwirlDistortNode::GetShaderCode(const ShaderRequest &request) const +{ + Q_UNUSED(request) + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/swirl.frag")); +} + +void SwirlDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + ShaderJob job; + + job.Insert(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + + // If there's no texture, no need to run an operation + if (job.Get(kTextureInput).toTexture()) { + // Only run shader if at least one of flip or flop are selected + if (!qIsNull(job.Get(kAngleInput).toDouble()) && !qIsNull(job.Get(kRadiusInput).toDouble())) { + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + } else { + // If we're not flipping or flopping just push the texture + table->Push(job.Get(kTextureInput)); + } + } +} + +void SwirlDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +{ + QPointF half_res(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2); + + gizmo_->SetPoint(half_res + row[kPositionInput].toVec2().toPointF()); +} + +void SwirlDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) +{ + NodeInputDragger &x_drag = gizmo_->GetDraggers()[0]; + NodeInputDragger &y_drag = gizmo_->GetDraggers()[1]; + + x_drag.Drag(x_drag.GetStartValue().toDouble() + x); + y_drag.Drag(y_drag.GetStartValue().toDouble() + y); +} + +} diff --git a/app/node/distort/swirl/swirldistortnode.h b/app/node/distort/swirl/swirldistortnode.h new file mode 100644 index 000000000..744704ddc --- /dev/null +++ b/app/node/distort/swirl/swirldistortnode.h @@ -0,0 +1,64 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 SWIRLDISTORTNODE_H +#define SWIRLDISTORTNODE_H + +#include "node/gizmo/point.h" +#include "node/node.h" + +namespace olive { + +class SwirlDistortNode : public Node +{ + Q_OBJECT +public: + SwirlDistortNode(); + + NODE_DEFAULT_FUNCTIONS(SwirlDistortNode) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + + static const QString kTextureInput; + static const QString kRadiusInput; + static const QString kAngleInput; + static const QString kPositionInput; + +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + +private: + PointGizmo *gizmo_; + +}; + +} + +#endif // SWIRLDISTORTNODE_H diff --git a/app/node/distort/tile/CMakeLists.txt b/app/node/distort/tile/CMakeLists.txt new file mode 100644 index 000000000..c2f83cbf6 --- /dev/null +++ b/app/node/distort/tile/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 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/tile/tiledistortnode.cpp + node/distort/tile/tiledistortnode.h + PARENT_SCOPE +) diff --git a/app/node/distort/tile/tiledistortnode.cpp b/app/node/distort/tile/tiledistortnode.cpp new file mode 100644 index 000000000..acdb32aee --- /dev/null +++ b/app/node/distort/tile/tiledistortnode.cpp @@ -0,0 +1,165 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 "tiledistortnode.h" + +#include "widget/slider/floatslider.h" + +namespace olive { + +const QString TileDistortNode::kTextureInput = QStringLiteral("tex_in"); +const QString TileDistortNode::kScaleInput = QStringLiteral("scale_in"); +const QString TileDistortNode::kPositionInput = QStringLiteral("position_in"); +const QString TileDistortNode::kAnchorInput = QStringLiteral("anchor_in"); +const QString TileDistortNode::kMirrorXInput = QStringLiteral("mirrorx_in"); +const QString TileDistortNode::kMirrorYInput = QStringLiteral("mirrory_in"); + +#define super Node + +TileDistortNode::TileDistortNode() +{ + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + AddInput(kScaleInput, NodeValue::kFloat, 0.5); + SetInputProperty(kScaleInput, QStringLiteral("min"), 0); + SetInputProperty(kScaleInput, QStringLiteral("view"), FloatSlider::kPercentage); + + AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0, 0)); + + AddInput(kAnchorInput, NodeValue::kCombo, kMiddleCenter); + + AddInput(kMirrorXInput, NodeValue::kBoolean, false); + AddInput(kMirrorYInput, NodeValue::kBoolean, false); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); + + gizmo_ = AddDraggableGizmo({ + NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), + NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), + }); + gizmo_->SetShape(PointGizmo::kAnchorPoint); +} + +QString TileDistortNode::Name() const +{ + return tr("Tile"); +} + +QString TileDistortNode::id() const +{ + return QStringLiteral("org.oliveeditor.Olive.tile"); +} + +QVector TileDistortNode::Category() const +{ + return {kCategoryDistort}; +} + +QString TileDistortNode::Description() const +{ + return tr("Infinitely tile an image horizontally and vertically."); +} + +void TileDistortNode::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTextureInput, tr("Input")); + SetInputName(kScaleInput, tr("Scale")); + SetInputName(kPositionInput, tr("Position")); + SetInputName(kMirrorXInput, tr("Mirror Horizontally")); + SetInputName(kMirrorYInput, tr("Mirror Vertically")); + + SetInputName(kAnchorInput, tr("Anchor")); + SetComboBoxStrings(kAnchorInput, { + tr("Top-Left"), + tr("Top-Center"), + tr("Top-Right"), + tr("Middle-Left"), + tr("Middle-Center"), + tr("Middle-Right"), + tr("Bottom-Left"), + tr("Bottom-Center"), + tr("Bottom-Right"), + }); +} + +ShaderCode TileDistortNode::GetShaderCode(const ShaderRequest &request) const +{ + Q_UNUSED(request) + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/tile.frag")); +} + +void TileDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + ShaderJob job; + + job.Insert(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + + // If there's no texture, no need to run an operation + if (job.Get(kTextureInput).toTexture()) { + // Only run shader if at least one of flip or flop are selected + if (!qFuzzyCompare(job.Get(kScaleInput).toDouble(), 1.0)) { + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + } else { + // If we're not flipping or flopping just push the texture + table->Push(job.Get(kTextureInput)); + } + } +} + +void TileDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +{ + QPointF res = globals.resolution_by_par().toPointF(); + QPointF pos = row[kPositionInput].toVec2().toPointF(); + qreal x = pos.x(); + qreal y = pos.y(); + + Anchor a = static_cast(row[kAnchorInput].toInt()); + if (a == kTopLeft || a == kTopCenter || a == kTopRight) { + // Do nothing + } else if (a == kMiddleLeft || a == kMiddleCenter || a == kMiddleRight) { + y += res.y()/2; + } else if (a == kBottomLeft || a == kBottomCenter || a == kBottomRight) { + y += res.y(); + } + if (a == kTopLeft || a == kMiddleLeft || a == kBottomLeft) { + // Do nothing + } else if (a == kTopCenter || a == kMiddleCenter || a == kBottomCenter) { + x += res.x()/2; + } else if (a == kTopRight || a == kMiddleRight || a == kBottomRight) { + x += res.x(); + } + + gizmo_->SetPoint(QPointF(x, y)); +} + +void TileDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) +{ + NodeInputDragger &x_drag = gizmo_->GetDraggers()[0]; + NodeInputDragger &y_drag = gizmo_->GetDraggers()[1]; + + x_drag.Drag(x_drag.GetStartValue().toDouble() + x); + y_drag.Drag(y_drag.GetStartValue().toDouble() + y); +} + +} diff --git a/app/node/distort/tile/tiledistortnode.h b/app/node/distort/tile/tiledistortnode.h new file mode 100644 index 000000000..44d9b9c1e --- /dev/null +++ b/app/node/distort/tile/tiledistortnode.h @@ -0,0 +1,78 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 TILEDISTORTNODE_H +#define TILEDISTORTNODE_H + +#include "node/gizmo/point.h" +#include "node/node.h" + +namespace olive { + +class TileDistortNode : public Node +{ + Q_OBJECT +public: + TileDistortNode(); + + NODE_DEFAULT_FUNCTIONS(TileDistortNode) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + + static const QString kTextureInput; + static const QString kScaleInput; + static const QString kPositionInput; + static const QString kAnchorInput; + static const QString kMirrorXInput; + static const QString kMirrorYInput; + +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + +private: + enum Anchor { + kTopLeft, + kTopCenter, + kTopRight, + kMiddleLeft, + kMiddleCenter, + kMiddleRight, + kBottomLeft, + kBottomCenter, + kBottomRight + }; + + PointGizmo *gizmo_; + +}; + +} + +#endif // TILEDISTORTNODE_H diff --git a/app/node/distort/wave/CMakeLists.txt b/app/node/distort/wave/CMakeLists.txt new file mode 100644 index 000000000..5cf818d4c --- /dev/null +++ b/app/node/distort/wave/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 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/wave/wavedistortnode.cpp + node/distort/wave/wavedistortnode.h + PARENT_SCOPE +) diff --git a/app/node/distort/wave/wavedistortnode.cpp b/app/node/distort/wave/wavedistortnode.cpp new file mode 100644 index 000000000..6c49c1e0a --- /dev/null +++ b/app/node/distort/wave/wavedistortnode.cpp @@ -0,0 +1,104 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 "wavedistortnode.h" + +namespace olive { + +const QString WaveDistortNode::kTextureInput = QStringLiteral("tex_in"); +const QString WaveDistortNode::kFrequencyInput = QStringLiteral("frequency_in"); +const QString WaveDistortNode::kIntensityInput = QStringLiteral("intensity_in"); +const QString WaveDistortNode::kEvolutionInput = QStringLiteral("evolution_in"); +const QString WaveDistortNode::kVerticalInput = QStringLiteral("vertical_in"); + +#define super Node + +WaveDistortNode::WaveDistortNode() +{ + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + AddInput(kFrequencyInput, NodeValue::kFloat, 10); + AddInput(kIntensityInput, NodeValue::kFloat, 10); + AddInput(kEvolutionInput, NodeValue::kFloat, 0); + + AddInput(kVerticalInput, NodeValue::kCombo, false); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); +} + +QString WaveDistortNode::Name() const +{ + return tr("Wave"); +} + +QString WaveDistortNode::id() const +{ + return QStringLiteral("org.oliveeditor.Olive.wave"); +} + +QVector WaveDistortNode::Category() const +{ + return {kCategoryDistort}; +} + +QString WaveDistortNode::Description() const +{ + return tr("Distorts an image along a sine wave."); +} + +void WaveDistortNode::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTextureInput, tr("Input")); + SetInputName(kFrequencyInput, tr("Frequency")); + SetInputName(kIntensityInput, tr("Intensity")); + SetInputName(kEvolutionInput, tr("Evolution")); + SetInputName(kVerticalInput, tr("Direction")); + SetComboBoxStrings(kVerticalInput, {tr("Horizontal"), tr("Vertical")}); +} + +ShaderCode WaveDistortNode::GetShaderCode(const ShaderRequest &request) const +{ + Q_UNUSED(request) + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/wave.frag")); +} + +void WaveDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + ShaderJob job; + + job.Insert(value); + + // If there's no texture, no need to run an operation + if (job.Get(kTextureInput).toTexture()) { + // Only run shader if at least one of flip or flop are selected + if (!qIsNull(job.Get(kIntensityInput).toDouble())) { + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + } else { + // If we're not flipping or flopping just push the texture + table->Push(job.Get(kTextureInput)); + } + } + +} + +} diff --git a/app/node/distort/wave/wavedistortnode.h b/app/node/distort/wave/wavedistortnode.h new file mode 100644 index 000000000..aa253dc0e --- /dev/null +++ b/app/node/distort/wave/wavedistortnode.h @@ -0,0 +1,56 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 WAVEDISTORTNODE_H +#define WAVEDISTORTNODE_H + +#include "node/node.h" + +namespace olive { + +class WaveDistortNode : public Node +{ + Q_OBJECT +public: + WaveDistortNode(); + + NODE_DEFAULT_FUNCTIONS(WaveDistortNode) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + static const QString kTextureInput; + static const QString kFrequencyInput; + static const QString kIntensityInput; + static const QString kEvolutionInput; + static const QString kVerticalInput; + +}; + +} + +#endif // WAVEDISTORTNODE_H diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 41d6f55a5..b67090ba8 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -35,7 +35,11 @@ #include "distort/crop/cropdistortnode.h" #include "distort/flip/flipdistortnode.h" #include "distort/mask/mask.h" +#include "distort/ripple/rippledistortnode.h" +#include "distort/swirl/swirldistortnode.h" +#include "distort/tile/tiledistortnode.h" #include "distort/transform/transformdistortnode.h" +#include "distort/wave/wavedistortnode.h" #include "effect/opacity/opacityeffect.h" #include "filter/blur/blur.h" #include "filter/dropshadow/dropshadowfilter.h" @@ -294,6 +298,14 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new DropShadowFilter(); case kTimeFormat: return new TimeFormatNode(); + case kWaveDistort: + return new WaveDistortNode(); + case kTileDistort: + return new TileDistortNode(); + case kSwirlDistort: + return new SwirlDistortNode(); + case kRippleDistort: + return new RippleDistortNode(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index b6f6037f0..192765ab4 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -76,6 +76,10 @@ public: kMaskDistort, kDropShadowFilter, kTimeFormat, + kWaveDistort, + kRippleDistort, + kTileDistort, + kSwirlDistort, // Count value kInternalNodeCount diff --git a/app/shaders/ripple.frag b/app/shaders/ripple.frag new file mode 100644 index 000000000..2fa4dc31b --- /dev/null +++ b/app/shaders/ripple.frag @@ -0,0 +1,38 @@ +uniform float evolution_in; +uniform float intensity_in; +uniform float frequency_in; +uniform vec2 position_in; +uniform bool stretch_in; + +uniform vec2 resolution_in; +uniform sampler2D tex_in; + +in vec2 ove_texcoord; +out vec4 frag_color; + +void main(void) { + vec2 center = position_in/resolution_in; + + vec2 adj_texcoord = ove_texcoord; + + adj_texcoord -= 0.5; + if (!stretch_in) { + // Adjust by aspect ratio + float ar = (resolution_in.x/resolution_in.y); + if (resolution_in.x > resolution_in.y) { + adj_texcoord.y /= ar; + center.y /= ar; + } else { + adj_texcoord.x *= ar; + center.x *= ar; + } + } + adj_texcoord += 0.5; + center += 0.5; + + adj_texcoord -= center; + + float len = length(adj_texcoord); + vec2 uv = ove_texcoord + (adj_texcoord/len)*cos((frequency_in)*(len*12.0-evolution_in))*(intensity_in*0.0005); + frag_color = texture(tex_in, uv); +} diff --git a/app/shaders/swirl.frag b/app/shaders/swirl.frag new file mode 100644 index 000000000..910f10a6e --- /dev/null +++ b/app/shaders/swirl.frag @@ -0,0 +1,29 @@ +// Swirl effect parameters +uniform float radius_in; +uniform float angle_in; +uniform vec2 pos_in; +uniform vec2 resolution_in; + +uniform sampler2D tex_in; + +in vec2 ove_texcoord; +out vec4 frag_color; + +void main(void) { + vec2 center = resolution_in*0.5 + pos_in; + + vec2 uv = ove_texcoord; + + vec2 tc = uv * resolution_in; + tc -= center; + float dist = length(tc); + if (dist < radius_in) { + float percent = (radius_in - dist) / radius_in; + float theta = percent * percent * -angle_in; + float s = sin(theta); + float c = cos(theta); + tc = vec2(dot(tc, vec2(c, -s)), dot(tc, vec2(s, c))); + } + tc += center; + frag_color = texture(tex_in, tc / resolution_in); +} diff --git a/app/shaders/tile.frag b/app/shaders/tile.frag new file mode 100644 index 000000000..d0fcb0f08 --- /dev/null +++ b/app/shaders/tile.frag @@ -0,0 +1,61 @@ +uniform float scale_in; +uniform vec2 position_in; +uniform vec2 resolution_in; +uniform bool mirrorx_in; +uniform bool mirrory_in; +uniform int anchor_in; + +uniform sampler2D tex_in; + +in vec2 ove_texcoord; +out vec4 frag_color; + +#define TOP_LEFT 0 +#define TOP_CENTER 1 +#define TOP_RIGHT 2 +#define MIDDLE_LEFT 3 +#define MIDDLE_CENTER 4 +#define MIDDLE_RIGHT 5 +#define BOTTOM_LEFT 6 +#define BOTTOM_CENTER 7 +#define BOTTOM_RIGHT 8 + +void main(void) { + vec2 coord = ove_texcoord; + + vec2 offset; + + if (anchor_in == TOP_LEFT || anchor_in == TOP_CENTER || anchor_in == TOP_RIGHT) { + offset.y = 0.0; + } else if (anchor_in == MIDDLE_LEFT || anchor_in == MIDDLE_CENTER || anchor_in == MIDDLE_RIGHT) { + offset.y = 0.5; + } else if (anchor_in == BOTTOM_LEFT || anchor_in == BOTTOM_CENTER || anchor_in == BOTTOM_RIGHT) { + offset.y = 1.0; + } + + if (anchor_in == TOP_LEFT || anchor_in == MIDDLE_LEFT || anchor_in == BOTTOM_LEFT) { + offset.x = 0.0; + } else if (anchor_in == TOP_CENTER || anchor_in == MIDDLE_CENTER || anchor_in == BOTTOM_CENTER) { + offset.x = 0.5; + } else if (anchor_in == TOP_RIGHT || anchor_in == MIDDLE_RIGHT || anchor_in == BOTTOM_RIGHT) { + offset.x = 1.0; + } + + coord -= position_in/resolution_in; + + coord -= offset; + coord /= scale_in; + coord += offset; + + vec2 modcoord = mod(coord, 1.0); + + if (mirrorx_in && mod(coord.x, 2.0) > 1.0) { + modcoord.x = 1.0 - modcoord.x; + } + + if (mirrory_in && mod(coord.y, 2.0) > 1.0) { + modcoord.y = 1.0 - modcoord.y; + } + + frag_color = vec4(texture(tex_in, modcoord)); +} diff --git a/app/shaders/wave.frag b/app/shaders/wave.frag new file mode 100644 index 000000000..386a3e534 --- /dev/null +++ b/app/shaders/wave.frag @@ -0,0 +1,25 @@ +uniform float frequency_in; +uniform float intensity_in; +uniform float evolution_in; +uniform bool vertical_in; + +uniform sampler2D tex_in; + +in vec2 ove_texcoord; +out vec4 frag_color; + +void main(void) { + vec2 pos = ove_texcoord; + + if (vertical_in) { + pos.x -= sin((ove_texcoord.y-(evolution_in*0.01))*frequency_in)*intensity_in*0.01; + } else { + pos.y -= sin((ove_texcoord.x-(evolution_in*0.01))*frequency_in)*intensity_in*0.01; + } + + if (pos.x < 0.0 || pos.x >= 1.0 || pos.y < 0.0 || pos.y >= 1.0) { + discard; + } else { + frag_color = texture(tex_in, pos); + } +} From b19730b1f5aabf75a2e827b6ce0aaa7301686943 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 23 Sep 2022 02:55:18 -0700 Subject: [PATCH 04/19] nodes: store time with accelerated jobs Fixes #2031 --- app/node/audio/pan/pan.cpp | 4 +--- app/node/audio/volume/volume.cpp | 4 +--- app/node/math/math/mathbase.cpp | 2 +- app/node/project/footage/footage.cpp | 2 +- app/node/traverser.cpp | 22 +++++++++++----------- app/node/traverser.h | 6 +++--- app/render/job/footagejob.h | 7 ++++++- app/render/job/samplejob.h | 11 +++++++++-- app/render/renderprocessor.cpp | 6 +++--- app/render/renderprocessor.h | 2 +- 10 files changed, 37 insertions(+), 29 deletions(-) diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp index 77e194506..434480dd9 100644 --- a/app/node/audio/pan/pan.cpp +++ b/app/node/audio/pan/pan.cpp @@ -64,8 +64,6 @@ QString PanNode::Description() const void PanNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - Q_UNUSED(globals) - // Create a sample job SampleBuffer samples = value[kSamplesInput].toSamples(); if (samples.is_allocated()) { @@ -85,7 +83,7 @@ void PanNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV table->Push(NodeValue(NodeValue::kSamples, samples, this)); } else { // Requires job - table->Push(NodeValue::kSamples, SampleJob(kSamplesInput, value), this); + table->Push(NodeValue::kSamples, SampleJob(globals.time(), kSamplesInput, value), this); } } else { // Pass right through diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index 81a2306a7..7fe0a888a 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -63,8 +63,6 @@ QString VolumeNode::Description() const void VolumeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - Q_UNUSED(globals) - // Create a sample job SampleBuffer buffer = value[kSamplesInput].toSamples(); @@ -80,7 +78,7 @@ void VolumeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, No table->Push(NodeValue::kSamples, QVariant::fromValue(buffer), this); } else { // Requires job - SampleJob job(kSamplesInput, value); + SampleJob job(globals.time(), kSamplesInput, value); job.Insert(kVolumeInput, value); table->Push(NodeValue::kSamples, QVariant::fromValue(job), this); } diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 14045b054..7f3c12440 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -410,7 +410,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt output->Push(NodeValue::kSamples, QVariant::fromValue(buffer), this); } else { - SampleJob job(val_a.type() == NodeValue::kSamples ? val_a : val_b); + SampleJob job(globals.time(), val_a.type() == NodeValue::kSamples ? val_a : val_b); job.Insert(number_param, NodeValue(NodeValue::kFloat, number, this)); output->Push(NodeValue::kSamples, QVariant::fromValue(job), this); } diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 21b837704..cbc600779 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -265,7 +265,7 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV // Push each stream as a footage job for (int i=0; i()) { @@ -374,7 +374,7 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) ShaderJob job = val.value(); - PreProcessRow(range, job.GetValues()); + PreProcessRow(job.GetValues()); VideoParams tex_params = GetCacheVideoParams(); tex_params.set_channel_count(GetChannelCountFromJob(job)); @@ -389,7 +389,7 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) TexturePtr tex = CreateTexture(tex_params); - ProcessShader(tex, val.source(), range, job); + ProcessShader(tex, val.source(), job); val.set_value(tex); @@ -407,7 +407,7 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) TexturePtr tex = CreateTexture(upload_params); - PreProcessRow(range, job.GetValues()); + PreProcessRow(job.GetValues()); ProcessFrameGeneration(tex, val.source(), job); if (!job.GetColorspace().isEmpty()) { @@ -440,7 +440,7 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) if (job.type() == Track::kVideo) { - rational footage_time = Footage::AdjustTimeByLoopMode(range.in(), loop_mode_, job.length(), job.video_params().video_type(), job.video_params().frame_rate_as_time_base()); + rational footage_time = Footage::AdjustTimeByLoopMode(job.time().in(), loop_mode_, job.length(), job.video_params().video_type(), job.video_params().frame_rate_as_time_base()); TexturePtr tex; @@ -473,8 +473,8 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) } else if (job.type() == Track::kAudio) { - SampleBuffer buffer = CreateSampleBuffer(GetCacheAudioParams(), range.length()); - ProcessAudioFootage(buffer, job, range); + SampleBuffer buffer = CreateSampleBuffer(GetCacheAudioParams(), job.time().length()); + ProcessAudioFootage(buffer, job, job.time()); val.set_value(buffer); } @@ -483,7 +483,7 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) SampleJob job = val.value(); SampleBuffer output_buffer = CreateSampleBuffer(job.samples().audio_params(), job.samples().sample_count()); - ProcessSamples(output_buffer, val.source(), range, job); + ProcessSamples(output_buffer, val.source(), job.time(), job); val.set_value(QVariant::fromValue(output_buffer)); } @@ -491,7 +491,7 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) } } -void NodeTraverser::PreProcessRow(const TimeRange &range, NodeValueRow &row) +void NodeTraverser::PreProcessRow(NodeValueRow &row) { QByteArray cached_node_hash; @@ -500,7 +500,7 @@ void NodeTraverser::PreProcessRow(const TimeRange &range, NodeValueRow &row) // Jobs will almost always be submitted with one of these types NodeValue &val = it.value(); - ResolveJobs(val, range); + ResolveJobs(val); } } diff --git a/app/node/traverser.h b/app/node/traverser.h index bde1733ae..308ab675c 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -93,7 +93,7 @@ protected: virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time){} - virtual void ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob& job){} + virtual void ProcessShader(TexturePtr destination, const Node *node, const ShaderJob& job){} virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job){} @@ -140,7 +140,7 @@ protected: CancelAtom *GetCancelPointer() const { return cancel_; } void SetCancelPointer(CancelAtom *cancel) { cancel_ = cancel; } - void ResolveJobs(NodeValue &value, const TimeRange &range); + void ResolveJobs(NodeValue &value); Block *GetCurrentBlock() const { @@ -150,7 +150,7 @@ protected: Decoder::LoopMode loop_mode() const { return loop_mode_; } private: - void PreProcessRow(const TimeRange &range, NodeValueRow &row); + void PreProcessRow(NodeValueRow &row); TexturePtr CreateDummyTexture(const VideoParams &p); diff --git a/app/render/job/footagejob.h b/app/render/job/footagejob.h index 319be8a38..3684fa999 100644 --- a/app/render/job/footagejob.h +++ b/app/render/job/footagejob.h @@ -33,7 +33,8 @@ public: { } - FootageJob(const QString& decoder, const QString& filename, Track::Type type, const rational& length) : + FootageJob(const TimeRange &time, const QString& decoder, const QString& filename, Track::Type type, const rational& length) : + time_(time), decoder_(decoder), filename_(filename), type_(type), @@ -96,7 +97,11 @@ public: length_ = length; } + const TimeRange &time() const { return time_; } + private: + TimeRange time_; + QString decoder_; QString filename_; diff --git a/app/render/job/samplejob.h b/app/render/job/samplejob.h index 15fd71dc9..03bc00e6e 100644 --- a/app/render/job/samplejob.h +++ b/app/render/job/samplejob.h @@ -23,6 +23,7 @@ #include "acceleratedjob.h" #include "codec/samplebuffer.h" +#include "common/timerange.h" namespace olive { @@ -32,14 +33,16 @@ public: { } - SampleJob(const NodeValue& value) + SampleJob(const TimeRange &time, const NodeValue& value) { samples_ = value.toSamples(); + time_ = time; } - SampleJob(const QString& from, const NodeValueRow& row) + SampleJob(const TimeRange &time, const QString& from, const NodeValueRow& row) { samples_ = row[from].toSamples(); + time_ = time; } const SampleBuffer &samples() const @@ -52,9 +55,13 @@ public: return samples_.is_allocated(); } + const TimeRange &time() const { return time_; } + private: SampleBuffer samples_; + TimeRange time_; + }; } diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 426b0a9ed..1c6f49fe6 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -54,7 +54,7 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational NodeValue tex_val = table.Get(NodeValue::kTexture); - ResolveJobs(tex_val, range); + ResolveJobs(tex_val); return tex_val.toTexture(); } @@ -226,7 +226,7 @@ void RenderProcessor::Run() NodeValue sample_val = table.Get(NodeValue::kSamples); - ResolveJobs(sample_val, time); + ResolveJobs(sample_val); SampleBuffer samples = sample_val.toSamples(); if (samples.is_allocated()) { @@ -522,7 +522,7 @@ void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const Foota } } -void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob &job) +void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, const ShaderJob &job) { if (!render_ctx_) { return; diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index a537ccf89..6738a605c 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -48,7 +48,7 @@ protected: virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time) override; - virtual void ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob& job) override; + virtual void ProcessShader(TexturePtr destination, const Node *node, const ShaderJob& job) override; virtual void ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job) override; From 825b06f7a72fab5ab441c121f1df58f425fbcc14 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 23 Sep 2022 12:31:09 -0700 Subject: [PATCH 05/19] render: only use cache in offline mode --- app/node/traverser.cpp | 2 +- app/node/traverser.h | 2 ++ app/render/renderprocessor.cpp | 5 +++++ app/render/renderprocessor.h | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 224364bc2..3c8053e10 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -109,7 +109,7 @@ NodeValue NodeTraverser::GenerateRowValueElement(const Node *node, const QString NodeValue value = table->TakeAt(value_index); - if (value.type() == NodeValue::kTexture) { + if (value.type() == NodeValue::kTexture && UseCache()) { QMutexLocker locker(node->video_frame_cache()->mutex()); node->video_frame_cache()->LoadState(); diff --git a/app/node/traverser.h b/app/node/traverser.h index 308ab675c..1defd0168 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -149,6 +149,8 @@ protected: Decoder::LoopMode loop_mode() const { return loop_mode_; } + virtual bool UseCache() const { return false; } + private: void PreProcessRow(NodeValueRow &row); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 1c6f49fe6..fc4f26c02 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -649,4 +649,9 @@ void RenderProcessor::ConvertToReferenceSpace(TexturePtr destination, TexturePtr render_ctx_->BlitColorManaged(ctj, destination.get()); } +bool RenderProcessor::UseCache() const +{ + return static_cast(ticket_->property("mode").toInt()) == RenderMode::kOffline; +} + } diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index 6738a605c..8ef6699bb 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -67,6 +67,8 @@ protected: virtual void ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs) override; + virtual bool UseCache() const override; + private: RenderProcessor(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache); From d5e12eb0919fbc28ce3312a0383acd190a91345c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 23 Sep 2022 16:58:49 -0700 Subject: [PATCH 06/19] mask: add invert option --- app/node/distort/mask/mask.cpp | 23 +++++++++++++++++++---- app/node/distort/mask/mask.h | 1 + app/shaders/invertrgb.frag | 12 ++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 app/shaders/invertrgb.frag diff --git a/app/node/distort/mask/mask.cpp b/app/node/distort/mask/mask.cpp index 13bc6840e..f19b8e961 100644 --- a/app/node/distort/mask/mask.cpp +++ b/app/node/distort/mask/mask.cpp @@ -27,12 +27,15 @@ namespace olive { #define super PolygonGenerator const QString MaskDistortNode::kFeatherInput = QStringLiteral("feather_in"); +const QString MaskDistortNode::kInvertInput = QStringLiteral("invert_in"); MaskDistortNode::MaskDistortNode() { // Mask should always be (1.0, 1.0, 1.0) for multiply to work correctly SetInputFlags(kColorInput, InputFlags(GetInputFlags(kColorInput) | kInputFlagHidden)); + AddInput(kInvertInput, NodeValue::kBoolean, false); + AddInput(kFeatherInput, NodeValue::kFloat, 0.0); SetInputProperty(kFeatherInput, QStringLiteral("min"), 0.0); } @@ -43,6 +46,8 @@ ShaderCode MaskDistortNode::GetShaderCode(const ShaderRequest &request) const return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/multiply.frag"))); } else if (request.id == QStringLiteral("feather")) { return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/blur.frag"))); + } else if (request.id == QStringLiteral("invert")) { + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/invertrgb.frag"))); } else { return super::GetShaderCode(request); } @@ -53,12 +58,20 @@ void MaskDistortNode::Retranslate() super::Retranslate(); SetInputName(kBaseInput, tr("Texture")); + SetInputName(kInvertInput, tr("Invert")); SetInputName(kFeatherInput, tr("Feather")); } void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job = GetGenerateJob(value); + NodeValue job(NodeValue::kTexture, GetGenerateJob(value), this); + + if (value[kInvertInput].toBool()) { + ShaderJob invert; + invert.SetShaderID(QStringLiteral("invert")); + invert.Insert(QStringLiteral("tex_in"), job); + job.set_value(invert); + } if (value[kBaseInput].toTexture()) { // Push as merge node @@ -72,7 +85,7 @@ void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global ShaderJob feather; feather.SetShaderID(QStringLiteral("feather")); - feather.Insert(BlurFilterNode::kTextureInput, NodeValue(NodeValue::kTexture, job, this)); + feather.Insert(BlurFilterNode::kTextureInput, job); feather.Insert(BlurFilterNode::kMethodInput, NodeValue(NodeValue::kInt, int(BlurFilterNode::kGaussian), this)); feather.Insert(BlurFilterNode::kHorizInput, NodeValue(NodeValue::kBoolean, true, this)); feather.Insert(BlurFilterNode::kVertInput, NodeValue(NodeValue::kBoolean, true, this)); @@ -83,10 +96,12 @@ void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global merge.Insert(QStringLiteral("tex_b"), NodeValue(NodeValue::kTexture, feather, this)); } else { - merge.Insert(QStringLiteral("tex_b"), NodeValue(NodeValue::kTexture, job, this)); + merge.Insert(QStringLiteral("tex_b"), job); } - table->Push(NodeValue::kTexture, QVariant::fromValue(merge), this); + table->Push(NodeValue::kTexture, merge, this); + } else { + table->Push(job); } } diff --git a/app/node/distort/mask/mask.h b/app/node/distort/mask/mask.h index 5565d3b65..e5e44c2c9 100644 --- a/app/node/distort/mask/mask.h +++ b/app/node/distort/mask/mask.h @@ -59,6 +59,7 @@ public: virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + static const QString kInvertInput; static const QString kFeatherInput; }; diff --git a/app/shaders/invertrgb.frag b/app/shaders/invertrgb.frag new file mode 100644 index 000000000..d5c38a7e4 --- /dev/null +++ b/app/shaders/invertrgb.frag @@ -0,0 +1,12 @@ +// Input texture +uniform sampler2D tex_in; + +// Input texture coordinate +in vec2 ove_texcoord; +out vec4 frag_color; + +void main() { + vec4 color = texture(tex_in, ove_texcoord); + color.rgb = 1.0 - color.rgb; + frag_color = color; +} From cde6129868e648b8fc158f4303fe146ce3cd3378 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 23 Sep 2022 16:59:12 -0700 Subject: [PATCH 07/19] node: simplify traverser/node globals --- app/node/globals.h | 42 ++++++++-------------------------------- app/node/traverser.cpp | 2 +- app/render/videoparams.h | 10 ++++++++++ 3 files changed, 19 insertions(+), 35 deletions(-) diff --git a/app/node/globals.h b/app/node/globals.h index 42d527ed0..be1d11d6f 100644 --- a/app/node/globals.h +++ b/app/node/globals.h @@ -24,6 +24,7 @@ #include #include "common/timerange.h" +#include "render/videoparams.h" namespace olive { @@ -32,46 +33,19 @@ class NodeGlobals public: NodeGlobals(){} - NodeGlobals(const QVector2D &resolution, const rational &pixel_aspect, const TimeRange &time) : - resolution_(resolution), - pixel_aspect_(pixel_aspect), + NodeGlobals(const VideoParams &vparam, const TimeRange &time) : + video_params_(vparam), time_(time) { - resolution_by_par_ = QVector2D(resolution_.x() * pixel_aspect_.toDouble(), resolution_.y()); } - const QVector2D &resolution() const - { - return resolution_; - } - - const QVector2D &resolution_by_par() const - { - return resolution_by_par_; - } - - const rational &pixel_aspect() const - { - return pixel_aspect_; - } - - const TimeRange &time() const - { - return time_; - } - - void set_time(const TimeRange &time) - { - time_ = time; - } + QVector2D resolution() const { return video_params_.resolution(); } + QVector2D resolution_by_par() const { return video_params_.square_resolution(); } + const VideoParams &video_params() const { return video_params_; } + const TimeRange &time() const { return time_; } private: - QVector2D resolution_; - - rational pixel_aspect_; - - QVector2D resolution_by_par_; - + VideoParams video_params_; TimeRange time_; }; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 3c8053e10..6b1dae1cf 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -171,7 +171,7 @@ void NodeTraverser::Transform(QTransform *transform, const Node *start, const No NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams ¶ms, const TimeRange &time) { - return NodeGlobals(QVector2D(params.width(), params.height()), params.pixel_aspect_ratio(), time); + return NodeGlobals(params, time); } int NodeTraverser::GetChannelCountFromJob(const GenerateJob &job) diff --git a/app/render/videoparams.h b/app/render/videoparams.h index 1a75d2bed..e7cbcb883 100644 --- a/app/render/videoparams.h +++ b/app/render/videoparams.h @@ -107,6 +107,16 @@ public: return par_width_; } + QVector2D resolution() const + { + return QVector2D(width_, height_); + } + + QVector2D square_resolution() const + { + return QVector2D(par_width_, height_); + } + int height() const { return height_; From 81b7c79a143995ae2e977289da6024e5b3dd4a7e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 23 Sep 2022 17:53:43 -0700 Subject: [PATCH 08/19] openglrenderer: use glFinish again... This will break some systems, but is necessary for frames not to corrupt under some circumstances. It sucks but that's the tradeoff we have to make. I think it's time to get off OpenGL. It's not worth it anymore. --- app/render/opengl/openglrenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 7ef116a21..8c6f2a703 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -364,7 +364,7 @@ void OpenGLRenderer::Flush() { GL_PREAMBLE; - functions_->glFlush(); + functions_->glFinish(); } Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt) From 658fe9da7eb00a1f830e606dae8894578de53c72 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 23 Sep 2022 17:54:46 -0700 Subject: [PATCH 09/19] playbackcache: keep track of cache file time to prevent unnecessary loads --- app/render/playbackcache.cpp | 11 +++++++++-- app/render/playbackcache.h | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 0f179dab8..487eac2de 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -68,7 +68,9 @@ void PlaybackCache::LoadState() { QDir cache_dir = GetThisCacheDirectory(); QFile f(cache_dir.filePath(QStringLiteral("state"))); - if (f.open(QFile::ReadOnly)) { + + qint64 file_time = f.fileTime(QFileDevice::FileModificationTime).toMSecsSinceEpoch(); + if (file_time > last_loaded_state_ && f.open(QFile::ReadOnly)) { QDataStream s(&f); uint32_t version; @@ -116,6 +118,8 @@ void PlaybackCache::LoadState() } f.close(); + + last_loaded_state_ = file_time; } } @@ -161,6 +165,8 @@ void PlaybackCache::SaveState() } f.close(); + + last_loaded_state_ = f.fileTime(QFileDevice::FileModificationTime).toMSecsSinceEpoch(); } } } @@ -236,7 +242,8 @@ Project *PlaybackCache::GetProject() const PlaybackCache::PlaybackCache(QObject *parent) : QObject(parent), - saving_enabled_(true) + saving_enabled_(true), + last_loaded_state_(0) { uuid_ = QUuid::createUuid(); } diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index d96709355..3485fdcb4 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -132,6 +132,8 @@ private: QVector passthroughs_; + qint64 last_loaded_state_; + }; } From c0d8ff403f86743af3fca5e537b76f65673c6c3f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 24 Sep 2022 18:44:59 -0700 Subject: [PATCH 10/19] nodes: rework so that jobs can be completely deferred Big optimization requiring a lot of refactoring. --- app/codec/encoder.h | 1 + app/core.cpp | 3 - .../diptocolor/diptocolortransition.cpp | 4 +- .../diptocolor/diptocolortransition.h | 2 +- app/node/block/transition/transition.cpp | 4 +- app/node/block/transition/transition.h | 2 +- app/node/color/ociobase/ociobase.cpp | 8 +- .../ociogradingtransformlinear.cpp | 72 ++--- .../cornerpin/cornerpindistortnode.cpp | 95 +++--- app/node/distort/crop/cropdistortnode.cpp | 40 +-- app/node/distort/crop/cropdistortnode.h | 1 + app/node/distort/flip/flipdistortnode.cpp | 12 +- app/node/distort/mask/mask.cpp | 15 +- app/node/distort/ripple/rippledistortnode.cpp | 22 +- app/node/distort/swirl/swirldistortnode.cpp | 17 +- app/node/distort/tile/tiledistortnode.cpp | 57 ++-- .../transform/transformdistortnode.cpp | 17 +- app/node/distort/wave/wavedistortnode.cpp | 12 +- app/node/effect/opacity/opacityeffect.cpp | 12 +- app/node/filter/blur/blur.cpp | 44 +-- .../filter/dropshadow/dropshadowfilter.cpp | 9 +- app/node/filter/mosaic/mosaicfilternode.cpp | 24 +- app/node/filter/stroke/stroke.cpp | 17 +- app/node/generator/noise/noise.cpp | 6 +- app/node/generator/polygon/polygon.cpp | 22 +- app/node/generator/polygon/polygon.h | 2 +- .../generator/shape/generatorwithmerge.cpp | 8 +- app/node/generator/shape/generatorwithmerge.h | 2 +- app/node/generator/shape/shapenode.cpp | 9 +- app/node/generator/shape/shapenodebase.cpp | 4 +- app/node/generator/solid/solid.cpp | 4 +- app/node/generator/text/textv1.cpp | 7 +- app/node/generator/text/textv2.cpp | 11 +- app/node/generator/text/textv3.cpp | 22 +- app/node/globals.h | 6 +- app/node/keying/chromakey/chromakey.cpp | 17 +- .../colordifferencekey/colordifferencekey.cpp | 9 +- app/node/keying/despill/despill.cpp | 4 +- app/node/math/math/mathbase.cpp | 4 +- app/node/math/merge/merge.cpp | 12 +- app/node/project/footage/footage.cpp | 17 +- app/node/traverser.cpp | 294 ++++++++---------- app/node/traverser.h | 21 +- app/node/value.h | 5 + app/render/job/acceleratedjob.h | 5 +- app/render/job/cachejob.h | 15 +- app/render/job/colortransformjob.h | 24 +- app/render/job/footagejob.h | 2 +- app/render/job/generatejob.h | 25 +- app/render/job/samplejob.h | 3 +- app/render/job/shaderjob.h | 20 +- app/render/opengl/openglrenderer.cpp | 1 + app/render/renderer.cpp | 2 +- app/render/renderprocessor.cpp | 51 +-- app/render/renderprocessor.h | 12 +- app/render/texture.cpp | 4 + app/render/texture.h | 41 ++- 57 files changed, 593 insertions(+), 588 deletions(-) diff --git a/app/codec/encoder.h b/app/codec/encoder.h index e33f1de9a..e27cd1417 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -33,6 +33,7 @@ #include "common/timerange.h" #include "node/block/subtitle/subtitle.h" #include "render/audioparams.h" +#include "render/colortransform.h" #include "render/subtitleparams.h" #include "render/videoparams.h" diff --git a/app/core.cpp b/app/core.cpp index 97a49f8e0..0a90e92d4 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -116,9 +116,6 @@ void Core::DeclareTypesForQt() qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); diff --git a/app/node/block/transition/diptocolor/diptocolortransition.cpp b/app/node/block/transition/diptocolor/diptocolortransition.cpp index f9945dad0..5b262288e 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.cpp +++ b/app/node/block/transition/diptocolor/diptocolortransition.cpp @@ -56,9 +56,9 @@ ShaderCode DipToColorTransition::GetShaderCode(const ShaderRequest &request) con return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/diptoblack.frag"), QString()); } -void DipToColorTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob &job) const +void DipToColorTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const { - job.Insert(kColorInput, value); + job->Insert(kColorInput, value); } } diff --git a/app/node/block/transition/diptocolor/diptocolortransition.h b/app/node/block/transition/diptocolor/diptocolortransition.h index c1f3dca0f..f0554f6c7 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.h +++ b/app/node/block/transition/diptocolor/diptocolortransition.h @@ -43,7 +43,7 @@ public: static const QString kColorInput; protected: - virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob& job) const override; + virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const override; }; diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index f8b86d67d..d5c048eeb 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -182,10 +182,10 @@ void TransitionBlock::Value(const NodeValueRow &value, const NodeGlobals &global double time = globals.time().in().toDouble(); InsertTransitionTimes(&job, time); - ShaderJobEvent(value, job); + ShaderJobEvent(value, &job); job_type = NodeValue::kTexture; - push_job = QVariant::fromValue(job); + push_job = QVariant::fromValue(Texture::Job(globals.vparams(), job)); } else if (data_type == NodeValue::kSamples) { // This must be an audio transition SampleBuffer from_samples = out_buffer.toSamples(); diff --git a/app/node/block/transition/transition.h b/app/node/block/transition/transition.h index 0babf1516..554a7c469 100644 --- a/app/node/block/transition/transition.h +++ b/app/node/block/transition/transition.h @@ -73,7 +73,7 @@ public: static const QString kCenterInput; protected: - virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob& job) const {} + virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const {} virtual void SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const {} diff --git a/app/node/color/ociobase/ociobase.cpp b/app/node/color/ociobase/ociobase.cpp index 831c2f8be..498596b68 100644 --- a/app/node/color/ociobase/ociobase.cpp +++ b/app/node/color/ociobase/ociobase.cpp @@ -60,13 +60,15 @@ void OCIOBaseNode::RemovedFromGraph() void OCIOBaseNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - if (value[kTextureInput].toTexture() && processor_) { + auto tex_met = value[kTextureInput]; + TexturePtr t = tex_met.toTexture(); + if (t && processor_) { ColorTransformJob job; job.SetColorProcessor(processor_); - job.SetInputTexture(value[kTextureInput].toTexture()); + job.SetInputTexture(tex_met); - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + table->Push(NodeValue::kTexture, t->toJob(job), this); } } diff --git a/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp index 2ef925652..a6a37012f 100644 --- a/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp +++ b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp @@ -155,50 +155,50 @@ void OCIOGradingTransformLinearNode::GenerateProcessor() void OCIOGradingTransformLinearNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - if (value[kTextureInput].toTexture() && processor()) { - ColorTransformJob job; + if (TexturePtr tex = value[kTextureInput].toTexture()) { + if (processor()) { + ColorTransformJob job(value); - job.SetColorProcessor(processor()); - job.SetInputTexture(value[kTextureInput].toTexture()); + job.SetColorProcessor(processor()); + job.SetInputTexture(value[kTextureInput]); - job.Insert(value); + const int MASTER_CHANNEL = 0; + const int RED_CHANNEL = 1; + const int GREEN_CHANNEL = 2; + const int BLUE_CHANNEL = 3; - const int MASTER_CHANNEL = 0; - const int RED_CHANNEL = 1; - const int GREEN_CHANNEL = 2; - const int BLUE_CHANNEL = 3; + // Oddly, OCIO uses RGBMs when setting the GradingPrimary on the CPU, but uses vec3s on the GPU. + // Even more oddly, the conversion from RGBM to vec3 does not appear to have a public API. + // Therefore, this code has been duplicated from OCIO here: + // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/3abbe5b20521169580fcfe3692aca81859859953/src/OpenColorIO/ops/gradingprimary/GradingPrimary.cpp#L157 + QVector4D offset = value[kOffsetInput].toVec4(); + offset[RED_CHANNEL] += offset[MASTER_CHANNEL]; + offset[GREEN_CHANNEL] += offset[MASTER_CHANNEL]; + offset[BLUE_CHANNEL] += offset[MASTER_CHANNEL]; + job.Insert(kOffsetInput, NodeValue(NodeValue::kVec3, QVector3D(offset[RED_CHANNEL], offset[GREEN_CHANNEL], offset[BLUE_CHANNEL]))); - // Oddly, OCIO uses RGBMs when setting the GradingPrimary on the CPU, but uses vec3s on the GPU. - // Even more oddly, the conversion from RGBM to vec3 does not appear to have a public API. - // Therefore, this code has been duplicated from OCIO here: - // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/3abbe5b20521169580fcfe3692aca81859859953/src/OpenColorIO/ops/gradingprimary/GradingPrimary.cpp#L157 - QVector4D offset = value[kOffsetInput].toVec4(); - offset[RED_CHANNEL] += offset[MASTER_CHANNEL]; - offset[GREEN_CHANNEL] += offset[MASTER_CHANNEL]; - offset[BLUE_CHANNEL] += offset[MASTER_CHANNEL]; - job.Insert(kOffsetInput, NodeValue(NodeValue::kVec3, QVector3D(offset[RED_CHANNEL], offset[GREEN_CHANNEL], offset[BLUE_CHANNEL]))); + QVector4D exposure = value[kExposureInput].toVec4(); + exposure[RED_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[RED_CHANNEL]); + exposure[GREEN_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[GREEN_CHANNEL]); + exposure[BLUE_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[BLUE_CHANNEL]); + job.Insert(kExposureInput, NodeValue(NodeValue::kVec3, QVector3D(exposure[RED_CHANNEL], exposure[GREEN_CHANNEL], exposure[BLUE_CHANNEL]))); - QVector4D exposure = value[kExposureInput].toVec4(); - exposure[RED_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[RED_CHANNEL]); - exposure[GREEN_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[GREEN_CHANNEL]); - exposure[BLUE_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[BLUE_CHANNEL]); - job.Insert(kExposureInput, NodeValue(NodeValue::kVec3, QVector3D(exposure[RED_CHANNEL], exposure[GREEN_CHANNEL], exposure[BLUE_CHANNEL]))); + QVector4D contrast = value[kContrastInput].toVec4(); + contrast[RED_CHANNEL] *= contrast[MASTER_CHANNEL]; + contrast[GREEN_CHANNEL] *= contrast[MASTER_CHANNEL]; + contrast[BLUE_CHANNEL] *= contrast[MASTER_CHANNEL]; + job.Insert(kContrastInput, NodeValue(NodeValue::kVec3, QVector3D(contrast[RED_CHANNEL], contrast[GREEN_CHANNEL], contrast[BLUE_CHANNEL]))); - QVector4D contrast = value[kContrastInput].toVec4(); - contrast[RED_CHANNEL] *= contrast[MASTER_CHANNEL]; - contrast[GREEN_CHANNEL] *= contrast[MASTER_CHANNEL]; - contrast[BLUE_CHANNEL] *= contrast[MASTER_CHANNEL]; - job.Insert(kContrastInput, NodeValue(NodeValue::kVec3, QVector3D(contrast[RED_CHANNEL], contrast[GREEN_CHANNEL], contrast[BLUE_CHANNEL]))); + if (!value[kClampBlackEnableInput].toBool()) { + job.Insert(kClampBlackInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampBlack())); + } - if (!value[kClampBlackEnableInput].toBool()) { - job.Insert(kClampBlackInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampBlack())); + if (!value[kClampWhiteEnableInput].toBool()) { + job.Insert(kClampWhiteInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampWhite())); + } + + table->Push(NodeValue::kTexture, tex->toJob(job), this); } - - if (!value[kClampWhiteEnableInput].toBool()) { - job.Insert(kClampWhiteInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampWhite())); - } - - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } } diff --git a/app/node/distort/cornerpin/cornerpindistortnode.cpp b/app/node/distort/cornerpin/cornerpindistortnode.cpp index 38fc58104..2bb8a4a90 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.cpp +++ b/app/node/distort/cornerpin/cornerpindistortnode.cpp @@ -69,40 +69,39 @@ void CornerPinDistortNode::Retranslate() void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - - // Convert slider values to their pixel values and then convert to clip space (-1.0 ... 1.0) for overriding the - // vertex coordinates. - const QVector2D &resolution = globals.resolution(); - QVector2D half_resolution = resolution * 0.5; - QVector2D top_left = QVector2D(ValueToPixel(0, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); - QVector2D top_right = QVector2D(ValueToPixel(1, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); - QVector2D bottom_right = QVector2D(ValueToPixel(2, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); - QVector2D bottom_left = QVector2D(ValueToPixel(3, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); - - // Override default vertex coordinates. - QVector adjusted_vertices = {top_left.x(), top_left.y(), 0.0f, - top_right.x(), top_right.y(), 0.0f, - bottom_right.x(), bottom_right.y(), 0.0f, - - top_left.x(), top_left.y(), 0.0f, - bottom_left.x(), bottom_left.y(), 0.0f, - bottom_right.x(), bottom_right.y(), 0.0f}; - job.SetVertexCoordinates(adjusted_vertices); - // If no texture do nothing - if (job.Get(kTextureInput).toTexture()) { + if (TexturePtr tex = value[kTextureInput].toTexture()) { // In the special case that all sliders are in their default position just // push the texture. - if (!(job.Get(kTopLeftInput).toVec2().isNull() - && job.Get(kTopRightInput).toVec2().isNull() && - job.Get(kBottomRightInput).toVec2().isNull() && - job.Get(kBottomLeftInput).toVec2().isNull())) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (!(value[kTopLeftInput].toVec2().isNull() + && value[kTopRightInput].toVec2().isNull() && + value[kBottomRightInput].toVec2().isNull() && + value[kBottomLeftInput].toVec2().isNull())) { + ShaderJob job(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); + + // Convert slider values to their pixel values and then convert to clip space (-1.0 ... 1.0) for overriding the + // vertex coordinates. + const QVector2D &resolution = tex->virtual_resolution(); + QVector2D half_resolution = resolution * 0.5; + QVector2D top_left = QVector2D(ValueToPixel(0, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); + QVector2D top_right = QVector2D(ValueToPixel(1, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); + QVector2D bottom_right = QVector2D(ValueToPixel(2, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); + QVector2D bottom_left = QVector2D(ValueToPixel(3, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); + + // Override default vertex coordinates. + QVector adjusted_vertices = {top_left.x(), top_left.y(), 0.0f, + top_right.x(), top_right.y(), 0.0f, + bottom_right.x(), bottom_right.y(), 0.0f, + + top_left.x(), top_left.y(), 0.0f, + bottom_left.x(), bottom_left.y(), 0.0f, + bottom_right.x(), bottom_right.y(), 0.0f}; + job.SetVertexCoordinates(adjusted_vertices); + + table->Push(NodeValue::kTexture, tex->toJob(job), this); } else { - table->Push(job.Get(kTextureInput)); + table->Push(value[kTextureInput]); } } } @@ -151,27 +150,29 @@ void CornerPinDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardM void CornerPinDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { - const QVector2D &resolution = globals.resolution(); + if (TexturePtr tex = row[kTextureInput].toTexture()) { + const QVector2D &resolution = tex->virtual_resolution(); - QPointF top_left = ValueToPixel(0, row, resolution); - QPointF top_right = ValueToPixel(1, row, resolution); - QPointF bottom_right = ValueToPixel(2, row, resolution); - QPointF bottom_left = ValueToPixel(3, row, resolution); + QPointF top_left = ValueToPixel(0, row, resolution); + QPointF top_right = ValueToPixel(1, row, resolution); + QPointF bottom_right = ValueToPixel(2, row, resolution); + QPointF bottom_left = ValueToPixel(3, row, resolution); - // Add the correct offset to each slider - SetInputProperty(kTopLeftInput, QStringLiteral("offset"), QVector2D(0.0, 0.0)); - SetInputProperty(kTopRightInput, QStringLiteral("offset"), QVector2D(resolution.x() , 0.0)); - SetInputProperty(kBottomRightInput, QStringLiteral("offset"), resolution); - SetInputProperty(kBottomLeftInput, QStringLiteral("offset"), QVector2D(0.0, resolution.y())); + // Add the correct offset to each slider + SetInputProperty(kTopLeftInput, QStringLiteral("offset"), QVector2D(0.0, 0.0)); + SetInputProperty(kTopRightInput, QStringLiteral("offset"), QVector2D(resolution.x() , 0.0)); + SetInputProperty(kBottomRightInput, QStringLiteral("offset"), resolution); + SetInputProperty(kBottomLeftInput, QStringLiteral("offset"), QVector2D(0.0, resolution.y())); - // Draw bounding box - gizmo_whole_rect_->SetPolygon(QPolygonF({top_left, top_right, bottom_right, bottom_left, top_left})); + // Draw bounding box + gizmo_whole_rect_->SetPolygon(QPolygonF({top_left, top_right, bottom_right, bottom_left, top_left})); - // Create handles - gizmo_resize_handle_[0]->SetPoint(top_left); - gizmo_resize_handle_[1]->SetPoint(top_right); - gizmo_resize_handle_[2]->SetPoint(bottom_right); - gizmo_resize_handle_[3]->SetPoint(bottom_left); + // Create handles + gizmo_resize_handle_[0]->SetPoint(top_left); + gizmo_resize_handle_[1]->SetPoint(top_right); + gizmo_resize_handle_[2]->SetPoint(bottom_right); + gizmo_resize_handle_[3]->SetPoint(bottom_left); + } } } diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index b5bbbef1a..1d2dfd4e3 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -79,7 +79,6 @@ void CropDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global { ShaderJob job; job.Insert(value); - job.SetWillChangeImageSize(false); if (TexturePtr texture = job.Get(kTextureInput).toTexture()) { job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(texture->params().width(), texture->params().height()), this)); @@ -88,7 +87,7 @@ void CropDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global || !qIsNull(job.Get(kRightInput).toDouble()) || !qIsNull(job.Get(kTopInput).toDouble()) || !qIsNull(job.Get(kBottomInput).toDouble())) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + table->Push(NodeValue::kTexture, texture->toJob(job), this); } else { table->Push(job.Get(kTextureInput)); } @@ -103,32 +102,35 @@ ShaderCode CropDistortNode::GetShaderCode(const ShaderRequest &request) const void CropDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { - const QVector2D &resolution = globals.resolution(); + if (TexturePtr tex = row[kTextureInput].toTexture()) { + const QVector2D &resolution = tex->virtual_resolution(); + temp_resolution_ = resolution; - double left_pt = resolution.x() * row[kLeftInput].toDouble(); - double top_pt = resolution.y() * row[kTopInput].toDouble(); - double right_pt = resolution.x() * (1.0 - row[kRightInput].toDouble()); - double bottom_pt = resolution.y() * (1.0 - row[kBottomInput].toDouble()); - double center_x_pt = mid(left_pt, right_pt); - double center_y_pt = mid(top_pt, bottom_pt); + double left_pt = resolution.x() * row[kLeftInput].toDouble(); + double top_pt = resolution.y() * row[kTopInput].toDouble(); + double right_pt = resolution.x() * (1.0 - row[kRightInput].toDouble()); + double bottom_pt = resolution.y() * (1.0 - row[kBottomInput].toDouble()); + double center_x_pt = mid(left_pt, right_pt); + double center_y_pt = mid(top_pt, bottom_pt); - point_gizmo_[kGizmoScaleTopLeft]->SetPoint(QPointF(left_pt, top_pt)); - point_gizmo_[kGizmoScaleTopCenter]->SetPoint(QPointF(center_x_pt, top_pt)); - point_gizmo_[kGizmoScaleTopRight]->SetPoint(QPointF(right_pt, top_pt)); - point_gizmo_[kGizmoScaleBottomLeft]->SetPoint(QPointF(left_pt, bottom_pt)); - point_gizmo_[kGizmoScaleBottomCenter]->SetPoint(QPointF(center_x_pt, bottom_pt)); - point_gizmo_[kGizmoScaleBottomRight]->SetPoint(QPointF(right_pt, bottom_pt)); - point_gizmo_[kGizmoScaleCenterLeft]->SetPoint(QPointF(left_pt, center_y_pt)); - point_gizmo_[kGizmoScaleCenterRight]->SetPoint(QPointF(right_pt, center_y_pt)); + point_gizmo_[kGizmoScaleTopLeft]->SetPoint(QPointF(left_pt, top_pt)); + point_gizmo_[kGizmoScaleTopCenter]->SetPoint(QPointF(center_x_pt, top_pt)); + point_gizmo_[kGizmoScaleTopRight]->SetPoint(QPointF(right_pt, top_pt)); + point_gizmo_[kGizmoScaleBottomLeft]->SetPoint(QPointF(left_pt, bottom_pt)); + point_gizmo_[kGizmoScaleBottomCenter]->SetPoint(QPointF(center_x_pt, bottom_pt)); + point_gizmo_[kGizmoScaleBottomRight]->SetPoint(QPointF(right_pt, bottom_pt)); + point_gizmo_[kGizmoScaleCenterLeft]->SetPoint(QPointF(left_pt, center_y_pt)); + point_gizmo_[kGizmoScaleCenterRight]->SetPoint(QPointF(right_pt, center_y_pt)); - poly_gizmo_->SetPolygon(QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt)); + poly_gizmo_->SetPolygon(QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt)); + } } void CropDistortNode::GizmoDragMove(double x_diff, double y_diff, const Qt::KeyboardModifiers &modifiers) { DraggableGizmo *gizmo = static_cast(sender()); - QVector2D res = gizmo->GetGlobals().resolution(); + QVector2D res = temp_resolution_; x_diff /= res.x(); y_diff /= res.y(); diff --git a/app/node/distort/crop/cropdistortnode.h b/app/node/distort/crop/cropdistortnode.h index 1d6530152..ebbab1946 100644 --- a/app/node/distort/crop/cropdistortnode.h +++ b/app/node/distort/crop/cropdistortnode.h @@ -82,6 +82,7 @@ private: // Gizmo variables PointGizmo *point_gizmo_[kGizmoScaleCount]; PolygonGizmo *poly_gizmo_; + QVector2D temp_resolution_; }; diff --git a/app/node/distort/flip/flipdistortnode.cpp b/app/node/distort/flip/flipdistortnode.cpp index e2355ade7..6a1083099 100644 --- a/app/node/distort/flip/flipdistortnode.cpp +++ b/app/node/distort/flip/flipdistortnode.cpp @@ -77,18 +77,14 @@ ShaderCode FlipDistortNode::GetShaderCode(const ShaderRequest &request) const void FlipDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - - job.Insert(value); - // If there's no texture, no need to run an operation - if (job.Get(kTextureInput).toTexture()) { + if (TexturePtr tex = value[kTextureInput].toTexture()) { // Only run shader if at least one of flip or flop are selected - if (job.Get(kHorizontalInput).toBool() || job.Get(kVerticalInput).toBool()) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (value[kHorizontalInput].toBool() || value[kVerticalInput].toBool()) { + table->Push(NodeValue::kTexture, tex->toJob(ShaderJob(value)), this); } else { // If we're not flipping or flopping just push the texture - table->Push(job.Get(kTextureInput)); + table->Push(value[kTextureInput]); } } } diff --git a/app/node/distort/mask/mask.cpp b/app/node/distort/mask/mask.cpp index f19b8e961..dc1941b03 100644 --- a/app/node/distort/mask/mask.cpp +++ b/app/node/distort/mask/mask.cpp @@ -64,16 +64,19 @@ void MaskDistortNode::Retranslate() void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - NodeValue job(NodeValue::kTexture, GetGenerateJob(value), this); + TexturePtr texture = value[kBaseInput].toTexture(); + + VideoParams job_params = texture ? texture->params() : globals.vparams(); + NodeValue job(NodeValue::kTexture, Texture::Job(job_params, GetGenerateJob(value, job_params)), this); if (value[kInvertInput].toBool()) { ShaderJob invert; invert.SetShaderID(QStringLiteral("invert")); invert.Insert(QStringLiteral("tex_in"), job); - job.set_value(invert); + job.set_value(Texture::Job(job_params, invert)); } - if (value[kBaseInput].toTexture()) { + if (texture) { // Push as merge node ShaderJob merge; @@ -92,14 +95,14 @@ void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global feather.Insert(BlurFilterNode::kRepeatEdgePixelsInput, NodeValue(NodeValue::kBoolean, true, this)); feather.Insert(BlurFilterNode::kRadiusInput, NodeValue(NodeValue::kFloat, value[kFeatherInput].toDouble(), this)); feather.SetIterations(2, BlurFilterNode::kTextureInput); - feather.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + feather.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, texture ? texture->virtual_resolution() : globals.square_resolution(), this)); - merge.Insert(QStringLiteral("tex_b"), NodeValue(NodeValue::kTexture, feather, this)); + merge.Insert(QStringLiteral("tex_b"), NodeValue(NodeValue::kTexture, Texture::Job(job_params, feather), this)); } else { merge.Insert(QStringLiteral("tex_b"), job); } - table->Push(NodeValue::kTexture, merge, this); + table->Push(NodeValue::kTexture, Texture::Job(job_params, merge), this); } else { table->Push(job); } diff --git a/app/node/distort/ripple/rippledistortnode.cpp b/app/node/distort/ripple/rippledistortnode.cpp index bddb679d4..2e137ff3c 100644 --- a/app/node/distort/ripple/rippledistortnode.cpp +++ b/app/node/distort/ripple/rippledistortnode.cpp @@ -94,28 +94,26 @@ ShaderCode RippleDistortNode::GetShaderCode(const ShaderRequest &request) const void RippleDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - - job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - // If there's no texture, no need to run an operation - if (job.Get(kTextureInput).toTexture()) { + if (TexturePtr tex = value[kTextureInput].toTexture()) { // Only run shader if at least one of flip or flop are selected - if (!qIsNull(job.Get(kIntensityInput).toDouble())) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (!qIsNull(value[kIntensityInput].toDouble())) { + ShaderJob job(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); + table->Push(NodeValue::kTexture, tex->toJob(job), this); } else { // If we're not flipping or flopping just push the texture - table->Push(job.Get(kTextureInput)); + table->Push(value[kTextureInput]); } } } void RippleDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { - QPointF half_res(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2); - - gizmo_->SetPoint(half_res + row[kPositionInput].toVec2().toPointF()); + if (TexturePtr tex = row[kTextureInput].toTexture()) { + QPointF half_res(tex->virtual_resolution().x()/2, tex->virtual_resolution().y()/2); + gizmo_->SetPoint(half_res + row[kPositionInput].toVec2().toPointF()); + } } void RippleDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) diff --git a/app/node/distort/swirl/swirldistortnode.cpp b/app/node/distort/swirl/swirldistortnode.cpp index 658feb134..ddab58a60 100644 --- a/app/node/distort/swirl/swirldistortnode.cpp +++ b/app/node/distort/swirl/swirldistortnode.cpp @@ -89,26 +89,23 @@ ShaderCode SwirlDistortNode::GetShaderCode(const ShaderRequest &request) const void SwirlDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - - job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - // If there's no texture, no need to run an operation - if (job.Get(kTextureInput).toTexture()) { + if (TexturePtr tex = value[kTextureInput].toTexture()) { // Only run shader if at least one of flip or flop are selected - if (!qIsNull(job.Get(kAngleInput).toDouble()) && !qIsNull(job.Get(kRadiusInput).toDouble())) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (!qIsNull(value[kAngleInput].toDouble()) && !qIsNull(value[kRadiusInput].toDouble())) { + ShaderJob job(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); + table->Push(NodeValue::kTexture, tex->toJob(job), this); } else { // If we're not flipping or flopping just push the texture - table->Push(job.Get(kTextureInput)); + table->Push(value[kTextureInput]); } } } void SwirlDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { - QPointF half_res(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2); + QPointF half_res(globals.square_resolution().x()/2, globals.square_resolution().y()/2); gizmo_->SetPoint(half_res + row[kPositionInput].toVec2().toPointF()); } diff --git a/app/node/distort/tile/tiledistortnode.cpp b/app/node/distort/tile/tiledistortnode.cpp index acdb32aee..ec13a1ee4 100644 --- a/app/node/distort/tile/tiledistortnode.cpp +++ b/app/node/distort/tile/tiledistortnode.cpp @@ -110,47 +110,46 @@ ShaderCode TileDistortNode::GetShaderCode(const ShaderRequest &request) const void TileDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - - job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - // If there's no texture, no need to run an operation - if (job.Get(kTextureInput).toTexture()) { + if (TexturePtr tex = value[kTextureInput].toTexture()) { // Only run shader if at least one of flip or flop are selected - if (!qFuzzyCompare(job.Get(kScaleInput).toDouble(), 1.0)) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (!qFuzzyCompare(value[kScaleInput].toDouble(), 1.0)) { + ShaderJob job(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); + table->Push(NodeValue::kTexture, tex->toJob(job), this); } else { // If we're not flipping or flopping just push the texture - table->Push(job.Get(kTextureInput)); + table->Push(value[kTextureInput]); } } } void TileDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { - QPointF res = globals.resolution_by_par().toPointF(); - QPointF pos = row[kPositionInput].toVec2().toPointF(); - qreal x = pos.x(); - qreal y = pos.y(); + if (TexturePtr tex = row[kTextureInput].toTexture()) { + QPointF res = tex->virtual_resolution().toPointF(); + QPointF pos = row[kPositionInput].toVec2().toPointF(); + qreal x = pos.x(); + qreal y = pos.y(); - Anchor a = static_cast(row[kAnchorInput].toInt()); - if (a == kTopLeft || a == kTopCenter || a == kTopRight) { - // Do nothing - } else if (a == kMiddleLeft || a == kMiddleCenter || a == kMiddleRight) { - y += res.y()/2; - } else if (a == kBottomLeft || a == kBottomCenter || a == kBottomRight) { - y += res.y(); - } - if (a == kTopLeft || a == kMiddleLeft || a == kBottomLeft) { - // Do nothing - } else if (a == kTopCenter || a == kMiddleCenter || a == kBottomCenter) { - x += res.x()/2; - } else if (a == kTopRight || a == kMiddleRight || a == kBottomRight) { - x += res.x(); - } + Anchor a = static_cast(row[kAnchorInput].toInt()); + if (a == kTopLeft || a == kTopCenter || a == kTopRight) { + // Do nothing + } else if (a == kMiddleLeft || a == kMiddleCenter || a == kMiddleRight) { + y += res.y()/2; + } else if (a == kBottomLeft || a == kBottomCenter || a == kBottomRight) { + y += res.y(); + } + if (a == kTopLeft || a == kMiddleLeft || a == kBottomLeft) { + // Do nothing + } else if (a == kTopCenter || a == kMiddleCenter || a == kBottomCenter) { + x += res.x()/2; + } else if (a == kTopRight || a == kMiddleRight || a == kBottomRight) { + x += res.x(); + } - gizmo_->SetPoint(QPointF(x, y)); + gizmo_->SetPoint(QPointF(x, y)); + } } void TileDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index ab58b3378..b19934615 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -89,7 +89,7 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g // Pop texture NodeValue texture_meta = value[kTextureInput]; - QVariant job_to_push; + TexturePtr job_to_push = nullptr; // If we have a texture, generate a matrix and make it happen if (TexturePtr texture = texture_meta.toTexture()) { @@ -99,17 +99,18 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g if (!real_matrix.isIdentity()) { // The matrix will transform things ShaderJob job; - job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture), this)); + job.Insert(QStringLiteral("ove_maintex"), texture_meta); job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, real_matrix, this)); job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast(value[kInterpolationInput].toInt())); - job_to_push = QVariant::fromValue(job); + // Use global resolution rather than texture resolution because this may result in a size change + job_to_push = Texture::Job(globals.vparams(), job); } } table->Push(NodeValue::kMatrix, QVariant::fromValue(generated_matrix), this); - if (job_to_push.isNull()) { + if (!job_to_push) { // Re-push whatever value we received table->Push(texture_meta); } else { @@ -142,7 +143,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou } gizmo_scale_uniform_ = row[kUniformScaleInput].toBool(); - gizmo_anchor_pt_ = (row[kAnchorInput].toVec2() + gizmo->GetGlobals().resolution()/2).toPointF(); + gizmo_anchor_pt_ = (row[kAnchorInput].toVec2() + gizmo->GetGlobals().nonsquare_resolution()/2).toPointF(); if (gizmo == point_gizmo_[kGizmoScaleTopLeft] || gizmo == point_gizmo_[kGizmoScaleTopRight] || gizmo == point_gizmo_[kGizmoScaleBottomLeft] || gizmo == point_gizmo_[kGizmoScaleBottomRight]) { @@ -177,7 +178,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou } else if (gizmo == rotation_gizmo_) { - gizmo_anchor_pt_ = (row[kAnchorInput].toVec2() + gizmo->GetGlobals().resolution()/2).toPointF(); + gizmo_anchor_pt_ = (row[kAnchorInput].toVec2() + gizmo->GetGlobals().nonsquare_resolution()/2).toPointF(); gizmo_start_angle_ = qAtan2(y - gizmo_anchor_pt_.y(), x - gizmo_anchor_pt_.x()); gizmo_last_angle_ = gizmo_start_angle_; gizmo_last_alt_angle_ = qAtan2(x - gizmo_anchor_pt_.x(), y - gizmo_anchor_pt_.y()); @@ -343,7 +344,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N } // Get the sequence resolution - const QVector2D &sequence_res = globals.resolution(); + const QVector2D &sequence_res = globals.nonsquare_resolution(); QVector2D sequence_half_res = sequence_res * 0.5; QPointF sequence_half_res_pt = sequence_half_res.toPointF(); @@ -418,7 +419,7 @@ QPointF TransformDistortNode::CreateScalePoint(double x, double y, const QPointF QMatrix4x4 TransformDistortNode::GenerateAutoScaledMatrix(const QMatrix4x4& generated_matrix, const NodeValueRow& value, const NodeGlobals &globals, const VideoParams& texture_params) const { - const QVector2D &sequence_res = globals.resolution(); + const QVector2D &sequence_res = globals.nonsquare_resolution(); QVector2D texture_res(texture_params.square_pixel_width(), texture_params.height()); AutoScaleType autoscale = static_cast(value[kAutoscaleInput].toInt()); diff --git a/app/node/distort/wave/wavedistortnode.cpp b/app/node/distort/wave/wavedistortnode.cpp index 6c49c1e0a..d3f948585 100644 --- a/app/node/distort/wave/wavedistortnode.cpp +++ b/app/node/distort/wave/wavedistortnode.cpp @@ -84,18 +84,14 @@ ShaderCode WaveDistortNode::GetShaderCode(const ShaderRequest &request) const void WaveDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - - job.Insert(value); - // If there's no texture, no need to run an operation - if (job.Get(kTextureInput).toTexture()) { + if (TexturePtr texture = value[kTextureInput].toTexture()) { // Only run shader if at least one of flip or flop are selected - if (!qIsNull(job.Get(kIntensityInput).toDouble())) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (!qIsNull(value[kIntensityInput].toDouble())) { + table->Push(NodeValue::kTexture, Texture::Job(texture->params(), ShaderJob(value)), this); } else { // If we're not flipping or flopping just push the texture - table->Push(job.Get(kTextureInput)); + table->Push(value[kTextureInput]); } } diff --git a/app/node/effect/opacity/opacityeffect.cpp b/app/node/effect/opacity/opacityeffect.cpp index 6c60b36f1..cc0f2d2c0 100644 --- a/app/node/effect/opacity/opacityeffect.cpp +++ b/app/node/effect/opacity/opacityeffect.cpp @@ -45,17 +45,13 @@ ShaderCode OpacityEffect::GetShaderCode(const ShaderRequest &request) const void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - - job.Insert(value); - // If there's no texture, no need to run an operation - if (job.Get(kTextureInput).toTexture()) { - if (!qFuzzyCompare(job.Get(kValueInput).toDouble(), 1.0)) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (TexturePtr tex = value[kTextureInput].toTexture()) { + if (!qFuzzyCompare(value[kValueInput].toDouble(), 1.0)) { + table->Push(NodeValue::kTexture, tex->toJob(ShaderJob(value)), this); } else { // 1.0 float is a no-op, so just push the texture - table->Push(job.Get(kTextureInput)); + table->Push(value[kTextureInput]); } } } diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index 0ac4eb6d5..3955608b7 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -120,33 +120,28 @@ ShaderCode BlurFilterNode::GetShaderCode(const ShaderRequest &request) const void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { // If there's no texture, no need to run an operation - if (value[kTextureInput].toTexture()) { - - ShaderJob job; - - job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - - Method method = static_cast(job.Get(kMethodInput).toInt()); + if (TexturePtr tex = value[kTextureInput].toTexture()) { + Method method = static_cast(value[kMethodInput].toInt()); bool can_push_job = true; + int iterations = 1; // Check if radius is > 0 - if (job.Get(kRadiusInput).toDouble() > 0.0) { + if (value[kRadiusInput].toDouble() > 0.0) { // Method-specific considerations switch (method) { case kBox: case kGaussian: { - bool horiz = job.Get(kHorizInput).toBool(); - bool vert = job.Get(kVertInput).toBool(); + bool horiz = value[kHorizInput].toBool(); + bool vert = value[kVertInput].toBool(); if (!horiz && !vert) { // Disable job if horiz and vert are unchecked can_push_job = false; } else if (horiz && vert) { // Set iteration count to 2 if we're blurring both horizontally and vertically - job.SetIterations(2, kTextureInput); + iterations = 2; } break; } @@ -159,10 +154,13 @@ void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals } if (can_push_job) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + ShaderJob job(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); + job.SetIterations(iterations, kTextureInput); + table->Push(NodeValue::kTexture, tex->toJob(job), this); } else { // If we're not performing the blur job, just push the texture - table->Push(job.Get(kTextureInput)); + table->Push(value[kTextureInput]); } } @@ -170,16 +168,18 @@ void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals void BlurFilterNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { - if (row[kMethodInput].toInt() == kRadial) { - const QVector2D &sequence_res = globals.resolution(); - QVector2D sequence_half_res = sequence_res * 0.5; + if (TexturePtr tex = row[kTextureInput].toTexture()) { + if (row[kMethodInput].toInt() == kRadial) { + const QVector2D &sequence_res = tex->virtual_resolution(); + QVector2D sequence_half_res = sequence_res * 0.5; - radial_center_gizmo_->SetVisible(true); - radial_center_gizmo_->SetPoint(sequence_half_res.toPointF() + row[kRadialCenterInput].toVec2().toPointF()); + radial_center_gizmo_->SetVisible(true); + radial_center_gizmo_->SetPoint(sequence_half_res.toPointF() + row[kRadialCenterInput].toVec2().toPointF()); - SetInputProperty(kRadialCenterInput, QStringLiteral("offset"), sequence_half_res); - } else{ - radial_center_gizmo_->SetVisible(false); + SetInputProperty(kRadialCenterInput, QStringLiteral("offset"), sequence_half_res); + } else{ + radial_center_gizmo_->SetVisible(false); + } } } diff --git a/app/node/filter/dropshadow/dropshadowfilter.cpp b/app/node/filter/dropshadow/dropshadowfilter.cpp index a20b20f23..f8c60cdd5 100644 --- a/app/node/filter/dropshadow/dropshadowfilter.cpp +++ b/app/node/filter/dropshadow/dropshadowfilter.cpp @@ -78,20 +78,19 @@ ShaderCode DropShadowFilter::GetShaderCode(const ShaderRequest &request) const void DropShadowFilter::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - if (value[kTextureInput].toTexture()) { - ShaderJob job; + if (TexturePtr tex = value[kTextureInput].toTexture()) { + ShaderJob job(value); QString iterative = QStringLiteral("previous_iteration_in"); - job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); job.Insert(iterative, value[kTextureInput]); if (!qIsNull(value[kSoftnessInput].toDouble())) { job.SetIterations(3, iterative); } - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + table->Push(NodeValue::kTexture, tex->toJob(job), this); } } diff --git a/app/node/filter/mosaic/mosaicfilternode.cpp b/app/node/filter/mosaic/mosaicfilternode.cpp index e5b5af8ca..9cb5f9af7 100644 --- a/app/node/filter/mosaic/mosaicfilternode.cpp +++ b/app/node/filter/mosaic/mosaicfilternode.cpp @@ -53,22 +53,18 @@ void MosaicFilterNode::Retranslate() void MosaicFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - - job.Insert(value); - - // Mipmapping makes this look weird, so we just use bilinear for finding the color of each block - job.SetInterpolation(kTextureInput, Texture::kLinear); - - if (job.Get(kTextureInput).toTexture()) { - TexturePtr texture = job.Get(kTextureInput).toTexture(); - + if (TexturePtr texture = value[kTextureInput].toTexture()) { if (texture - && job.Get(kHorizInput).toInt() != texture->width() - && job.Get(kVertInput).toInt() != texture->height()) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + && value[kHorizInput].toInt() != texture->width() + && value[kVertInput].toInt() != texture->height()) { + ShaderJob job(value); + + // Mipmapping makes this look weird, so we just use bilinear for finding the color of each block + job.SetInterpolation(kTextureInput, Texture::kLinear); + + table->Push(NodeValue::kTexture, texture->toJob(job), this); } else { - table->Push(job.Get(kTextureInput)); + table->Push(value[kTextureInput]); } } } diff --git a/app/node/filter/stroke/stroke.cpp b/app/node/filter/stroke/stroke.cpp index f8adc3f1f..389edb53b 100644 --- a/app/node/filter/stroke/stroke.cpp +++ b/app/node/filter/stroke/stroke.cpp @@ -86,17 +86,14 @@ void StrokeFilterNode::Retranslate() void StrokeFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - - job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - - if (job.Get(kTextureInput).toTexture()) { - if (job.Get(kRadiusInput).toDouble() > 0.0 - && job.Get(kOpacityInput).toDouble() > 0.0) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (TexturePtr tex = value[kTextureInput].toTexture()) { + if (value[kRadiusInput].toDouble() > 0.0 + && value[kOpacityInput].toDouble() > 0.0) { + ShaderJob job(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); + table->Push(NodeValue::kTexture, tex->toJob(job), this); } else { - table->Push(job.Get(kTextureInput)); + table->Push(value[kTextureInput]); } } } diff --git a/app/node/generator/noise/noise.cpp b/app/node/generator/noise/noise.cpp index 074a78278..94072db1d 100644 --- a/app/node/generator/noise/noise.cpp +++ b/app/node/generator/noise/noise.cpp @@ -80,11 +80,13 @@ ShaderCode NoiseGeneratorNode::GetShaderCode(const ShaderRequest &request) const void NoiseGeneratorNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; + ShaderJob job(value); job.Insert(value); job.Insert(QStringLiteral("time_in"), NodeValue(NodeValue::kFloat, globals.time().in().toDouble(), this)); - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + TexturePtr base = value[kBaseIn].toTexture(); + + table->Push(NodeValue::kTexture, Texture::Job(base ? base->params() : globals.vparams(), job), this); } } diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index ec65f8b7f..82a823556 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -87,12 +87,11 @@ void PolygonGenerator::Retranslate() SetInputName(kColorInput, tr("Color")); } -ShaderJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value) const +ShaderJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value, const VideoParams ¶ms) const { - GenerateJob job; - - job.Insert(value); - job.SetRequestedFormat(VideoParams::kFormatUnsigned8); + VideoParams p = params; + p.set_format(VideoParams::kFormatUnsigned8); + auto job = Texture::Job(p, GenerateJob(value)); // Conversion to RGB ShaderJob rgb; @@ -105,9 +104,7 @@ ShaderJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value) const void PolygonGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job = GetGenerateJob(value); - - PushMergableJob(value, QVariant::fromValue(job), table); + PushMergableJob(value, Texture::Job(globals.vparams(), GetGenerateJob(value, globals.vparams())), table); } void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) const @@ -169,7 +166,14 @@ void PolygonGenerator::ValidateGizmoVectorSize(QVector &vec, int new_sz) void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { - QPointF half_res(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2); + QVector2D res; + if (TexturePtr tex = row[kBaseInput].toTexture()) { + res = tex->virtual_resolution(); + } else { + res = globals.square_resolution(); + } + + QPointF half_res = res.toPointF()/2; QVector points = row[kPointsInput].value< QVector >(); diff --git a/app/node/generator/polygon/polygon.h b/app/node/generator/polygon/polygon.h index dd7e5623b..e768c2230 100644 --- a/app/node/generator/polygon/polygon.h +++ b/app/node/generator/polygon/polygon.h @@ -60,7 +60,7 @@ public: static const QString kColorInput; protected: - ShaderJob GetGenerateJob(const NodeValueRow &value) const; + ShaderJob GetGenerateJob(const NodeValueRow &value, const VideoParams ¶ms) const; protected slots: virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; diff --git a/app/node/generator/shape/generatorwithmerge.cpp b/app/node/generator/shape/generatorwithmerge.cpp index 8174b5bf2..8622c1881 100644 --- a/app/node/generator/shape/generatorwithmerge.cpp +++ b/app/node/generator/shape/generatorwithmerge.cpp @@ -51,17 +51,17 @@ ShaderCode GeneratorWithMerge::GetShaderCode(const ShaderRequest &request) const return ShaderCode(); } -void GeneratorWithMerge::PushMergableJob(const NodeValueRow &value, const QVariant &job, NodeValueTable *table) const +void GeneratorWithMerge::PushMergableJob(const NodeValueRow &value, TexturePtr job, NodeValueTable *table) const { - if (value[kBaseInput].toTexture()) { + if (TexturePtr base = value[kBaseInput].toTexture()) { // Push as merge node ShaderJob merge; merge.SetShaderID(QStringLiteral("mrg")); merge.Insert(MergeNode::kBaseIn, value[kBaseInput]); - merge.Insert(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, job, this)); + merge.Insert(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, base->toJob(*job->job()), this)); - table->Push(NodeValue::kTexture, QVariant::fromValue(merge), this); + table->Push(NodeValue::kTexture, base->toJob(merge), this); } else { // Just push generate job table->Push(NodeValue::kTexture, job, this); diff --git a/app/node/generator/shape/generatorwithmerge.h b/app/node/generator/shape/generatorwithmerge.h index fb7d729f0..cc462a559 100644 --- a/app/node/generator/shape/generatorwithmerge.h +++ b/app/node/generator/shape/generatorwithmerge.h @@ -38,7 +38,7 @@ public: static const QString kBaseInput; protected: - void PushMergableJob(const NodeValueRow &value, const QVariant &job, NodeValueTable *table) const; + void PushMergableJob(const NodeValueRow &value, TexturePtr job, NodeValueTable *table) const; }; diff --git a/app/node/generator/shape/shapenode.cpp b/app/node/generator/shape/shapenode.cpp index d4808d768..b8f5a7132 100644 --- a/app/node/generator/shape/shapenode.cpp +++ b/app/node/generator/shape/shapenode.cpp @@ -77,13 +77,14 @@ ShaderCode ShapeNode::GetShaderCode(const ShaderRequest &request) const void ShapeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; + TexturePtr base = value[kBaseInput].toTexture(); - job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + ShaderJob job(value); + + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, base ? base->virtual_resolution() : globals.square_resolution(), this)); job.SetShaderID(QStringLiteral("shape")); - PushMergableJob(value, QVariant::fromValue(job), table); + PushMergableJob(value, Texture::Job(base ? base->params() : globals.vparams(), job), table); } void ShapeNode::InputValueChangedEvent(const QString &input, int element) diff --git a/app/node/generator/shape/shapenodebase.cpp b/app/node/generator/shape/shapenodebase.cpp index 582725ec4..2a008526d 100644 --- a/app/node/generator/shape/shapenodebase.cpp +++ b/app/node/generator/shape/shapenodebase.cpp @@ -77,7 +77,7 @@ void ShapeNodeBase::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlob { // Use offsets to make the appearance of values that start in the top left, even though we // really anchor around the center - QVector2D center_pt = globals.resolution() * 0.5; + QVector2D center_pt = globals.square_resolution() * 0.5; SetInputProperty(kPositionInput, QStringLiteral("offset"), center_pt); QVector2D pos = row[kPositionInput].toVec2(); @@ -137,7 +137,7 @@ void ShapeNodeBase::GizmoDragMove(double x, double y, const Qt::KeyboardModifier QVector2D gizmo_sz_start(w_drag.GetStartValue().toDouble(), h_drag.GetStartValue().toDouble()); QVector2D gizmo_pos_start(x_drag.GetStartValue().toDouble(), y_drag.GetStartValue().toDouble()); - QVector2D gizmo_half_res = gizmo->GetGlobals().resolution()/2; + QVector2D gizmo_half_res = gizmo->GetGlobals().square_resolution()/2; QVector2D adjusted_pt(x, y); QVector2D new_size; QVector2D new_pos; diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp index a99a7f370..d485a48f9 100644 --- a/app/node/generator/solid/solid.cpp +++ b/app/node/generator/solid/solid.cpp @@ -63,9 +63,7 @@ void SolidGenerator::Retranslate() void SolidGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - job.Insert(value); - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + table->Push(NodeValue::kTexture, Texture::Job(globals.vparams(), ShaderJob(value)), this); } ShaderCode SolidGenerator::GetShaderCode(const ShaderRequest &request) const diff --git a/app/node/generator/text/textv1.cpp b/app/node/generator/text/textv1.cpp index 93d36737f..68c3a5e7d 100644 --- a/app/node/generator/text/textv1.cpp +++ b/app/node/generator/text/textv1.cpp @@ -92,11 +92,8 @@ void TextGeneratorV1::Retranslate() void TextGeneratorV1::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - GenerateJob job; - job.Insert(value); - - if (!job.Get(kTextInput).toString().isEmpty()) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (!value[kTextInput].toString().isEmpty()) { + table->Push(NodeValue::kTexture, Texture::Job(globals.vparams(), GenerateJob(value)), this); } } diff --git a/app/node/generator/text/textv2.cpp b/app/node/generator/text/textv2.cpp index e3032e55a..7cf82cd83 100644 --- a/app/node/generator/text/textv2.cpp +++ b/app/node/generator/text/textv2.cpp @@ -94,12 +94,11 @@ void TextGeneratorV2::Retranslate() void TextGeneratorV2::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - GenerateJob job; - job.Insert(value); - job.SetRequestedFormat(VideoParams::kFormatFloat32); - - if (!job.Get(kTextInput).toString().isEmpty()) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (!value[kTextInput].toString().isEmpty()) { + GenerateJob job(value); + auto text_params = globals.vparams(); + text_params.set_format(VideoParams::kFormatFloat32); + table->Push(NodeValue::kTexture, Texture::Job(text_params, job), this); } } diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index a723e67aa..afe900778 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -98,9 +98,7 @@ void TextGeneratorV3::Retranslate() void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - GenerateJob job; - job.Insert(value); - job.SetRequestedFormat(VideoParams::kFormatUnsigned8); + QString text = value[kTextInput].toString(); if (value[kUseArgsInput].toBool()) { auto args = value[kArgsInput].toArray(); @@ -111,17 +109,21 @@ void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &global list.append(args[i].toString()); } - NodeValue v = job.Get(kTextInput); - v.set_value(FormatString(v.toString(), list)); - job.Insert(kTextInput, v); + text = FormatString(text, list); } } - // FIXME: Provide user override for this - job.SetColorspace(project()->color_manager()->GetDefaultInputColorSpace()); + if (!text.isEmpty()) { + TexturePtr base = value[kTextInput].toTexture(); - if (!job.Get(kTextInput).toString().isEmpty()) { - PushMergableJob(value, QVariant::fromValue(job), table); + VideoParams text_params = base ? base->params() : globals.vparams(); + text_params.set_format(VideoParams::kFormatUnsigned8); + text_params.set_colorspace(project()->color_manager()->GetDefaultInputColorSpace()); + + GenerateJob job(value); + job.Insert(kTextInput, NodeValue(NodeValue::kText, text)); + + PushMergableJob(value, Texture::Job(text_params, job), table); } else if (value[kBaseInput].toTexture()) { table->Push(value[kBaseInput]); } diff --git a/app/node/globals.h b/app/node/globals.h index be1d11d6f..4db941be9 100644 --- a/app/node/globals.h +++ b/app/node/globals.h @@ -39,9 +39,9 @@ public: { } - QVector2D resolution() const { return video_params_.resolution(); } - QVector2D resolution_by_par() const { return video_params_.square_resolution(); } - const VideoParams &video_params() const { return video_params_; } + QVector2D square_resolution() const { return video_params_.square_resolution(); } + QVector2D nonsquare_resolution() const { return video_params_.resolution(); } + const VideoParams &vparams() const { return video_params_; } const TimeRange &time() const { return time_; } private: diff --git a/app/node/keying/chromakey/chromakey.cpp b/app/node/keying/chromakey/chromakey.cpp index 70babc998..f78d4db9a 100644 --- a/app/node/keying/chromakey/chromakey.cpp +++ b/app/node/keying/chromakey/chromakey.cpp @@ -128,16 +128,17 @@ void ChromaKeyNode::GenerateProcessor() void ChromaKeyNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - if (value[kTextureInput].toTexture() && processor()) { - ColorTransformJob job; + if (TexturePtr tex = value[kTextureInput].toTexture()) { + if (processor()) { + ColorTransformJob job(value); - job.Insert(value); - job.SetColorProcessor(processor()); - job.SetInputTexture(value[kTextureInput].toTexture()); - job.SetNeedsCustomShader(this); - job.SetFunctionName(QStringLiteral("SceneLinearToCIEXYZ_d65")); + job.SetColorProcessor(processor()); + job.SetInputTexture(value[kTextureInput]); + job.SetNeedsCustomShader(this); + job.SetFunctionName(QStringLiteral("SceneLinearToCIEXYZ_d65")); - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + table->Push(NodeValue::kTexture, tex->toJob(job), this); + } } } diff --git a/app/node/keying/colordifferencekey/colordifferencekey.cpp b/app/node/keying/colordifferencekey/colordifferencekey.cpp index 7ee988d66..8627efdda 100644 --- a/app/node/keying/colordifferencekey/colordifferencekey.cpp +++ b/app/node/keying/colordifferencekey/colordifferencekey.cpp @@ -93,12 +93,11 @@ ShaderCode ColorDifferenceKeyNode::GetShaderCode(const ShaderRequest &request) c void ColorDifferenceKeyNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - job.Insert(value); - // If there's no texture, no need to run an operation - if (job.Get(kTextureInput).toTexture()) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (TexturePtr tex = value[kTextureInput].toTexture()) { + ShaderJob job; + job.Insert(value); + table->Push(NodeValue::kTexture, tex->toJob(job), this); } } diff --git a/app/node/keying/despill/despill.cpp b/app/node/keying/despill/despill.cpp index 3f8e22723..8b91d3dfd 100644 --- a/app/node/keying/despill/despill.cpp +++ b/app/node/keying/despill/despill.cpp @@ -91,8 +91,8 @@ void DespillNode::Value(const NodeValueRow &value, const NodeGlobals &globals, N NodeValue(NodeValue::kVec3, QVector3D(luma_coeffs[0], luma_coeffs[1], luma_coeffs[2]))); // If there's no texture, no need to run an operation - if (job.Get(kTextureInput).toTexture()) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (TexturePtr tex = job.Get(kTextureInput).toTexture()) { + table->Push(NodeValue::kTexture, tex->toJob(job), this); } } diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 7f3c12440..4ed0ea5f6 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -358,7 +358,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt } } else if (pairing == kPairTextureMatrix) { // Only allow matrix multiplication - const QVector2D &sequence_res = globals.resolution(); + const QVector2D &sequence_res = globals.nonsquare_resolution(); QVector2D texture_res(texture->params().width() * texture->pixel_aspect_ratio().toDouble(), texture->params().height()); QMatrix4x4 adjusted_matrix = TransformDistortNode::AdjustMatrixByResolutions(number_val.toMatrix(), @@ -380,7 +380,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt output->Push(texture_val); } else { // Push shader job - output->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + output->Push(NodeValue::kTexture, Texture::Job(globals.vparams(), job), this); } break; } diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index acf271869..2285a6c32 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -76,21 +76,19 @@ ShaderCode MergeNode::GetShaderCode(const ShaderRequest &request) const void MergeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - job.Insert(value); - TexturePtr base_tex = job.Get(kBaseIn).toTexture(); - TexturePtr blend_tex = job.Get(kBlendIn).toTexture(); + TexturePtr base_tex = value[kBaseIn].toTexture(); + TexturePtr blend_tex = value[kBlendIn].toTexture(); if (base_tex || blend_tex) { if (!base_tex || (blend_tex && blend_tex->channel_count() < VideoParams::kRGBAChannelCount)) { // We only have a blend texture or the blend texture is RGB only, no need to alpha over - table->Push(job.Get(kBlendIn)); + table->Push(value[kBlendIn]); } else if (!blend_tex) { // We only have a base texture, no need to alpha over - table->Push(job.Get(kBaseIn)); + table->Push(value[kBaseIn]); } else { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + table->Push(NodeValue::kTexture, base_tex->toJob(ShaderJob(value)), this); } } } diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index cbc600779..307ce247b 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -267,26 +267,31 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV Track::Reference ref = GetReferenceFromRealIndex(i); FootageJob job(globals.time(), decoder_, filename(), ref.type(), GetLength()); - NodeValue::Type type; - if (ref.type() == Track::kVideo) { VideoParams vp = GetVideoParams(ref.index()); // Ensure the colorspace is valid and not empty vp.set_colorspace(GetColorspaceToUse(vp)); + // Adjust footage job's divider + if (globals.vparams().divider() > 1) { + // Use a divider appropriate for this target resolution + vp.set_divider(VideoParams::GetDividerForTargetResolution(vp.width(), vp.height(), globals.vparams().effective_width(), globals.vparams().effective_height())); + } else { + // Render everything at full res + vp.set_divider(1); + } + job.set_video_params(vp); - type = NodeValue::kTexture; + table->Push(NodeValue::kTexture, Texture::Job(vp, job), this, ref.ToString()); } else { AudioParams ap = GetAudioParams(ref.index()); job.set_audio_params(ap); job.set_cache_path(project()->cache_path()); - type = NodeValue::kSamples; + table->Push(NodeValue::kSamples, QVariant::fromValue(job), this, ref.ToString()); } - - table->Push(type, QVariant::fromValue(job), this, ref.ToString()); } } } diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 6b1dae1cf..028028f46 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -61,7 +61,7 @@ NodeValueRow NodeTraverser::GenerateRow(NodeValueDatabase *database, const Node row.insert(it.key(), value); } - PreProcessRow(row); + //PreProcessRow(row); return row; } @@ -110,13 +110,15 @@ NodeValue NodeTraverser::GenerateRowValueElement(const Node *node, const QString NodeValue value = table->TakeAt(value_index); if (value.type() == NodeValue::kTexture && UseCache()) { - QMutexLocker locker(node->video_frame_cache()->mutex()); + if (TexturePtr tex = value.toTexture()) { + QMutexLocker locker(node->video_frame_cache()->mutex()); - node->video_frame_cache()->LoadState(); + node->video_frame_cache()->LoadState(); - QString cache = node->video_frame_cache()->GetValidCacheFilename(time.in()); - if (!cache.isEmpty()) { - value.set_value(CacheJob(cache, value.data())); + QString cache = node->video_frame_cache()->GetValidCacheFilename(time.in()); + if (!cache.isEmpty()) { + value.set_value(tex->toJob(CacheJob(cache, value))); + } } } @@ -174,25 +176,6 @@ NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams ¶ms, const Time return NodeGlobals(params, time); } -int NodeTraverser::GetChannelCountFromJob(const GenerateJob &job) -{ - return VideoParams::kRGBAChannelCount; -} - -TexturePtr NodeTraverser::GetMainTextureFromJob(const GenerateJob &job) -{ - // FIXME: Should probably take Node::GetEffectInput into account here - for (auto it=job.GetValues().cbegin(); it!=job.GetValues().cend(); it++) { - if (it.value().type() == NodeValue::kTexture) { - if (TexturePtr t = it.value().toTexture()) { - return t; - } - } - } - - return nullptr; -} - NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range) { // If input is connected, retrieve value directly @@ -277,7 +260,13 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang return GenerateBlockTable(track, range); } - // FIXME: Cache certain values here if we've already processed them before + // Use table cache to skip processing where available + if (value_cache_.contains(n)) { + QHash &node_value_map = value_cache_[n]; + if (node_value_map.contains(range)) { + return node_value_map.value(range); + } + } // Generate row for node NodeValueDatabase database = GenerateDatabase(n, range); @@ -291,11 +280,13 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang is_enabled = database[Node::kEnabledInput].Get(NodeValue::kBoolean).toBool(); } + NodeValueTable table; + if (is_enabled) { NodeValueRow row = GenerateRow(&database, n, range); // Generate output table - NodeValueTable table = database.Merge(); + table = database.Merge(); // By this point, the node should have all the inputs it needs to render correctly NodeGlobals globals = GenerateGlobals(video_params_, range); @@ -316,8 +307,6 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang transform_now_ = next_node; } } - - return table; } else { // If this node has an effect input, ensure that is pushed last NodeValueTable primary; @@ -325,10 +314,13 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang primary = database.Take(n->GetEffectInputID()); } - NodeValueTable m = database.Merge(); - m.Push(primary); - return m; + table = database.Merge(); + table.Push(primary); } + + value_cache_[n][range] = table; + + return table; } NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeRange &range) @@ -347,7 +339,7 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeR return table; } -TexturePtr NodeTraverser::ProcessVideoCacheJob(const CacheJob &val) +TexturePtr NodeTraverser::ProcessVideoCacheJob(const CacheJob *val) { return nullptr; } @@ -359,151 +351,127 @@ QVector2D NodeTraverser::GenerateResolution() const void NodeTraverser::ResolveJobs(NodeValue &val) { - if (val.type() == NodeValue::kTexture || val.type() == NodeValue::kSamples) { - if (val.canConvert()) { - CacheJob job = val.value(); - TexturePtr tex = ProcessVideoCacheJob(job); - if (tex) { - val.set_value(tex); - } else { - val.set_value(job.GetFallback()); + if (val.type() == NodeValue::kTexture) { + + if (TexturePtr job_tex = val.toTexture()) { + if (AcceleratedJob *base_job = job_tex->job()) { + + if (resolved_texture_cache_.contains(job_tex.get())) { + val.set_value(resolved_texture_cache_.value(job_tex.get())); + } else { + // Resolve any sub-jobs + for (auto it=base_job->GetValues().begin(); it!=base_job->GetValues().end(); it++) { + // Jobs will almost always be submitted with one of these types + NodeValue &subval = it.value(); + ResolveJobs(subval); + } + + if (CacheJob *cj = dynamic_cast(base_job)) { + TexturePtr tex = ProcessVideoCacheJob(cj); + if (tex) { + val.set_value(tex); + } else { + val.set_value(cj->GetFallback()); + } + + } else if (ColorTransformJob *ctj = dynamic_cast(base_job)) { + + VideoParams ctj_params = job_tex->params(); + + ctj_params.set_format(GetCacheVideoParams().format()); + + TexturePtr dest = CreateTexture(ctj_params); + + // Resolve input texture + NodeValue v = ctj->GetInputTexture(); + ResolveJobs(v); + ctj->SetInputTexture(v); + + ProcessColorTransform(dest, val.source(), ctj); + + val.set_value(dest); + + } else if (ShaderJob *sj = dynamic_cast(base_job)) { + + VideoParams tex_params = job_tex->params(); + + TexturePtr tex = CreateTexture(tex_params); + + ProcessShader(tex, val.source(), sj); + + val.set_value(tex); + + } else if (GenerateJob *gj = dynamic_cast(base_job)) { + + VideoParams tex_params = job_tex->params(); + + TexturePtr tex = CreateTexture(tex_params); + + ProcessFrameGeneration(tex, val.source(), gj); + + // Convert to reference space + const QString &colorspace = tex_params.colorspace(); + if (!colorspace.isEmpty()) { + // Set format to primary format + tex_params.set_format(GetCacheVideoParams().format()); + + TexturePtr dest = CreateTexture(tex_params); + + ConvertToReferenceSpace(dest, tex, colorspace); + + tex = dest; + } + + val.set_value(tex); + + } else if (FootageJob *fj = dynamic_cast(base_job)) { + + rational footage_time = Footage::AdjustTimeByLoopMode(fj->time().in(), loop_mode_, fj->length(), fj->video_params().video_type(), fj->video_params().frame_rate_as_time_base()); + + TexturePtr tex; + + if (footage_time.isNaN()) { + // Push dummy texture + tex = CreateDummyTexture(fj->video_params()); + } else { + VideoParams managed_params = fj->video_params(); + managed_params.set_format(GetCacheVideoParams().format()); + + tex = CreateTexture(managed_params); + ProcessVideoFootage(tex, fj, footage_time); + } + + val.set_value(tex); + + } + + // Cache resolved value + resolved_texture_cache_.insert(job_tex.get(), val.toTexture()); + } } } - if (val.canConvert()) { + } else if (val.type() == NodeValue::kSamples) { - ShaderJob job = val.value(); - - PreProcessRow(job.GetValues()); - - VideoParams tex_params = GetCacheVideoParams(); - tex_params.set_channel_count(GetChannelCountFromJob(job)); - - if (!job.GetWillChangeImageSize()) { - if (TexturePtr texture = GetMainTextureFromJob(job)) { - tex_params.set_width(texture->params().width()); - tex_params.set_height(texture->params().height()); - tex_params.set_divider(texture->params().divider()); - } - } - - TexturePtr tex = CreateTexture(tex_params); - - ProcessShader(tex, val.source(), job); - - val.set_value(tex); - - } else if (val.canConvert()) { - - GenerateJob job = val.value(); - - VideoParams tex_params = GetCacheVideoParams(); - tex_params.set_channel_count(GetChannelCountFromJob(job)); - - VideoParams upload_params = tex_params; - if (job.GetRequestedFormat() != VideoParams::kFormatInvalid) { - upload_params.set_format(job.GetRequestedFormat()); - } - - TexturePtr tex = CreateTexture(upload_params); - - PreProcessRow(job.GetValues()); - ProcessFrameGeneration(tex, val.source(), job); - - if (!job.GetColorspace().isEmpty()) { - // Convert to reference space - TexturePtr dest = CreateTexture(tex_params); - - ConvertToReferenceSpace(dest, tex, job.GetColorspace()); - - tex = dest; - } - - val.set_value(tex); - - } else if (val.canConvert()) { - - ColorTransformJob job = val.value(); - - VideoParams src_params = job.GetInputTexture()->params(); - src_params.set_channel_count(GetChannelCountFromJob(job)); - - TexturePtr dest = CreateTexture(src_params); - - ProcessColorTransform(dest, val.source(), job); - - val.set_value(dest); - - } else if (val.canConvert()) { - - FootageJob job = val.value(); - - if (job.type() == Track::kVideo) { - - rational footage_time = Footage::AdjustTimeByLoopMode(job.time().in(), loop_mode_, job.length(), job.video_params().video_type(), job.video_params().frame_rate_as_time_base()); - - TexturePtr tex; - - // Adjust footage job's divider - VideoParams render_params = GetCacheVideoParams(); - VideoParams job_params = job.video_params(); - - if (render_params.divider() > 1) { - // Use a divider appropriate for this target resolution - job_params.set_divider(VideoParams::GetDividerForTargetResolution(job_params.width(), job_params.height(), render_params.effective_width(), render_params.effective_height())); - } else { - // Render everything at full res - job_params.set_divider(1); - } - - job.set_video_params(job_params); - - if (footage_time.isNaN()) { - // Push dummy texture - tex = CreateDummyTexture(job.video_params()); - } else { - VideoParams managed_params = job.video_params(); - managed_params.set_format(GetCacheVideoParams().format()); - - tex = CreateTexture(managed_params); - ProcessVideoFootage(tex, job, footage_time); - } - - val.set_value(tex); - - } else if (job.type() == Track::kAudio) { - - SampleBuffer buffer = CreateSampleBuffer(GetCacheAudioParams(), job.time().length()); - ProcessAudioFootage(buffer, job, job.time()); - val.set_value(buffer); - - } - - } else if (val.canConvert()) { + if (val.canConvert()) { SampleJob job = val.value(); SampleBuffer output_buffer = CreateSampleBuffer(job.samples().audio_params(), job.samples().sample_count()); ProcessSamples(output_buffer, val.source(), job.time(), job); val.set_value(QVariant::fromValue(output_buffer)); + } else if (val.canConvert()) { + + FootageJob job = val.value(); + SampleBuffer buffer = CreateSampleBuffer(GetCacheAudioParams(), job.time().length()); + ProcessAudioFootage(buffer, &job, job.time()); + val.set_value(buffer); + } } } -void NodeTraverser::PreProcessRow(NodeValueRow &row) -{ - QByteArray cached_node_hash; - - // Resolve any jobs - for (auto it=row.begin(); it!=row.end(); it++) { - // Jobs will almost always be submitted with one of these types - NodeValue &val = it.value(); - - ResolveJobs(val); - } -} - TexturePtr NodeTraverser::CreateDummyTexture(const VideoParams &p) { return std::make_shared(p); diff --git a/app/node/traverser.h b/app/node/traverser.h index 1defd0168..4ecf3982a 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -80,30 +80,26 @@ public: audio_params_ = params; } - static int GetChannelCountFromJob(const GenerateJob& job); - - static TexturePtr GetMainTextureFromJob(const GenerateJob& job); - protected: NodeValueTable ProcessInput(const Node *node, const QString &input, const TimeRange &range); virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange& range); - virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time){} + virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time){} - virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time){} + virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob *stream, const TimeRange &input_time){} - virtual void ProcessShader(TexturePtr destination, const Node *node, const ShaderJob& job){} + virtual void ProcessShader(TexturePtr destination, const Node *node, const ShaderJob *job){} - virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job){} + virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob *job){} virtual void ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job){} - virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job){} + virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob *job){} virtual void ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs){} - virtual TexturePtr ProcessVideoCacheJob(const CacheJob &val); + virtual TexturePtr ProcessVideoCacheJob(const CacheJob *val); virtual TexturePtr CreateTexture(const VideoParams &p) { @@ -152,8 +148,6 @@ protected: virtual bool UseCache() const { return false; } private: - void PreProcessRow(NodeValueRow &row); - TexturePtr CreateDummyTexture(const VideoParams &p); VideoParams video_params_; @@ -170,6 +164,9 @@ private: Decoder::LoopMode loop_mode_; + QHash > value_cache_; + QHash resolved_texture_cache_; + }; } diff --git a/app/node/value.h b/app/node/value.h index e4d373ae7..e90381069 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -264,6 +264,11 @@ public: return type_ == rhs.type_ && tag_ == rhs.tag_ && data_ == rhs.data_; } + operator bool() const + { + return !data_.isNull(); + } + static QString GetPrettyDataTypeName(Type type); static QString GetDataTypeName(Type type); diff --git a/app/render/job/acceleratedjob.h b/app/render/job/acceleratedjob.h index 9468a9a74..7373d52c1 100644 --- a/app/render/job/acceleratedjob.h +++ b/app/render/job/acceleratedjob.h @@ -26,10 +26,13 @@ namespace olive { -class AcceleratedJob { +class AcceleratedJob +{ public: AcceleratedJob() = default; + virtual ~AcceleratedJob(){} + NodeValue Get(const QString& input) const { return value_map_.value(input); diff --git a/app/render/job/cachejob.h b/app/render/job/cachejob.h index 67c6c36b9..4d7800ce1 100644 --- a/app/render/job/cachejob.h +++ b/app/render/job/cachejob.h @@ -24,13 +24,16 @@ #include #include +#include "node/value.h" +#include "render/job/acceleratedjob.h" + namespace olive { -class CacheJob +class CacheJob : public AcceleratedJob { public: CacheJob() = default; - CacheJob(const QString &filename, const QVariant &fallback = QVariant()) + CacheJob(const QString &filename, const NodeValue &fallback = NodeValue()) { filename_ = filename; } @@ -38,18 +41,16 @@ public: const QString &GetFilename() const { return filename_; } void SetFilename(const QString &s) { filename_ = s; } - const QVariant &GetFallback() const { return fallback_; } - void SetFallback(const QVariant &val) { fallback_ = val; } + const NodeValue &GetFallback() const { return fallback_; } + void SetFallback(const NodeValue &val) { fallback_ = val; } private: QString filename_; - QVariant fallback_; + NodeValue fallback_; }; } -Q_DECLARE_METATYPE(olive::CacheJob) - #endif // CACHEJOB_H diff --git a/app/render/job/colortransformjob.h b/app/render/job/colortransformjob.h index da7846d2c..15951b22e 100644 --- a/app/render/job/colortransformjob.h +++ b/app/render/job/colortransformjob.h @@ -24,7 +24,7 @@ #include #include -#include "render/job/generatejob.h" +#include "acceleratedjob.h" #include "render/alphaassoc.h" #include "render/colorprocessor.h" #include "render/texture.h" @@ -33,18 +33,23 @@ namespace olive { class Node; -class ColorTransformJob : public GenerateJob +class ColorTransformJob : public AcceleratedJob { public: ColorTransformJob() { processor_ = nullptr; - input_texture_ = nullptr; custom_shader_src_ = nullptr; input_alpha_association_ = kAlphaNone; clear_destination_ = true; } + ColorTransformJob(const NodeValueRow &row) : + ColorTransformJob() + { + Insert(row); + } + QString id() const { if (id_.isEmpty()) { @@ -56,8 +61,13 @@ public: void SetOverrideID(const QString &id) { id_ = id; } - TexturePtr GetInputTexture() const { return input_texture_; } - void SetInputTexture(TexturePtr tex) { input_texture_ = tex; } + const NodeValue &GetInputTexture() const { return input_texture_; } + void SetInputTexture(const NodeValue &tex) { input_texture_ = tex; } + void SetInputTexture(TexturePtr tex) + { + Q_ASSERT(!tex->IsDummy()); + input_texture_ = NodeValue(NodeValue::kTexture, tex); + } ColorProcessorPtr GetColorProcessor() const { return processor_; } void SetColorProcessor(ColorProcessorPtr p) { processor_ = p; } @@ -89,7 +99,7 @@ private: ColorProcessorPtr processor_; QString id_; - TexturePtr input_texture_; + NodeValue input_texture_; const Node *custom_shader_src_; QString custom_shader_id_; @@ -108,6 +118,4 @@ private: } -Q_DECLARE_METATYPE(olive::ColorTransformJob) - #endif // COLORTRANSFORMJOB_H diff --git a/app/render/job/footagejob.h b/app/render/job/footagejob.h index 3684fa999..dcafee554 100644 --- a/app/render/job/footagejob.h +++ b/app/render/job/footagejob.h @@ -25,7 +25,7 @@ namespace olive { -class FootageJob +class FootageJob : public AcceleratedJob { public: FootageJob() : diff --git a/app/render/job/generatejob.h b/app/render/job/generatejob.h index 0109e1195..ed13998ef 100644 --- a/app/render/job/generatejob.h +++ b/app/render/job/generatejob.h @@ -22,33 +22,22 @@ #define GENERATEJOB_H #include "acceleratedjob.h" -#include "render/videoparams.h" +#include "codec/frame.h" namespace olive { -class GenerateJob : public AcceleratedJob { +class GenerateJob : public AcceleratedJob +{ public: - GenerateJob() + GenerateJob() = default; + GenerateJob(const NodeValueRow &row) : + GenerateJob() { - requested_format_ = VideoParams::kFormatInvalid; + Insert(row); } - VideoParams::Format GetRequestedFormat() const { return requested_format_; } - - void SetRequestedFormat(VideoParams::Format f) { requested_format_ = f; } - - const QString &GetColorspace() const { return colorspace_; } - void SetColorspace(const QString &s) { colorspace_ = s; } - -private: - VideoParams::Format requested_format_; - - QString colorspace_; - }; } -Q_DECLARE_METATYPE(olive::GenerateJob) - #endif // GENERATEJOB_H diff --git a/app/render/job/samplejob.h b/app/render/job/samplejob.h index 03bc00e6e..f5602a8ba 100644 --- a/app/render/job/samplejob.h +++ b/app/render/job/samplejob.h @@ -27,7 +27,8 @@ namespace olive { -class SampleJob : public AcceleratedJob { +class SampleJob : public AcceleratedJob +{ public: SampleJob() { diff --git a/app/render/job/shaderjob.h b/app/render/job/shaderjob.h index 0669e0c42..4cba60e7a 100644 --- a/app/render/job/shaderjob.h +++ b/app/render/job/shaderjob.h @@ -24,19 +24,24 @@ #include #include -#include "generatejob.h" -#include "render/colorprocessor.h" +#include "acceleratedjob.h" #include "render/texture.h" namespace olive { -class ShaderJob : public GenerateJob { +class ShaderJob : public AcceleratedJob +{ public: ShaderJob() { iterations_ = 1; iterative_input_ = nullptr; - will_change_image_size_ = true; + } + + ShaderJob(const NodeValueRow &row) : + ShaderJob() + { + Insert(row); } const QString& GetShaderID() const @@ -100,9 +105,6 @@ public: return vertex_overrides_; } - bool GetWillChangeImageSize() const { return will_change_image_size_; } - void SetWillChangeImageSize(bool e) { will_change_image_size_ = e; } - private: QString shader_id_; @@ -114,12 +116,8 @@ private: QVector vertex_overrides_; - bool will_change_image_size_; - }; } -Q_DECLARE_METATYPE(olive::ShaderJob) - #endif // SHADERJOB_H diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 8c6f2a703..2cb608783 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -415,6 +415,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video // This variable is used in the shader, let's set it const NodeValue& value = it.value(); + // Arrays are not currently supported in this system if (value.array()) { continue; } diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index 9aa24dbd1..a5a3aaedb 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -292,7 +292,7 @@ void Renderer::BlitColorManaged(const ColorTransformJob &color_job, Texture *des } ShaderJob job; - job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(color_job.GetInputTexture()))); + job.Insert(QStringLiteral("ove_maintex"), color_job.GetInputTexture()); job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, color_job.GetTransformMatrix())); job.Insert(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, color_job.GetCropMatrix().inverted())); job.Insert(QStringLiteral("ove_maintex_alpha"), NodeValue(NodeValue::kInt, int(color_job.GetInputAlphaAssociation()))); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index fc4f26c02..4da2ea852 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -54,8 +54,13 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational NodeValue tex_val = table.Get(NodeValue::kTexture); + QElapsedTimer t; + t.restart(); + ResolveJobs(tex_val); + qDebug() << "Frame took" << t.elapsed(); + return tex_val.toTexture(); } @@ -406,7 +411,7 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim } } -void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time) +void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time) { if (ticket_->property("type").value() != RenderManager::kTypeVideo) { // Video cannot contribute to audio, so we do nothing here @@ -416,7 +421,7 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ // Check the still frame cache. On large frames such as high resolution still images, uploading // and color managing them for every frame is a waste of time, so we implement a small cache here // to optimize such a situation - VideoParams stream_data = stream.video_params(); + VideoParams stream_data = stream->video_params(); ColorManager* color_manager = Node::ValueToPtr(ticket_->property("colormanager")); @@ -427,9 +432,9 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ qWarning() << "HAVEN'T GOTTEN DEFAULT INPUT COLORSPACE"; } - Decoder::CodecStream default_codec_stream(stream.filename(), stream_data.stream_index(), GetCurrentBlock()); + Decoder::CodecStream default_codec_stream(stream->filename(), stream_data.stream_index(), GetCurrentBlock()); - QString decoder_id = stream.decoder(); + QString decoder_id = stream->decoder(); DecoderPtr decoder = nullptr; @@ -447,7 +452,7 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ QString frame_filename; int64_t frame_number = stream_data.get_time_in_timebase_units(input_time); - frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); + frame_filename = Decoder::TransformImageSequenceFileName(stream->filename(), frame_number); // Decoder will close automatically since it's a stream_ptr decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index(), GetCurrentBlock())); @@ -458,11 +463,11 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ if (decoder && render_ctx_) { Decoder::RetrieveVideoParams p; - p.divider = stream.video_params().divider(); + p.divider = stream->video_params().divider(); p.maximum_format = destination->format(); if (!IsCancelled()) { - VideoParams tex_params = stream.video_params(); + VideoParams tex_params = stream->video_params(); if (tex_params.is_valid()) { TexturePtr unmanaged_texture; @@ -503,16 +508,16 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ } } -void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time) +void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const FootageJob *stream, const TimeRange &input_time) { - DecoderPtr decoder = ResolveDecoderFromInput(stream.decoder(), Decoder::CodecStream(stream.filename(), stream.audio_params().stream_index(), nullptr)); + DecoderPtr decoder = ResolveDecoderFromInput(stream->decoder(), Decoder::CodecStream(stream->filename(), stream->audio_params().stream_index(), nullptr)); if (decoder) { const AudioParams& audio_params = GetCacheAudioParams(); Decoder::RetrieveAudioStatus status = decoder->RetrieveAudio(destination, input_time, audio_params, - stream.cache_path(), + stream->cache_path(), loop_mode(), static_cast(ticket_->property("mode").toInt())); @@ -522,13 +527,13 @@ void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const Foota } } -void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, const ShaderJob &job) +void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, const ShaderJob *job) { if (!render_ctx_) { return; } - QString full_shader_id = QStringLiteral("%1:%2").arg(node->id(), job.GetShaderID()); + QString full_shader_id = QStringLiteral("%1:%2").arg(node->id(), job->GetShaderID()); QMutexLocker locker(shader_cache_->mutex()); @@ -536,16 +541,20 @@ void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, co if (shader.isNull()) { // Since we have shader code, compile it now - shader = render_ctx_->CreateNativeShader(node->GetShaderCode(job.GetShaderID())); + shader = render_ctx_->CreateNativeShader(node->GetShaderCode(job->GetShaderID())); if (shader.isNull()) { // Couldn't find or build the shader required return; } + + shader_cache_->insert(full_shader_id, shader); } + locker.unlock(); + // Run shader - render_ctx_->BlitToTexture(shader, job, destination.get()); + render_ctx_->BlitToTexture(shader, *job, destination.get()); } void RenderProcessor::ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job) @@ -579,16 +588,16 @@ void RenderProcessor::ProcessSamples(SampleBuffer &destination, const Node *node } } -void RenderProcessor::ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob &job) +void RenderProcessor::ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob *job) { if (!render_ctx_) { return; } - render_ctx_->BlitColorManaged(job, destination.get()); + render_ctx_->BlitColorManaged(*job, destination.get()); } -void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob &job) +void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob *job) { if (!render_ctx_) { return; @@ -599,14 +608,14 @@ void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node frame->set_video_params(destination->params()); frame->allocate(); - node->GenerateFrame(frame, job); + node->GenerateFrame(frame, *job); destination->Upload(frame->data(), frame->linesize_pixels()); } -TexturePtr RenderProcessor::ProcessVideoCacheJob(const CacheJob &val) +TexturePtr RenderProcessor::ProcessVideoCacheJob(const CacheJob *val) { - FramePtr frame = FrameHashCache::LoadCacheFrame(val.GetFilename()); + FramePtr frame = FrameHashCache::LoadCacheFrame(val->GetFilename()); if (frame) { TexturePtr tex = CreateTexture(frame->video_params()); if (tex) { @@ -615,7 +624,7 @@ TexturePtr RenderProcessor::ProcessVideoCacheJob(const CacheJob &val) } } else { QStringList s = ticket_->property("badcache").toStringList(); - s.append(val.GetFilename()); + s.append(val->GetFilename()); ticket_->setProperty("badcache", s); } diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index 8ef6699bb..37924181f 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -44,19 +44,19 @@ public: protected: virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange &range) override; - virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time) override; + virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time) override; - virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time) override; + virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob *stream, const TimeRange &input_time) override; - virtual void ProcessShader(TexturePtr destination, const Node *node, const ShaderJob& job) override; + virtual void ProcessShader(TexturePtr destination, const Node *node, const ShaderJob *job) override; virtual void ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job) override; - virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job) override; + virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob *job) override; - virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job) override; + virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob *job) override; - virtual TexturePtr ProcessVideoCacheJob(const CacheJob &val) override; + virtual TexturePtr ProcessVideoCacheJob(const CacheJob *val) override; virtual TexturePtr CreateTexture(const VideoParams &p) override; diff --git a/app/render/texture.cpp b/app/render/texture.cpp index 2c5c9fdea..7903f486a 100644 --- a/app/render/texture.cpp +++ b/app/render/texture.cpp @@ -31,6 +31,10 @@ Texture::~Texture() if (renderer_) { renderer_->DestroyTexture(this); } + + if (job_) { + delete job_; + } } void Texture::Upload(void *data, int linesize) diff --git a/app/render/texture.h b/app/render/texture.h index 6179878a8..942c0c4f6 100644 --- a/app/render/texture.h +++ b/app/render/texture.h @@ -27,8 +27,12 @@ namespace olive { +class AcceleratedJob; class Renderer; +class Texture; +using TexturePtr = std::shared_ptr; + class Texture { public: @@ -45,17 +49,26 @@ public: */ Texture(const VideoParams& param) : renderer_(nullptr), - params_(param) + params_(param), + job_(nullptr) { } + template + Texture(const VideoParams &p, const T &j) : + Texture(p) + { + job_ = new T(j); + } + /** * @brief Construct a real texture linked to a renderer backend */ Texture(Renderer* renderer, const QVariant& native, const VideoParams& param) : renderer_(renderer), params_(param), - id_(native) + id_(native), + job_(nullptr) { } @@ -71,6 +84,18 @@ public: return params_; } + template + static TexturePtr Job(const VideoParams &p, const T &j) + { + return std::make_shared(p, j); + } + + template + TexturePtr toJob(const T &job) + { + return Texture::Job(params_, job); + } + void Upload(void* data, int linesize); void Download(void* data, int linesize); @@ -90,6 +115,11 @@ public: return params_.effective_height(); } + QVector2D virtual_resolution() const + { + return QVector2D(params_.square_pixel_width(), params_.height()); + } + VideoParams::Format format() const { return params_.format(); @@ -115,6 +145,9 @@ public: return renderer_; } + bool IsJob() const { return job_; } + AcceleratedJob *job() const { return job_; } + private: Renderer* renderer_; @@ -122,9 +155,9 @@ private: QVariant id_; -}; + AcceleratedJob *job_; -using TexturePtr = std::shared_ptr; +}; } From 6ce920c5c84591c83389c07e938a114effce8ec3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 24 Sep 2022 18:50:17 -0700 Subject: [PATCH 11/19] nodes: resolve audio jobs in the old way for now --- app/node/traverser.cpp | 10 +++++++++- app/node/traverser.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 028028f46..0749d3c80 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -61,7 +61,15 @@ NodeValueRow NodeTraverser::GenerateRow(NodeValueDatabase *database, const Node row.insert(it.key(), value); } - //PreProcessRow(row); + // TEMP: Audio needs to be refactored to work with new job system. But refactoring hasn't been + // done yet, so we emulate old behavior here JUST FOR AUDIO. + for (auto it=row.begin(); it!=row.end(); it++) { + NodeValue &val = it.value(); + if (val.type() == NodeValue::kSamples) { + ResolveJobs(val); + } + } + // END TEMP return row; } diff --git a/app/node/traverser.h b/app/node/traverser.h index 4ecf3982a..e008c1f85 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -137,6 +137,7 @@ protected: void SetCancelPointer(CancelAtom *cancel) { cancel_ = cancel; } void ResolveJobs(NodeValue &value); + void ResolveAudioJobs(NodeValue &value); Block *GetCurrentBlock() const { From f12553c80a17e8d9df5eae52d0d5f8a851d3b7fc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 24 Sep 2022 18:59:38 -0700 Subject: [PATCH 12/19] renderprocessor: remove debug lines --- app/render/renderprocessor.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 4da2ea852..0327c1e9e 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -54,13 +54,8 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational NodeValue tex_val = table.Get(NodeValue::kTexture); - QElapsedTimer t; - t.restart(); - ResolveJobs(tex_val); - qDebug() << "Frame took" << t.elapsed(); - return tex_val.toTexture(); } From 3138e676af15ad73ea58cf89873daa9d9d84f994 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 24 Sep 2022 19:04:57 -0700 Subject: [PATCH 13/19] playbackcache: clear cache if state file does not exist --- app/render/playbackcache.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 487eac2de..bf606e153 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -69,6 +69,13 @@ void PlaybackCache::LoadState() QDir cache_dir = GetThisCacheDirectory(); QFile f(cache_dir.filePath(QStringLiteral("state"))); + if (!f.exists()) { + // No state exists, assume nothing valid + validated_.clear(); + passthroughs_.clear(); + return; + } + qint64 file_time = f.fileTime(QFileDevice::FileModificationTime).toMSecsSinceEpoch(); if (file_time > last_loaded_state_ && f.open(QFile::ReadOnly)) { QDataStream s(&f); @@ -83,7 +90,6 @@ void PlaybackCache::LoadState() { int valid_count, pass_count; - validated_.clear(); s >> valid_count; for (int i=0; i> pass_count; for (int i=0; i Date: Sun, 25 Sep 2022 09:33:10 -0700 Subject: [PATCH 14/19] timeline: fast draw clips that take up a small amount of space --- .../timelinewidget/view/timelineview.cpp | 395 +++++++++--------- app/widget/timelinewidget/view/timelineview.h | 2 +- 2 files changed, 199 insertions(+), 198 deletions(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 555d3bc62..94fa7143e 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -439,8 +439,6 @@ TimelineViewMouseEvent TimelineView::CreateMouseEvent(const QPoint& pos, Qt::Mou void TimelineView::DrawBlocks(QPainter *painter, bool foreground) { - - rational start_time = SceneToTime(GetTimelineLeftBound()); rational end_time = SceneToTime(GetTimelineRightBound()); @@ -480,217 +478,220 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q QColor shadow_color = block->color().toQColor().darker(); - QFontMetrics fm = fontMetrics(); - int text_height = fm.height(); - int text_padding = text_height/4; // This ties into the track minimum height being 1.5 - int text_total_height = text_height + text_padding + text_padding; - - if (foreground) { - painter->setBrush(Qt::NoBrush); - - QString using_label = block->GetLabelOrName(); - - QRectF text_rect = r.adjusted(text_padding, text_padding, -text_padding, -text_padding); - painter->setPen(block->is_enabled() ? ColorCoding::GetUISelectorColor(block->color()) : Qt::lightGray); - painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignTop, using_label); - - if (block->HasLinks()) { - int text_width = qMin(qRound(text_rect.width()), - QtUtils::QFontMetricsWidth(fm, using_label)); - - int underline_y = text_rect.y() + text_height; - - painter->drawLine(text_rect.x(), underline_y, text_width + text_rect.x(), underline_y); - } - - qreal line_bottom = block_top+block_height-1; - - painter->setPen(Qt::white); - painter->drawLine(block_left, block_top, block_right, block_top); - painter->drawLine(block_left, block_top, block_left, line_bottom); - - painter->setPen(shadow_color); - painter->drawLine(block_left, line_bottom, block_right, line_bottom); - painter->drawLine(block_right, line_bottom, block_right, block_top); + if (r.width() <= 3) { + painter->fillRect(r, shadow_color); } else { - painter->setPen(Qt::NoPen); - painter->setBrush(block->is_enabled() ? block->brush(block_top, block_top + block_height) : Qt::gray); - painter->drawRect(r); + QFontMetrics fm = fontMetrics(); + int text_height = fm.height(); + int text_padding = text_height/4; // This ties into the track minimum height being 1.5 + int text_total_height = text_height + text_padding + text_padding; - if (ClipBlock *clip = dynamic_cast(block)) { - QRect preview_rect = r.toRect(); + if (foreground) { + painter->setBrush(Qt::NoBrush); - // Draw clip thumbnails - if (clip->GetTrackType() == Track::kVideo - && OLIVE_CONFIG("TimelineThumbnailMode").toInt() != Timeline::kThumbnailOff - && preview_rect.height() > r.height()/3) { - if (const FrameHashCache *thumbs = clip->thumbnails()) { - // Start thumbnails underneath clip name - preview_rect.adjust(0, text_total_height, 0, 0); + QString using_label = block->GetLabelOrName(); - QRect thumb_rect; - painter->setRenderHint(QPainter::SmoothPixmapTransform); - painter->setClipRect(preview_rect); + QRectF text_rect = r.adjusted(text_padding, text_padding, -text_padding, -text_padding); + painter->setPen(block->is_enabled() ? ColorCoding::GetUISelectorColor(block->color()) : Qt::lightGray); + painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignTop, using_label); - if (OLIVE_CONFIG("TimelineThumbnailMode") == Timeline::kThumbnailOn) { + if (block->HasLinks()) { + int text_width = qMin(qRound(text_rect.width()), + QtUtils::QFontMetricsWidth(fm, using_label)); - Sequence *s = clip->track()->sequence(); - int width = s->GetVideoParams().width(); - int height = s->GetVideoParams().height(); - int start; - if (height > 0) { // Prevent divide by zero/invalid params - double scale = double(preview_rect.height())/double(height); - thumb_rect.setWidth(width * scale); - start = (((preview_rect.left() - int(qFloor(block_in))) / thumb_rect.width()) * thumb_rect.width()) + qFloor(block_in); - } else { - start = preview_rect.left(); - } + int underline_y = text_rect.y() + text_height; - for (int i=start; iparent()->GetVideoParams().frame_rate_as_time_base()) + media_in; - DrawThumbnail(painter, thumbs, time_here, i, preview_rect, &thumb_rect); - } - - } else { - - rational time = clip->media_range().in(); - time = Timecode::snap_time_to_timebase(time, thumbs->GetTimebase(), Timecode::kFloor); - DrawThumbnail(painter, thumbs, time, block_left, preview_rect, &thumb_rect); - - } - - painter->setClipping(false); - - } + painter->drawLine(text_rect.x(), underline_y, text_width + text_rect.x(), underline_y); } - // Draw waveform - if (clip->GetTrackType() == Track::kAudio - && OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled) { - if (const AudioWaveformCache *wave = clip->waveform()) { - rational waveform_start = SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in; - painter->setPen(shadow_color); + qreal line_bottom = block_top+block_height-1; - wave->Draw(painter, preview_rect, this->GetScale(), waveform_start); - } - } - - // Draw zebra stripes and markers - if (clip->connected_viewer()) { - if (!clip->connected_viewer()->GetLength().isNull()) { - painter->setPen(shadow_color); - - if (clip->media_in() < 0) { - qreal zebra_right = TimeToScene(clip->in() - clip->media_in()); - - switch (clip->loop_mode()) { - case Decoder::kLoopModeOff: - // Draw stripes for sections of clip < 0 - if (zebra_right > GetTimelineLeftBound()) { - DrawZebraStripes(painter, QRectF(block_left, block_top, zebra_right - block_left, block_height)); - } - break; - case Decoder::kLoopModeLoop: - for (qreal i=zebra_right; i>block_left; i-=TimeToScene(clip->connected_viewer()->GetLength())) { - painter->drawLine(i, block_top, i, block_top + block_height); - } - break; - case Decoder::kLoopModeClamp: - painter->drawLine(zebra_right, block_top, zebra_right, block_top + block_height); - break; - } - } - - if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) { - qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength())); - switch (clip->loop_mode()) { - case Decoder::kLoopModeOff: - // Draw stripes for sections for clip > clip length - if (zebra_left < GetTimelineRightBound()) { - DrawZebraStripes(painter, QRectF(zebra_left, block_top, block_right - zebra_left, block_height)); - } - break; - case Decoder::kLoopModeLoop: - for (qreal i=zebra_left; iconnected_viewer()->GetLength())) { - painter->drawLine(i, block_top, i, block_top + block_height); - } - break; - case Decoder::kLoopModeClamp: - painter->drawLine(zebra_left, block_top, zebra_left, block_top + block_height); - break; - } - } - } - - TimelineMarkerList *marker_list = clip->connected_viewer()->GetMarkers(); - if (!marker_list->empty()) { - - clip_marker_rects_.clear(); - - for (auto it=marker_list->cbegin(); it!=marker_list->cend(); it++) { - TimelineMarker *marker = *it; - // Make sure marker is within In/Out points of the clip - if (marker->time().in() >= clip->media_in() && marker->time().out() <= clip->media_in() + clip->length()) { - QPoint marker_pt(TimeToScene(clip->in() - clip->media_in() + marker->time().in()), block_top + block_height); - painter->setClipRect(r); - QRect marker_rect = marker->Draw(painter, marker_pt, -1, GetScale(), false); - clip_marker_rects_.insert(marker, marker_rect); - painter->setClipping(false); - } - } - } - } - - if (const FrameHashCache *cache = clip->connected_video_cache()) { - if (cache->HasValidatedRanges()) { - QRect cache_rect = r.adjusted(0, r.height() - PlaybackCache::GetCacheIndicatorHeight(), 0, 0).toRect(); - cache->Draw(painter, clip->media_in(), GetScale(), cache_rect); - } - } - } - - // For transitions, show lines representing a transition - if (TransitionBlock* transition = dynamic_cast(block)) { - QVector lines; - - if (transition->connected_in_block()) { - lines.append(QLineF(r.bottomLeft(), r.topRight())); - } - - if (transition->connected_out_block()) { - lines.append(QLineF(r.topLeft(), r.bottomRight())); - } + painter->setPen(Qt::white); + painter->drawLine(block_left, block_top, block_right, block_top); + painter->drawLine(block_left, block_top, block_left, line_bottom); painter->setPen(shadow_color); - painter->drawLines(lines); - } - - if (transition_overlay_out_ == block || transition_overlay_in_ == block) { - QRectF transition_overlay_rect = r; - - qreal transition_overlay_width = TimeToScene(block->length()) * 0.5; - if (transition_overlay_out_ && transition_overlay_in_) { - // This is a dual transition, use the smallest width - Block *other_block = (transition_overlay_out_ == block) ? transition_overlay_in_ : transition_overlay_out_; - - qreal other_width = TimeToScene(other_block->length()) * 0.5; - - transition_overlay_width = qMin(transition_overlay_width, other_width); - } - - if (transition_overlay_out_ == block) { - transition_overlay_rect.setLeft(transition_overlay_rect.right() - transition_overlay_width); - } else { - transition_overlay_rect.setRight(transition_overlay_rect.left() + transition_overlay_width); - } - + painter->drawLine(block_left, line_bottom, block_right, line_bottom); + painter->drawLine(block_right, line_bottom, block_right, block_top); + } else { painter->setPen(Qt::NoPen); - painter->setBrush(QColor(0, 0, 0, 64)); + painter->setBrush(block->is_enabled() ? block->brush(block_top, block_top + block_height) : Qt::gray); + painter->drawRect(r); - painter->drawRect(transition_overlay_rect); + if (ClipBlock *clip = dynamic_cast(block)) { + QRect preview_rect = r.toRect(); + + // Draw clip thumbnails + if (clip->GetTrackType() == Track::kVideo + && OLIVE_CONFIG("TimelineThumbnailMode").toInt() != Timeline::kThumbnailOff + && preview_rect.height() > r.height()/3) { + if (const FrameHashCache *thumbs = clip->thumbnails()) { + // Start thumbnails underneath clip name + preview_rect.adjust(0, text_total_height, 0, 0); + + QRect thumb_rect; + painter->setRenderHint(QPainter::SmoothPixmapTransform); + painter->setClipRect(preview_rect); + + if (OLIVE_CONFIG("TimelineThumbnailMode") == Timeline::kThumbnailOn) { + + Sequence *s = clip->track()->sequence(); + int width = s->GetVideoParams().width(); + int height = s->GetVideoParams().height(); + int start; + if (height > 0) { // Prevent divide by zero/invalid params + double scale = double(preview_rect.height())/double(height); + thumb_rect.setWidth(width * scale); + start = (((preview_rect.left() - int(qFloor(block_in))) / thumb_rect.width()) * thumb_rect.width()) + qFloor(block_in); + } else { + start = preview_rect.left(); + } + + for (int i=start; iparent()->GetVideoParams().frame_rate_as_time_base()) + media_in; + DrawThumbnail(painter, thumbs, time_here, i, preview_rect, &thumb_rect); + } + + } else { + + rational time = clip->media_range().in(); + time = Timecode::snap_time_to_timebase(time, thumbs->GetTimebase(), Timecode::kFloor); + DrawThumbnail(painter, thumbs, time, block_left, preview_rect, &thumb_rect); + + } + + painter->setClipping(false); + + } + } + + // Draw waveform + if (clip->GetTrackType() == Track::kAudio + && OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled) { + if (const AudioWaveformCache *wave = clip->waveform()) { + rational waveform_start = SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in; + painter->setPen(shadow_color); + + wave->Draw(painter, preview_rect, this->GetScale(), waveform_start); + } + } + + // Draw zebra stripes and markers + if (clip->connected_viewer()) { + if (!clip->connected_viewer()->GetLength().isNull()) { + painter->setPen(shadow_color); + + if (clip->media_in() < 0) { + qreal zebra_right = TimeToScene(clip->in() - clip->media_in()); + + switch (clip->loop_mode()) { + case Decoder::kLoopModeOff: + // Draw stripes for sections of clip < 0 + if (zebra_right > GetTimelineLeftBound()) { + DrawZebraStripes(painter, QRectF(block_left, block_top, zebra_right - block_left, block_height)); + } + break; + case Decoder::kLoopModeLoop: + for (qreal i=zebra_right; i>block_left; i-=TimeToScene(clip->connected_viewer()->GetLength())) { + painter->drawLine(i, block_top, i, block_top + block_height); + } + break; + case Decoder::kLoopModeClamp: + painter->drawLine(zebra_right, block_top, zebra_right, block_top + block_height); + break; + } + } + + if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) { + qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength())); + switch (clip->loop_mode()) { + case Decoder::kLoopModeOff: + // Draw stripes for sections for clip > clip length + if (zebra_left < GetTimelineRightBound()) { + DrawZebraStripes(painter, QRectF(zebra_left, block_top, block_right - zebra_left, block_height)); + } + break; + case Decoder::kLoopModeLoop: + for (qreal i=zebra_left; iconnected_viewer()->GetLength())) { + painter->drawLine(i, block_top, i, block_top + block_height); + } + break; + case Decoder::kLoopModeClamp: + painter->drawLine(zebra_left, block_top, zebra_left, block_top + block_height); + break; + } + } + } + + TimelineMarkerList *marker_list = clip->connected_viewer()->GetMarkers(); + if (!marker_list->empty()) { + + clip_marker_rects_.clear(); + + for (auto it=marker_list->cbegin(); it!=marker_list->cend(); it++) { + TimelineMarker *marker = *it; + // Make sure marker is within In/Out points of the clip + if (marker->time().in() >= clip->media_in() && marker->time().out() <= clip->media_in() + clip->length()) { + QPoint marker_pt(TimeToScene(clip->in() - clip->media_in() + marker->time().in()), block_top + block_height); + painter->setClipRect(r); + QRect marker_rect = marker->Draw(painter, marker_pt, -1, GetScale(), false); + clip_marker_rects_.insert(marker, marker_rect); + painter->setClipping(false); + } + } + } + } + + if (const FrameHashCache *cache = clip->connected_video_cache()) { + if (cache->HasValidatedRanges()) { + QRect cache_rect = r.adjusted(0, r.height() - PlaybackCache::GetCacheIndicatorHeight(), 0, 0).toRect(); + cache->Draw(painter, clip->media_in(), GetScale(), cache_rect); + } + } + } + + // For transitions, show lines representing a transition + if (TransitionBlock* transition = dynamic_cast(block)) { + QVector lines; + + if (transition->connected_in_block()) { + lines.append(QLineF(r.bottomLeft(), r.topRight())); + } + + if (transition->connected_out_block()) { + lines.append(QLineF(r.topLeft(), r.bottomRight())); + } + + painter->setPen(shadow_color); + painter->drawLines(lines); + } + + if (transition_overlay_out_ == block || transition_overlay_in_ == block) { + QRectF transition_overlay_rect = r; + + qreal transition_overlay_width = TimeToScene(block->length()) * 0.5; + if (transition_overlay_out_ && transition_overlay_in_) { + // This is a dual transition, use the smallest width + Block *other_block = (transition_overlay_out_ == block) ? transition_overlay_in_ : transition_overlay_out_; + + qreal other_width = TimeToScene(other_block->length()) * 0.5; + + transition_overlay_width = qMin(transition_overlay_width, other_width); + } + + if (transition_overlay_out_ == block) { + transition_overlay_rect.setLeft(transition_overlay_rect.right() - transition_overlay_width); + } else { + transition_overlay_rect.setRight(transition_overlay_rect.left() + transition_overlay_width); + } + + painter->setPen(Qt::NoPen); + painter->setBrush(QColor(0, 0, 0, 64)); + + painter->drawRect(transition_overlay_rect); + } } } - } } diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 6ce72ca01..6255af2da 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -119,7 +119,7 @@ private: void DrawBlock(QPainter *painter, bool foreground, Block *block, qreal top, qreal height) { ClipBlock *cb = dynamic_cast(block); - DrawBlock(painter, foreground, block, top, height, block->in(), block->out(), cb ? cb->media_in() : 0); + return DrawBlock(painter, foreground, block, top, height, block->in(), block->out(), cb ? cb->media_in() : 0); } void DrawZebraStripes(QPainter *painter, const QRectF &r); From b0b1c031d1030e860637fbc9a18b05e067ff8c86 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 25 Sep 2022 10:26:10 -0700 Subject: [PATCH 15/19] aboutdialog: make text selectable Fixes #2038 --- app/dialog/about/about.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/dialog/about/about.cpp b/app/dialog/about/about.cpp index e53b44dda..bb584af42 100644 --- a/app/dialog/about/about.cpp +++ b/app/dialog/about/about.cpp @@ -72,6 +72,8 @@ AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) : label->setWordWrap(true); label->setOpenExternalLinks(true); label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); + label->setCursor(Qt::IBeamCursor); horiz_layout->addWidget(label); layout->addLayout(horiz_layout); From f4b6156488a2857b4c89a2a81965b1e2f70648b5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 25 Sep 2022 10:27:47 -0700 Subject: [PATCH 16/19] markerpropertiesdialog: focus label edit on startup --- app/dialog/markerproperties/markerpropertiesdialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/dialog/markerproperties/markerpropertiesdialog.cpp b/app/dialog/markerproperties/markerpropertiesdialog.cpp index 18fca8cfa..6809b3385 100644 --- a/app/dialog/markerproperties/markerpropertiesdialog.cpp +++ b/app/dialog/markerproperties/markerpropertiesdialog.cpp @@ -119,6 +119,7 @@ MarkerPropertiesDialog::MarkerPropertiesDialog(const std::vectoraddWidget(buttons, row, 0, 1, 2); setWindowTitle(tr("Edit Markers")); + label_edit_->setFocus(); } void MarkerPropertiesDialog::accept() From 490677fbca1492f38717bacc15be5a356f4084c8 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 25 Sep 2022 10:37:02 -0700 Subject: [PATCH 17/19] chromakey: add invert mask option --- app/node/keying/chromakey/chromakey.cpp | 4 ++++ app/node/keying/chromakey/chromakey.h | 1 + app/shaders/chromakey.frag | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/app/node/keying/chromakey/chromakey.cpp b/app/node/keying/chromakey/chromakey.cpp index f78d4db9a..cfbea289b 100644 --- a/app/node/keying/chromakey/chromakey.cpp +++ b/app/node/keying/chromakey/chromakey.cpp @@ -24,6 +24,7 @@ namespace olive { const QString ChromaKeyNode::kColorInput = QStringLiteral("color_key"); const QString ChromaKeyNode::kMaskOnlyInput = QStringLiteral("mask_only_in"); +const QString ChromaKeyNode::kInvertInput = QStringLiteral("invert_in"); const QString ChromaKeyNode::kUpperToleranceInput = QStringLiteral("upper_tolerence_in"); const QString ChromaKeyNode::kLowerToleranceInput = QStringLiteral("lower_tolerence_in"); const QString ChromaKeyNode::kGarbageMatteInput = QStringLiteral("garbage_in"); @@ -59,6 +60,8 @@ ChromaKeyNode::ChromaKeyNode() SetInputProperty(kShadowsInput, QStringLiteral("min"), 0.0); SetInputProperty(kShadowsInput, QStringLiteral("base"), 0.1); + AddInput(kInvertInput, NodeValue::kBoolean, false); + AddInput(kMaskOnlyInput, NodeValue::kBoolean, false); } @@ -93,6 +96,7 @@ void ChromaKeyNode::Retranslate() SetInputName(kHighlightsInput, tr("Highlights")); SetInputName(kUpperToleranceInput, tr("Upper Tolerance")); SetInputName(kLowerToleranceInput, tr("Lower Tolerance")); + SetInputName(kInvertInput, tr("Invert Mask")); SetInputName(kMaskOnlyInput, tr("Show Mask Only")); } diff --git a/app/node/keying/chromakey/chromakey.h b/app/node/keying/chromakey/chromakey.h index 653d31c87..5082dc32f 100644 --- a/app/node/keying/chromakey/chromakey.h +++ b/app/node/keying/chromakey/chromakey.h @@ -42,6 +42,7 @@ class ChromaKeyNode : public OCIOBaseNode { virtual void ConfigChanged() override; static const QString kColorInput; + static const QString kInvertInput; static const QString kMaskOnlyInput; static const QString kUpperToleranceInput; static const QString kLowerToleranceInput; diff --git a/app/shaders/chromakey.frag b/app/shaders/chromakey.frag index 8c707e644..dc665b2e4 100644 --- a/app/shaders/chromakey.frag +++ b/app/shaders/chromakey.frag @@ -9,6 +9,7 @@ uniform sampler2D garbage_in; uniform sampler2D core_in; uniform bool garbage_in_enabled; uniform bool core_in_enabled; +uniform bool invert_in; uniform float highlights_in; uniform float shadows_in; @@ -94,6 +95,11 @@ void main() { mask = shadows_in * 0.01 * (highlights_in * 0.01 * mask - 1.0) + 1.0; mask = clamp(mask, 0.0, 1.0); + // Invert + if (invert_in) { + mask = 1.0 - mask; + } + col.rgb *= mask; col.w = mask; From c34416a478345736c5b79b512b128fe18bc28172 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 25 Sep 2022 14:27:15 -0700 Subject: [PATCH 18/19] colordialog: add support for swatches --- app/dialog/color/colordialog.cpp | 50 ++++- app/dialog/color/colordialog.h | 6 + app/widget/colorbutton/colorbutton.cpp | 6 +- app/widget/colorbutton/colorbutton.h | 6 +- app/widget/colorwheel/CMakeLists.txt | 2 + app/widget/colorwheel/colorswatchchooser.cpp | 223 +++++++++++++++++++ app/widget/colorwheel/colorswatchchooser.h | 73 ++++++ app/widget/colorwheel/colorvalueswidget.cpp | 81 ++++++- 8 files changed, 424 insertions(+), 23 deletions(-) create mode 100644 app/widget/colorwheel/colorswatchchooser.cpp create mode 100644 app/widget/colorwheel/colorswatchchooser.h diff --git a/app/dialog/color/colordialog.cpp b/app/dialog/color/colordialog.cpp index e15e8d209..360a8445d 100644 --- a/app/dialog/color/colordialog.cpp +++ b/app/dialog/color/colordialog.cpp @@ -40,9 +40,13 @@ ColorDialog::ColorDialog(ColorManager* color_manager, const ManagedColor& start, splitter->setChildrenCollapsible(false); layout->addWidget(splitter); - QWidget* wheel_area = new QWidget(); - QHBoxLayout* wheel_layout = new QHBoxLayout(wheel_area); - splitter->addWidget(wheel_area); + QWidget* graphics_area = new QWidget(); + splitter->addWidget(graphics_area); + + QVBoxLayout *graphics_layout = new QVBoxLayout(graphics_area); + + QHBoxLayout* wheel_layout = new QHBoxLayout(); + graphics_layout->addLayout(wheel_layout); color_wheel_ = new ColorWheelWidget(); wheel_layout->addWidget(color_wheel_); @@ -51,6 +55,17 @@ ColorDialog::ColorDialog(ColorManager* color_manager, const ManagedColor& start, hsv_value_gradient_->setFixedWidth(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("HHH"))); wheel_layout->addWidget(hsv_value_gradient_); + QHBoxLayout *swatch_layout = new QHBoxLayout(); + graphics_layout->addLayout(swatch_layout); + + swatch_layout->addStretch(); + + swatch_ = new ColorSwatchChooser(color_manager_); + swatch_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + swatch_layout->addWidget(swatch_); + + swatch_layout->addStretch(); + QWidget* value_area = new QWidget(); QVBoxLayout* value_layout = new QVBoxLayout(value_area); value_layout->setSpacing(0); @@ -61,8 +76,6 @@ ColorDialog::ColorDialog(ColorManager* color_manager, const ManagedColor& start, value_layout->addWidget(color_values_widget_); chooser_ = new ColorSpaceChooser(color_manager_); - chooser_->set_input(start.color_input()); - chooser_->set_output(start.color_output()); value_layout->addWidget(chooser_); @@ -71,10 +84,16 @@ ColorDialog::ColorDialog(ColorManager* color_manager, const ManagedColor& start, connect(color_wheel_, &ColorWheelWidget::SelectedColorChanged, color_values_widget_, &ColorValuesWidget::SetColor); connect(color_wheel_, &ColorWheelWidget::SelectedColorChanged, hsv_value_gradient_, &ColorGradientWidget::SetSelectedColor); + connect(color_wheel_, &ColorWheelWidget::SelectedColorChanged, swatch_, &ColorSwatchChooser::SetCurrentColor); connect(hsv_value_gradient_, &ColorGradientWidget::SelectedColorChanged, color_values_widget_, &ColorValuesWidget::SetColor); connect(hsv_value_gradient_, &ColorGradientWidget::SelectedColorChanged, color_wheel_, &ColorWheelWidget::SetSelectedColor); + connect(hsv_value_gradient_, &ColorGradientWidget::SelectedColorChanged, swatch_, &ColorSwatchChooser::SetCurrentColor); connect(color_values_widget_, &ColorValuesWidget::ColorChanged, hsv_value_gradient_, &ColorGradientWidget::SetSelectedColor); connect(color_values_widget_, &ColorValuesWidget::ColorChanged, color_wheel_, &ColorWheelWidget::SetSelectedColor); + connect(color_values_widget_, &ColorValuesWidget::ColorChanged, swatch_, &ColorSwatchChooser::SetCurrentColor); + connect(swatch_, &ColorSwatchChooser::ColorClicked, hsv_value_gradient_, &ColorGradientWidget::SetSelectedColor); + connect(swatch_, &ColorSwatchChooser::ColorClicked, color_wheel_, &ColorWheelWidget::SetSelectedColor); + connect(swatch_, &ColorSwatchChooser::ColorClicked, color_values_widget_, &ColorValuesWidget::SetColor); connect(color_wheel_, &ColorWheelWidget::DiameterChanged, hsv_value_gradient_, &ColorGradientWidget::setFixedHeight); @@ -83,6 +102,20 @@ ColorDialog::ColorDialog(ColorManager* color_manager, const ManagedColor& start, connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); layout->addWidget(buttons); + SetColor(start); + + connect(chooser_, &ColorSpaceChooser::ColorSpaceChanged, this, &ColorDialog::ColorSpaceChanged); + ColorSpaceChanged(chooser_->input(), chooser_->output()); + + // Set default size ratio to 2:1 + resize(sizeHint().height() * 2, sizeHint().height()); +} + +void ColorDialog::SetColor(const ManagedColor &start) +{ + chooser_->set_input(start.color_input()); + chooser_->set_output(start.color_output()); + Color managed_start; if (start.color_input().isEmpty()) { @@ -103,12 +136,7 @@ ColorDialog::ColorDialog(ColorManager* color_manager, const ManagedColor& start, color_wheel_->SetSelectedColor(managed_start); hsv_value_gradient_->SetSelectedColor(managed_start); color_values_widget_->SetColor(managed_start); - - connect(chooser_, &ColorSpaceChooser::ColorSpaceChanged, this, &ColorDialog::ColorSpaceChanged); - ColorSpaceChanged(chooser_->input(), chooser_->output()); - - // Set default size ratio to 2:1 - resize(sizeHint().height() * 2, sizeHint().height()); + swatch_->SetCurrentColor(managed_start); } ManagedColor ColorDialog::GetSelectedColor() const diff --git a/app/dialog/color/colordialog.h b/app/dialog/color/colordialog.h index 837feee95..bdd517e50 100644 --- a/app/dialog/color/colordialog.h +++ b/app/dialog/color/colordialog.h @@ -28,6 +28,7 @@ #include "render/managedcolor.h" #include "widget/colorwheel/colorgradientwidget.h" #include "widget/colorwheel/colorspacechooser.h" +#include "widget/colorwheel/colorswatchchooser.h" #include "widget/colorwheel/colorvalueswidget.h" #include "widget/colorwheel/colorwheelwidget.h" @@ -69,6 +70,9 @@ public: ColorTransform GetColorSpaceOutput() const; +public slots: + void SetColor(const ManagedColor &c); + private: ColorManager* color_manager_; @@ -82,6 +86,8 @@ private: ColorSpaceChooser* chooser_; + ColorSwatchChooser *swatch_; + private slots: void ColorSpaceChanged(const QString& input, const ColorTransform &output); diff --git a/app/widget/colorbutton/colorbutton.cpp b/app/widget/colorbutton/colorbutton.cpp index 4abf33a30..0e1a9cf14 100644 --- a/app/widget/colorbutton/colorbutton.cpp +++ b/app/widget/colorbutton/colorbutton.cpp @@ -24,14 +24,16 @@ namespace olive { -ColorButton::ColorButton(ColorManager* color_manager, QWidget *parent) : +ColorButton::ColorButton(ColorManager* color_manager, bool show_dialog_on_click, QWidget *parent) : QPushButton(parent), color_manager_(color_manager), color_processor_(nullptr) { setAutoFillBackground(true); - connect(this, &ColorButton::clicked, this, &ColorButton::ShowColorDialog); + if (show_dialog_on_click) { + connect(this, &ColorButton::clicked, this, &ColorButton::ShowColorDialog); + } SetColor(Color(1.0f, 1.0f, 1.0f)); } diff --git a/app/widget/colorbutton/colorbutton.h b/app/widget/colorbutton/colorbutton.h index 31ce5dfaa..c325fc8bd 100644 --- a/app/widget/colorbutton/colorbutton.h +++ b/app/widget/colorbutton/colorbutton.h @@ -32,7 +32,11 @@ class ColorButton : public QPushButton { Q_OBJECT public: - ColorButton(ColorManager* color_manager, QWidget* parent = nullptr); + ColorButton(ColorManager* color_manager, bool show_dialog_on_click, QWidget* parent = nullptr); + ColorButton(ColorManager* color_manager, QWidget* parent = nullptr) : + ColorButton(color_manager, true, parent) + { + } const ManagedColor& GetColor() const; diff --git a/app/widget/colorwheel/CMakeLists.txt b/app/widget/colorwheel/CMakeLists.txt index c5a8407f3..fb1f43082 100644 --- a/app/widget/colorwheel/CMakeLists.txt +++ b/app/widget/colorwheel/CMakeLists.txt @@ -22,6 +22,8 @@ set(OLIVE_SOURCES widget/colorwheel/colorpreviewbox.cpp widget/colorwheel/colorspacechooser.h widget/colorwheel/colorspacechooser.cpp + widget/colorwheel/colorswatchchooser.h + widget/colorwheel/colorswatchchooser.cpp widget/colorwheel/colorswatchwidget.h widget/colorwheel/colorswatchwidget.cpp widget/colorwheel/colorvalueswidget.h diff --git a/app/widget/colorwheel/colorswatchchooser.cpp b/app/widget/colorwheel/colorswatchchooser.cpp new file mode 100644 index 000000000..18214f707 --- /dev/null +++ b/app/widget/colorwheel/colorswatchchooser.cpp @@ -0,0 +1,223 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 "colorswatchchooser.h" + +#include + +#include "common/filefunctions.h" +#include "widget/menu/menu.h" + +namespace olive { + +const int kDefaultColorCount = 16; +const Color kDefaultColors[kDefaultColorCount] = { + Color(1.0, 1.0, 1.0), + Color(1.0, 1.0, 0.0), + Color(1.0, 0.5, 0.0), + Color(1.0, 0.0, 0.0), + Color(1.0, 0.0, 1.0), + Color(0.5, 0.0, 1.0), + Color(0.0, 0.0, 1.0), + Color(0.0, 0.5, 1.0), + Color(0.0, 1.0, 0.0), + Color(0.0, 0.5, 0.0), + Color(0.5, 0.25, 0.0), + Color(0.75, 0.5, 0.25), + Color(0.75, 0.75, 0.75), + Color(0.5, 0.5, 0.5), + Color(0.25, 0.25, 0.25), + Color(0.0, 0.0, 0.0) +}; + +ColorSwatchChooser::ColorSwatchChooser(ColorManager *manager, QWidget *parent) : + QWidget(parent) +{ + auto layout = new QGridLayout(this); + + for (int x=0; xsetFixedWidth(b->sizeHint().height()/2*3); + b->setContextMenuPolicy(Qt::CustomContextMenu); + layout->addWidget(b, y, x); + + // Save button in buttons array + int btn_index = x + kColCount*y; + buttons_[btn_index] = b; + + // Set default color + SetDefaultColor(btn_index); + + // Connect clicks + connect(b, &ColorButton::clicked, this, &ColorSwatchChooser::HandleButtonClick); + connect(b, &ColorButton::customContextMenuRequested, this, &ColorSwatchChooser::HandleContextMenu); + } + } + + LoadSwatches(); +} + +void ColorSwatchChooser::SetDefaultColor(int index) +{ + if (index < kDefaultColorCount) { + buttons_[index]->SetColor(kDefaultColors[index]); + } else { + buttons_[index]->SetColor(Color(1.0, 1.0, 1.0)); + } +} + +void ColorSwatchChooser::HandleButtonClick() +{ + auto b = static_cast(sender()); + + emit ColorClicked(b->GetColor()); + SetCurrentColor(b->GetColor()); +} + +void ColorSwatchChooser::HandleContextMenu() +{ + Menu m(this); + + auto save_action = m.addAction(tr("Save Color Here")); + connect(save_action, &QAction::triggered, this, &ColorSwatchChooser::SaveCurrentColor); + + m.addSeparator(); + + auto reset_action = m.addAction(tr("Reset To Default")); + connect(reset_action, &QAction::triggered, this, &ColorSwatchChooser::ResetMenuButton); + + menu_btn_ = static_cast(sender()); + + m.exec(QCursor::pos()); +} + +void ColorSwatchChooser::SaveCurrentColor() +{ + menu_btn_->SetColor(current_); + + SaveSwatches(); +} + +void ColorSwatchChooser::ResetMenuButton() +{ + for (int i=0; i> version; + + if (version == 1) { + int index = 0; + while (index < kBtnCount && !d.atEnd()) { + Color::DataType r; + QString s; + ManagedColor c; + ColorTransform t; + bool is_display; + + c.set_alpha(1.0); + + d >> r; + c.set_red(r); + + d >> r; + c.set_green(r); + + d >> r; + c.set_blue(r); + + d >> s; + c.set_color_input(s); + + d >> is_display; + if (is_display) { + QString display, view, look; + d >> display; + d >> view; + d >> look; + c.set_color_output(ColorTransform(display, view, look)); + } else { + d >> s; + c.set_color_output(ColorTransform(s)); + } + + buttons_[index]->SetColor(c); + + index++; + } + } + + f.close(); + } +} + +void ColorSwatchChooser::SaveSwatches() +{ + QString fn = GetSwatchFilename(); + QFile f(fn); + + if (f.open(QFile::WriteOnly)) { + QDataStream d(&f); + + const uint version = 1; + d << version; + + for (int i=0; iGetColor(); + d << c.red(); + d << c.green(); + d << c.blue(); + d << c.color_input(); + d << c.color_output().is_display(); + + if (c.color_output().is_display()) { + d << c.color_output().display(); + d << c.color_output().view(); + d << c.color_output().look(); + } else { + d << c.color_output().output(); + } + } + + f.close(); + } else { + qCritical() << "Failed to open swatch file" << fn << "for writing"; + } +} + +} diff --git a/app/widget/colorwheel/colorswatchchooser.h b/app/widget/colorwheel/colorswatchchooser.h new file mode 100644 index 000000000..a2b4c571d --- /dev/null +++ b/app/widget/colorwheel/colorswatchchooser.h @@ -0,0 +1,73 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 COLORSWATCHCHOOSER_H +#define COLORSWATCHCHOOSER_H + +#include "node/color/colormanager/colormanager.h" +#include "widget/colorbutton/colorbutton.h" + +namespace olive { + +class ColorSwatchChooser : public QWidget +{ + Q_OBJECT +public: + ColorSwatchChooser(ColorManager *manager, QWidget *parent = nullptr); + +public slots: + void SetCurrentColor(const ManagedColor &c) + { + current_ = c; + } + +signals: + void ColorClicked(const ManagedColor &c); + +private: + void SetDefaultColor(int index); + + static QString GetSwatchFilename(); + + void LoadSwatches(); + void SaveSwatches(); + + static const int kRowCount = 4; + static const int kColCount = 8; + static const int kBtnCount = kRowCount*kColCount; + ColorButton *buttons_[kBtnCount]; + + ManagedColor current_; + ColorButton *menu_btn_; + +private slots: + void HandleButtonClick(); + + void HandleContextMenu(); + + void SaveCurrentColor(); + + void ResetMenuButton(); + +}; + +} + +#endif // COLORSWATCHCHOOSER_H diff --git a/app/widget/colorwheel/colorvalueswidget.cpp b/app/widget/colorwheel/colorvalueswidget.cpp index 85ac6fb62..e125decd5 100644 --- a/app/widget/colorwheel/colorvalueswidget.cpp +++ b/app/widget/colorwheel/colorvalueswidget.cpp @@ -270,14 +270,16 @@ ColorValuesTab::ColorValuesTab(bool with_legacy_option, QWidget *parent) : row++; - hex_lbl_ = new QLabel(tr("Hex")); + hex_lbl_ = new QLabel(tr("Web")); layout->addWidget(hex_lbl_, row, 0); hex_slider_ = new StringSlider(); connect(hex_slider_, &StringSlider::ValueChanged, this, &ColorValuesTab::HexChanged); layout->addWidget(hex_slider_, row, 1); - LegacyChanged(AreSlidersLegacyValues()); + if (legacy_box_) { + LegacyChanged(AreSlidersLegacyValues()); + } } Color ColorValuesTab::GetColor() const @@ -371,11 +373,20 @@ void ColorValuesTab::LegacyChanged(bool legacy) s->SetDragMultiplier(drag_multiplier); } - hex_lbl_->setVisible(legacy); - hex_slider_->setVisible(legacy); UpdateHex(); } +QString RGBValToString(double d) +{ + QString s = QString::number(d); + + if (!s.contains('.')) { + s.append(QStringLiteral(".0")); + } + + return s; +} + void ColorValuesTab::UpdateHex() { if (AreSlidersLegacyValues()) { @@ -390,9 +401,39 @@ void ColorValuesTab::UpdateHex() hex_slider_->SetValue(QStringLiteral("%1").arg(rgb, 6, 16, QLatin1Char('0')).toUpper()); } + } else { + hex_slider_->SetValue(QStringLiteral("rgb(%1, %2, %3)").arg(RGBValToString(red_slider_->GetValue()), RGBValToString(green_slider_->GetValue()), RGBValToString(blue_slider_->GetValue()))); } } +bool ParseRGBString(QString s, double *r, double *g, double *b) +{ + // Trim whitespace + s = s.trimmed(); + + s.remove(QStringLiteral("rgba"), Qt::CaseInsensitive); + s.remove(QStringLiteral("rgb"), Qt::CaseInsensitive); + s.remove('('); + s.remove(')'); + + QStringList vals = s.split(','); + if (vals.size() < 3) { + return false; + } + + bool ok; + *r = vals.at(0).toDouble(&ok); + if (!ok) return false; + + *g = vals.at(1).toDouble(&ok); + if (!ok) return false; + + *b = vals.at(2).toDouble(&ok); + if (!ok) return false; + + return true; +} + void ColorValuesTab::HexChanged(const QString &s) { bool ok; @@ -403,15 +444,37 @@ void ColorValuesTab::HexChanged(const QString &s) uint32_t g = (hex & 0x00FF00) >> 8; uint32_t b = (hex & 0x0000FF); - red_slider_->SetValue(r); - green_slider_->SetValue(g); - blue_slider_->SetValue(b); + if (AreSlidersLegacyValues()) { + red_slider_->SetValue(r); + green_slider_->SetValue(g); + blue_slider_->SetValue(b); + } else { + red_slider_->SetValue(double(r)/kLegacyMultiplier); + green_slider_->SetValue(double(g)/kLegacyMultiplier); + blue_slider_->SetValue(double(b)/kLegacyMultiplier); + } emit ColorChanged(GetColor()); } else { - // Return to original value - UpdateHex(); + // Attempt to parse rgb/rgba + double r, g, b; + if (ParseRGBString(s, &r, &g, &b)) { + if (AreSlidersLegacyValues()) { + red_slider_->SetValue(r*kLegacyMultiplier); + green_slider_->SetValue(g*kLegacyMultiplier); + blue_slider_->SetValue(b*kLegacyMultiplier); + } else { + red_slider_->SetValue(r); + green_slider_->SetValue(g); + blue_slider_->SetValue(b); + } + + emit ColorChanged(GetColor()); + } } + + // Conform string to our formatting + UpdateHex(); } bool ColorValuesTab::AreSlidersLegacyValues() const From 00526fe954d28f0c4034bd73386ab4fc4d107917 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 25 Sep 2022 16:36:12 -0700 Subject: [PATCH 19/19] timeline: use different shadow color if block is disabled --- app/widget/timelinewidget/view/timelineview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 94fa7143e..5bad687f6 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -476,7 +476,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q block_right - block_left, block_height); - QColor shadow_color = block->color().toQColor().darker(); + QColor shadow_color = block->is_enabled() ? block->color().toQColor().darker() : QColor(Qt::darkGray).darker(); if (r.width() <= 3) { painter->fillRect(r, shadow_color);