Add per-channel parameters to the 'Levels' node and make its midtones a gamma value (#4535)

* Add per-channel records and a gamma midtones value to the 'Levels' node, with a channel selector in its Properties panel

* Migrate the old 'Levels' midtones through its output range and bound a lone midtone marker by the track edges
This commit is contained in:
Keavon Chambers
2026-09-14 22:51:43 -07:00
committed by GitHub
parent 0bede7969c
commit 68d2a06c80
5 changed files with 412 additions and 84 deletions
@@ -2196,6 +2196,31 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
inputs_count = 3;
}
// Levels' Midtones became the gamma value it encoded, and each channel gained its own record after the composite one
if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster::levels::IDENTIFIER) && inputs_count == 6 {
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
let output_level = |index: usize, default: f32| match old_inputs.get(index).and_then(|input| input.as_value()) {
Some(TaggedValue::F32(percent)) => percent / 100.,
_ => default,
};
let (output_minimums, output_maximums) = (output_level(4, 0.), output_level(5, 1.));
for (index, input) in old_inputs.iter().take(6).enumerate() {
let input = match (index, input.as_value()) {
(2, Some(TaggedValue::F32(percent))) => {
// The old node's midtones-to-gamma mapping, from https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels
let midtones = output_minimums + (output_maximums - output_minimums) * percent / 100.;
let gamma = if midtones < 0.5 { 1. + 9. * (1. - midtones * 2.) } else { ((1. - midtones) * 2.).max(0.01) };
NodeInput::value(TaggedValue::F32(gamma.clamp(0.01, 9.99)), input.is_exposed())
}
_ => input.clone(),
};
document.network_interface.set_input(&InputConnector::node_at_index(*node_id, index), input, network_path);
}
inputs_count = 27;
}
if reference == DefinitionIdentifier::ProtoNode(graphene_std::repeat::repeat_on_points::IDENTIFIER) && inputs_count == 2 {
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);