diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/document_node_types.rs index 2ef6c16d..1e8d222a 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_types.rs @@ -2282,7 +2282,7 @@ fn static_nodes() -> Vec { }, }, 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 { }, }, 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 { }, }, 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 { }, }, 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 { }, }, category: "Raster", - properties: node_properties::adjust_selective_color_properties, + properties: node_properties::selective_color_properties, }, DocumentNodeDefinition { identifier: "Opacity", diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 41504b89..3e4860aa 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -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 { +pub fn hue_saturation_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { 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 { +pub fn threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { 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 { +pub fn gradient_map_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + 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 { 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 { +pub fn channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { // 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 { +pub fn selective_color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { // Colors choice let colors_index = 38; let mut colors = vec![TextLabel::new("Colors").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()]; diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 968bccec..ff514348 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -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: 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, diff --git a/node-graph/gcore/src/vector/style.rs b/node-graph/gcore/src/vector/style.rs index f1212f6f..31ed960d 100644 --- a/node-graph/gcore/src/vector/style.rs +++ b/node-graph/gcore/src/vector/style.rs @@ -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. diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 9f0c9a99..125ac0a6 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -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, 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<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>,