diff --git a/app/node/color/CMakeLists.txt b/app/node/color/CMakeLists.txt
index afaa401ff..046c499da 100644
--- a/app/node/color/CMakeLists.txt
+++ b/app/node/color/CMakeLists.txt
@@ -15,6 +15,9 @@
# along with this program. If not, see .
add_subdirectory(colormanager)
+add_subdirectory(displaytransform)
+add_subdirectory(ociobase)
+add_subdirectory(ociogradingtransformlinear)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
diff --git a/app/node/color/colormanager/colormanager.cpp b/app/node/color/colormanager/colormanager.cpp
index 895a522b4..6164592b6 100644
--- a/app/node/color/colormanager/colormanager.cpp
+++ b/app/node/color/colormanager/colormanager.cpp
@@ -273,6 +273,7 @@ void ColorManager::InputValueChangedEvent(const QString &input, int element)
try {
SetConfig(OCIO::Config::CreateFromFile(GetConfigFilename().toUtf8()));
+ emit ConfigChanged();
} catch (OCIO::Exception&) {}
}
diff --git a/app/node/color/colormanager/colormanager.h b/app/node/color/colormanager/colormanager.h
index 06e0a8abd..a26d3b424 100644
--- a/app/node/color/colormanager/colormanager.h
+++ b/app/node/color/colormanager/colormanager.h
@@ -121,6 +121,9 @@ public:
virtual void Retranslate() override;
+signals:
+ void ConfigChanged();
+
protected:
virtual void InputValueChangedEvent(const QString &input, int element) override;
diff --git a/app/node/color/displaytransform/CMakeLists.txt b/app/node/color/displaytransform/CMakeLists.txt
new file mode 100644
index 000000000..4bede1600
--- /dev/null
+++ b/app/node/color/displaytransform/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2021 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/color/displaytransform/displaytransform.cpp
+ node/color/displaytransform/displaytransform.h
+ PARENT_SCOPE
+)
diff --git a/app/node/color/displaytransform/displaytransform.cpp b/app/node/color/displaytransform/displaytransform.cpp
new file mode 100644
index 000000000..5152d6614
--- /dev/null
+++ b/app/node/color/displaytransform/displaytransform.cpp
@@ -0,0 +1,144 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 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 "displaytransform.h"
+
+#include "node/color/colormanager/colormanager.h"
+
+namespace olive {
+
+const QString DisplayTransformNode::kDisplayInput = QStringLiteral("display_in");
+const QString DisplayTransformNode::kViewInput = QStringLiteral("view_in");
+const QString DisplayTransformNode::kDirectionInput = QStringLiteral("dir_in");
+
+#define super OCIOBaseNode
+
+DisplayTransformNode::DisplayTransformNode()
+{
+ AddInput(kDisplayInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable));
+
+ AddInput(kViewInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable));
+
+ AddInput(kDirectionInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable));
+}
+
+QString DisplayTransformNode::Name() const
+{
+ return tr("Display Transform");
+}
+
+QString DisplayTransformNode::id() const
+{
+ return QStringLiteral("org.olivevideoeditor.Olive.displaytransform");
+}
+
+QVector DisplayTransformNode::Category() const
+{
+ return {kCategoryColor};
+}
+
+QString DisplayTransformNode::Description() const
+{
+ return tr("Converts an image to or from a display color space.");
+}
+
+void DisplayTransformNode::Retranslate()
+{
+ super::Retranslate();
+
+ SetInputName(kTextureInput, tr("Input"));
+ SetInputName(kDisplayInput, tr("Display"));
+ SetInputName(kViewInput, tr("View"));
+ SetInputName(kDirectionInput, tr("Direction"));
+ SetComboBoxStrings(kDirectionInput, {tr("Forward"), tr("Inverse")});
+}
+
+void DisplayTransformNode::InputValueChangedEvent(const QString &input, int element)
+{
+ Q_UNUSED(element);
+ if (input == kDisplayInput || input == kDirectionInput || input == kViewInput) {
+ if (input == kDisplayInput) {
+ UpdateViews();
+ }
+ GenerateProcessor();
+ }
+}
+
+QString DisplayTransformNode::GetDisplay() const
+{
+ if (manager()) {
+ int index = GetStandardValue(kDisplayInput).toInt();
+ if (index < manager()->ListAvailableDisplays().size()) {
+ return manager()->ListAvailableDisplays().at(index);
+ }
+ }
+ return QString();
+}
+
+QString DisplayTransformNode::GetView() const
+{
+ if (manager()) {
+ QString display = GetDisplay();
+ if (!display.isEmpty()) {
+ int index = GetStandardValue(kViewInput).toInt();
+ QStringList views = manager()->ListAvailableViews(display);
+ if (index < views.size()) {
+ return views.at(index);
+ }
+ }
+ }
+ return QString();
+}
+
+ColorProcessor::Direction DisplayTransformNode::GetDirection() const
+{
+ return static_cast(GetStandardValue(kDirectionInput).toInt());;
+}
+
+void DisplayTransformNode::UpdateDisplays()
+{
+ if (manager()) {
+ SetComboBoxStrings(kDisplayInput, manager()->ListAvailableDisplays());
+ }
+}
+
+void DisplayTransformNode::UpdateViews()
+{
+ if (manager()) {
+ SetComboBoxStrings(kViewInput, manager()->ListAvailableViews(GetDisplay()));
+ }
+}
+
+void DisplayTransformNode::ConfigChanged()
+{
+ UpdateDisplays();
+ UpdateViews();
+ GenerateProcessor();
+}
+
+void DisplayTransformNode::GenerateProcessor()
+{
+ if (manager()) {
+ ColorTransform transform(GetDisplay(), GetView(), QString());
+ set_processor(ColorProcessor::Create(manager(), manager()->GetReferenceColorSpace(), transform, GetDirection()));
+ }
+}
+
+}
diff --git a/app/node/color/displaytransform/displaytransform.h b/app/node/color/displaytransform/displaytransform.h
new file mode 100644
index 000000000..ac6be6bef
--- /dev/null
+++ b/app/node/color/displaytransform/displaytransform.h
@@ -0,0 +1,67 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 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 DISPLAYTRANSFORMNODE_H
+#define DISPLAYTRANSFORMNODE_H
+
+#include "node/color/ociobase/ociobase.h"
+#include "render/colorprocessor.h"
+
+namespace olive {
+
+class DisplayTransformNode : public OCIOBaseNode
+{
+ Q_OBJECT
+ public:
+ DisplayTransformNode();
+
+ NODE_DEFAULT_FUNCTIONS(DisplayTransformNode)
+
+ 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 void InputValueChangedEvent(const QString &input, int element) override;
+
+ QString GetDisplay() const;
+ QString GetView() const;
+ ColorProcessor::Direction GetDirection() const;
+
+ static const QString kDisplayInput;
+ static const QString kViewInput;
+ static const QString kDirectionInput;
+
+protected slots:
+ virtual void ConfigChanged() override;
+
+private:
+ void GenerateProcessor();
+
+ void UpdateDisplays();
+
+ void UpdateViews();
+
+};
+
+} // olive
+
+#endif // DISPLAYTRANSFORMNODE_H
diff --git a/app/node/color/ociobase/CMakeLists.txt b/app/node/color/ociobase/CMakeLists.txt
new file mode 100644
index 000000000..fa00411a4
--- /dev/null
+++ b/app/node/color/ociobase/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2021 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/color/ociobase/ociobase.cpp
+ node/color/ociobase/ociobase.h
+ PARENT_SCOPE
+)
diff --git a/app/node/color/ociobase/ociobase.cpp b/app/node/color/ociobase/ociobase.cpp
new file mode 100644
index 000000000..22e03f3ba
--- /dev/null
+++ b/app/node/color/ociobase/ociobase.cpp
@@ -0,0 +1,69 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 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 "ociobase.h"
+
+#include "node/color/colormanager/colormanager.h"
+#include "node/project/project.h"
+
+namespace olive {
+
+const QString OCIOBaseNode::kTextureInput = QStringLiteral("tex_in");
+
+OCIOBaseNode::OCIOBaseNode() :
+ manager_(nullptr),
+ processor_(nullptr)
+{
+ AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
+
+ SetEffectInput(kTextureInput);
+
+ connect(this, &Node::AddedToGraph, this, &OCIOBaseNode::ParentChanged);
+
+ SetFlags(kVideoEffect);
+}
+
+void OCIOBaseNode::ParentChanged(NodeGraph *graph)
+{
+ if (manager_) {
+ disconnect(manager_, &ColorManager::ConfigChanged, this, &OCIOBaseNode::ConfigChanged);
+ manager_ = nullptr;
+ }
+
+ if (Project *p = dynamic_cast(graph)) {
+ manager_ = p->color_manager();
+ connect(manager_, &ColorManager::ConfigChanged, this, &OCIOBaseNode::ConfigChanged);
+ ConfigChanged();
+ }
+}
+
+void OCIOBaseNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
+{
+ if (!value[kTextureInput].data().isNull() && processor_) {
+ ColorTransformJob job;
+
+ job.SetColorProcessor(processor_);
+ job.SetInputTexture(value[kTextureInput].data().value());
+
+ table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
+ }
+}
+
+}
diff --git a/app/node/color/ociobase/ociobase.h b/app/node/color/ociobase/ociobase.h
new file mode 100644
index 000000000..a0b6fc0f2
--- /dev/null
+++ b/app/node/color/ociobase/ociobase.h
@@ -0,0 +1,60 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 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 OCIOBASENODE_H
+#define OCIOBASENODE_H
+
+#include "node/node.h"
+#include "render/job/colortransformjob.h"
+
+namespace olive {
+
+class OCIOBaseNode : public Node
+{
+ Q_OBJECT
+public:
+ OCIOBaseNode();
+
+ virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override;
+
+ static const QString kTextureInput;
+
+protected slots:
+ virtual void ConfigChanged() = 0;
+
+protected:
+ ColorManager *manager() const { return manager_; }
+
+ ColorProcessorPtr processor() const { return processor_; }
+ void set_processor(ColorProcessorPtr p) { processor_ = p; }
+
+private:
+ ColorManager *manager_;
+
+ ColorProcessorPtr processor_;
+
+private slots:
+ void ParentChanged(olive::NodeGraph *graph);
+
+};
+
+}
+
+#endif // OCIOBASENODE_H
diff --git a/app/node/color/ociogradingtransformlinear/CMakeLists.txt b/app/node/color/ociogradingtransformlinear/CMakeLists.txt
new file mode 100644
index 000000000..05a3d4ae9
--- /dev/null
+++ b/app/node/color/ociogradingtransformlinear/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2021 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/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp
+ node/color/ociogradingtransformlinear/ociogradingtransformlinear.h
+ PARENT_SCOPE
+)
diff --git a/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp
new file mode 100644
index 000000000..7a8bc1c74
--- /dev/null
+++ b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp
@@ -0,0 +1,218 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 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 "ociogradingtransformlinear.h"
+
+#include
+
+#include "common/ocioutils.h"
+#include "node/project/project.h"
+#include "render/colorprocessor.h"
+#include "widget/slider/floatslider.h"
+
+namespace olive {
+
+const QString OCIOGradingTransformLinearNode::kContrastInput = QStringLiteral("ocio_grading_primary_contrast");
+const QString OCIOGradingTransformLinearNode::kOffsetInput = QStringLiteral("ocio_grading_primary_offset");
+const QString OCIOGradingTransformLinearNode::kExposureInput = QStringLiteral("ocio_grading_primary_exposure");
+const QString OCIOGradingTransformLinearNode::kSaturationInput = QStringLiteral("ocio_grading_primary_saturation");
+const QString OCIOGradingTransformLinearNode::kPivotInput = QStringLiteral("ocio_grading_primary_pivot");
+const QString OCIOGradingTransformLinearNode::kClampBlackEnableInput = QStringLiteral("clamp_black_enable_in");
+const QString OCIOGradingTransformLinearNode::kClampBlackInput = QStringLiteral("ocio_grading_primary_clampBlack");
+const QString OCIOGradingTransformLinearNode::kClampWhiteEnableInput = QStringLiteral("clamp_white_enable_in");
+const QString OCIOGradingTransformLinearNode::kClampWhiteInput = QStringLiteral("ocio_grading_primary_clampWhite");
+
+#define super OCIOBaseNode
+
+OCIOGradingTransformLinearNode::OCIOGradingTransformLinearNode()
+{
+ AddInput(kContrastInput, NodeValue::kVec4, QVector4D{1.0, 1.0, 1.0, 1.0});
+ // Minimum based on OCIO::GradingPrimary::validate
+ SetInputProperty(kContrastInput, QStringLiteral("min"), QVector4D{0.01f, 0.01f, 0.01f, 0.01f});
+ SetInputProperty(kContrastInput, QStringLiteral("base"), 0.01);
+ SetVec4InputColors(kContrastInput);
+
+ AddInput(kOffsetInput, NodeValue::kVec4, QVector4D{0.0, 0.0, 0.0, 0.0});
+ SetInputProperty(kOffsetInput, QStringLiteral("base"), 0.01);
+ SetVec4InputColors(kOffsetInput);
+
+ AddInput(kExposureInput, NodeValue::kVec4, QVector4D{0.0, 0.0, 0.0, 0.0});
+ SetInputProperty(kExposureInput, QStringLiteral("base"), 0.01);
+ SetVec4InputColors(kExposureInput);
+
+ AddInput(kSaturationInput, NodeValue::kFloat, 1.0);
+ SetInputProperty(kSaturationInput, QStringLiteral("view"), FloatSlider::kPercentage);
+ SetInputProperty(kSaturationInput, QStringLiteral("min"), 0.0);
+
+ AddInput(kPivotInput, NodeValue::kFloat, 0.18); // Default listed in OCIO::GradingPrimary
+ SetInputProperty(kPivotInput, QStringLiteral("base"), 0.01);
+
+ AddInput(kClampBlackEnableInput, NodeValue::kBoolean, false);
+
+ AddInput(kClampBlackInput, NodeValue::kFloat, 0.0);
+ SetInputProperty(kClampBlackInput, QStringLiteral("enabled"), GetStandardValue(kClampBlackEnableInput).toBool());
+ SetInputProperty(kClampBlackInput, QStringLiteral("base"), 0.01);
+
+ AddInput(kClampWhiteEnableInput, NodeValue::kBoolean, false);
+
+ AddInput(kClampWhiteInput, NodeValue::kFloat, 1.0);
+ SetInputProperty(kClampWhiteInput, QStringLiteral("enabled"), GetStandardValue(kClampWhiteEnableInput).toBool());
+ SetInputProperty(kClampWhiteInput, QStringLiteral("base"), 0.01);
+
+ // FIXME: Temporarily disabled. This will break if "clamp black" is keyframed or connected to
+ // something and there's currently no solution to remedy that. If there is in the future,
+ // we can look into re-enabling this.
+ //SetInputProperty(kClampWhiteInput, QStringLiteral("min"), GetStandardValue(kClampBlackInput).toDouble() + 0.000001);
+}
+
+QString OCIOGradingTransformLinearNode::Name() const
+{
+ return tr("OCIO Color Grading (Linear)");
+}
+
+QString OCIOGradingTransformLinearNode::id() const
+{
+ return QStringLiteral("org.olivevideoeditor.Olive.ociogradingtransformlinear");
+}
+
+QVector OCIOGradingTransformLinearNode::Category() const
+{
+ return {kCategoryColor};
+}
+
+QString OCIOGradingTransformLinearNode::Description() const
+{
+ return tr("Simple linear color grading using OpenColorIO.");
+}
+
+void OCIOGradingTransformLinearNode::Retranslate()
+{
+ super::Retranslate();
+
+ SetInputName(kTextureInput, tr("Input"));
+ SetInputName(kContrastInput, tr("Contrast"));
+ SetInputName(kOffsetInput, tr("Offset"));
+ SetInputName(kExposureInput, tr("Exposure"));
+ SetInputProperty(kExposureInput, QStringLiteral("tooltip"), tr("Exposure increments in stops."));
+ SetInputName(kSaturationInput, tr("Saturation"));
+ SetInputName(kPivotInput, tr("Pivot"));
+ SetInputName(kClampBlackEnableInput, tr("Enable Black Clamp"));
+ SetInputName(kClampBlackInput, tr("Black Clamp"));
+ SetInputName(kClampWhiteEnableInput, tr("Enable White Clamp"));
+ SetInputName(kClampWhiteInput, tr("White Clamp"));
+}
+
+void OCIOGradingTransformLinearNode::InputValueChangedEvent(const QString &input, int element)
+{
+ Q_UNUSED(element);
+
+ if (input == kClampWhiteEnableInput) {
+ SetInputProperty(kClampWhiteInput, QStringLiteral("enabled"), GetStandardValue(kClampWhiteEnableInput).toBool());
+ } else if (input == kClampBlackEnableInput) {
+ SetInputProperty(kClampBlackInput, QStringLiteral("enabled"), GetStandardValue(kClampBlackEnableInput).toBool());
+ } else if (input == kClampBlackInput) {
+ // Ensure the white clamp is always greater than the black clamp as per OCIO::GradingPrimary::validate
+ // FIXME: Temporarily disabled. This will break if "clamp black" is keyframed or connected to
+ // something and there's currently no solution to remedy that. If there is in the future,
+ // we can look into re-enabling this.
+ //SetInputProperty(kClampWhiteInput, QStringLiteral("min"), GetStandardValue(kClampBlackInput).toDouble() + 0.000001);
+ }
+
+ GenerateProcessor();
+}
+
+void OCIOGradingTransformLinearNode::GenerateProcessor()
+{
+ if (manager()) {
+ OCIO::GradingPrimaryTransformRcPtr gp = OCIO::GradingPrimaryTransform::Create(OCIO::GRADING_LIN);
+ gp->makeDynamic();
+ gp->setDirection(OCIO::TransformDirection::TRANSFORM_DIR_FORWARD);
+
+ try {
+ set_processor(ColorProcessor::Create(manager()->GetConfig()->getProcessor(gp)));
+ } catch (const OCIO::Exception &e) {
+ std::cerr << std::endl << e.what() << std::endl;
+ }
+ }
+}
+
+void OCIOGradingTransformLinearNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
+{
+ if (!value[kTextureInput].data().isNull() && processor()) {
+ ColorTransformJob job;
+
+ job.SetColorProcessor(processor());
+ job.SetInputTexture(value[kTextureInput].data().value());
+
+ job.InsertValue(value);
+
+ 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].value();
+ offset[RED_CHANNEL] += offset[MASTER_CHANNEL];
+ offset[GREEN_CHANNEL] += offset[MASTER_CHANNEL];
+ offset[BLUE_CHANNEL] += offset[MASTER_CHANNEL];
+ job.InsertValue(kOffsetInput, NodeValue(NodeValue::kVec3, QVector3D(offset[RED_CHANNEL], offset[GREEN_CHANNEL], offset[BLUE_CHANNEL])));
+
+ QVector4D exposure = value[kExposureInput].value();
+ 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.InsertValue(kExposureInput, NodeValue(NodeValue::kVec3, QVector3D(exposure[RED_CHANNEL], exposure[GREEN_CHANNEL], exposure[BLUE_CHANNEL])));
+
+ QVector4D contrast = value[kContrastInput].value();
+ contrast[RED_CHANNEL] *= contrast[MASTER_CHANNEL];
+ contrast[GREEN_CHANNEL] *= contrast[MASTER_CHANNEL];
+ contrast[BLUE_CHANNEL] *= contrast[MASTER_CHANNEL];
+ job.InsertValue(kContrastInput, NodeValue(NodeValue::kVec3, QVector3D(contrast[RED_CHANNEL], contrast[GREEN_CHANNEL], contrast[BLUE_CHANNEL])));
+
+ if (!value[kClampBlackEnableInput].data().toBool()) {
+ job.InsertValue(kClampBlackInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampBlack()));
+ }
+
+ if (!value[kClampWhiteEnableInput].data().toBool()) {
+ job.InsertValue(kClampWhiteInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampWhite()));
+ }
+
+ table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
+ }
+}
+
+void OCIOGradingTransformLinearNode::ConfigChanged()
+{
+ GenerateProcessor();
+}
+
+void OCIOGradingTransformLinearNode::SetVec4InputColors(const QString &input)
+{
+ SetInputProperty(input, QStringLiteral("color0"), QColor(192, 192, 192).name());
+ SetInputProperty(input, QStringLiteral("color1"), QColor(255, 0, 0).name());
+ SetInputProperty(input, QStringLiteral("color2"), QColor(0, 255, 0).name());
+ SetInputProperty(input, QStringLiteral("color3"), QColor(0, 0, 255).name());
+}
+
+}
diff --git a/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.h b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.h
new file mode 100644
index 000000000..8e5d59cca
--- /dev/null
+++ b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.h
@@ -0,0 +1,68 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 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 OCIOGRADINGTRANSFORMLINEARNODE_H
+#define OCIOGRADINGTRANSFORMLINEARNODE_H
+
+#include "node/color/ociobase/ociobase.h"
+#include "render/colorprocessor.h"
+
+namespace olive {
+
+class OCIOGradingTransformLinearNode : public OCIOBaseNode
+{
+ Q_OBJECT
+ public:
+ OCIOGradingTransformLinearNode();
+
+ NODE_DEFAULT_FUNCTIONS(OCIOGradingTransformLinearNode)
+
+ 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 void InputValueChangedEvent(const QString &input, int element) override;
+ void GenerateProcessor();
+
+ virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override;
+
+ static const QString kContrastInput;
+ static const QString kOffsetInput;
+ static const QString kExposureInput;
+ static const QString kSaturationInput;
+ static const QString kPivotInput;
+ static const QString kClampBlackEnableInput;
+ static const QString kClampBlackInput;
+ static const QString kClampWhiteEnableInput;
+ static const QString kClampWhiteInput;
+
+protected slots:
+ virtual void ConfigChanged() override;
+
+private:
+ void SetVec4InputColors(const QString &input);
+
+};
+
+} // olive
+
+#endif
diff --git a/app/node/factory.cpp b/app/node/factory.cpp
index cf5e3b0c4..4292d5037 100644
--- a/app/node/factory.cpp
+++ b/app/node/factory.cpp
@@ -29,6 +29,8 @@
#include "block/subtitle/subtitle.h"
#include "block/transition/crossdissolve/crossdissolvetransition.h"
#include "block/transition/diptocolor/diptocolortransition.h"
+#include "color/displaytransform/displaytransform.h"
+#include "color/ociogradingtransformlinear/ociogradingtransformlinear.h"
#include "distort/cornerpin/cornerpindistortnode.h"
#include "distort/crop/cropdistortnode.h"
#include "distort/flip/flipdistortnode.h"
@@ -52,6 +54,7 @@
#include "math/trigonometry/trigonometry.h"
#include "keying/colordifferencekey/colordifferencekey.h"
#include "keying/despill/despill.h"
+#include "keying/chromakey/chromakey.h"
#include "output/track/track.h"
#include "output/viewer/viewer.h"
#include "project/folder/folder.h"
@@ -281,6 +284,12 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new TimeOffsetNode();
case kCornerPinDistort:
return new CornerPinDistortNode();
+ case kDisplayTransform:
+ return new DisplayTransformNode();
+ case kOCIOGradingTransformLinear:
+ return new OCIOGradingTransformLinearNode();
+ case kChromaKey:
+ return new ChromaKeyNode();
case kInternalNodeCount:
break;
diff --git a/app/node/factory.h b/app/node/factory.h
index 56192cc7b..54e417b3e 100644
--- a/app/node/factory.h
+++ b/app/node/factory.h
@@ -70,6 +70,9 @@ public:
kNoiseGenerator,
kTimeOffsetNode,
kCornerPinDistort,
+ kDisplayTransform,
+ kOCIOGradingTransformLinear,
+ kChromaKey,
// Count value
kInternalNodeCount
diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp
index 30cbf6065..475aca566 100644
--- a/app/node/generator/matrix/matrix.cpp
+++ b/app/node/generator/matrix/matrix.cpp
@@ -183,7 +183,7 @@ void MatrixGenerator::InputValueChangedEvent(const QString &input, int element)
Q_UNUSED(element)
if (input == kUniformScaleInput) {
- SetInputProperty(kScaleInput, QStringLiteral("disabley"), GetStandardValue(kUniformScaleInput).toBool());
+ SetInputProperty(kScaleInput, QStringLiteral("disable1"), GetStandardValue(kUniformScaleInput).toBool());
}
}
diff --git a/app/node/hashtraverser.cpp b/app/node/hashtraverser.cpp
index 78edf681b..fe469535e 100644
--- a/app/node/hashtraverser.cpp
+++ b/app/node/hashtraverser.cpp
@@ -98,6 +98,12 @@ void HashTraverser::ProcessShader(TexturePtr destination, const Node *node, cons
texture_ids_.insert(destination.get(), hash_.result());
}
+void HashTraverser::ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob &job)
+{
+ Hash(job.GetColorProcessor()->id());
+ texture_ids_.insert(destination.get(), hash_.result());
+}
+
void HashTraverser::ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job)
{
texture_ids_.insert(destination.get(), hash_.result());
diff --git a/app/node/hashtraverser.h b/app/node/hashtraverser.h
index 707d89b9f..c013f26b8 100644
--- a/app/node/hashtraverser.h
+++ b/app/node/hashtraverser.h
@@ -39,6 +39,8 @@ protected:
virtual void ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob& job) override;
+ virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job) override;
+
virtual void ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job) override;
virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job) override;
diff --git a/app/node/keying/CMakeLists.txt b/app/node/keying/CMakeLists.txt
index 2177a550a..4dbd437cd 100644
--- a/app/node/keying/CMakeLists.txt
+++ b/app/node/keying/CMakeLists.txt
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+add_subdirectory(chromakey)
add_subdirectory(colordifferencekey)
add_subdirectory(despill)
diff --git a/app/node/keying/chromakey/CMakeLists.txt b/app/node/keying/chromakey/CMakeLists.txt
new file mode 100644
index 000000000..c2af2bba4
--- /dev/null
+++ b/app/node/keying/chromakey/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2021 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/keying/chromakey/chromakey.h
+ node/keying/chromakey/chromakey.cpp
+ PARENT_SCOPE
+)
\ No newline at end of file
diff --git a/app/node/keying/chromakey/chromakey.cpp b/app/node/keying/chromakey/chromakey.cpp
new file mode 100644
index 000000000..18910e886
--- /dev/null
+++ b/app/node/keying/chromakey/chromakey.cpp
@@ -0,0 +1,150 @@
+/***
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2019 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 "chromakey.h"
+
+#include "node/color/colormanager/colormanager.h"
+#include "render/colorprocessor.h"
+
+namespace olive {
+
+#define super OCIOBaseNode
+
+const QString ChromaKeyNode::kColorInput = QStringLiteral("color_key");
+const QString ChromaKeyNode::kMaskOnlyInput = QStringLiteral("mask_only_in");
+const QString ChromaKeyNode::kUpperToleranceInput = QStringLiteral("upper_tolerence_in");
+const QString ChromaKeyNode::kLowerToleranceInput = QStringLiteral("lower_tolerence_in");
+const QString ChromaKeyNode::kGarbageMatteInput = QStringLiteral("garbage_in");
+const QString ChromaKeyNode::kCoreMatteInput = QStringLiteral("core_in");
+const QString ChromaKeyNode::kShadowsInput = QStringLiteral("shadows_in");
+const QString ChromaKeyNode::kHighlightsInput = QStringLiteral("highlights_in");
+
+ChromaKeyNode::ChromaKeyNode()
+{
+ AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0.0f, 1.0f, 0.0f, 1.0f)));
+
+ AddInput(kLowerToleranceInput, NodeValue::kFloat, 5.0);
+ SetInputProperty(kLowerToleranceInput, QStringLiteral("min"), 0.0);
+ SetInputProperty(kLowerToleranceInput, QStringLiteral("base"), 0.1);
+
+ AddInput(kUpperToleranceInput, NodeValue::kFloat, 25.0);
+ SetInputProperty(kUpperToleranceInput, QStringLiteral("base"), 0.1);
+
+ // FIXME: Temporarily disabled. This will break if "lower tolerance" is keyframed or connected to
+ // something and there's currently no solution to remedy that. If there is in the future,
+ // we can look into re-enabling this.
+ //SetInputProperty(kUpperToleranceInput, QStringLiteral("min"), GetStandardValue(kLowerToleranceInput).toDouble());
+
+ AddInput(kGarbageMatteInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
+
+ AddInput(kCoreMatteInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
+
+ AddInput(kHighlightsInput, NodeValue::kFloat, 100.0f);
+ SetInputProperty(kHighlightsInput, QStringLiteral("min"), 0.0);
+ SetInputProperty(kHighlightsInput, QStringLiteral("base"), 0.1);
+
+ AddInput(kShadowsInput, NodeValue::kFloat, 100.0f);
+ SetInputProperty(kShadowsInput, QStringLiteral("min"), 0.0);
+ SetInputProperty(kShadowsInput, QStringLiteral("base"), 0.1);
+
+ AddInput(kMaskOnlyInput, NodeValue::kBoolean, false);
+}
+
+QString ChromaKeyNode::Name() const
+{
+ return tr("Chroma Key");
+}
+
+QString ChromaKeyNode::id() const
+{
+ return QStringLiteral("org.olivevideoeditor.Olive.chromakey");
+}
+
+QVector ChromaKeyNode::Category() const
+{
+ return {kCategoryKeying};
+}
+
+QString ChromaKeyNode::Description() const
+{
+ return tr("A simple color key based on the distance from the chroma of a selected color.");
+}
+
+void ChromaKeyNode::Retranslate()
+{
+ super::Retranslate();
+ SetInputName(kTextureInput, tr("Input"));
+ SetInputName(kGarbageMatteInput, tr("Garbage Matte"));
+ SetInputName(kCoreMatteInput, tr("Core Matte"));
+ SetInputName(kColorInput, tr("Key Color"));
+ SetInputName(kShadowsInput, tr("Shadows"));
+ SetInputName(kHighlightsInput, tr("Highlights"));
+ SetInputName(kUpperToleranceInput, tr("Upper Tolerance"));
+ SetInputName(kLowerToleranceInput, tr("Lower Tolerance"));
+ SetInputName(kMaskOnlyInput, tr("Show Mask Only"));
+}
+
+void ChromaKeyNode::InputValueChangedEvent(const QString &input, int element)
+{
+ Q_UNUSED(element);
+ if (input == kLowerToleranceInput) {
+ // FIXME: Temporarily disabled. This will break if "lower tolerance" is keyframed or connected to
+ // something and there's currently no solution to remedy that. If there is in the future,
+ // we can look into re-enabling this.
+ //SetInputProperty(kUpperToleranceInput, QStringLiteral("min"), GetStandardValue(kLowerToleranceInput).toDouble());
+ }
+
+ GenerateProcessor();
+}
+
+ShaderCode ChromaKeyNode::GetShaderCode(const ShaderRequest &request) const
+{
+ return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/chromakey.frag")).arg(request.stub));
+}
+
+void ChromaKeyNode::GenerateProcessor()
+{
+ if (manager()){
+ try {
+ ColorTransform transform("cie_xyz_d65_interchange");
+ set_processor(ColorProcessor::Create(manager(), manager()->GetReferenceColorSpace(), transform));
+ } catch (const OCIO::Exception &e) {
+ std::cerr << std::endl << e.what() << std::endl;
+ }
+ }
+}
+
+void ChromaKeyNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
+{
+ if (!value[kTextureInput].data().isNull() && processor()) {
+ ColorTransformJob job;
+
+ job.InsertValue(value);
+ job.SetAlphaChannelRequired(ColorTransformJob::kAlphaForceOn);
+ job.SetColorProcessor(processor());
+ job.SetInputTexture(value[kTextureInput].data().value());
+ job.SetNeedsCustomShader(this);
+ job.SetFunctionName(QStringLiteral("SceneLinearToCIEXYZ_d65"));
+
+ table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
+ }
+}
+
+void ChromaKeyNode::ConfigChanged()
+{
+ GenerateProcessor();
+}
+
+} // namespace olive
diff --git a/app/node/keying/chromakey/chromakey.h b/app/node/keying/chromakey/chromakey.h
new file mode 100644
index 000000000..653d31c87
--- /dev/null
+++ b/app/node/keying/chromakey/chromakey.h
@@ -0,0 +1,62 @@
+/***
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2019 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 CHROMAKEYNODE_H
+#define CHROMAKEYNODE_H
+
+#include "node/color/ociobase/ociobase.h"
+
+namespace olive {
+
+class ChromaKeyNode : public OCIOBaseNode {
+ Q_OBJECT
+ public:
+ ChromaKeyNode();
+
+ NODE_DEFAULT_FUNCTIONS(ChromaKeyNode)
+
+ 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 void InputValueChangedEvent(const QString& input, int element) override;
+
+ virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
+ virtual void Value(const NodeValueRow& value, const NodeGlobals& globals, NodeValueTable* table) const override;
+
+ virtual void ConfigChanged() override;
+
+ static const QString kColorInput;
+ static const QString kMaskOnlyInput;
+ static const QString kUpperToleranceInput;
+ static const QString kLowerToleranceInput;
+ static const QString kGarbageMatteInput;
+ static const QString kCoreMatteInput;
+ static const QString kShadowsInput;
+ static const QString kHighlightsInput;
+
+private:
+ void GenerateProcessor();
+
+
+
+};
+
+} // namespace olive
+
+#endif // CHROMAKEYNODE_H
diff --git a/app/node/node.cpp b/app/node/node.cpp
index a510bbbe4..b76f27d83 100644
--- a/app/node/node.cpp
+++ b/app/node/node.cpp
@@ -50,8 +50,7 @@ Node::Node() :
folder_(nullptr),
operation_stack_(0),
cache_result_(false),
- flags_(kNone),
- effect_element_(-1)
+ flags_(kNone)
{
AddInput(kEnabledInput, NodeValue::kBoolean, true);
}
diff --git a/app/node/node.h b/app/node/node.h
index f0df101f8..6125dc01c 100644
--- a/app/node/node.h
+++ b/app/node/node.h
@@ -552,7 +552,12 @@ public:
NodeInput GetEffectInput()
{
- return effect_input_.isEmpty() ? NodeInput() : NodeInput(this, effect_input_, effect_element_);
+ return effect_input_.isEmpty() ? NodeInput() : NodeInput(this, effect_input_);
+ }
+
+ const QString &GetEffectInputID() const
+ {
+ return effect_input_;
}
class ValueHint {
@@ -667,6 +672,12 @@ public:
id = shader_id;
}
+ ShaderRequest(const QString &shader_id, const QString &shader_stub)
+ {
+ id = shader_id;
+ stub = shader_stub;
+ }
+
QString id;
QString stub;
};
@@ -1058,10 +1069,9 @@ protected:
virtual void childEvent(QChildEvent *event) override;
- void SetEffectInput(const QString &input, int element = -1)
+ void SetEffectInput(const QString &input)
{
effect_input_ = input;
- effect_element_ = element;
}
void SetToolTip(const QString& s)
@@ -1380,7 +1390,6 @@ private:
QVector gizmos_;
QString effect_input_;
- int effect_element_;
private slots:
/**
diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp
index 86c116391..7c5c0e37f 100644
--- a/app/node/traverser.cpp
+++ b/app/node/traverser.cpp
@@ -269,7 +269,15 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const Node::ValueHint
return table;
} else {
- return database.Merge();
+ // If this node has an effect input, ensure that is pushed last
+ NodeValueTable primary;
+ if (!n->GetEffectInputID().isEmpty()) {
+ primary = database.Take(n->GetEffectInputID());
+ }
+
+ NodeValueTable m = database.Merge();
+ m.Push(primary);
+ return m;
}
}
@@ -287,6 +295,7 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeR
return table;
}
+
QVector2D NodeTraverser::GenerateResolution() const
{
return QVector2D(video_params_.square_pixel_width(), video_params_.height());
@@ -339,6 +348,19 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range)
val.set_data(QVariant::fromValue(tex));
+ } else if (v.canConvert()) {
+
+ ColorTransformJob job = v.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_data(QVariant::fromValue(dest));
+
} else if (v.canConvert()) {
FootageJob job = v.value();
diff --git a/app/node/traverser.h b/app/node/traverser.h
index 63feed9ab..b806230cb 100644
--- a/app/node/traverser.h
+++ b/app/node/traverser.h
@@ -27,6 +27,7 @@
#include "common/cancelableobject.h"
#include "node/output/track/track.h"
#include "render/job/footagejob.h"
+#include "render/job/colortransformjob.h"
#include "value.h"
namespace olive {
@@ -88,6 +89,8 @@ protected:
virtual void ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob& job){}
+ virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job){}
+
virtual void ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job){}
virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job){}
diff --git a/app/node/value.h b/app/node/value.h
index d894a7d35..e16fc1055 100644
--- a/app/node/value.h
+++ b/app/node/value.h
@@ -203,6 +203,12 @@ public:
return type_;
}
+ template
+ T value() const
+ {
+ return data_.value();
+ }
+
const QVariant& data() const
{
return data_;
@@ -340,6 +346,11 @@ public:
values_.append(value);
}
+ void Push(const NodeValueTable& value)
+ {
+ values_.append(value.values_);
+ }
+
void Push(NodeValue::Type type, const QVariant& data, const Node *from, bool array = false, const QString& tag = QString())
{
Push(NodeValue(type, data, from, array, tag));
diff --git a/app/node/valuedatabase.h b/app/node/valuedatabase.h
index 725ed1e62..80574b371 100644
--- a/app/node/valuedatabase.h
+++ b/app/node/valuedatabase.h
@@ -41,6 +41,11 @@ public:
tables_.insert(key, value);
}
+ NodeValueTable Take(const QString &key)
+ {
+ return tables_.take(key);
+ }
+
NodeValueTable Merge() const;
using Tables = QHash;
diff --git a/app/render/alphaassoc.h b/app/render/alphaassoc.h
new file mode 100644
index 000000000..22eb74835
--- /dev/null
+++ b/app/render/alphaassoc.h
@@ -0,0 +1,34 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 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 ALPHAASSOC_H
+#define ALPHAASSOC_H
+
+namespace olive {
+
+enum AlphaAssociated {
+ kAlphaNone,
+ kAlphaUnassociated,
+ kAlphaAssociated
+};
+
+}
+
+#endif // ALPHAASSOC_H
diff --git a/app/render/colorprocessor.cpp b/app/render/colorprocessor.cpp
index 9d981871f..b7349e4b2 100644
--- a/app/render/colorprocessor.cpp
+++ b/app/render/colorprocessor.cpp
@@ -26,7 +26,7 @@
namespace olive {
-ColorProcessor::ColorProcessor(ColorManager *config, const QString &input, const ColorTransform &transform)
+ColorProcessor::ColorProcessor(ColorManager *config, const QString &input, const ColorTransform &transform, Direction direction)
{
QMutexLocker locker(config->mutex());
@@ -41,6 +41,7 @@ ColorProcessor::ColorProcessor(ColorManager *config, const QString &input, const
display_transform->setSrc(input.toUtf8());
display_transform->setDisplay(output.toUtf8());
display_transform->setView(view.toUtf8());
+ display_transform->setDirection(direction == kNormal ? OCIO::TRANSFORM_DIR_FORWARD : OCIO::TRANSFORM_DIR_INVERSE);
OCIO_SET_C_LOCALE_FOR_SCOPE;
@@ -69,13 +70,25 @@ ColorProcessor::ColorProcessor(ColorManager *config, const QString &input, const
} else {
OCIO_SET_C_LOCALE_FOR_SCOPE;
- processor_ = config->GetConfig()->getProcessor(input.toUtf8(),
- output.toUtf8());
+ try {
+ if (direction == kNormal) {
+ processor_ = config->GetConfig()->getProcessor(input.toUtf8(), output.toUtf8());
+ } else {
+ processor_ = config->GetConfig()->getProcessor(output.toUtf8(), input.toUtf8());
+ }
+ } catch (OCIO::Exception &e) {
+ qWarning() << "ColorProcessor exception:" << e.what();
+ }
}
cpu_processor_ = processor_->getDefaultCPUProcessor();
- id_ = GenerateID(config, input, transform);
+}
+
+ColorProcessor::ColorProcessor(OCIO::ConstProcessorRcPtr processor)
+{
+ processor_ = processor;
+ cpu_processor_ = processor_->getDefaultCPUProcessor();
}
void ColorProcessor::ConvertFrame(Frame *f)
@@ -109,18 +122,14 @@ Color ColorProcessor::ConvertColor(const Color& in)
return Color(c[0], c[1], c[2], c[3]);
}
-QString ColorProcessor::GenerateID(ColorManager *config, const QString &input, const ColorTransform &transform)
+ColorProcessorPtr ColorProcessor::Create(ColorManager *config, const QString& input, const ColorTransform &transform, Direction direction)
{
- return QStringLiteral("%1:%2:%3:%4:%5").arg(config->GetConfigFilename(),
- input,
- transform.display(),
- transform.view(),
- transform.look());
+ return std::make_shared(config, input, transform, direction);
}
-ColorProcessorPtr ColorProcessor::Create(ColorManager *config, const QString& input, const ColorTransform &transform)
+ColorProcessorPtr ColorProcessor::Create(OCIO::ConstProcessorRcPtr processor)
{
- return std::make_shared(config, input, transform);
+ return std::make_shared(processor);
}
OCIO::ConstProcessorRcPtr ColorProcessor::GetProcessor()
diff --git a/app/render/colorprocessor.h b/app/render/colorprocessor.h
index 60205055a..9d1c9130a 100644
--- a/app/render/colorprocessor.h
+++ b/app/render/colorprocessor.h
@@ -41,11 +41,13 @@ public:
kInverse
};
- ColorProcessor(ColorManager* config, const QString& input, const ColorTransform& dest_space);
+ ColorProcessor(ColorManager* config, const QString& input, const ColorTransform& dest_space, Direction direction = kNormal);
+ ColorProcessor(OCIO::ConstProcessorRcPtr processor);
DISABLE_COPY_MOVE(ColorProcessor)
- static ColorProcessorPtr Create(ColorManager* config, const QString& input, const ColorTransform& dest_space);
+ static ColorProcessorPtr Create(ColorManager* config, const QString& input, const ColorTransform& dest_space, Direction direction = kNormal);
+ static ColorProcessorPtr Create(OCIO::ConstProcessorRcPtr processor);
OCIO::ConstProcessorRcPtr GetProcessor();
@@ -54,20 +56,16 @@ public:
Color ConvertColor(const Color &in);
- const QString& id() const
+ const char *id() const
{
- return id_;
+ return processor_->getCacheID();
}
- static QString GenerateID(ColorManager* config, const QString& input, const ColorTransform& dest_space);
-
private:
OCIO::ConstProcessorRcPtr processor_;
OCIO::ConstCPUProcessorRcPtr cpu_processor_;
- QString id_;
-
};
using ColorProcessorChain = QVector;
diff --git a/app/render/job/colortransformjob.h b/app/render/job/colortransformjob.h
new file mode 100644
index 000000000..18f48a7cb
--- /dev/null
+++ b/app/render/job/colortransformjob.h
@@ -0,0 +1,113 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 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 COLORTRANSFORMJOB_H
+#define COLORTRANSFORMJOB_H
+
+#include
+#include
+
+#include "render/job/generatejob.h"
+#include "render/alphaassoc.h"
+#include "render/colorprocessor.h"
+#include "render/texture.h"
+
+namespace olive {
+
+class Node;
+
+class ColorTransformJob : public GenerateJob
+{
+public:
+ ColorTransformJob()
+ {
+ processor_ = nullptr;
+ input_texture_ = nullptr;
+ custom_shader_src_ = nullptr;
+ input_alpha_association_ = kAlphaNone;
+ clear_destination_ = true;
+ }
+
+ QString id() const
+ {
+ if (id_.isEmpty()) {
+ return processor_->id();
+ } else {
+ return id_;
+ }
+ }
+
+ void SetOverrideID(const QString &id) { id_ = id; }
+
+ TexturePtr GetInputTexture() const { return input_texture_; }
+ void SetInputTexture(TexturePtr tex) { input_texture_ = tex; }
+
+ ColorProcessorPtr GetColorProcessor() const { return processor_; }
+ void SetColorProcessor(ColorProcessorPtr p) { processor_ = p; }
+
+ const AlphaAssociated &GetInputAlphaAssociation() const { return input_alpha_association_; }
+ void SetInputAlphaAssociation(const AlphaAssociated &e) { input_alpha_association_ = e; }
+
+ const Node *CustomShaderSource() const { return custom_shader_src_; }
+ const QString &CustomShaderID() const { return custom_shader_id_; }
+ void SetNeedsCustomShader(const Node *node, const QString &id = QString())
+ {
+ custom_shader_src_ = node;
+ custom_shader_id_ = id;
+ }
+
+ bool IsClearDestinationEnabled() const { return clear_destination_; }
+ void SetClearDestinationEnabled(bool e) { clear_destination_ = e; }
+
+ const QMatrix4x4 &GetTransformMatrix() const { return matrix_; }
+ void SetTransformMatrix(const QMatrix4x4 &m) { matrix_ = m; }
+
+ const QMatrix4x4 &GetCropMatrix() const { return crop_matrix_; }
+ void SetCropMatrix(const QMatrix4x4 &m) { crop_matrix_ = m; }
+
+ const QString &GetFunctionName() const { return function_name_; }
+ void SetFunctionName(const QString &function_name = QString()) { function_name_ = function_name; };
+
+private:
+ ColorProcessorPtr processor_;
+ QString id_;
+
+ TexturePtr input_texture_;
+
+ const Node *custom_shader_src_;
+ QString custom_shader_id_;
+
+ AlphaAssociated input_alpha_association_;
+
+ bool clear_destination_;
+
+ QMatrix4x4 matrix_;
+
+ QMatrix4x4 crop_matrix_;
+
+ QString function_name_;
+
+};
+
+}
+
+Q_DECLARE_METATYPE(olive::ColorTransformJob)
+
+#endif // COLORTRANSFORMJOB_H
diff --git a/app/render/job/shaderjob.h b/app/render/job/shaderjob.h
index 77a08ae9f..53cd836d6 100644
--- a/app/render/job/shaderjob.h
+++ b/app/render/job/shaderjob.h
@@ -25,6 +25,7 @@
#include
#include "generatejob.h"
+#include "render/colorprocessor.h"
#include "render/texture.h"
namespace olive {
diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp
index b78df3df4..2d75ae47f 100644
--- a/app/render/renderer.cpp
+++ b/app/render/renderer.cpp
@@ -52,16 +52,6 @@ TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data,
return CreateTexture(params, Texture::k2D, data, linesize);
}
-void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, AlphaAssociated source_alpha_association, Texture *destination, bool clear_destination, const QMatrix4x4 &matrix, const QMatrix4x4 &crop_matrix)
-{
- BlitColorManagedInternal(color_processor, source, source_alpha_association, destination, destination->params(), clear_destination, matrix, crop_matrix);
-}
-
-void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, AlphaAssociated source_alpha_association, VideoParams params, bool clear_destination, const QMatrix4x4& matrix, const QMatrix4x4 &crop_matrix)
-{
- BlitColorManagedInternal(color_processor, source, source_alpha_association, nullptr, params, clear_destination, matrix, crop_matrix);
-}
-
TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const VideoParams ¶ms)
{
color_cache_mutex_.lock();
@@ -103,35 +93,45 @@ TexturePtr Renderer::CreateTextureFromNativeHandle(const QVariant &v, const Vide
return std::make_shared(this, v, params, type);
}
-bool Renderer::GetColorContext(ColorProcessorPtr color_processor, Renderer::ColorContext *ctx)
+bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::ColorContext *ctx)
{
QMutexLocker locker(&color_cache_mutex_);
ColorContext& color_ctx = *ctx;
- if (color_cache_.contains(color_processor->id())) {
- color_ctx = color_cache_.value(color_processor->id());
+ QString proc_id = color_job.id();
+
+ if (color_cache_.contains(proc_id)) {
+ color_ctx = color_cache_.value(proc_id);
return true;
} else {
// Create shader description
- const char* ocio_func_name = "OCIODisplay";
+ QString ocio_func_name;
+ if (color_job.GetFunctionName().isEmpty()) {
+ ocio_func_name = "OCIODisplay";
+ } else {
+ ocio_func_name = color_job.GetFunctionName();
+ }
auto shader_desc = OCIO::GpuShaderDesc::CreateShaderDesc();
shader_desc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_ES_3_0);
- shader_desc->setFunctionName(ocio_func_name);
+ shader_desc->setFunctionName(ocio_func_name.toUtf8());
shader_desc->setResourcePrefix("ocio_");
// Generate shader
- color_processor->GetProcessor()->getDefaultGPUProcessor()->extractGpuShaderInfo(shader_desc);
+ color_job.GetColorProcessor()->GetProcessor()->getDefaultGPUProcessor()->extractGpuShaderInfo(shader_desc);
- // Generate shader code using OCIO stub and our auto-generated name
- QString shader_frag = FileFunctions::ReadFileAsString(QStringLiteral(":shaders/colormanage.frag")).arg(
- shader_desc->getShaderText(),
- ocio_func_name
- );
+ ShaderCode code;
+ if (const Node *shader_src = color_job.CustomShaderSource()) {
+ // Use shader code from associated node
+ code = shader_src->GetShaderCode({color_job.CustomShaderID(), shader_desc->getShaderText()});
+ } else {
+ // Generate shader code using OCIO stub and our auto-generated name
+ code = FileFunctions::ReadFileAsString(QStringLiteral(":shaders/colormanage.frag"));
+ code.set_frag_code(code.frag_code().arg(shader_desc->getShaderText()));
+ }
// Try to compile shader
- color_ctx.compiled_shader = CreateNativeShader(ShaderCode(shader_frag,
- FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/default.vert"))));
+ color_ctx.compiled_shader = CreateNativeShader(code);
if (color_ctx.compiled_shader.isNull()) {
return false;
@@ -199,28 +199,26 @@ bool Renderer::GetColorContext(ColorProcessorPtr color_processor, Renderer::Colo
color_ctx.lut1d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear;
}
- color_cache_.insert(color_processor->id(), color_ctx);
+ color_cache_.insert(proc_id, color_ctx);
return true;
}
}
-void Renderer::BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source,
- AlphaAssociated source_alpha_association, Texture *destination,
- VideoParams params, bool clear_destination, const QMatrix4x4& matrix,
- const QMatrix4x4& crop_matrix)
+void Renderer::BlitColorManaged(const ColorTransformJob &color_job, Texture *destination, const VideoParams ¶ms)
{
ColorContext color_ctx;
- if (!GetColorContext(color_processor, &color_ctx)) {
+ if (!GetColorContext(color_job, &color_ctx)) {
return;
}
ShaderJob job;
-
- job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(source)));
- job.InsertValue(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, matrix));
- job.InsertValue(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, crop_matrix.inverted()));
- job.InsertValue(QStringLiteral("ove_maintex_alpha"), NodeValue(NodeValue::kInt, source_alpha_association));
+ job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(color_job.GetInputTexture())));
+ job.InsertValue(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, color_job.GetTransformMatrix()));
+ job.InsertValue(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, color_job.GetCropMatrix().inverted()));
+ job.InsertValue(QStringLiteral("ove_maintex_alpha"), NodeValue(NodeValue::kInt, color_job.GetInputAlphaAssociation()));
+ job.InsertValue(color_job.GetValues());
+ job.SetAlphaChannelRequired(color_job.GetAlphaChannelRequired());
foreach (const ColorContext::LUT& l, color_ctx.lut3d_textures) {
job.InsertValue(l.name, NodeValue(NodeValue::kTexture, QVariant::fromValue(l.texture)));
@@ -232,9 +230,9 @@ void Renderer::BlitColorManagedInternal(ColorProcessorPtr color_processor, Textu
}
if (destination) {
- BlitToTexture(color_ctx.compiled_shader, job, destination, clear_destination);
+ BlitToTexture(color_ctx.compiled_shader, job, destination, color_job.IsClearDestinationEnabled());
} else {
- Blit(color_ctx.compiled_shader, job, params, clear_destination);
+ Blit(color_ctx.compiled_shader, job, params, color_job.IsClearDestinationEnabled());
}
}
diff --git a/app/render/renderer.h b/app/render/renderer.h
index 977fc98ce..e46ec7990 100644
--- a/app/render/renderer.h
+++ b/app/render/renderer.h
@@ -29,6 +29,7 @@
#include "common/timerange.h"
#include "node/node.h"
#include "render/colorprocessor.h"
+#include "render/job/colortransformjob.h"
#include "render/videoparams.h"
#include "texture.h"
@@ -63,14 +64,15 @@ public:
Blit(shader, job, nullptr, params, clear_destination);
}
- enum AlphaAssociated {
- kAlphaNone,
- kAlphaUnassociated,
- kAlphaAssociated
- };
-
- void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, AlphaAssociated source_alpha_association, Texture* destination, bool clear_destination = true, const QMatrix4x4& matrix = QMatrix4x4(), const QMatrix4x4 &crop_matrix = QMatrix4x4());
- void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, AlphaAssociated source_alpha_association, VideoParams params, bool clear_destination = true, const QMatrix4x4& matrix = QMatrix4x4(), const QMatrix4x4 &crop_matrix = QMatrix4x4());
+ void BlitColorManaged(const ColorTransformJob &color_job, Texture* destination, const VideoParams ¶ms);
+ void BlitColorManaged(const ColorTransformJob &job, Texture* destination)
+ {
+ BlitColorManaged(job, destination, destination->params());
+ }
+ void BlitColorManaged(const ColorTransformJob &job, const VideoParams ¶ms)
+ {
+ BlitColorManaged(job, nullptr, params);
+ }
TexturePtr InterlaceTexture(TexturePtr top, TexturePtr bottom, const VideoParams ¶ms);
@@ -126,12 +128,7 @@ private:
};
- bool GetColorContext(ColorProcessorPtr color_processor, ColorContext* ctx);
-
- void BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source,
- AlphaAssociated source_alpha_association,
- Texture* destination, VideoParams params, bool clear_destination,
- const QMatrix4x4 &matrix, const QMatrix4x4 &crop_matrix);
+ bool GetColorContext(const ColorTransformJob &color_job, ColorContext* ctx);
QHash color_cache_;
diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp
index f58b7ea3c..c5586022b 100644
--- a/app/render/renderprocessor.cpp
+++ b/app/render/renderprocessor.cpp
@@ -103,9 +103,14 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time
if (output_color_transform) {
// Yes color transform, blit color managed
- render_ctx_->BlitColorManaged(output_color_transform, texture,
- OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? Renderer::kAlphaAssociated : Renderer::kAlphaNone,
- blit_tex.get(), true, matrix);
+ ColorTransformJob job;
+
+ job.SetColorProcessor(output_color_transform);
+ job.SetInputTexture(texture);
+ job.SetInputAlphaAssociation(OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? kAlphaAssociated : kAlphaNone);
+ job.SetTransformMatrix(matrix);
+
+ render_ctx_->BlitColorManaged(job, blit_tex.get());
} else {
// No color transform, just blit
ShaderJob job;
@@ -459,19 +464,21 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ
using_colorspace,
color_manager->GetReferenceColorSpace());
- Renderer::AlphaAssociated alpha_assoc;
+ ColorTransformJob job;
+
+ job.SetColorProcessor(processor);
+ job.SetInputTexture(unmanaged_texture);
+
if (stream_data.channel_count() != VideoParams::kRGBAChannelCount
|| stream_data.colorspace() == color_manager->GetReferenceColorSpace()) {
- alpha_assoc = Renderer::kAlphaNone;
+ job.SetInputAlphaAssociation(kAlphaNone);
} else if (stream_data.premultiplied_alpha()) {
- alpha_assoc = Renderer::kAlphaAssociated;
+ job.SetInputAlphaAssociation(kAlphaAssociated);
} else {
- alpha_assoc = Renderer::kAlphaUnassociated;
+ job.SetInputAlphaAssociation(kAlphaUnassociated);
}
- render_ctx_->BlitColorManaged(processor, unmanaged_texture,
- alpha_assoc,
- destination.get());
+ render_ctx_->BlitColorManaged(job, destination.get());
}
}
}
@@ -550,6 +557,11 @@ void RenderProcessor::ProcessSamples(SampleBufferPtr destination, const Node *no
}
}
+void RenderProcessor::ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob &job)
+{
+ render_ctx_->BlitColorManaged(job, destination.get());
+}
+
void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob &job)
{
FramePtr frame = Frame::Create();
@@ -571,7 +583,14 @@ void RenderProcessor::ConvertToReferenceSpace(TexturePtr destination, TexturePtr
{
ColorManager* color_manager = Node::ValueToPtr(ticket_->property("colormanager"));
ColorProcessorPtr cp = ColorProcessor::Create(color_manager, input_cs, color_manager->GetReferenceColorSpace());
- render_ctx_->BlitColorManaged(cp, source, Renderer::kAlphaAssociated, destination.get());
+
+ ColorTransformJob ctj;
+
+ ctj.SetColorProcessor(cp);
+ ctj.SetInputTexture(source);
+ ctj.SetInputAlphaAssociation(kAlphaAssociated);
+
+ render_ctx_->BlitColorManaged(ctj, destination.get());
}
}
diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h
index b39f2d7ff..8397b5f45 100644
--- a/app/render/renderprocessor.h
+++ b/app/render/renderprocessor.h
@@ -52,6 +52,8 @@ protected:
virtual void ProcessSamples(SampleBufferPtr 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 ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job) override;
virtual bool CanCacheFrames() override;
diff --git a/app/render/shadercode.h b/app/render/shadercode.h
index c41422122..20c8b8079 100644
--- a/app/render/shadercode.h
+++ b/app/render/shadercode.h
@@ -33,15 +33,11 @@ public:
{
}
- const QString& frag_code() const
- {
- return frag_code_;
- }
+ const QString& frag_code() const { return frag_code_; }
+ void set_frag_code(const QString &f) { frag_code_ = f; }
- const QString& vert_code() const
- {
- return vert_code_;
- }
+ const QString& vert_code() const { return vert_code_; }
+ void set_vert_code(const QString &v) { vert_code_ = v; }
private:
QString frag_code_;
diff --git a/app/shaders/chromakey.frag b/app/shaders/chromakey.frag
new file mode 100644
index 000000000..8c707e644
--- /dev/null
+++ b/app/shaders/chromakey.frag
@@ -0,0 +1,105 @@
+// Main texture input
+uniform sampler2D tex_in;
+uniform vec4 color_key;
+uniform bool mask_only_in;
+uniform float upper_tolerence_in;
+uniform float lower_tolerence_in;
+
+uniform sampler2D garbage_in;
+uniform sampler2D core_in;
+uniform bool garbage_in_enabled;
+uniform bool core_in_enabled;
+
+uniform float highlights_in;
+uniform float shadows_in;
+
+
+// Main texture coordinate
+in vec2 ove_texcoord;
+out vec4 frag_color;
+
+// Program will replace this with OCIO's auto-generated shader code
+%1
+
+// Assume D65 white point
+float Xn = 95.0489;
+float Yn = 100.0;
+float Zn = 108.8840;
+float delta = 0.20689655172; // 6/29
+
+float func(float t) {
+ if (t > pow(delta, 3.0)){
+ return pow(t, 1.0/3.0);
+ } else{
+ return (t / (3.0 * pow(delta, 2))) + 4.0/29.0;
+ }
+}
+
+vec4 CIExyz_to_Lab(vec4 CIE) {
+ vec4 lab;
+ lab.r = 116.0 * func(CIE.g / Yn) - 16.0;
+ lab.g = 500.0 * (func(CIE.r / Xn) - func(CIE.g / Yn));
+ lab.b = 200.0 * (func(CIE.g / Yn) - func(CIE.b / Zn));
+ lab.w = CIE.w;
+
+ return lab;
+}
+
+float colorclose(vec4 col, vec4 key, float tola,float tolb) {
+ // Decides if a color is close to the specified hue
+ float temp = sqrt(((key.g-col.g)*(key.g-col.g))+((key.b-col.b)*(key.b-col.b)));
+ if (temp < tola) {return (0.0);}
+ if (temp < tolb) {return ((temp-tola)/(tolb-tola));}
+ return (1.0);
+}
+
+
+void main() {
+
+ vec4 col = texture(tex_in, ove_texcoord);
+
+ vec4 unassoc = col;
+ if (unassoc.a > 0) {
+ unassoc.rgb /= unassoc.a;
+ }
+
+ // Perform color conversion
+ vec4 cie_xyz = SceneLinearToCIEXYZ_d65(unassoc);
+ vec4 lab = CIExyz_to_Lab(cie_xyz);
+
+ vec4 cie_xyz_key = SceneLinearToCIEXYZ_d65(color_key);
+ vec4 lab_key = CIExyz_to_Lab(cie_xyz_key);
+
+ float mask = colorclose(lab, lab_key, lower_tolerence_in, upper_tolerence_in);
+
+ mask = clamp(mask, 0.0, 1.0);
+
+ if (garbage_in_enabled) {
+ // Force anything we want to remove to be 0.0
+ vec4 garbage = texture(garbage_in, ove_texcoord);
+ // Assumes garbage is achromatic
+ mask -= garbage.r;
+ mask = clamp(mask, 0.0, 1.0);
+ }
+
+ if (core_in_enabled) {
+ // Force anything we want to keep to be 1.0
+ vec3 core = texture(core_in, ove_texcoord).rgb;
+ // Assumes core is achromatic
+ mask += core.r;
+ mask = clamp(mask, 0.0, 1.0);
+ }
+
+ // Crush blacks and push whites
+ mask = shadows_in * 0.01 * (highlights_in * 0.01 * mask - 1.0) + 1.0;
+ mask = clamp(mask, 0.0, 1.0);
+
+ col.rgb *= mask;
+ col.w = mask;
+
+ if (!mask_only_in) {
+ frag_color = col;
+ } else {
+ frag_color = vec4(vec3(mask), 1.0);
+ }
+}
diff --git a/app/shaders/colormanage.frag b/app/shaders/colormanage.frag
index e9461e71e..25efe5b50 100644
--- a/app/shaders/colormanage.frag
+++ b/app/shaders/colormanage.frag
@@ -44,7 +44,7 @@ void main() {
}
// Perform color conversion
- col = %2(col);
+ col = OCIODisplay(col);
// Associate or re-associate here
if (ove_maintex_alpha == ALPHA_ASSOC) {
diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp
index e030d1b15..c25848f03 100644
--- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp
+++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp
@@ -547,22 +547,33 @@ void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant &
NodeValue::Type data_type = GetDataType();
// Parameters for all types
- if (key == QStringLiteral("enabled")) {
- foreach (QWidget* w, widgets_) {
- w->setEnabled(value.toBool());
+ bool key_is_disable = key.startsWith(QStringLiteral("disable"));
+ if (key_is_disable || key.startsWith(QStringLiteral("enabled"))) {
+
+ bool e = value.toBool();
+ if (key_is_disable) {
+ e = !e;
}
+
+ if (key.size() == 7) { // just the word "disable" or "enabled"
+ for (int i=0; isetEnabled(e);
+ }
+ } else { // set specific track/widget
+ bool ok;
+ int element = key.midRef(7).toInt(&ok);
+ int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);
+
+ if (ok && element >= 0 && element < tracks) {
+ widgets_.at(element)->setEnabled(e);
+ }
+ }
+
}
- // Parameters for vectors only
- if (NodeValue::type_is_vector(data_type)) {
- if (key == QStringLiteral("disablex")) {
- static_cast(widgets_.at(0))->setEnabled(!value.toBool());
- } else if (key == QStringLiteral("disabley")) {
- static_cast(widgets_.at(1))->setEnabled(!value.toBool());
- } else if (widgets_.size() > 2 && key == QStringLiteral("disablez")) {
- static_cast(widgets_.at(2))->setEnabled(!value.toBool());
- } else if (widgets_.size() > 3 && key == QStringLiteral("disablew")) {
- static_cast(widgets_.at(3))->setEnabled(!value.toBool());
+ if (key == QStringLiteral("tooltip")) {
+ for (int i = 0; i < widgets_.size(); i++) {
+ widgets_.at(i)->setToolTip(value.toString());
}
}
@@ -645,6 +656,7 @@ void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant &
break;
}
} else if (key == QStringLiteral("offset")) {
+
int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);
QVector offsets = NodeValue::split_normal_value_into_track_values(data_type, value);
@@ -654,6 +666,33 @@ void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant &
}
UpdateWidgetValues();
+
+ } else if (key.startsWith(QStringLiteral("color"))) {
+
+ QColor c(value.toString());
+
+ int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);
+
+ if (key.size() == 5) {
+ // Set for all tracks
+ for (int i=0; i(widgets_.at(i))->SetColor(c);
+ }
+ } else {
+ bool ok;
+ int element = key.midRef(5).toInt(&ok);
+ if (ok && element >= 0 && element < tracks) {
+ static_cast(widgets_.at(element))->SetColor(c);
+ }
+ }
+
+ } else if (key == QStringLiteral("base")) {
+
+ double d = value.toDouble();
+ for (int i=0; i(widgets_.at(i))->SetDragMultiplier(d);
+ }
+
}
}
diff --git a/app/widget/scope/scopebase/scopebase.cpp b/app/widget/scope/scopebase/scopebase.cpp
index 94eb210cd..fc7c11774 100644
--- a/app/widget/scope/scopebase/scopebase.cpp
+++ b/app/widget/scope/scopebase/scopebase.cpp
@@ -74,7 +74,13 @@ void ScopeBase::OnPaint()
if (!managed_tex_ || !managed_tex_up_to_date_
|| managed_tex_->params() != texture_->params()) {
managed_tex_ = renderer()->CreateTexture(texture_->params());
- renderer()->BlitColorManaged(color_service(), texture_, Renderer::kAlphaNone, managed_tex_.get());
+
+ ColorTransformJob job;
+ job.SetColorProcessor(color_service());
+ job.SetInputTexture(texture_);
+ job.SetInputAlphaAssociation(kAlphaNone);
+
+ renderer()->BlitColorManaged(job, managed_tex_.get());
}
DrawScope(managed_tex_, pipeline_);
diff --git a/app/widget/slider/base/numericsliderbase.cpp b/app/widget/slider/base/numericsliderbase.cpp
index 3e65d83ee..81b3951ac 100644
--- a/app/widget/slider/base/numericsliderbase.cpp
+++ b/app/widget/slider/base/numericsliderbase.cpp
@@ -69,7 +69,7 @@ void NumericSliderBase::LadderDragged(int value, double multiplier)
{
dragged_ = true;
- dragged_diff_ += value * drag_multiplier_ * multiplier;
+ dragged_diff_ += value * multiplier;
// Store current value to try and prevent any unnecessary signalling if the value doesn't change
QVariant pre_set_value = GetValueInternal();
diff --git a/app/widget/slider/base/sliderbase.h b/app/widget/slider/base/sliderbase.h
index 64d4ed2f4..4663525d9 100644
--- a/app/widget/slider/base/sliderbase.h
+++ b/app/widget/slider/base/sliderbase.h
@@ -55,6 +55,11 @@ public:
UpdateLabel();
}
+ void SetColor(const QColor &c)
+ {
+ label_->SetColor(c);
+ }
+
public slots:
void ShowEditor();
diff --git a/app/widget/slider/base/sliderlabel.cpp b/app/widget/slider/base/sliderlabel.cpp
index b5c851515..4271bd1e5 100644
--- a/app/widget/slider/base/sliderlabel.cpp
+++ b/app/widget/slider/base/sliderlabel.cpp
@@ -27,7 +27,8 @@
namespace olive {
SliderLabel::SliderLabel(QWidget *parent) :
- QLabel(parent)
+ QLabel(parent),
+ override_color_enabled_(false)
{
QPalette p = palette();
@@ -52,6 +53,26 @@ SliderLabel::SliderLabel(QWidget *parent) :
setContextMenuPolicy(Qt::CustomContextMenu);
}
+void SliderLabel::SetColor(const QColor &c)
+{
+ // Prevent infinite loop in changeEvent when we set the stylesheet
+ override_color_enabled_ = false;
+ override_color_ = c;
+
+ // Different colors will look different depending on the theme (light/dark mode). We abstract
+ // that away here so that other classes can simply choose a color and we will handle making it
+ // more legible based on the background
+ QColor adjusted;
+ if (palette().window().color().lightness() < 128) {
+ adjusted = override_color_.lighter(150);
+ } else {
+ adjusted = override_color_.darker(150);
+ }
+
+ setStyleSheet(QStringLiteral("color: %1").arg(adjusted.name()));
+ override_color_enabled_ = true;
+}
+
void SliderLabel::mousePressEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton) {
@@ -81,4 +102,13 @@ void SliderLabel::focusInEvent(QFocusEvent *event)
}
}
+void SliderLabel::changeEvent(QEvent *event)
+{
+ QWidget::changeEvent(event);
+
+ if (override_color_enabled_ && event->type() == QEvent::StyleChange) {
+ SetColor(override_color_);
+ }
+}
+
}
diff --git a/app/widget/slider/base/sliderlabel.h b/app/widget/slider/base/sliderlabel.h
index 747ad44ce..948b0d77a 100644
--- a/app/widget/slider/base/sliderlabel.h
+++ b/app/widget/slider/base/sliderlabel.h
@@ -33,6 +33,8 @@ class SliderLabel : public QLabel
public:
SliderLabel(QWidget* parent);
+ void SetColor(const QColor &c);
+
protected:
virtual void mousePressEvent(QMouseEvent *e) override;
@@ -40,6 +42,8 @@ protected:
virtual void focusInEvent(QFocusEvent *event) override;
+ virtual void changeEvent(QEvent *event) override;
+
signals:
void LabelPressed();
@@ -51,6 +55,10 @@ signals:
void ChangeSliderType();
+private:
+ bool override_color_enabled_;
+ QColor override_color_;
+
};
}
diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp
index 7ba11417c..2d6d0513e 100644
--- a/app/widget/viewer/viewerdisplay.cpp
+++ b/app/widget/viewer/viewerdisplay.cpp
@@ -485,10 +485,15 @@ void ViewerDisplayWidget::OnPaint()
texture_to_draw = deinterlace_texture_;
}
- renderer()->BlitColorManaged(color_service(), texture_to_draw,
- OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? Renderer::kAlphaAssociated : Renderer::kAlphaNone,
- device_params, false,
- combined_matrix_flipped_, crop_matrix_);
+ ColorTransformJob ctj;
+ ctj.SetColorProcessor(color_service());
+ ctj.SetInputTexture(texture_to_draw);
+ ctj.SetInputAlphaAssociation(OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? kAlphaAssociated : kAlphaNone);
+ ctj.SetClearDestinationEnabled(false);
+ ctj.SetTransformMatrix(combined_matrix_flipped_);
+ ctj.SetCropMatrix(crop_matrix_);
+
+ renderer()->BlitColorManaged(ctj, device_params);
}
}