build: split the engine into liboakengine.so; worker drops the UI entirely
Physical split: app/{audio,cli,codec,common,config,node,pluginSupport,
render,task,timeline,undo,tool,shaders} plus coreengine, version and
ui/icons+colorcoding move to a new top-level engine/ tree, built as
liboakengine.so (shared). The render backends (oakgl/oakvulkan) move
with it and link the engine library instead of embedding a static
render-core subset (libolive-rendercore is gone).
- oak-render-worker now links liboakengine instead of the whole
libolive-editor object set: 336MB -> 2.9MB, no Qt Widgets UI
- the editor links liboakengine for the engine and keeps only UI
objects in libolive-editor
- install/packaging: GNUInstallDirs libdir on Linux, bundle copy on
macOS, oakengine.dll staged for NSIS, AppImage validation entry
- fix backend lookup for the new layout: DynamicRenderer searched
../app but backends now live in engine/; a stale pre-split liboakgl
in the build tree got dlopened instead, re-initialized and later
destroyed the interposed engine statics (full-suite segfault at
DialogSequenceParameterTab, found via gdb watchpoint)
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(colormanager)
|
||||
add_subdirectory(displaytransform)
|
||||
add_subdirectory(ociobase)
|
||||
add_subdirectory(ociogradingtransformlinear)
|
||||
add_subdirectory(ociogradingtransformlog)
|
||||
add_subdirectory(ociolut)
|
||||
add_subdirectory(threewaycolor)
|
||||
add_subdirectory(whitebalance)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/colormanager/colormanager.cpp
|
||||
node/color/colormanager/colormanager.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,328 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "colormanager.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "common/filefunctions.h"
|
||||
#include "config/config.h"
|
||||
#include "node/project.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super Node
|
||||
|
||||
ocio::ConstConfigRcPtr ColorManager::default_config = nullptr;
|
||||
|
||||
ColorManager::ColorManager(Project *project)
|
||||
: QObject(project)
|
||||
, config_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void ColorManager::init()
|
||||
{
|
||||
// Set config to our built-in default
|
||||
config_ = get_default_config();
|
||||
set_default_input_color_space(config_->getCanonicalName(ocio::ROLE_DEFAULT));
|
||||
project()->set_color_reference_space(ocio::ROLE_SCENE_LINEAR);
|
||||
}
|
||||
|
||||
ocio::ConstConfigRcPtr ColorManager::get_config() const
|
||||
{
|
||||
return config_;
|
||||
}
|
||||
|
||||
ocio::ConstConfigRcPtr
|
||||
ColorManager::create_config_from_file(const QString &filename)
|
||||
{
|
||||
return ocio::Config::CreateFromFile(filename.toUtf8());
|
||||
}
|
||||
|
||||
QString ColorManager::get_config_filename() const
|
||||
{
|
||||
return project()->get_color_config_filename();
|
||||
}
|
||||
|
||||
ocio::ConstConfigRcPtr ColorManager::get_default_config()
|
||||
{
|
||||
// Set up on first use: Project construction calls ColorManager::Init()
|
||||
// unconditionally, so without this any Project created before
|
||||
// SetUpDefaultConfig() crashed dereferencing a null config.
|
||||
if (!default_config) {
|
||||
set_up_default_config();
|
||||
}
|
||||
|
||||
return default_config;
|
||||
}
|
||||
|
||||
void ColorManager::set_up_default_config()
|
||||
{
|
||||
if (!qEnvironmentVariableIsEmpty("OCIO")) {
|
||||
// Attempt to set config from "OCIO" environment variable
|
||||
try {
|
||||
default_config = ocio::Config::CreateFromEnv();
|
||||
|
||||
return;
|
||||
} catch (ocio::Exception &e) {
|
||||
qWarning()
|
||||
<< "Failed to load config from OCIO environment variable config:"
|
||||
<< e.what();
|
||||
}
|
||||
}
|
||||
|
||||
// Extract OCIO config - kind of hacky, but it'll work
|
||||
QString dir =
|
||||
QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
||||
.filePath(QStringLiteral("ocioconf"));
|
||||
|
||||
FileFunctions::copy_directory(QStringLiteral(":/ocioconf"), dir, true);
|
||||
|
||||
qDebug() << "Extracting default OCIO config to" << dir;
|
||||
|
||||
default_config =
|
||||
create_config_from_file(QDir(dir).filePath(QStringLiteral("config.ocio")));
|
||||
}
|
||||
|
||||
void ColorManager::set_config_filename(const QString &filename)
|
||||
{
|
||||
project()->set_color_config_filename(filename);
|
||||
}
|
||||
|
||||
QStringList ColorManager::list_available_displays()
|
||||
{
|
||||
QStringList displays;
|
||||
|
||||
int number_of_displays = config_->getNumDisplays();
|
||||
|
||||
for (int i = 0; i < number_of_displays; i++) {
|
||||
displays.append(config_->getDisplay(i));
|
||||
}
|
||||
|
||||
return displays;
|
||||
}
|
||||
|
||||
QString ColorManager::get_default_display()
|
||||
{
|
||||
return config_->getDefaultDisplay();
|
||||
}
|
||||
|
||||
QStringList ColorManager::list_available_views(QString display)
|
||||
{
|
||||
QStringList views;
|
||||
|
||||
int number_of_views = config_->getNumViews(display.toUtf8());
|
||||
|
||||
for (int i = 0; i < number_of_views; i++) {
|
||||
views.append(config_->getView(display.toUtf8(), i));
|
||||
}
|
||||
|
||||
return views;
|
||||
}
|
||||
|
||||
QString ColorManager::get_default_view(const QString &display)
|
||||
{
|
||||
return config_->getDefaultView(display.toUtf8());
|
||||
}
|
||||
|
||||
QStringList ColorManager::list_available_looks()
|
||||
{
|
||||
QStringList looks;
|
||||
|
||||
int number_of_looks = config_->getNumLooks();
|
||||
|
||||
for (int i = 0; i < number_of_looks; i++) {
|
||||
looks.append(config_->getLookNameByIndex(i));
|
||||
}
|
||||
|
||||
return looks;
|
||||
}
|
||||
|
||||
QStringList ColorManager::list_available_colorspaces() const
|
||||
{
|
||||
return list_available_colorspaces(config_);
|
||||
}
|
||||
|
||||
QString ColorManager::get_default_input_color_space() const
|
||||
{
|
||||
return project()->get_default_input_color_space();
|
||||
}
|
||||
|
||||
void ColorManager::set_default_input_color_space(const QString &s)
|
||||
{
|
||||
project()->set_default_input_color_space(s);
|
||||
}
|
||||
|
||||
QString ColorManager::get_colorspace_for_ffmpeg_tags(int primaries,
|
||||
int trc) const
|
||||
{
|
||||
// FFmpeg AVColorPrimaries/AVColorTransferCharacteristic values mapped to
|
||||
// candidate colorspace names, in order of preference
|
||||
struct TagMapping {
|
||||
int primaries;
|
||||
int trc;
|
||||
const char *candidates[3];
|
||||
};
|
||||
|
||||
static const TagMapping k_tag_mappings[] = {
|
||||
{ 1, 1, { "Rec.709 OETF", "Rec.709", "BT.709" } },
|
||||
{ 1, 13, { "sRGB OETF", "sRGB", nullptr } },
|
||||
{ 6, 6, { "Rec.601 OETF (NTSC)", "Rec.601 NTSC", nullptr } },
|
||||
{ 5, 5, { "Rec.601 OETF (PAL)", "Rec.601 PAL", nullptr } },
|
||||
{ 5, 6, { "Rec.601 OETF (PAL)", "Rec.601 PAL", nullptr } },
|
||||
{ 9, 16, { "Rec.2020 PQ", "BT.2020 PQ", "ST 2084 PQ" } },
|
||||
{ 9, 18, { "Rec.2020 HLG", "BT.2020 HLG", "HLG" } },
|
||||
{ 9, 1, { "Rec.2020", "BT.2020", nullptr } },
|
||||
{ 9, 14, { "Rec.2020", "BT.2020", nullptr } },
|
||||
{ 9, 15, { "Rec.2020", "BT.2020", nullptr } },
|
||||
};
|
||||
|
||||
// 0 = unset, 2 = AVCOL_PRI/TRC_UNSPECIFIED
|
||||
if (primaries == 0 || primaries == 2 || trc == 0 || trc == 2) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
const QStringList available = list_available_colorspaces();
|
||||
|
||||
for (const TagMapping &mapping : k_tag_mappings) {
|
||||
if (mapping.primaries == primaries && mapping.trc == trc) {
|
||||
for (const char *candidate : mapping.candidates) {
|
||||
if (candidate &&
|
||||
available.contains(QString::fromUtf8(candidate))) {
|
||||
return QString::fromUtf8(candidate);
|
||||
}
|
||||
}
|
||||
// Known tag pair, but the config has no matching colorspace
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString ColorManager::get_reference_color_space() const
|
||||
{
|
||||
return project()->get_color_reference_space();
|
||||
}
|
||||
|
||||
QString ColorManager::get_compliant_color_space(const QString &s)
|
||||
{
|
||||
if (list_available_colorspaces().contains(s)) {
|
||||
return s;
|
||||
} else {
|
||||
return get_default_input_color_space();
|
||||
}
|
||||
}
|
||||
|
||||
ColorTransform
|
||||
ColorManager::get_compliant_color_space(const ColorTransform &transform,
|
||||
bool force_display)
|
||||
{
|
||||
if (transform.is_display() || force_display) {
|
||||
// Get display information
|
||||
QString display = transform.display();
|
||||
QString view = transform.view();
|
||||
QString look = transform.look();
|
||||
|
||||
// Check if display still exists in config
|
||||
if (!list_available_displays().contains(display)) {
|
||||
display = get_default_display();
|
||||
}
|
||||
|
||||
// Check if view still exists in display
|
||||
if (!list_available_views(display).contains(view)) {
|
||||
view = get_default_view(display);
|
||||
}
|
||||
|
||||
// Check if looks still exists
|
||||
if (!list_available_looks().contains(look)) {
|
||||
look.clear();
|
||||
}
|
||||
|
||||
return ColorTransform(display, view, look);
|
||||
|
||||
} else {
|
||||
QString output = transform.output();
|
||||
|
||||
if (!list_available_colorspaces().contains(output)) {
|
||||
output = get_default_input_color_space();
|
||||
}
|
||||
|
||||
return ColorTransform(output);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList
|
||||
ColorManager::list_available_colorspaces(ocio::ConstConfigRcPtr config)
|
||||
{
|
||||
QStringList spaces;
|
||||
|
||||
if (config) {
|
||||
int number_of_colorspaces = config->getNumColorSpaces();
|
||||
|
||||
for (int i = 0; i < number_of_colorspaces; i++) {
|
||||
spaces.append(config->getColorSpaceNameByIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
return spaces;
|
||||
}
|
||||
|
||||
void ColorManager::get_default_luma_coefs(double *rgb) const
|
||||
{
|
||||
config_->getDefaultLumaCoefs(rgb);
|
||||
}
|
||||
|
||||
Project *ColorManager::project() const
|
||||
{
|
||||
return static_cast<Project *>(parent());
|
||||
}
|
||||
|
||||
void ColorManager::update_config_from_filename()
|
||||
{
|
||||
try {
|
||||
QString config_filename = get_config_filename();
|
||||
QString old_default_cs = get_default_input_color_space();
|
||||
|
||||
config_ = ocio::Config::CreateFromFile(config_filename.toUtf8());
|
||||
|
||||
// Set new default colorspace appropriately
|
||||
QString new_default = old_default_cs;
|
||||
QStringList available_cs = list_available_colorspaces();
|
||||
for (int i = 0; i < available_cs.size(); i++) {
|
||||
const QString &c = available_cs.at(i);
|
||||
if (c.compare(old_default_cs, Qt::CaseInsensitive)) {
|
||||
new_default = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
set_default_input_color_space(new_default);
|
||||
|
||||
emit config_changed(config_filename);
|
||||
} catch (ocio::Exception &) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_COLORSERVICE_H
|
||||
#define OAK_COLORSERVICE_H
|
||||
|
||||
#include <memory>
|
||||
#include <QMutex>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "node/node.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ColorManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ColorManager(Project *project);
|
||||
|
||||
void init();
|
||||
|
||||
ocio::ConstConfigRcPtr get_config() const;
|
||||
|
||||
static ocio::ConstConfigRcPtr create_config_from_file(const QString &filename);
|
||||
|
||||
QString get_config_filename() const;
|
||||
|
||||
static ocio::ConstConfigRcPtr get_default_config();
|
||||
|
||||
static void set_up_default_config();
|
||||
|
||||
void set_config_filename(const QString &filename);
|
||||
|
||||
QStringList list_available_displays();
|
||||
|
||||
QString get_default_display();
|
||||
|
||||
QStringList list_available_views(QString display);
|
||||
|
||||
QString get_default_view(const QString &display);
|
||||
|
||||
QStringList list_available_looks();
|
||||
|
||||
QStringList list_available_colorspaces() const;
|
||||
|
||||
QString get_default_input_color_space() const;
|
||||
|
||||
/**
|
||||
* @brief Auto-detects an input colorspace from media color tags
|
||||
*
|
||||
* Maps raw FFmpeg color primaries/transfer values (as exposed on
|
||||
* VideoParams) to a colorspace of the active OCIO config. Returns an
|
||||
* empty string when the tags are unknown or the config has no matching
|
||||
* colorspace, in which case the default input colorspace applies.
|
||||
*/
|
||||
QString get_colorspace_for_ffmpeg_tags(int primaries, int trc) const;
|
||||
|
||||
void set_default_input_color_space(const QString &s);
|
||||
|
||||
QString get_reference_color_space() const;
|
||||
|
||||
QString get_compliant_color_space(const QString &s);
|
||||
|
||||
ColorTransform get_compliant_color_space(const ColorTransform &transform,
|
||||
bool force_display = false);
|
||||
|
||||
static QStringList list_available_colorspaces(ocio::ConstConfigRcPtr config);
|
||||
|
||||
void get_default_luma_coefs(double *rgb) const;
|
||||
|
||||
Project *project() const;
|
||||
|
||||
void update_config_from_filename();
|
||||
|
||||
signals:
|
||||
void config_changed(const QString &s);
|
||||
|
||||
void reference_space_changed(const QString &s);
|
||||
|
||||
void default_input_changed(const QString &s);
|
||||
|
||||
private:
|
||||
ocio::ConstConfigRcPtr config_;
|
||||
|
||||
static ocio::ConstConfigRcPtr default_config;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_COLORSERVICE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/displaytransform/displaytransform.cpp
|
||||
node/color/displaytransform/displaytransform.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,157 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "displaytransform.h"
|
||||
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const QString DisplayTransformNode::k_display_input =
|
||||
QStringLiteral("display_in");
|
||||
const QString DisplayTransformNode::k_view_input = QStringLiteral("view_in");
|
||||
const QString DisplayTransformNode::k_direction_input = QStringLiteral("dir_in");
|
||||
|
||||
#define super OCIOBaseNode
|
||||
|
||||
DisplayTransformNode::DisplayTransformNode()
|
||||
{
|
||||
add_input(k_display_input, NodeValue::k_combo, 0,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
|
||||
add_input(k_view_input, NodeValue::k_combo, 0,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
|
||||
add_input(k_direction_input, NodeValue::k_combo, 0,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
}
|
||||
|
||||
QString DisplayTransformNode::name() const
|
||||
{
|
||||
return tr("Display Transform");
|
||||
}
|
||||
|
||||
QString DisplayTransformNode::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.displaytransform");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> DisplayTransformNode::category() const
|
||||
{
|
||||
return { k_category_color };
|
||||
}
|
||||
|
||||
QString DisplayTransformNode::description() const
|
||||
{
|
||||
return tr("Converts an image to or from a display color space.");
|
||||
}
|
||||
|
||||
void DisplayTransformNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_texture_input, tr("Input"));
|
||||
set_input_name(k_display_input, tr("Display"));
|
||||
set_input_name(k_view_input, tr("View"));
|
||||
set_input_name(k_direction_input, tr("Direction"));
|
||||
set_combo_box_strings(k_direction_input, { tr("Forward"), tr("Inverse") });
|
||||
}
|
||||
|
||||
void DisplayTransformNode::InputValueChangedEvent(const QString &input,
|
||||
int element)
|
||||
{
|
||||
Q_UNUSED(element);
|
||||
if (input == k_display_input || input == k_direction_input ||
|
||||
input == k_view_input) {
|
||||
if (input == k_display_input) {
|
||||
update_views();
|
||||
}
|
||||
generate_processor();
|
||||
}
|
||||
}
|
||||
|
||||
QString DisplayTransformNode::get_display() const
|
||||
{
|
||||
if (manager()) {
|
||||
int index = get_standard_value(k_display_input).toInt();
|
||||
if (index < manager()->list_available_displays().size()) {
|
||||
return manager()->list_available_displays().at(index);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString DisplayTransformNode::get_view() const
|
||||
{
|
||||
if (manager()) {
|
||||
QString display = get_display();
|
||||
if (!display.isEmpty()) {
|
||||
int index = get_standard_value(k_view_input).toInt();
|
||||
QStringList views = manager()->list_available_views(display);
|
||||
if (index < views.size()) {
|
||||
return views.at(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
ColorProcessor::Direction DisplayTransformNode::get_direction() const
|
||||
{
|
||||
return static_cast<ColorProcessor::Direction>(
|
||||
get_standard_value(k_direction_input).toInt());
|
||||
;
|
||||
}
|
||||
|
||||
void DisplayTransformNode::update_displays()
|
||||
{
|
||||
if (manager()) {
|
||||
set_combo_box_strings(k_display_input, manager()->list_available_displays());
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayTransformNode::update_views()
|
||||
{
|
||||
if (manager()) {
|
||||
set_combo_box_strings(k_view_input,
|
||||
manager()->list_available_views(get_display()));
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayTransformNode::config_changed()
|
||||
{
|
||||
update_displays();
|
||||
update_views();
|
||||
generate_processor();
|
||||
}
|
||||
|
||||
void DisplayTransformNode::generate_processor()
|
||||
{
|
||||
if (manager()) {
|
||||
ColorTransform transform(get_display(), get_view(), QString());
|
||||
set_processor(ColorProcessor::create(
|
||||
manager(), manager()->get_reference_color_space(), transform,
|
||||
get_direction()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_DISPLAYTRANSFORMNODE_H
|
||||
#define OAK_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<CategoryID> category() const override;
|
||||
virtual QString description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
virtual void InputValueChangedEvent(const QString &input,
|
||||
int element) override;
|
||||
|
||||
QString get_display() const;
|
||||
QString get_view() const;
|
||||
ColorProcessor::Direction get_direction() const;
|
||||
|
||||
static const QString k_display_input;
|
||||
static const QString k_view_input;
|
||||
static const QString k_direction_input;
|
||||
|
||||
protected slots:
|
||||
virtual void config_changed() override;
|
||||
|
||||
private:
|
||||
void generate_processor();
|
||||
|
||||
void update_displays();
|
||||
|
||||
void update_views();
|
||||
};
|
||||
|
||||
} // olive
|
||||
|
||||
#endif // OAK_DISPLAYTRANSFORMNODE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/ociobase/ociobase.cpp
|
||||
node/color/ociobase/ociobase.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "ociobase.h"
|
||||
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
#include "node/project.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const QString OCIOBaseNode::k_texture_input = QStringLiteral("tex_in");
|
||||
|
||||
OCIOBaseNode::OCIOBaseNode()
|
||||
: manager_(nullptr)
|
||||
, processor_(nullptr)
|
||||
{
|
||||
add_input(k_texture_input, NodeValue::k_texture,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
|
||||
set_effect_input(k_texture_input);
|
||||
|
||||
set_flag(k_video_effect);
|
||||
}
|
||||
|
||||
void OCIOBaseNode::AddedToGraphEvent(Project *p)
|
||||
{
|
||||
manager_ = p->color_manager();
|
||||
connect(manager_, &ColorManager::config_changed, this,
|
||||
&OCIOBaseNode::config_changed);
|
||||
config_changed();
|
||||
}
|
||||
|
||||
void OCIOBaseNode::RemovedFromGraphEvent(Project *p)
|
||||
{
|
||||
if (manager_) {
|
||||
disconnect(manager_, &ColorManager::config_changed, this,
|
||||
&OCIOBaseNode::config_changed);
|
||||
manager_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOBaseNode::value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
auto tex_met = value[k_texture_input];
|
||||
TexturePtr t = tex_met.to_texture();
|
||||
if (t) {
|
||||
if (processor_) {
|
||||
ColorTransformJob job;
|
||||
|
||||
job.set_color_processor(processor_);
|
||||
job.set_input_texture(tex_met);
|
||||
|
||||
table->push(NodeValue::k_texture, t->to_job(job), this);
|
||||
} else {
|
||||
// Processor isn't ready yet (e.g. still being generated
|
||||
// asynchronously), pass the input through unchanged.
|
||||
table->push(NodeValue::k_texture, QVariant::fromValue(t), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_OCIOBASENODE_H
|
||||
#define OAK_OCIOBASENODE_H
|
||||
|
||||
#include "node/node.h"
|
||||
#include "render/job/colortransformjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class OCIOBaseNode : public Node {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OCIOBaseNode();
|
||||
|
||||
virtual void AddedToGraphEvent(Project *p) override;
|
||||
virtual void RemovedFromGraphEvent(Project *p) override;
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
static const QString k_texture_input;
|
||||
|
||||
protected slots:
|
||||
virtual void config_changed() = 0;
|
||||
|
||||
protected:
|
||||
ColorManager *manager() const
|
||||
{
|
||||
return manager_;
|
||||
}
|
||||
|
||||
ColorProcessorPtr processor() const
|
||||
{
|
||||
return processor_;
|
||||
}
|
||||
void set_processor(ColorProcessorPtr p)
|
||||
{
|
||||
processor_ = p;
|
||||
}
|
||||
|
||||
private:
|
||||
ColorManager *manager_;
|
||||
|
||||
ColorProcessorPtr processor_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_OCIOBASENODE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp
|
||||
node/color/ociogradingtransformlinear/ociogradingtransformlinear.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,311 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "ociogradingtransformlinear.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "common/ocioutils.h"
|
||||
#include "node/project.h"
|
||||
#include "render/colorprocessor.h"
|
||||
#include "node/sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const QString OCIOGradingTransformLinearNode::k_contrast_input =
|
||||
QStringLiteral("ocio_grading_primary_contrast");
|
||||
const QString OCIOGradingTransformLinearNode::k_offset_input =
|
||||
QStringLiteral("ocio_grading_primary_offset");
|
||||
const QString OCIOGradingTransformLinearNode::k_exposure_input =
|
||||
QStringLiteral("ocio_grading_primary_exposure");
|
||||
const QString OCIOGradingTransformLinearNode::k_saturation_input =
|
||||
QStringLiteral("ocio_grading_primary_saturation");
|
||||
const QString OCIOGradingTransformLinearNode::k_pivot_input =
|
||||
QStringLiteral("ocio_grading_primary_pivot");
|
||||
const QString OCIOGradingTransformLinearNode::k_clamp_black_enable_input =
|
||||
QStringLiteral("clamp_black_enable_in");
|
||||
const QString OCIOGradingTransformLinearNode::k_clamp_black_input =
|
||||
QStringLiteral("ocio_grading_primary_clampBlack");
|
||||
const QString OCIOGradingTransformLinearNode::k_clamp_white_enable_input =
|
||||
QStringLiteral("clamp_white_enable_in");
|
||||
const QString OCIOGradingTransformLinearNode::k_clamp_white_input =
|
||||
QStringLiteral("ocio_grading_primary_clampWhite");
|
||||
|
||||
#define super OCIOBaseNode
|
||||
|
||||
OCIOGradingTransformLinearNode::OCIOGradingTransformLinearNode()
|
||||
{
|
||||
add_input(k_contrast_input, NodeValue::k_vec4, QVector4D{ 1.0, 1.0, 1.0, 1.0 });
|
||||
// Minimum based on ocio::GradingPrimary::validate
|
||||
set_input_property(k_contrast_input, QStringLiteral("min"),
|
||||
QVector4D{ 0.01f, 0.01f, 0.01f, 0.01f });
|
||||
set_input_property(k_contrast_input, QStringLiteral("base"), 0.01);
|
||||
set_vec4_input_colors(k_contrast_input);
|
||||
|
||||
add_input(k_offset_input, NodeValue::k_vec4, QVector4D{ 0.0, 0.0, 0.0, 0.0 });
|
||||
set_input_property(k_offset_input, QStringLiteral("base"), 0.01);
|
||||
set_vec4_input_colors(k_offset_input);
|
||||
|
||||
add_input(k_exposure_input, NodeValue::k_vec4, QVector4D{ 0.0, 0.0, 0.0, 0.0 });
|
||||
set_input_property(k_exposure_input, QStringLiteral("base"), 0.01);
|
||||
set_vec4_input_colors(k_exposure_input);
|
||||
|
||||
add_input(k_saturation_input, NodeValue::k_float, 1.0);
|
||||
set_input_property(k_saturation_input, QStringLiteral("view"),
|
||||
slider::k_percentage);
|
||||
set_input_property(k_saturation_input, QStringLiteral("min"), 0.0);
|
||||
|
||||
add_input(k_pivot_input, NodeValue::k_float,
|
||||
0.18); // Default listed in ocio::GradingPrimary
|
||||
set_input_property(k_pivot_input, QStringLiteral("base"), 0.01);
|
||||
|
||||
add_input(k_clamp_black_enable_input, NodeValue::k_boolean, false);
|
||||
|
||||
add_input(k_clamp_black_input, NodeValue::k_float, 0.0);
|
||||
set_input_property(k_clamp_black_input, QStringLiteral("enabled"),
|
||||
get_standard_value(k_clamp_black_enable_input).toBool());
|
||||
set_input_property(k_clamp_black_input, QStringLiteral("base"), 0.01);
|
||||
|
||||
add_input(k_clamp_white_enable_input, NodeValue::k_boolean, false);
|
||||
|
||||
add_input(k_clamp_white_input, NodeValue::k_float, 1.0);
|
||||
set_input_property(k_clamp_white_input, QStringLiteral("enabled"),
|
||||
get_standard_value(k_clamp_white_enable_input).toBool());
|
||||
set_input_property(k_clamp_white_input, QStringLiteral("base"), 0.01);
|
||||
|
||||
// Constrain the white clamp minimum to just above the (static) black clamp
|
||||
// as per ocio::GradingPrimary::validate. When the black clamp is keyframed
|
||||
// or connected, Value() enforces the invariant per frame instead.
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
|
||||
QString OCIOGradingTransformLinearNode::name() const
|
||||
{
|
||||
return tr("OCIO Color Grading (Linear)");
|
||||
}
|
||||
|
||||
QString OCIOGradingTransformLinearNode::id() const
|
||||
{
|
||||
return QStringLiteral(
|
||||
"org.olivevideoeditor.Olive.ociogradingtransformlinear");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> OCIOGradingTransformLinearNode::category() const
|
||||
{
|
||||
return { k_category_color };
|
||||
}
|
||||
|
||||
QString OCIOGradingTransformLinearNode::description() const
|
||||
{
|
||||
return tr("Simple linear color grading using OpenColorIO.");
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_texture_input, tr("Input"));
|
||||
set_input_name(k_contrast_input, tr("Contrast"));
|
||||
set_input_name(k_offset_input, tr("Offset"));
|
||||
set_input_name(k_exposure_input, tr("Exposure"));
|
||||
set_input_property(k_exposure_input, QStringLiteral("tooltip"),
|
||||
tr("Exposure increments in stops."));
|
||||
set_input_name(k_saturation_input, tr("Saturation"));
|
||||
set_input_name(k_pivot_input, tr("Pivot"));
|
||||
set_input_name(k_clamp_black_enable_input, tr("Enable Black Clamp"));
|
||||
set_input_name(k_clamp_black_input, tr("Black Clamp"));
|
||||
set_input_name(k_clamp_white_enable_input, tr("Enable White Clamp"));
|
||||
set_input_name(k_clamp_white_input, tr("White Clamp"));
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::InputValueChangedEvent(
|
||||
const QString &input, int element)
|
||||
{
|
||||
Q_UNUSED(element);
|
||||
|
||||
if (input == k_clamp_white_enable_input) {
|
||||
set_input_property(k_clamp_white_input, QStringLiteral("enabled"),
|
||||
get_standard_value(k_clamp_white_enable_input).toBool());
|
||||
} else if (input == k_clamp_black_enable_input) {
|
||||
set_input_property(k_clamp_black_input, QStringLiteral("enabled"),
|
||||
get_standard_value(k_clamp_black_enable_input).toBool());
|
||||
} else if (input == k_clamp_black_input) {
|
||||
// Ensure the white clamp is always greater than the black clamp as per
|
||||
// ocio::GradingPrimary::validate
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
|
||||
generate_processor();
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::InputConnectedEvent(const QString &input,
|
||||
int element, Node *output)
|
||||
{
|
||||
super::InputConnectedEvent(input, element, output);
|
||||
|
||||
if (input == k_clamp_black_input) {
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::InputDisconnectedEvent(const QString &input,
|
||||
int element,
|
||||
Node *output)
|
||||
{
|
||||
super::InputDisconnectedEvent(input, element, output);
|
||||
|
||||
if (input == k_clamp_black_input) {
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::update_clamp_white_minimum()
|
||||
{
|
||||
// A static UI minimum cannot follow an animated black clamp; for keyframed
|
||||
// or connected values the white>black invariant is enforced per frame in
|
||||
// Value() instead
|
||||
if (is_input_keyframing(k_clamp_black_input) ||
|
||||
is_input_connected(k_clamp_black_input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_input_property(k_clamp_white_input, QStringLiteral("min"),
|
||||
get_standard_value(k_clamp_black_input).toDouble() + 0.000001);
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::generate_processor()
|
||||
{
|
||||
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()->get_config()->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 (TexturePtr tex = value[k_texture_input].to_texture()) {
|
||||
if (processor()) {
|
||||
ColorTransformJob job(value);
|
||||
|
||||
job.set_color_processor(processor());
|
||||
job.set_input_texture(value[k_texture_input]);
|
||||
|
||||
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[k_offset_input].to_vec4();
|
||||
offset[red_channel] += offset[master_channel];
|
||||
offset[green_channel] += offset[master_channel];
|
||||
offset[blue_channel] += offset[master_channel];
|
||||
job.insert(k_offset_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
QVector3D(offset[red_channel],
|
||||
offset[green_channel],
|
||||
offset[blue_channel])));
|
||||
|
||||
QVector4D exposure = value[k_exposure_input].to_vec4();
|
||||
exposure[red_channel] = std::pow(2.0f, exposure[master_channel] +
|
||||
exposure[red_channel]);
|
||||
exposure[green_channel] = std::pow(
|
||||
2.0f, exposure[master_channel] + exposure[green_channel]);
|
||||
exposure[blue_channel] = std::pow(2.0f, exposure[master_channel] +
|
||||
exposure[blue_channel]);
|
||||
job.insert(k_exposure_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
QVector3D(exposure[red_channel],
|
||||
exposure[green_channel],
|
||||
exposure[blue_channel])));
|
||||
|
||||
QVector4D contrast = value[k_contrast_input].to_vec4();
|
||||
contrast[red_channel] *= contrast[master_channel];
|
||||
contrast[green_channel] *= contrast[master_channel];
|
||||
contrast[blue_channel] *= contrast[master_channel];
|
||||
job.insert(k_contrast_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
QVector3D(contrast[red_channel],
|
||||
contrast[green_channel],
|
||||
contrast[blue_channel])));
|
||||
|
||||
if (!value[k_clamp_black_enable_input].to_bool()) {
|
||||
job.insert(k_clamp_black_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
ocio::GradingPrimary::NoClampBlack()));
|
||||
}
|
||||
|
||||
if (!value[k_clamp_white_enable_input].to_bool()) {
|
||||
job.insert(k_clamp_white_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
ocio::GradingPrimary::NoClampWhite()));
|
||||
}
|
||||
|
||||
if (value[k_clamp_black_enable_input].to_bool() &&
|
||||
value[k_clamp_white_enable_input].to_bool()) {
|
||||
// ocio::GradingPrimary::validate requires the white clamp to be
|
||||
// greater than the black clamp. Keyframed or connected values
|
||||
// can violate this at arbitrary times, so enforce the invariant
|
||||
// per frame here.
|
||||
const double clamp_black = value[k_clamp_black_input].to_double();
|
||||
const double clamp_white = value[k_clamp_white_input].to_double();
|
||||
if (clamp_white <= clamp_black) {
|
||||
job.insert(k_clamp_white_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
clamp_black + 0.000001));
|
||||
}
|
||||
}
|
||||
|
||||
table->push(NodeValue::k_texture, tex->to_job(job), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::config_changed()
|
||||
{
|
||||
generate_processor();
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::set_vec4_input_colors(const QString &input)
|
||||
{
|
||||
set_input_property(input, QStringLiteral("color0"),
|
||||
QColor(192, 192, 192).name());
|
||||
set_input_property(input, QStringLiteral("color1"), QColor(255, 0, 0).name());
|
||||
set_input_property(input, QStringLiteral("color2"), QColor(0, 255, 0).name());
|
||||
set_input_property(input, QStringLiteral("color3"), QColor(0, 0, 255).name());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_OCIOGRADINGTRANSFORMLINEARNODE_H
|
||||
#define OAK_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<CategoryID> category() const override;
|
||||
virtual QString description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
virtual void InputValueChangedEvent(const QString &input,
|
||||
int element) override;
|
||||
virtual void InputConnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
virtual void InputDisconnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
void generate_processor();
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
static const QString k_contrast_input;
|
||||
static const QString k_offset_input;
|
||||
static const QString k_exposure_input;
|
||||
static const QString k_saturation_input;
|
||||
static const QString k_pivot_input;
|
||||
static const QString k_clamp_black_enable_input;
|
||||
static const QString k_clamp_black_input;
|
||||
static const QString k_clamp_white_enable_input;
|
||||
static const QString k_clamp_white_input;
|
||||
|
||||
protected slots:
|
||||
virtual void config_changed() override;
|
||||
|
||||
private:
|
||||
void set_vec4_input_colors(const QString &input);
|
||||
|
||||
/**
|
||||
* @brief Constrains the white clamp UI minimum to just above the black
|
||||
* clamp, as required by ocio::GradingPrimary::validate
|
||||
*
|
||||
* Only applies while the black clamp is a static value; when it is
|
||||
* keyframed or connected the invariant is enforced per frame in Value()
|
||||
* instead.
|
||||
*/
|
||||
void update_clamp_white_minimum();
|
||||
};
|
||||
|
||||
} // olive
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
# Modifications Copyright (C) 2026 Oak 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/ociogradingtransformlog/ociogradingtransformlog.cpp
|
||||
node/color/ociogradingtransformlog/ociogradingtransformlog.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,302 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2026 Oak 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "ociogradingtransformlog.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "common/ocioutils.h"
|
||||
#include "node/project.h"
|
||||
#include "render/colorprocessor.h"
|
||||
#include "node/sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
// These ids double as the OCIO GPU uniform names for the dynamic
|
||||
// GradingPrimaryTransform; do not rename them. OCIO's log style maps the
|
||||
// classic wheels as: brightness = lift, contrast = gain, gamma = gamma.
|
||||
const QString OCIOGradingTransformLogNode::k_lift_input =
|
||||
QStringLiteral("ocio_grading_primary_brightness");
|
||||
const QString OCIOGradingTransformLogNode::k_gain_input =
|
||||
QStringLiteral("ocio_grading_primary_contrast");
|
||||
const QString OCIOGradingTransformLogNode::k_gamma_input =
|
||||
QStringLiteral("ocio_grading_primary_gamma");
|
||||
const QString OCIOGradingTransformLogNode::k_saturation_input =
|
||||
QStringLiteral("ocio_grading_primary_saturation");
|
||||
const QString OCIOGradingTransformLogNode::k_pivot_input =
|
||||
QStringLiteral("ocio_grading_primary_pivot");
|
||||
const QString OCIOGradingTransformLogNode::k_clamp_black_enable_input =
|
||||
QStringLiteral("clamp_black_enable_in");
|
||||
const QString OCIOGradingTransformLogNode::k_clamp_black_input =
|
||||
QStringLiteral("ocio_grading_primary_clampBlack");
|
||||
const QString OCIOGradingTransformLogNode::k_clamp_white_enable_input =
|
||||
QStringLiteral("clamp_white_enable_in");
|
||||
const QString OCIOGradingTransformLogNode::k_clamp_white_input =
|
||||
QStringLiteral("ocio_grading_primary_clampWhite");
|
||||
|
||||
#define super OCIOBaseNode
|
||||
|
||||
OCIOGradingTransformLogNode::OCIOGradingTransformLogNode()
|
||||
{
|
||||
add_input(k_lift_input, NodeValue::k_vec4, QVector4D{ 0.0, 0.0, 0.0, 0.0 });
|
||||
set_input_property(k_lift_input, QStringLiteral("base"), 0.01);
|
||||
set_vec4_input_colors(k_lift_input);
|
||||
|
||||
add_input(k_gain_input, NodeValue::k_vec4, QVector4D{ 1.0, 1.0, 1.0, 1.0 });
|
||||
set_input_property(k_gain_input, QStringLiteral("base"), 0.01);
|
||||
set_vec4_input_colors(k_gain_input);
|
||||
|
||||
add_input(k_gamma_input, NodeValue::k_vec4, QVector4D{ 1.0, 1.0, 1.0, 1.0 });
|
||||
set_input_property(k_gamma_input, QStringLiteral("base"), 0.01);
|
||||
set_vec4_input_colors(k_gamma_input);
|
||||
|
||||
add_input(k_saturation_input, NodeValue::k_float, 1.0);
|
||||
set_input_property(k_saturation_input, QStringLiteral("view"),
|
||||
slider::k_percentage);
|
||||
set_input_property(k_saturation_input, QStringLiteral("min"), 0.0);
|
||||
|
||||
add_input(k_pivot_input, NodeValue::k_float,
|
||||
-0.2); // Default for GRADING_LOG listed in ocio::GradingPrimary
|
||||
set_input_property(k_pivot_input, QStringLiteral("base"), 0.01);
|
||||
|
||||
add_input(k_clamp_black_enable_input, NodeValue::k_boolean, false);
|
||||
|
||||
add_input(k_clamp_black_input, NodeValue::k_float, 0.0);
|
||||
set_input_property(k_clamp_black_input, QStringLiteral("enabled"),
|
||||
get_standard_value(k_clamp_black_enable_input).toBool());
|
||||
set_input_property(k_clamp_black_input, QStringLiteral("base"), 0.01);
|
||||
|
||||
add_input(k_clamp_white_enable_input, NodeValue::k_boolean, false);
|
||||
|
||||
add_input(k_clamp_white_input, NodeValue::k_float, 1.0);
|
||||
set_input_property(k_clamp_white_input, QStringLiteral("enabled"),
|
||||
get_standard_value(k_clamp_white_enable_input).toBool());
|
||||
set_input_property(k_clamp_white_input, QStringLiteral("base"), 0.01);
|
||||
|
||||
// Constrain the white clamp minimum to just above the (static) black clamp
|
||||
// as per ocio::GradingPrimary::validate. When the black clamp is keyframed
|
||||
// or connected, Value() enforces the invariant per frame instead.
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
|
||||
QString OCIOGradingTransformLogNode::name() const
|
||||
{
|
||||
return tr("OCIO Color Grading (Log)");
|
||||
}
|
||||
|
||||
QString OCIOGradingTransformLogNode::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.ociogradingtransformlog");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> OCIOGradingTransformLogNode::category() const
|
||||
{
|
||||
return { k_category_color };
|
||||
}
|
||||
|
||||
QString OCIOGradingTransformLogNode::description() const
|
||||
{
|
||||
return tr("Lift/gamma/gain color grading using OpenColorIO.");
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_texture_input, tr("Input"));
|
||||
set_input_name(k_lift_input, tr("Lift"));
|
||||
set_input_name(k_gain_input, tr("Gain"));
|
||||
set_input_name(k_gamma_input, tr("Gamma"));
|
||||
set_input_name(k_saturation_input, tr("Saturation"));
|
||||
set_input_name(k_pivot_input, tr("Pivot"));
|
||||
set_input_name(k_clamp_black_enable_input, tr("Enable Black Clamp"));
|
||||
set_input_name(k_clamp_black_input, tr("Black Clamp"));
|
||||
set_input_name(k_clamp_white_enable_input, tr("Enable White Clamp"));
|
||||
set_input_name(k_clamp_white_input, tr("White Clamp"));
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::InputValueChangedEvent(const QString &input,
|
||||
int element)
|
||||
{
|
||||
Q_UNUSED(element);
|
||||
|
||||
if (input == k_clamp_white_enable_input) {
|
||||
set_input_property(k_clamp_white_input, QStringLiteral("enabled"),
|
||||
get_standard_value(k_clamp_white_enable_input).toBool());
|
||||
} else if (input == k_clamp_black_enable_input) {
|
||||
set_input_property(k_clamp_black_input, QStringLiteral("enabled"),
|
||||
get_standard_value(k_clamp_black_enable_input).toBool());
|
||||
} else if (input == k_clamp_black_input) {
|
||||
// Ensure the white clamp is always greater than the black clamp as per
|
||||
// ocio::GradingPrimary::validate
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
|
||||
generate_processor();
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::InputConnectedEvent(const QString &input,
|
||||
int element, Node *output)
|
||||
{
|
||||
super::InputConnectedEvent(input, element, output);
|
||||
|
||||
if (input == k_clamp_black_input) {
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::InputDisconnectedEvent(const QString &input,
|
||||
int element,
|
||||
Node *output)
|
||||
{
|
||||
super::InputDisconnectedEvent(input, element, output);
|
||||
|
||||
if (input == k_clamp_black_input) {
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::update_clamp_white_minimum()
|
||||
{
|
||||
// A static UI minimum cannot follow an animated black clamp; for keyframed
|
||||
// or connected values the white>black invariant is enforced per frame in
|
||||
// Value() instead
|
||||
if (is_input_keyframing(k_clamp_black_input) ||
|
||||
is_input_connected(k_clamp_black_input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_input_property(k_clamp_white_input, QStringLiteral("min"),
|
||||
get_standard_value(k_clamp_black_input).toDouble() + 0.000001);
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::generate_processor()
|
||||
{
|
||||
if (manager()) {
|
||||
ocio::GradingPrimaryTransformRcPtr gp =
|
||||
ocio::GradingPrimaryTransform::Create(ocio::GRADING_LOG);
|
||||
gp->makeDynamic();
|
||||
gp->setDirection(ocio::TransformDirection::TRANSFORM_DIR_FORWARD);
|
||||
|
||||
try {
|
||||
set_processor(ColorProcessor::create(
|
||||
manager()->get_config()->getProcessor(gp)));
|
||||
} catch (const ocio::Exception &e) {
|
||||
std::cerr << std::endl << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::value(const NodeValueRow &value,
|
||||
const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
if (TexturePtr tex = value[k_texture_input].to_texture()) {
|
||||
if (processor()) {
|
||||
ColorTransformJob job(value);
|
||||
|
||||
job.set_color_processor(processor());
|
||||
job.set_input_texture(value[k_texture_input]);
|
||||
|
||||
const int master_channel = 0;
|
||||
const int red_channel = 1;
|
||||
const int green_channel = 2;
|
||||
const int blue_channel = 3;
|
||||
|
||||
// OCIO expects vec3s on the GPU but RGBMs (master + RGB) on the
|
||||
// CPU; the per-style master combination below mirrors
|
||||
// ocio::GradingPrimary. Lift is additive, gain/gamma multiply.
|
||||
QVector4D lift = value[k_lift_input].to_vec4();
|
||||
lift[red_channel] += lift[master_channel];
|
||||
lift[green_channel] += lift[master_channel];
|
||||
lift[blue_channel] += lift[master_channel];
|
||||
job.insert(k_lift_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
QVector3D(lift[red_channel], lift[green_channel],
|
||||
lift[blue_channel])));
|
||||
|
||||
QVector4D gain = value[k_gain_input].to_vec4();
|
||||
gain[red_channel] *= gain[master_channel];
|
||||
gain[green_channel] *= gain[master_channel];
|
||||
gain[blue_channel] *= gain[master_channel];
|
||||
job.insert(k_gain_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
QVector3D(gain[red_channel], gain[green_channel],
|
||||
gain[blue_channel])));
|
||||
|
||||
QVector4D gamma = value[k_gamma_input].to_vec4();
|
||||
gamma[red_channel] *= gamma[master_channel];
|
||||
gamma[green_channel] *= gamma[master_channel];
|
||||
gamma[blue_channel] *= gamma[master_channel];
|
||||
job.insert(k_gamma_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
QVector3D(gamma[red_channel],
|
||||
gamma[green_channel],
|
||||
gamma[blue_channel])));
|
||||
|
||||
if (!value[k_clamp_black_enable_input].to_bool()) {
|
||||
job.insert(k_clamp_black_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
ocio::GradingPrimary::NoClampBlack()));
|
||||
}
|
||||
|
||||
if (!value[k_clamp_white_enable_input].to_bool()) {
|
||||
job.insert(k_clamp_white_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
ocio::GradingPrimary::NoClampWhite()));
|
||||
}
|
||||
|
||||
if (value[k_clamp_black_enable_input].to_bool() &&
|
||||
value[k_clamp_white_enable_input].to_bool()) {
|
||||
// ocio::GradingPrimary::validate requires the white clamp to be
|
||||
// greater than the black clamp. Keyframed or connected values
|
||||
// can violate this at arbitrary times, so enforce the invariant
|
||||
// per frame here.
|
||||
const double clamp_black = value[k_clamp_black_input].to_double();
|
||||
const double clamp_white = value[k_clamp_white_input].to_double();
|
||||
if (clamp_white <= clamp_black) {
|
||||
job.insert(k_clamp_white_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
clamp_black + 0.000001));
|
||||
}
|
||||
}
|
||||
|
||||
table->push(NodeValue::k_texture, tex->to_job(job), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::config_changed()
|
||||
{
|
||||
generate_processor();
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::set_vec4_input_colors(const QString &input)
|
||||
{
|
||||
set_input_property(input, QStringLiteral("color0"),
|
||||
QColor(192, 192, 192).name());
|
||||
set_input_property(input, QStringLiteral("color1"), QColor(255, 0, 0).name());
|
||||
set_input_property(input, QStringLiteral("color2"), QColor(0, 255, 0).name());
|
||||
set_input_property(input, QStringLiteral("color3"), QColor(0, 0, 255).name());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2026 Oak 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_OCIOGRADINGTRANSFORMLOGNODE_H
|
||||
#define OAK_OCIOGRADINGTRANSFORMLOGNODE_H
|
||||
|
||||
#include "node/color/ociobase/ociobase.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Lift/gamma/gain grading node built on ocio::GRADING_LOG
|
||||
*
|
||||
* Mirrors OCIOGradingTransformLinearNode for the log grading style. OCIO's
|
||||
* log-style GPU uniforms map to the classic wheels as: brightness = lift,
|
||||
* contrast = gain, gamma = gamma.
|
||||
*/
|
||||
class OCIOGradingTransformLogNode : public OCIOBaseNode {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OCIOGradingTransformLogNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(OCIOGradingTransformLogNode)
|
||||
|
||||
virtual QString name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> category() const override;
|
||||
virtual QString description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
virtual void InputValueChangedEvent(const QString &input,
|
||||
int element) override;
|
||||
virtual void InputConnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
virtual void InputDisconnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
void generate_processor();
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
static const QString k_lift_input;
|
||||
static const QString k_gain_input;
|
||||
static const QString k_gamma_input;
|
||||
static const QString k_saturation_input;
|
||||
static const QString k_pivot_input;
|
||||
static const QString k_clamp_black_enable_input;
|
||||
static const QString k_clamp_black_input;
|
||||
static const QString k_clamp_white_enable_input;
|
||||
static const QString k_clamp_white_input;
|
||||
|
||||
protected slots:
|
||||
virtual void config_changed() override;
|
||||
|
||||
private:
|
||||
void set_vec4_input_colors(const QString &input);
|
||||
|
||||
/**
|
||||
* @brief Constrains the white clamp UI minimum to just above the black
|
||||
* clamp, as required by ocio::GradingPrimary::validate
|
||||
*
|
||||
* Only applies while the black clamp is a static value; when it is
|
||||
* keyframed or connected the invariant is enforced per frame in Value()
|
||||
* instead.
|
||||
*/
|
||||
void update_clamp_white_minimum();
|
||||
};
|
||||
|
||||
} // olive
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2026 Oak 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.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/ociolut/ociolut.cpp
|
||||
node/color/ociolut/ociolut.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,304 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "ociolut.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QMetaObject>
|
||||
|
||||
|
||||
#include "coreengine.h"
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
#include "render/lutlibrary.h"
|
||||
#include "render/previewautocacher.h"
|
||||
#include "render/rendermanager.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const QString OCIOLutNode::k_file_input = QStringLiteral("lut_file_in");
|
||||
const QString OCIOLutNode::k_direction_input = QStringLiteral("lut_dir_in");
|
||||
|
||||
#define super OCIOBaseNode
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool is_main_process()
|
||||
{
|
||||
return qobject_cast<QApplication *>(QCoreApplication::instance()) !=
|
||||
nullptr;
|
||||
}
|
||||
|
||||
int read_direction_input(const Node *node)
|
||||
{
|
||||
QVariant v = node->get_standard_value(OCIOLutNode::k_direction_input);
|
||||
|
||||
bool ok = false;
|
||||
int direction = v.toInt(&ok);
|
||||
if (ok) {
|
||||
return direction;
|
||||
}
|
||||
|
||||
// Some old serializers stored the combo value as a string.
|
||||
const QString s = v.toString().toLower();
|
||||
if (s == QStringLiteral("forward") || s == QStringLiteral("0")) {
|
||||
return 0;
|
||||
}
|
||||
if (s == QStringLiteral("inverse") || s == QStringLiteral("1")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
qWarning() << "OCIOLutNode: unexpected direction value" << v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OCIOLutNode::OCIOLutNode()
|
||||
{
|
||||
add_input(k_file_input, NodeValue::k_file, QString(),
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
const QString all_luts =
|
||||
QStringLiteral("*.") +
|
||||
LUTLibrary::supported_extensions().join(QStringLiteral(" *."));
|
||||
set_input_property(k_file_input, QStringLiteral("filter"),
|
||||
tr("LUT Files (%1);;All Files (*)").arg(all_luts));
|
||||
set_input_property(k_file_input, QStringLiteral("placeholder"),
|
||||
tr("Select a LUT file"));
|
||||
// Allow the UI to offer the global LUT library for this input
|
||||
set_input_property(k_file_input, QStringLiteral("lut_library"), true);
|
||||
|
||||
add_input(k_direction_input, NodeValue::k_combo, 0,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
|
||||
qRegisterMetaType<olive::ColorProcessorPtr>();
|
||||
}
|
||||
|
||||
QString OCIOLutNode::name() const
|
||||
{
|
||||
return tr("OCIO LUT");
|
||||
}
|
||||
|
||||
QString OCIOLutNode::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.ociolut");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> OCIOLutNode::category() const
|
||||
{
|
||||
return { k_category_color };
|
||||
}
|
||||
|
||||
QString OCIOLutNode::description() const
|
||||
{
|
||||
return tr("Applies a LUT file through OpenColorIO.");
|
||||
}
|
||||
|
||||
void OCIOLutNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_texture_input, tr("Input"));
|
||||
set_input_name(k_file_input, tr("LUT File"));
|
||||
set_input_name(k_direction_input, tr("Direction"));
|
||||
set_combo_box_strings(k_direction_input, { tr("Forward"), tr("Inverse") });
|
||||
}
|
||||
|
||||
void OCIOLutNode::InputValueChangedEvent(const QString &input, int element)
|
||||
{
|
||||
Q_UNUSED(element)
|
||||
|
||||
if (input == k_file_input || input == k_direction_input) {
|
||||
// In the worker process, creating the OCIO processor can be slow and we
|
||||
// are often called from LoadGraph while the main process is blocked
|
||||
// waiting for a response. Defer generation to Value() time so the worker
|
||||
// can ack the graph load immediately.
|
||||
if (is_main_process()) {
|
||||
generate_processor();
|
||||
} else {
|
||||
QMutexLocker locker(&gen_mutex_);
|
||||
processor_dirty_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOLutNode::config_changed()
|
||||
{
|
||||
if (is_main_process()) {
|
||||
generate_processor();
|
||||
} else {
|
||||
QMutexLocker locker(&gen_mutex_);
|
||||
processor_dirty_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOLutNode::value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
// Ensure the processor is up-to-date before the base class emits the color
|
||||
// transform job. This is especially important in the render worker, where
|
||||
// processor creation is deferred until the first render.
|
||||
ensure_processor();
|
||||
|
||||
super::value(value, globals, table);
|
||||
}
|
||||
|
||||
void OCIOLutNode::generate_processor()
|
||||
{
|
||||
ensure_processor();
|
||||
|
||||
// The processor has changed. In the main GUI process, refresh the viewer by
|
||||
// invalidating the cache and cancelling background cache jobs.
|
||||
// Invalidating first ensures any in-flight renders that complete afterwards
|
||||
// won't write stale frames back. The worker process uses QGuiApplication and
|
||||
// has no RenderManager/PreviewAutoCacher, so skip this step to avoid crashing.
|
||||
if (is_main_process()) {
|
||||
invalidate_all(k_texture_input);
|
||||
if (RenderManager *rm = RenderManager::instance()) {
|
||||
if (PreviewAutoCacher *cacher = rm->get_cacher()) {
|
||||
cacher->cancel_video_tasks(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOLutNode::ensure_processor() const
|
||||
{
|
||||
QMutexLocker locker(&gen_mutex_);
|
||||
|
||||
if (!processor_dirty_ && last_processor_ &&
|
||||
get_standard_value(k_file_input).toString() == last_path_ &&
|
||||
read_direction_input(this) == last_direction_) {
|
||||
return;
|
||||
}
|
||||
|
||||
create_processor_from_inputs();
|
||||
}
|
||||
|
||||
void OCIOLutNode::set_last_error(const QString &error) const
|
||||
{
|
||||
if (last_error_ == error) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_error_ = error;
|
||||
|
||||
// Make the error visible to the user instead of failing silently, but only
|
||||
// from the main process (the render worker has no status bar)
|
||||
if (!error.isEmpty() && is_main_process() && EngineCore::instance()) {
|
||||
EngineCore::instance()->show_status_bar_message(error, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
bool OCIOLutNode::create_processor_from_inputs() const
|
||||
{
|
||||
if (!manager()) {
|
||||
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
|
||||
last_processor_.reset();
|
||||
last_path_.clear();
|
||||
last_direction_ = -1;
|
||||
processor_dirty_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString path = get_standard_value(k_file_input).toString();
|
||||
const int direction = read_direction_input(this);
|
||||
|
||||
if (path.isEmpty()) {
|
||||
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
|
||||
last_processor_.reset();
|
||||
last_path_.clear();
|
||||
last_direction_ = -1;
|
||||
processor_dirty_ = false;
|
||||
set_last_error(QString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Re-use the existing processor if the file and direction haven't changed.
|
||||
if (path == last_path_ && direction == last_direction_ && last_processor_) {
|
||||
processor_dirty_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
const QFileInfo info(path);
|
||||
if (!info.exists() || !info.isFile()) {
|
||||
qWarning() << "OCIO LUT file does not exist:" << path;
|
||||
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
|
||||
last_processor_.reset();
|
||||
last_path_.clear();
|
||||
last_direction_ = -1;
|
||||
processor_dirty_ = false;
|
||||
set_last_error(tr("OCIO LUT: file does not exist: %1").arg(path));
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString suffix = info.suffix();
|
||||
if (!LUTLibrary::is_supported_extension(suffix)) {
|
||||
qWarning() << "Unsupported OCIO LUT file extension:" << path;
|
||||
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
|
||||
last_processor_.reset();
|
||||
last_path_.clear();
|
||||
last_direction_ = -1;
|
||||
processor_dirty_ = false;
|
||||
set_last_error(tr("OCIO LUT: unsupported LUT file extension: %1")
|
||||
.arg(path));
|
||||
return false;
|
||||
}
|
||||
|
||||
ColorProcessorPtr processor;
|
||||
try {
|
||||
const bool forward = static_cast<ColorProcessor::Direction>(
|
||||
direction) == ColorProcessor::k_normal;
|
||||
qDebug() << "OCIOLutNode: creating processor for" << path
|
||||
<< "direction=" << direction
|
||||
<< "ocio_dir=" << (forward ? "FORWARD" : "INVERSE")
|
||||
<< "process=" << (is_main_process() ? "main" : "worker");
|
||||
|
||||
ocio::FileTransformRcPtr transform = ocio::FileTransform::Create();
|
||||
transform->setSrc(path.toUtf8().constData());
|
||||
transform->setInterpolation(ocio::INTERP_LINEAR);
|
||||
transform->setDirection(forward ? ocio::TRANSFORM_DIR_FORWARD :
|
||||
ocio::TRANSFORM_DIR_INVERSE);
|
||||
|
||||
processor = ColorProcessor::create(
|
||||
manager()->get_config()->getProcessor(transform));
|
||||
} catch (const std::exception &e) {
|
||||
qWarning() << "OCIO LUT processor error:" << e.what();
|
||||
processor = nullptr;
|
||||
}
|
||||
|
||||
if (!processor) {
|
||||
set_last_error(tr("OCIO LUT: failed to load LUT file: %1").arg(path));
|
||||
} else {
|
||||
set_last_error(QString());
|
||||
}
|
||||
|
||||
last_path_ = path;
|
||||
last_direction_ = direction;
|
||||
last_processor_ = processor;
|
||||
const_cast<OCIOLutNode *>(this)->set_processor(processor);
|
||||
processor_dirty_ = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
@@ -0,0 +1,85 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_OCIOLUTNODE_H
|
||||
#define OAK_OCIOLUTNODE_H
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
#include "node/color/ociobase/ociobase.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class OCIOLutNode : public OCIOBaseNode {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OCIOLutNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(OCIOLutNode)
|
||||
|
||||
virtual QString name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> category() const override;
|
||||
virtual QString description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
virtual void InputValueChangedEvent(const QString &input,
|
||||
int element) override;
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
static const QString k_file_input;
|
||||
static const QString k_direction_input;
|
||||
|
||||
/**
|
||||
* @brief Human-readable description of why no LUT processor is active
|
||||
*
|
||||
* Empty when a valid LUT processor is in use or no LUT file has been
|
||||
* selected yet. This allows the UI (and tests) to surface silent
|
||||
* passthrough states (missing file, unsupported extension, OCIO errors).
|
||||
*/
|
||||
const QString &last_error() const
|
||||
{
|
||||
return last_error_;
|
||||
}
|
||||
|
||||
protected slots:
|
||||
virtual void config_changed() override;
|
||||
|
||||
private:
|
||||
void generate_processor();
|
||||
void ensure_processor() const;
|
||||
bool create_processor_from_inputs() const;
|
||||
|
||||
void set_last_error(const QString &error) const;
|
||||
|
||||
mutable QMutex gen_mutex_;
|
||||
mutable bool processor_dirty_ = true;
|
||||
mutable QString last_path_;
|
||||
mutable int last_direction_ = -1;
|
||||
mutable ColorProcessorPtr last_processor_;
|
||||
mutable QString last_error_;
|
||||
};
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_OCIOLUTNODE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/threewaycolor/threewaycolor.h
|
||||
node/color/threewaycolor/threewaycolor.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,123 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2026 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "threewaycolor.h"
|
||||
|
||||
#include <QVector3D>
|
||||
|
||||
#include "node/project.h"
|
||||
#include "node/sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super Node
|
||||
|
||||
const QString ThreeWayColorNode::k_texture_input = QStringLiteral("tex_in");
|
||||
const QString ThreeWayColorNode::k_shadows_color_input =
|
||||
QStringLiteral("shadows_color_in");
|
||||
const QString ThreeWayColorNode::k_midtones_color_input =
|
||||
QStringLiteral("midtones_color_in");
|
||||
const QString ThreeWayColorNode::k_highlights_color_input =
|
||||
QStringLiteral("highlights_color_in");
|
||||
const QString ThreeWayColorNode::k_shadows_amount_input =
|
||||
QStringLiteral("shadows_amount_in");
|
||||
const QString ThreeWayColorNode::k_midtones_amount_input =
|
||||
QStringLiteral("midtones_amount_in");
|
||||
const QString ThreeWayColorNode::k_highlights_amount_input =
|
||||
QStringLiteral("highlights_amount_in");
|
||||
const QString ThreeWayColorNode::k_luma_coefficients_input =
|
||||
QStringLiteral("luma_coefficients_in");
|
||||
|
||||
ThreeWayColorNode::ThreeWayColorNode()
|
||||
{
|
||||
add_input(k_texture_input, NodeValue::k_texture,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
|
||||
const QVariant neutral = QVariant::fromValue(Color(0.5, 0.5, 0.5, 1.0));
|
||||
add_input(k_shadows_color_input, NodeValue::k_color, neutral);
|
||||
add_input(k_midtones_color_input, NodeValue::k_color, neutral);
|
||||
add_input(k_highlights_color_input, NodeValue::k_color, neutral);
|
||||
|
||||
add_input(k_shadows_amount_input, NodeValue::k_float, 1.0);
|
||||
add_input(k_midtones_amount_input, NodeValue::k_float, 1.0);
|
||||
add_input(k_highlights_amount_input, NodeValue::k_float, 1.0);
|
||||
|
||||
const QString min = QStringLiteral("min");
|
||||
const QString view = QStringLiteral("view");
|
||||
set_input_property(k_shadows_amount_input, min, 0.0);
|
||||
set_input_property(k_midtones_amount_input, min, 0.0);
|
||||
set_input_property(k_highlights_amount_input, min, 0.0);
|
||||
set_input_property(k_shadows_amount_input, view, slider::k_percentage);
|
||||
set_input_property(k_midtones_amount_input, view, slider::k_percentage);
|
||||
set_input_property(k_highlights_amount_input, view, slider::k_percentage);
|
||||
|
||||
set_effect_input(k_texture_input);
|
||||
set_flag(k_video_effect);
|
||||
}
|
||||
|
||||
void ThreeWayColorNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_texture_input, tr("Input"));
|
||||
set_input_name(k_shadows_color_input, tr("Shadows"));
|
||||
set_input_name(k_midtones_color_input, tr("Midtones"));
|
||||
set_input_name(k_highlights_color_input, tr("Highlights"));
|
||||
set_input_name(k_shadows_amount_input, tr("Shadows Amount"));
|
||||
set_input_name(k_midtones_amount_input, tr("Midtones Amount"));
|
||||
set_input_name(k_highlights_amount_input, tr("Highlights Amount"));
|
||||
}
|
||||
|
||||
ShaderCode ThreeWayColorNode::get_shader_code(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(request)
|
||||
return ShaderCode(
|
||||
FileFunctions::read_file_as_string(":/shaders/threewaycolor.frag"));
|
||||
}
|
||||
|
||||
void ThreeWayColorNode::value(const NodeValueRow &value,
|
||||
const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
Q_UNUSED(globals)
|
||||
|
||||
if (TexturePtr tex = value[k_texture_input].to_texture()) {
|
||||
ShaderJob job(value);
|
||||
|
||||
double luma_coeffs[3] = { 0.0, 0.0, 0.0 };
|
||||
if (project() && project()->color_manager()) {
|
||||
project()->color_manager()->get_default_luma_coefs(luma_coeffs);
|
||||
} else {
|
||||
luma_coeffs[0] = 0.2126;
|
||||
luma_coeffs[1] = 0.7152;
|
||||
luma_coeffs[2] = 0.0722;
|
||||
}
|
||||
job.insert(k_luma_coefficients_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
QVector3D(luma_coeffs[0], luma_coeffs[1],
|
||||
luma_coeffs[2])));
|
||||
|
||||
table->push(NodeValue::k_texture, tex->to_job(job), this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2026 mikesolar
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_THREEWAYCOLORNODE_H
|
||||
#define OAK_THREEWAYCOLORNODE_H
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ThreeWayColorNode : public Node {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ThreeWayColorNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(ThreeWayColorNode)
|
||||
|
||||
virtual QString name() const override
|
||||
{
|
||||
return tr("Three-Way Color");
|
||||
}
|
||||
|
||||
virtual QString id() const override
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.threewaycolor");
|
||||
}
|
||||
|
||||
virtual QVector<CategoryID> category() const override
|
||||
{
|
||||
return { k_category_color };
|
||||
}
|
||||
|
||||
virtual QString description() const override
|
||||
{
|
||||
return tr("Adjusts shadows, midtones, and highlights separately.");
|
||||
}
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
virtual ShaderCode
|
||||
get_shader_code(const ShaderRequest &request) const override;
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
static const QString k_texture_input;
|
||||
static const QString k_shadows_color_input;
|
||||
static const QString k_midtones_color_input;
|
||||
static const QString k_highlights_color_input;
|
||||
static const QString k_shadows_amount_input;
|
||||
static const QString k_midtones_amount_input;
|
||||
static const QString k_highlights_amount_input;
|
||||
static const QString k_luma_coefficients_input;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_THREEWAYCOLORNODE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Oak - Non-Linear Video Editor
|
||||
# Copyright (C) 2026 Oak 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/whitebalance/whitebalance.cpp
|
||||
node/color/whitebalance/whitebalance.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,156 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "whitebalance.h"
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "render/job/shaderjob.h"
|
||||
#include "render/texture.h"
|
||||
#include "node/sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super Node
|
||||
|
||||
const QString WhiteBalanceNode::k_texture_input = QStringLiteral("tex_in");
|
||||
const QString WhiteBalanceNode::k_temperature_input =
|
||||
QStringLiteral("temperature_in");
|
||||
const QString WhiteBalanceNode::k_tint_input = QStringLiteral("tint_in");
|
||||
const QString WhiteBalanceNode::k_gain_input = QStringLiteral("wb_gain_in");
|
||||
|
||||
WhiteBalanceNode::WhiteBalanceNode()
|
||||
{
|
||||
add_input(k_texture_input, NodeValue::k_texture,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_temperature_input, NodeValue::k_float, 6500.0);
|
||||
set_input_property(k_temperature_input, QStringLiteral("min"), 1000.0);
|
||||
set_input_property(k_temperature_input, QStringLiteral("max"), 40000.0);
|
||||
set_input_property(k_temperature_input, QStringLiteral("view"),
|
||||
slider::k_normal);
|
||||
|
||||
add_input(k_tint_input, NodeValue::k_float, 0.0);
|
||||
set_input_property(k_tint_input, QStringLiteral("min"), -1.0);
|
||||
set_input_property(k_tint_input, QStringLiteral("max"), 1.0);
|
||||
set_input_property(k_tint_input, QStringLiteral("base"), 0.01);
|
||||
|
||||
set_effect_input(k_texture_input);
|
||||
set_flag(k_video_effect);
|
||||
}
|
||||
|
||||
QString WhiteBalanceNode::name() const
|
||||
{
|
||||
return tr("White Balance");
|
||||
}
|
||||
|
||||
QString WhiteBalanceNode::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.whitebalance");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> WhiteBalanceNode::category() const
|
||||
{
|
||||
return { k_category_color };
|
||||
}
|
||||
|
||||
QString WhiteBalanceNode::description() const
|
||||
{
|
||||
return tr("Adjust white balance by color temperature and tint.");
|
||||
}
|
||||
|
||||
void WhiteBalanceNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_texture_input, tr("Input"));
|
||||
set_input_name(k_temperature_input, tr("Temperature (K)"));
|
||||
set_input_name(k_tint_input, tr("Tint"));
|
||||
}
|
||||
|
||||
ShaderCode WhiteBalanceNode::get_shader_code(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(request)
|
||||
return ShaderCode(
|
||||
FileFunctions::read_file_as_string(":/shaders/whitebalance.frag"));
|
||||
}
|
||||
|
||||
void WhiteBalanceNode::value(const NodeValueRow &value,
|
||||
const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
Q_UNUSED(globals)
|
||||
|
||||
if (TexturePtr tex = value[k_texture_input].to_texture()) {
|
||||
ShaderJob job(value);
|
||||
|
||||
const QVector3D gain = get_gain_for_temperature(
|
||||
value[k_temperature_input].to_double(),
|
||||
value[k_tint_input].to_double());
|
||||
job.insert(k_gain_input, NodeValue(NodeValue::k_vec3, gain));
|
||||
|
||||
table->push(NodeValue::k_texture, tex->to_job(job), this);
|
||||
}
|
||||
}
|
||||
|
||||
QVector3D WhiteBalanceNode::get_gain_for_temperature(double kelvin, double tint)
|
||||
{
|
||||
// Tanner Helland blackbody approximation (1000K-40000K), returning
|
||||
// 0-255 per channel
|
||||
kelvin = qBound(1000.0, kelvin, 40000.0);
|
||||
const double t = kelvin / 100.0;
|
||||
|
||||
double red;
|
||||
if (t <= 66.0) {
|
||||
red = 255.0;
|
||||
} else {
|
||||
red = 329.698727446 * qPow(t - 60.0, -0.1332047592);
|
||||
}
|
||||
|
||||
double green;
|
||||
if (t <= 66.0) {
|
||||
green = 99.4708025861 * qLn(t) - 161.1195681661;
|
||||
} else {
|
||||
green = 288.1221695283 * qPow(t - 60.0, -0.0755148492);
|
||||
}
|
||||
|
||||
double blue;
|
||||
if (t >= 66.0) {
|
||||
blue = 255.0;
|
||||
} else if (t <= 19.0) {
|
||||
blue = 0.0;
|
||||
} else {
|
||||
blue = 138.5177312231 * qLn(t - 10.0) - 305.0447927307;
|
||||
}
|
||||
|
||||
// Normalize to the green channel so temperature shifts do not change
|
||||
// exposure, then let tint move along the green-magenta axis
|
||||
red /= green;
|
||||
blue /= green;
|
||||
green = 1.0;
|
||||
|
||||
const double tint_gain = qBound(0.0, 1.0 + tint, 2.0);
|
||||
|
||||
return QVector3D(float(red), float(green * tint_gain), float(blue));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_WHITEBALANCENODE_H
|
||||
#define OAK_WHITEBALANCENODE_H
|
||||
|
||||
#include <QVector3D>
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief White balance correction by color temperature and tint
|
||||
*
|
||||
* Converts a scene illuminant temperature (in Kelvin) into per-channel RGB
|
||||
* gains using the Tanner Helland blackbody approximation, normalized so the
|
||||
* green channel is preserved (no exposure shift). Tint shifts the image
|
||||
* along the green-magenta axis.
|
||||
*/
|
||||
class WhiteBalanceNode : public Node {
|
||||
Q_OBJECT
|
||||
public:
|
||||
WhiteBalanceNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(WhiteBalanceNode)
|
||||
|
||||
virtual QString name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> category() const override;
|
||||
virtual QString description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
virtual ShaderCode get_shader_code(const ShaderRequest &request) const override;
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
/**
|
||||
* @brief RGB gains for a given illuminant temperature and tint
|
||||
*
|
||||
* Extracted for testability. Kelvin is clamped to [1000, 40000]; the
|
||||
* result is normalized so the green channel gain is 1.0 at tint 0.
|
||||
*/
|
||||
static QVector3D get_gain_for_temperature(double kelvin, double tint);
|
||||
|
||||
static const QString k_texture_input;
|
||||
static const QString k_temperature_input;
|
||||
static const QString k_tint_input;
|
||||
static const QString k_gain_input;
|
||||
};
|
||||
|
||||
} // olive
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user