diff --git a/app/node/plugins/Plugin.cpp b/app/node/plugins/Plugin.cpp index c089fffa9..83737c423 100644 --- a/app/node/plugins/Plugin.cpp +++ b/app/node/plugins/Plugin.cpp @@ -100,6 +100,99 @@ QVariant DefaultValueForParam(const OFX::Host::Param::Base *param) return QVariant(); } +/** + * @brief Deduce whether an RGB/RGBA parameter semantically represents a color + * pickers or per-channel scalar values (e.g. gamma, contrast). + * + * Uses heuristics based on label, hint, display range, default values, + * and parent group name. + */ +QString DeduceColorSemantic(const OFX::Host::Param::Base *param, + const QHash &group_labels) +{ + const std::string &ofxType = param->getType(); + if (ofxType != kOfxParamTypeRGB && ofxType != kOfxParamTypeRGBA) { + return QStringLiteral("color"); + } + + const QString label = QString::fromStdString(param->getLabel()).toLower(); + const QString hint = QString::fromStdString(param->getHint()).toLower(); + const QString name = QString::fromStdString(param->getName()).toLower(); + + // Rule 1: explicit color keywords → color + static const QStringList kColorKeywords = { + QStringLiteral("color"), QStringLiteral("colour"), + QStringLiteral("fill"), QStringLiteral("tint"), + QStringLiteral("key") + }; + for (const QString &kw : kColorKeywords) { + if (label.contains(kw) || hint.contains(kw) || name.contains(kw)) { + return QStringLiteral("color"); + } + } + + // Rule 2: explicit scalar/adjustment keywords → scalar + static const QStringList kScalarKeywords = { + QStringLiteral("gamma"), QStringLiteral("contrast"), + QStringLiteral("gain"), QStringLiteral("offset"), + QStringLiteral("saturation"), QStringLiteral("exposure"), + QStringLiteral("brightness"), QStringLiteral("lift"), + QStringLiteral("multiply"), QStringLiteral("scale"), + QStringLiteral("pivot") + }; + for (const QString &kw : kScalarKeywords) { + if (label.contains(kw) || hint.contains(kw) || name.contains(kw)) { + return QStringLiteral("scalar"); + } + } + + // Rule 3: display range significantly outside/asymmetric to [0,1] → scalar + const auto &props = param->getProperties(); + const int dim = (ofxType == kOfxParamTypeRGBA) ? 4 : 3; + double dmin[4] = {0, 0, 0, 0}; + double dmax[4] = {1, 1, 1, 1}; + props.getDoublePropertyN(kOfxParamPropDisplayMin, dmin, dim); + props.getDoublePropertyN(kOfxParamPropDisplayMax, dmax, dim); + bool range_looks_scalar = false; + for (int i = 0; i < dim; ++i) { + if (dmin[i] < -0.01 || dmax[i] > 1.01) { + range_looks_scalar = true; + break; + } + } + if (range_looks_scalar) { + return QStringLiteral("scalar"); + } + + // Rule 4: default values all equal → scalar (lean) + double defs[4] = {0, 0, 0, 1}; + props.getDoublePropertyN(kOfxParamPropDefault, defs, dim); + bool all_equal = true; + for (int i = 1; i < dim; ++i) { + if (defs[i] != defs[0]) { + all_equal = false; + break; + } + } + if (all_equal) { + return QStringLiteral("scalar"); + } + + // Rule 5: parent group contains scalar keywords → scalar + const QString parent = + QString::fromStdString(param->getParentName()).toLower(); + if (!parent.isEmpty()) { + for (const QString &kw : kScalarKeywords) { + if (parent.contains(kw)) { + return QStringLiteral("scalar"); + } + } + } + + // Fallback + return QStringLiteral("color"); +} + QHash BuildDefaultValues(const std::map ¶ms) { @@ -274,6 +367,36 @@ olive::plugin::PluginNode::PluginNode( SetInputProperty(input_id, QStringLiteral("ui_page"), page_for_param.value(input_id)); } + if (type == NodeValue::kColor) { + QString semantic = + DeduceColorSemantic(param.second, group_labels); + SetInputProperty(input_id, + QStringLiteral("color_semantic"), + semantic); + + const int dim = + (ofxType == kOfxParamTypeRGBA) ? 4 : 3; + double dmin[4] = {0, 0, 0, 0}; + double dmax[4] = {1, 1, 1, 1}; + props.getDoublePropertyN(kOfxParamPropDisplayMin, + dmin, dim); + props.getDoublePropertyN(kOfxParamPropDisplayMax, + dmax, dim); + SetInputProperty(input_id, + QStringLiteral("min"), + dmin[0]); + SetInputProperty(input_id, + QStringLiteral("max"), + dmax[0]); + + const QString hint = QString::fromStdString( + param.second->getHint()); + if (!hint.isEmpty()) { + SetInputProperty(input_id, + QStringLiteral("tooltip"), + hint); + } + } if (type == NodeValue::kCombo || type == NodeValue::kStrCombo) { QStringList option_labels; QStringList option_values; diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 7b6cbd185..5a384db55 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -146,11 +146,18 @@ void NodeParamViewWidgetBridge::CreateWidgets() break; } case NodeValue::kColor: { - ColorButton *color_button = new ColorButton( - GetInnerInput().node()->project()->color_manager(), parent); - widgets_.append(color_button); - connect(color_button, &ColorButton::ColorChanged, this, + if (GetInnerInput().GetProperty("color_semantic") + .toString() == QStringLiteral("scalar")) { + CreateSliders(4, parent); + } else { + ColorButton *color_button = new ColorButton( + GetInnerInput().node()->project()->color_manager(), + parent); + widgets_.append(color_button); + connect( + color_button, &ColorButton::ColorChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); + } break; } case NodeValue::kText: { @@ -322,30 +329,42 @@ void NodeParamViewWidgetBridge::WidgetCallback() break; } case NodeValue::kColor: { - // Sender is a ColorButton - ManagedColor c = static_cast(sender())->GetColor(); + if (GetInnerInput().GetProperty("color_semantic") + .toString() == QStringLiteral("scalar")) { + FloatSlider *slider = + static_cast(sender()); + ProcessSlider(slider, slider->GetValue()); + } else { + // Sender is a ColorButton + ManagedColor c = + static_cast(sender())->GetColor(); - MultiUndoCommand *command = new MultiUndoCommand(); + MultiUndoCommand *command = new MultiUndoCommand(); - SetInputValueInternal(c.red(), 0, command, false); - SetInputValueInternal(c.green(), 1, command, false); - SetInputValueInternal(c.blue(), 2, command, false); - SetInputValueInternal(c.alpha(), 3, command, false); + SetInputValueInternal(c.red(), 0, command, false); + SetInputValueInternal(c.green(), 1, command, false); + SetInputValueInternal(c.blue(), 2, command, false); + SetInputValueInternal(c.alpha(), 3, command, false); - Node *n = GetInnerInput().node(); - n->blockSignals(true); - n->SetInputProperty(GetInnerInput().input(), - QStringLiteral("col_input"), c.color_input()); - n->SetInputProperty(GetInnerInput().input(), - QStringLiteral("col_display"), - c.color_output().display()); - n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_view"), - c.color_output().view()); - n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_look"), - c.color_output().look()); - n->blockSignals(false); + Node *n = GetInnerInput().node(); + n->blockSignals(true); + n->SetInputProperty( + GetInnerInput().input(), QStringLiteral("col_input"), + c.color_input()); + n->SetInputProperty( + GetInnerInput().input(), QStringLiteral("col_display"), + c.color_output().display()); + n->SetInputProperty( + GetInnerInput().input(), QStringLiteral("col_view"), + c.color_output().view()); + n->SetInputProperty( + GetInnerInput().input(), QStringLiteral("col_look"), + c.color_output().look()); + n->blockSignals(false); - Core::instance()->undo_stack()->push(command, GetCommandName()); + Core::instance()->undo_stack()->push(command, + GetCommandName()); + } break; } case NodeValue::kText: { @@ -539,18 +558,42 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() break; } case NodeValue::kColor: { - ManagedColor mc = - GetInnerInput().GetValueAtTime(node_time).value(); + if (GetInnerInput().GetProperty("color_semantic") + .toString() == QStringLiteral("scalar")) { + Color c = GetInnerInput() + .GetValueAtTime(node_time) + .value(); + static_cast(widgets_.at(0)) + ->SetValue(static_cast(c.red())); + static_cast(widgets_.at(1)) + ->SetValue(static_cast(c.green())); + static_cast(widgets_.at(2)) + ->SetValue(static_cast(c.blue())); + static_cast(widgets_.at(3)) + ->SetValue(static_cast(c.alpha())); + } else { + ManagedColor mc = GetInnerInput() + .GetValueAtTime(node_time) + .value(); - mc.set_color_input(GetInnerInput().GetProperty("col_input").toString()); + mc.set_color_input( + GetInnerInput().GetProperty("col_input").toString()); - QString d = GetInnerInput().GetProperty("col_display").toString(); - QString v = GetInnerInput().GetProperty("col_view").toString(); - QString l = GetInnerInput().GetProperty("col_look").toString(); + QString d = GetInnerInput() + .GetProperty("col_display") + .toString(); + QString v = GetInnerInput() + .GetProperty("col_view") + .toString(); + QString l = GetInnerInput() + .GetProperty("col_look") + .toString(); - mc.set_color_output(ColorTransform(d, v, l)); + mc.set_color_output(ColorTransform(d, v, l)); - static_cast(widgets_.first())->SetColor(mc); + static_cast(widgets_.first()) + ->SetColor(mc); + } break; } case NodeValue::kText: {