diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 6930dfce..7cfa7c75 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -7,8 +7,9 @@ use graph_craft::document::value::*; use graph_craft::document::*; use graph_craft::imaginate_input::ImaginateSamplingMethod; +use graph_craft::concrete; use graph_craft::NodeIdentifier; -use graphene_core::raster::{Color, Image, ImageFrame, LuminanceCalculation}; +use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation}; use graphene_core::*; use std::collections::VecDeque; @@ -192,6 +193,19 @@ fn static_nodes() -> Vec { outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], properties: |_document_node, _node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform"), }, + DocumentNodeType { + name: "Blend Node", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::BlendNode<_, _, _, _>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Second", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("BlendMode", TaggedValue::BlendMode(BlendMode::Normal), false), + DocumentInputType::value("Opacity", TaggedValue::F64(100.), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::blend_properties, + }, DocumentNodeType { name: "Levels", category: "Image Adjustments", diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index ae6e1e81..f17a703b 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -8,7 +8,7 @@ use glam::DVec2; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; use graph_craft::imaginate_input::*; -use graphene_core::raster::{Color, LuminanceCalculation}; +use graphene_core::raster::{BlendMode, Color, LuminanceCalculation}; use super::document_node_types::NodePropertiesContext; use super::{FrontendGraphDataType, IMAGINATE_NODE}; @@ -148,7 +148,27 @@ fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na widgets } -// TODO: Generalize this for all dropdowns +//TODO Use generalized Version of this as soon as it's available +fn blend_mode(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { + let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); + if let &NodeInput::Value { + tagged_value: TaggedValue::BlendMode(mode), + exposed: false, + } = &document_node.inputs[index] + { + let calculation_modes = BlendMode::list(); + let mut entries = Vec::with_capacity(calculation_modes.len()); + for method in calculation_modes { + entries.push(DropdownEntryData::new(method.to_string()).on_update(update_value(move |_| TaggedValue::BlendMode(method), node_id, index))); + } + let entries = vec![entries]; + + widgets.extend_from_slice(&[WidgetHolder::unrelated_separator(), DropdownInput::new(entries).selected_index(Some(mode as u32)).widget_holder()]); + } + LayoutGroup::Row { widgets }.with_tooltip("Formula used for blending") +} + +// TODO: Generalize this for all dropdowns ( also see blend_mode ) fn luminance_calculation(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); if let &NodeInput::Value { @@ -238,6 +258,14 @@ pub fn grayscale_properties(document_node: &DocumentNode, node_id: NodeId, _cont ] } +pub fn blend_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + let backdrop = color_widget(document_node, node_id, 1, "Backdrop", ColorInput::default(), true); + let blend_mode = blend_mode(document_node, node_id, 2, "Blend Mode", true); + let opacity = number_widget(document_node, node_id, 3, "Opacity", NumberInput::default().min(0.).max(100.).unit("%"), true); + + vec![backdrop, blend_mode, LayoutGroup::Row { widgets: opacity }] +} + pub fn luminance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { let luma_calculation = luminance_calculation(document_node, node_id, 1, "Luma Calculation", true); diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index dbb7c47c..2d6de6ff 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -33,11 +33,13 @@ impl NodeGraphExecutor { network.duplicate_outputs(&mut generate_uuid); network.remove_dead_nodes(); + debug!("Execute document network:\n{network:#?}"); + // We assume only one output assert_eq!(network.outputs.len(), 1, "Graph with multiple outputs not yet handled"); let c = Compiler {}; let proto_network = c.compile_single(network, true)?; - + debug!("Execute proto network:\n{proto_network}"); assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?"); if let Err(e) = self.executor.update(proto_network) { error!("Failed to update executor:\n{}", e); diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 4450a0cf..5a89cbbb 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -39,6 +39,121 @@ impl std::fmt::Display for LuminanceCalculation { } } +impl BlendMode { + pub fn list() -> [BlendMode; 26] { + [ + BlendMode::Normal, + BlendMode::Multiply, + BlendMode::Darken, + BlendMode::ColorBurn, + BlendMode::LinearBurn, + BlendMode::DarkerColor, + BlendMode::Screen, + BlendMode::Lighten, + BlendMode::ColorDodge, + BlendMode::LinearDodge, + BlendMode::LighterColor, + BlendMode::Overlay, + BlendMode::SoftLight, + BlendMode::HardLight, + BlendMode::VividLight, + BlendMode::LinearLight, + BlendMode::PinLight, + BlendMode::HardMix, + BlendMode::Difference, + BlendMode::Exclusion, + BlendMode::Subtract, + BlendMode::Divide, + BlendMode::Hue, + BlendMode::Saturation, + BlendMode::Color, + BlendMode::Luminosity, + ] + } +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, specta::Type, Hash)] +pub enum BlendMode { + #[default] + // Basic group + Normal, + // Not supported by SVG, but we should someday support: Dissolve + + // Darken group + Multiply, + Darken, + ColorBurn, + LinearBurn, + DarkerColor, + + // Lighten group + Screen, + Lighten, + ColorDodge, + LinearDodge, + LighterColor, + + // Contrast group + Overlay, + SoftLight, + HardLight, + VividLight, + LinearLight, + PinLight, + HardMix, + + // Inversion group + Difference, + Exclusion, + Subtract, + Divide, + + // Component group + Hue, + Saturation, + Color, + Luminosity, +} + +impl std::fmt::Display for BlendMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BlendMode::Normal => write!(f, "Normal"), + + BlendMode::Multiply => write!(f, "Multiply"), + BlendMode::Darken => write!(f, "Darken"), + BlendMode::ColorBurn => write!(f, "Color Burn"), + BlendMode::LinearBurn => write!(f, "Linear Burn"), + BlendMode::DarkerColor => write!(f, "Darker Color"), + + BlendMode::Screen => write!(f, "Screen"), + BlendMode::Lighten => write!(f, "Lighten"), + BlendMode::ColorDodge => write!(f, "Color Dodge"), + BlendMode::LinearDodge => write!(f, "Linear Dodge"), + BlendMode::LighterColor => write!(f, "Lighter Color"), + + BlendMode::Overlay => write!(f, "Overlay"), + BlendMode::SoftLight => write!(f, "Soft Light"), + BlendMode::HardLight => write!(f, "Hard Light"), + BlendMode::VividLight => write!(f, "Vivid Light"), + BlendMode::LinearLight => write!(f, "Linear Light"), + BlendMode::PinLight => write!(f, "Pin Light"), + BlendMode::HardMix => write!(f, "Hard Mix"), + + BlendMode::Difference => write!(f, "Difference"), + BlendMode::Exclusion => write!(f, "Exclusion"), + BlendMode::Subtract => write!(f, "Subtract"), + BlendMode::Divide => write!(f, "Divide"), + + BlendMode::Hue => write!(f, "Hue"), + BlendMode::Saturation => write!(f, "Saturation"), + BlendMode::Color => write!(f, "Color"), + BlendMode::Luminosity => write!(f, "Luminosity"), + } + } +} + #[derive(Debug, Clone, Copy, Default)] pub struct LuminanceNode { luma_calculation: LuminanceCalculation, @@ -211,6 +326,52 @@ fn threshold_node(color: Color, luma_calculation: LuminanceCalculation, threshol } } +#[derive(Debug, Clone, Copy)] +pub struct BlendNode { + blend_mode: BlendMode, + opacity: Opacity, +} + +#[node_macro::node_fn(BlendNode)] +fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f64) -> Color { + let (source_color, backdrop) = input; + let actual_opacity = 1. - (opacity / 100.) as f32; + return match blend_mode { + BlendMode::Normal => backdrop.blend_rgb(source_color, Color::blend_normal), + BlendMode::Multiply => backdrop.blend_rgb(source_color, Color::blend_multiply), + BlendMode::Darken => backdrop.blend_rgb(source_color, Color::blend_darken), + BlendMode::ColorBurn => backdrop.blend_rgb(source_color, Color::blend_color_burn), + BlendMode::LinearBurn => backdrop.blend_rgb(source_color, Color::blend_linear_burn), + BlendMode::DarkerColor => backdrop.blend_darker_color(source_color), + + BlendMode::Screen => backdrop.blend_rgb(source_color, Color::blend_screen), + BlendMode::Lighten => backdrop.blend_rgb(source_color, Color::blend_lighten), + BlendMode::ColorDodge => backdrop.blend_rgb(source_color, Color::blend_color_dodge), + BlendMode::LinearDodge => backdrop.blend_rgb(source_color, Color::blend_linear_dodge), + BlendMode::LighterColor => backdrop.blend_lighter_color(source_color), + + BlendMode::Overlay => source_color.blend_rgb(backdrop, Color::blend_hardlight), + BlendMode::SoftLight => backdrop.blend_rgb(source_color, Color::blend_softlight), + BlendMode::HardLight => backdrop.blend_rgb(source_color, Color::blend_hardlight), + BlendMode::VividLight => backdrop.blend_rgb(source_color, Color::blend_vivid_light), + BlendMode::LinearLight => backdrop.blend_rgb(source_color, Color::blend_linear_light), + BlendMode::PinLight => backdrop.blend_rgb(source_color, Color::blend_pin_light), + BlendMode::HardMix => backdrop.blend_rgb(source_color, Color::blend_hard_mix), + + BlendMode::Difference => backdrop.blend_rgb(source_color, Color::blend_exclusion), + BlendMode::Exclusion => backdrop.blend_rgb(source_color, Color::blend_exclusion), + BlendMode::Subtract => backdrop.blend_rgb(source_color, Color::blend_subtract), + BlendMode::Divide => backdrop.blend_rgb(source_color, Color::blend_divide), + + BlendMode::Hue => backdrop.blend_hue(source_color), + BlendMode::Saturation => backdrop.blend_saturation(source_color), + BlendMode::Color => backdrop.blend_color(source_color), + BlendMode::Luminosity => backdrop.blend_luminosity(source_color), + } + .lerp(backdrop, actual_opacity) + .unwrap(); +} + #[derive(Debug, Clone, Copy)] pub struct VibranceNode { vibrance: Vibrance, diff --git a/node-graph/gcore/src/raster/color.rs b/node-graph/gcore/src/raster/color.rs index 210e8ab4..14c8111a 100644 --- a/node-graph/gcore/src/raster/color.rs +++ b/node-graph/gcore/src/raster/color.rs @@ -239,6 +239,174 @@ impl Color { self.map_rgb(|c| (c + d).clamp(0., 1.)) } + pub fn saturation(&self) -> f32 { + let max = (self.red).max(self.green).max(self.blue); + let min = (self.red).min(self.green).min(self.blue); + + max - min + } + + pub fn with_saturation(&self, saturation: f32) -> Color { + let [hue, _, lightness, alpha] = self.to_hsla(); + Color::from_hsla(hue, saturation, lightness, alpha) + } + + pub fn blend_normal(_c_b: f32, c_s: f32) -> f32 { + c_s + } + + pub fn blend_multiply(c_b: f32, c_s: f32) -> f32 { + c_s * c_b + } + + pub fn blend_darken(c_b: f32, c_s: f32) -> f32 { + c_s.min(c_b) + } + + pub fn blend_color_burn(c_b: f32, c_s: f32) -> f32 { + if c_b == 1. { + 1. + } else if c_s == 0. { + 0. + } else { + 1. - ((1. - c_b) / c_s).min(1.) + } + } + + pub fn blend_linear_burn(c_b: f32, c_s: f32) -> f32 { + c_b + c_s - 1. + } + + pub fn blend_darker_color(&self, other: Color) -> Color { + if self.average_rgb_channels() <= other.average_rgb_channels() { + *self + } else { + other + } + } + + pub fn blend_screen(c_b: f32, c_s: f32) -> f32 { + 1. - (1. - c_s) * (1. - c_b) + } + + pub fn blend_lighten(c_b: f32, c_s: f32) -> f32 { + c_s.max(c_b) + } + + pub fn blend_color_dodge(c_b: f32, c_s: f32) -> f32 { + if c_s == 1. { + 1. + } else { + (c_b / (1. - c_s)).min(1.) + } + } + + pub fn blend_linear_dodge(c_b: f32, c_s: f32) -> f32 { + c_b + c_s + } + + pub fn blend_lighter_color(&self, other: Color) -> Color { + if self.average_rgb_channels() >= other.average_rgb_channels() { + *self + } else { + other + } + } + + pub fn blend_softlight(c_b: f32, c_s: f32) -> f32 { + if c_s <= 0.5 { + c_b - (1. - 2. * c_s) * c_b * (1. - c_b) + } else { + let d: fn(f32) -> f32 = |x| if x <= 0.25 { ((16. * x - 12.) * x + 4.) * x } else { x.sqrt() }; + c_b + (2. * c_s - 1.) * (d(c_b) - c_b) + } + } + + pub fn blend_hardlight(c_b: f32, c_s: f32) -> f32 { + if c_s <= 0.5 { + Color::blend_multiply(2. * c_s, c_b) + } else { + Color::blend_screen(2. * c_s - 1., c_b) + } + } + + pub fn blend_vivid_light(c_b: f32, c_s: f32) -> f32 { + if c_s <= 0.5 { + Color::blend_color_burn(2. * c_s, c_b) + } else { + Color::blend_color_dodge(2. * c_s - 1., c_b) + } + } + + pub fn blend_linear_light(c_b: f32, c_s: f32) -> f32 { + if c_s <= 0.5 { + Color::blend_linear_burn(2. * c_s, c_b) + } else { + Color::blend_linear_dodge(2. * c_s - 1., c_b) + } + } + + pub fn blend_pin_light(c_b: f32, c_s: f32) -> f32 { + if c_s <= 0.5 { + Color::blend_darken(2. * c_s, c_b) + } else { + Color::blend_lighten(2. * c_s - 1., c_b) + } + } + + pub fn blend_hard_mix(c_b: f32, c_s: f32) -> f32 { + if Color::blend_linear_light(c_b, c_s) < 0.5 { + 0. + } else { + 1. + } + } + + pub fn blend_difference(c_b: f32, c_s: f32) -> f32 { + (c_b - c_s).abs() + } + + pub fn blend_exclusion(c_b: f32, c_s: f32) -> f32 { + c_b + c_s - 2. * c_b * c_s + } + + pub fn blend_subtract(c_b: f32, c_s: f32) -> f32 { + c_b - c_s + } + + pub fn blend_divide(c_b: f32, c_s: f32) -> f32 { + if c_b == 0. { + 1. + } else { + c_b / c_s + } + } + + pub fn blend_hue(&self, c_s: Color) -> Color { + let sat_b = self.saturation(); + let lum_b = self.luminance_rec_601(); + c_s.with_saturation(sat_b).with_luminance(lum_b) + } + + pub fn blend_saturation(&self, c_s: Color) -> Color { + let sat_s = c_s.saturation(); + let lum_b = self.luminance_rec_601(); + + self.with_saturation(sat_s).with_luminance(lum_b) + } + + pub fn blend_color(&self, c_s: Color) -> Color { + let lum_b = self.luminance_rec_601(); + + c_s.with_luminance(lum_b) + } + + pub fn blend_luminosity(&self, c_s: Color) -> Color { + let lum_s = c_s.luminance_rec_601(); + + self.with_luminance(lum_s) + } + /// Return the all components as a tuple, first component is red, followed by green, followed by blue, followed by alpha. /// /// # Examples @@ -425,6 +593,23 @@ impl Color { pub fn map_rgb f32>(&self, f: F) -> Self { Self::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), self.a()) } + pub fn blend_rgb f32>(&self, other: Color, f: F) -> Self { + let color = Color { + red: f(self.red, other.red), + green: f(self.green, other.green), + blue: f(self.blue, other.blue), + alpha: self.alpha, + }; + if *self == Color::RED { + debug!("{} {} {} {}", color.red, color.green, color.blue, color.alpha); + } + Color { + red: f(self.red, other.red).clamp(0., 1.), + green: f(self.green, other.green).clamp(0., 1.), + blue: f(self.blue, other.blue).clamp(0., 1.), + alpha: self.alpha, + } + } } #[test] diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 4244a77d..39e500c6 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -2,7 +2,7 @@ pub use dyn_any::StaticType; use dyn_any::{DynAny, Upcast}; use dyn_clone::DynClone; pub use glam::{DAffine2, DVec2}; -use graphene_core::raster::LuminanceCalculation; +use graphene_core::raster::{BlendMode, LuminanceCalculation}; use graphene_core::{Node, Type}; use std::hash::Hash; pub use std::sync::Arc; @@ -29,6 +29,7 @@ pub enum TaggedValue { Color(graphene_core::raster::color::Color), Subpath(graphene_core::vector::subpath::Subpath), RcSubpath(Arc), + BlendMode(BlendMode), LuminanceCalculation(LuminanceCalculation), ImaginateSamplingMethod(ImaginateSamplingMethod), ImaginateMaskStartingFill(ImaginateMaskStartingFill), @@ -94,24 +95,28 @@ impl Hash for TaggedValue { 14.hash(state); s.hash(state) } - Self::LuminanceCalculation(l) => { + Self::BlendMode(b) => { 15.hash(state); + b.hash(state) + } + Self::LuminanceCalculation(l) => { + 16.hash(state); l.hash(state) } Self::ImaginateSamplingMethod(m) => { - 16.hash(state); + 17.hash(state); m.hash(state) } Self::ImaginateMaskStartingFill(f) => { - 17.hash(state); + 18.hash(state); f.hash(state) } Self::ImaginateStatus(s) => { - 18.hash(state); + 19.hash(state); s.hash(state) } Self::LayerPath(p) => { - 19.hash(state); + 20.hash(state); p.hash(state) } Self::ImageFrame(i) => { @@ -142,6 +147,7 @@ impl<'a> TaggedValue { TaggedValue::Color(x) => Box::new(x), TaggedValue::Subpath(x) => Box::new(x), TaggedValue::RcSubpath(x) => Box::new(x), + TaggedValue::BlendMode(x) => Box::new(x), TaggedValue::LuminanceCalculation(x) => Box::new(x), TaggedValue::ImaginateSamplingMethod(x) => Box::new(x), TaggedValue::ImaginateMaskStartingFill(x) => Box::new(x), @@ -168,6 +174,7 @@ impl<'a> TaggedValue { TaggedValue::Color(_) => concrete!(graphene_core::raster::Color), TaggedValue::Subpath(_) => concrete!(graphene_core::vector::subpath::Subpath), TaggedValue::RcSubpath(_) => concrete!(Arc), + TaggedValue::BlendMode(_) => concrete!(BlendMode), TaggedValue::ImaginateSamplingMethod(_) => concrete!(ImaginateSamplingMethod), TaggedValue::ImaginateMaskStartingFill(_) => concrete!(ImaginateMaskStartingFill), TaggedValue::ImaginateStatus(_) => concrete!(ImaginateStatus), diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index c4c254a4..3fcf3a4d 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -23,6 +23,51 @@ pub struct ProtoNetwork { pub nodes: Vec<(NodeId, ProtoNode)>, } +impl core::fmt::Display for ProtoNetwork { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("Proto Network with nodes: ")?; + fn write_node(f: &mut core::fmt::Formatter<'_>, network: &ProtoNetwork, id: NodeId, indent: usize) -> core::fmt::Result { + f.write_str(&"\t".repeat(indent))?; + let Some((_, node)) = network.nodes.iter().find(|(node_id, _)|*node_id == id) else{ + return f.write_str("{{Unknown Node}}"); + }; + f.write_str("Node: ")?; + f.write_str(&node.identifier.name)?; + + f.write_str("\n")?; + f.write_str(&"\t".repeat(indent))?; + f.write_str("{\n")?; + + f.write_str(&"\t".repeat(indent + 1))?; + f.write_str("Primary input: ")?; + match &node.input { + ProtoNodeInput::None => f.write_str("None")?, + ProtoNodeInput::Network(ty) => f.write_fmt(format_args!("Network (type = {:?})", ty))?, + ProtoNodeInput::Node(_) => f.write_str("Node")?, + } + f.write_str("\n")?; + + match &node.construction_args { + ConstructionArgs::Value(value) => { + f.write_str(&"\t".repeat(indent + 1))?; + f.write_fmt(format_args!("Value construction argument: {value:?}"))? + } + ConstructionArgs::Nodes(nodes) => { + for id in nodes { + write_node(f, network, *id, indent + 1)?; + } + } + } + f.write_str(&"\t".repeat(indent))?; + f.write_str("}\n")?; + Ok(()) + } + + let id = self.output; + write_node(f, self, id, 0) + } +} + #[derive(Debug, Clone)] pub enum ConstructionArgs { Value(value::TaggedValue), diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 56d3c89c..60fb3d6a 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -107,6 +107,24 @@ where image } +#[derive(Debug, Clone, Copy)] +pub struct BlendImageNode { + second: Second, + map_fn: MapFn, +} + +#[node_macro::node_fn(BlendImageNode)] +fn blend_image(image: Image, second: Image, map_fn: &'any_input MapFn) -> Image +where + MapFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color> + 'input, +{ + let mut image = image; + for (pixel, sec_pixel) in &mut image.data.iter_mut().zip(second.data.iter()) { + *pixel = map_fn.eval((*pixel, *sec_pixel)); + } + image +} + #[derive(Debug, Clone, Copy)] pub struct ImaginateNode { cached: E, diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 7c817daf..2aca8d6d 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -115,4 +115,49 @@ mod tests { let val = *dyn_any::downcast::(result).unwrap(); assert_eq!(val, 33_u32); } + + #[test] + fn double_number() { + use graph_craft::document::*; + + use graph_craft::*; + + let network = NodeNetwork { + inputs: vec![0], + outputs: vec![NodeOutput::new(1, 0)], + nodes: [ + // Simple identity node taking a number as input from ouside the graph + ( + 0, + DocumentNode { + name: "id".into(), + inputs: vec![NodeInput::Network(concrete!(u32))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), + metadata: DocumentNodeMetadata::default(), + }, + ), + // An add node adding the result of the id node to its self + ( + 1, + DocumentNode { + name: "Add".into(), + inputs: vec![NodeInput::node(0, 0), NodeInput::node(0, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddParameterNode<_>")), + metadata: DocumentNodeMetadata::default(), + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() + }; + + use crate::executor::DynamicExecutor; + use graph_craft::executor::{Compiler, Executor}; + + let compiler = Compiler {}; + let protograph = compiler.compile_single(network, true).expect("Graph should be generated"); + + let exec = DynamicExecutor::new(protograph).map(|e| panic!("The network should not type check: {:#?}", e)).unwrap_err(); + } } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 7d57581c..b8c03c90 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -115,6 +115,25 @@ fn node_registry() -> HashMap, params: [LuminanceCalculation]), raster_node!(graphene_core::raster::LevelsNode<_, _, _, _, _>, params: [f64, f64, f64, f64, f64]), + ( + NodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"), + |args| { + use graphene_core::Node; + let image: DowncastBothNode<(), Image> = DowncastBothNode::new(args[0]); + let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]); + let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]); + let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(blend_mode.eval(())), ClonedNode::new(opacity.eval(()))); + let node = graphene_std::raster::BlendImageNode::new(image, ValueNode::new(blend_node)); + let _ = &node as &dyn for<'i> Node<'i, Image, Output = Image>; + let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); + any.into_type_erased() + }, + NodeIOTypes::new( + concrete!(Image), + concrete!(Image), + vec![(concrete!(()), concrete!(Image)), (concrete!(()), concrete!(BlendMode)), (concrete!(()), concrete!(f64))], + ), + ), raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]), raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]), raster_node!(graphene_core::raster::InvertRGBNode, params: []),