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