Add 'Gradient Map' adjustment node

This commit is contained in:
Keavon Chambers 2024-08-09 23:19:22 -07:00
parent f6ffa45a81
commit 501b562d0f
5 changed files with 108 additions and 10 deletions

View File

@ -2282,7 +2282,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
},
category: "Raster",
properties: node_properties::adjust_hsl_properties,
properties: node_properties::hue_saturation_properties,
},
DocumentNodeDefinition {
identifier: "Brightness/Contrast",
@ -2346,7 +2346,28 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
},
category: "Raster",
properties: node_properties::adjust_threshold_properties,
properties: node_properties::threshold_properties,
},
DocumentNodeDefinition {
identifier: "Gradient Map",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::raster::GradientMapNode<_, _>"),
inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
NodeInput::value(TaggedValue::GradientStops(vector::style::GradientStops::default()), false),
NodeInput::value(TaggedValue::Bool(false), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_names: vec!["Image".to_string(), "Gradient".to_string()],
output_names: vec!["Image".to_string()],
..Default::default()
},
},
category: "Raster",
properties: node_properties::gradient_map_properties,
},
DocumentNodeDefinition {
identifier: "Vibrance",
@ -2363,7 +2384,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
},
category: "Raster",
properties: node_properties::adjust_vibrance_properties,
properties: node_properties::vibrance_properties,
},
DocumentNodeDefinition {
identifier: "Channel Mixer",
@ -2426,7 +2447,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
},
category: "Raster",
properties: node_properties::adjust_channel_mixer_properties,
properties: node_properties::channel_mixer_properties,
},
DocumentNodeDefinition {
identifier: "Selective Color",
@ -2536,7 +2557,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
},
category: "Raster",
properties: node_properties::adjust_selective_color_properties,
properties: node_properties::selective_color_properties,
},
DocumentNodeDefinition {
identifier: "Opacity",

View File

@ -1091,7 +1091,7 @@ pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _
]
}
pub fn adjust_hsl_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub fn hue_saturation_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let hue_shift = number_widget(document_node, node_id, 1, "Hue Shift", NumberInput::default().min(-180.).max(180.).unit("°"), true);
let saturation_shift = number_widget(document_node, node_id, 2, "Saturation Shift", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
let lightness_shift = number_widget(document_node, node_id, 3, "Lightness Shift", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
@ -1128,7 +1128,7 @@ pub fn _blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _co
vec![LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: sigma }]
}
pub fn adjust_threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub fn threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let thereshold_min = number_widget(document_node, node_id, 1, "Min Luminance", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
let thereshold_max = number_widget(document_node, node_id, 2, "Max Luminance", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
let luminance_calc = luminance_calculation(document_node, node_id, 3, "Luminance Calc", true);
@ -1136,13 +1136,46 @@ pub fn adjust_threshold_properties(document_node: &DocumentNode, node_id: NodeId
vec![LayoutGroup::Row { widgets: thereshold_min }, LayoutGroup::Row { widgets: thereshold_max }, luminance_calc]
}
pub fn adjust_vibrance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub fn gradient_map_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let gradient_input = 1;
let reverse_input = 2;
let gradient = if let Some(TaggedValue::GradientStops(gradient)) = &document_node.inputs[gradient_input].as_value() {
gradient.clone()
} else {
return vec![LayoutGroup::Row { widgets: vec![] }];
};
let mut gradient_row = vec![TextLabel::new("Gradient").widget_holder()];
add_blank_assist(&mut gradient_row);
gradient_row.extend([
Separator::new(SeparatorType::Unrelated).widget_holder(),
ColorButton::default()
.allow_none(false)
.value(FillChoice::Gradient(gradient))
.on_update(move |x: &ColorButton| {
NodeGraphMessage::SetInputValue {
node_id,
input_index: gradient_input,
value: TaggedValue::GradientStops(x.value.as_gradient().unwrap().clone()),
}
.into()
})
.on_commit(commit_value)
.widget_holder(),
]);
let reverse_row = bool_widget(document_node, node_id, reverse_input, "Reverse", true);
vec![LayoutGroup::Row { widgets: gradient_row }, LayoutGroup::Row { widgets: reverse_row }]
}
pub fn vibrance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let vibrance = number_widget(document_node, node_id, 1, "Vibrance", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
vec![LayoutGroup::Row { widgets: vibrance }]
}
pub fn adjust_channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub fn channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
// Monochrome
let monochrome_index = 1;
let monochrome = bool_widget(document_node, node_id, monochrome_index, "Monochrome", true);
@ -1236,7 +1269,7 @@ pub fn adjust_channel_mixer_properties(document_node: &DocumentNode, node_id: No
layout
}
pub fn adjust_selective_color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub fn selective_color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
// Colors choice
let colors_index = 38;
let mut colors = vec![TextLabel::new("Colors").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];

View File

@ -5,6 +5,7 @@ use super::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
#[cfg(feature = "alloc")]
use super::ImageFrame;
use super::{Channel, Color, Node, RGBMut};
use crate::vector::style::GradientStops;
use crate::vector::VectorData;
use crate::GraphicGroup;
@ -554,6 +555,21 @@ pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode,
background.alpha_blend(target_color.to_associated_alpha(opacity))
}
#[derive(Debug, Clone, Copy)]
pub struct GradientMapNode<Gradient, Reverse> {
gradient: Gradient,
reverse: Reverse,
// TODO: Add support for dithering to break up gradient color banding
// TODO: Add support for controlling the gradient interpolation method (instead of always `luminance_srgb()`)
}
#[node_macro::node_fn(GradientMapNode)]
fn gradient_map_node(color: Color, gradient: GradientStops, reverse: bool) -> Color {
let intensity = color.luminance_srgb();
let intensity = if reverse { 1. - intensity } else { intensity };
gradient.evalute(intensity as f64)
}
#[derive(Debug, Clone, Copy)]
pub struct VibranceNode<Vibrance> {
vibrance: Vibrance,

View File

@ -36,6 +36,32 @@ impl Default for GradientStops {
}
}
impl GradientStops {
pub fn evalute(&self, t: f64) -> Color {
if self.0.is_empty() {
return Color::BLACK;
}
if t <= self.0[0].0 {
return self.0[0].1;
}
if t >= self.0[self.0.len() - 1].0 {
return self.0[self.0.len() - 1].1;
}
for i in 0..self.0.len() - 1 {
let (t1, c1) = self.0[i];
let (t2, c2) = self.0[i + 1];
if t >= t1 && t <= t2 {
let normalized_t = (t - t1) / (t2 - t1);
return c1.lerp(&c2, normalized_t as f32);
}
}
Color::BLACK
}
}
/// A gradient fill.
///
/// Contains the start and end points, along with the colors at varying points along the length.

View File

@ -19,6 +19,7 @@ use graphene_core::{Node, NodeIO, NodeIOTypes};
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
use graphene_std::application_io::{RenderConfig, TextureFrame};
use graphene_std::raster::*;
use graphene_std::vector::style::GradientStops;
use graphene_std::wasm_application_io::*;
use graphene_std::GraphicElement;
#[cfg(feature = "gpu")]
@ -484,6 +485,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]),
raster_node!(graphene_core::raster::InvertRGBNode, params: []),
raster_node!(graphene_core::raster::ThresholdNode<_, _, _>, params: [f64, f64, LuminanceCalculation]),
raster_node!(graphene_core::raster::GradientMapNode<_, _>, params: [GradientStops, bool]),
raster_node!(graphene_core::raster::VibranceNode<_>, params: [f64]),
raster_node!(
graphene_core::raster::ChannelMixerNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>,