diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 503f69a63..4c7d1acaf 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -32,7 +32,7 @@ use document_legacy::layers::style::{Fill, RenderData, ViewMode}; use document_legacy::layers::text_layer::Font; use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation}; use graph_craft::document::NodeId; -use graphene_core::raster::color::Color; +use graphene_core::raster::{Color, ImageFrame}; use graphene_std::vector::subpath::Subpath; use glam::{DAffine2, DVec2}; @@ -603,21 +603,37 @@ impl MessageHandler { let image_size = DVec2::new(image.width as f64, image.height as f64); - responses.push_back(DocumentMessage::StartTransaction.into()); - - let path = vec![generate_uuid()]; - let image_node_id = 100; - let mut network = crate::messages::portfolio::document::node_graph::new_image_network(32, image_node_id); - let Some(image_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Image") else { warn!("Image node should be in registry"); return; }; + let path = vec![generate_uuid()]; + let image_node_id = 100; + let mut network = crate::messages::portfolio::document::node_graph::new_image_network(32, image_node_id); + + // Transform of parent folder + let to_parent_folder = self.document_legacy.generate_transform_across_scope(&path[..path.len() - 1], None).unwrap_or_default(); + + // Align the layer with the mouse or center of viewport + let viewport_location = mouse.map_or(ipp.viewport_bounds.center(), |pos| pos.into()); + let center_in_viewport = DAffine2::from_translation(viewport_location - ipp.viewport_bounds.top_left); + let center_in_viewport_layerspace = to_parent_folder.inverse() * center_in_viewport; + + // Make layer the size of the image + let fit_image_size = DAffine2::from_scale_angle_translation(image_size, 0., image_size / -2.); + + let transform = (center_in_viewport_layerspace * fit_image_size); + + responses.push_back(DocumentMessage::StartTransaction.into()); + network.nodes.insert( image_node_id, image_node_type.to_document_node( - [graph_craft::document::NodeInput::value(graph_craft::document::value::TaggedValue::Image(image), false)], + [graph_craft::document::NodeInput::value( + graph_craft::document::value::TaggedValue::ImageFrame(ImageFrame { image, transform }), + false, + )], graph_craft::document::DocumentNodeMetadata::position((20, 4)), ), ); @@ -638,19 +654,13 @@ impl MessageHandler Vec { nodes: [ DocumentNode { name: "Identity".to_string(), - inputs: vec![NodeInput::Network(concrete!(Image))], + inputs: vec![NodeInput::Network(concrete!(ImageFrame))], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), metadata: Default::default(), }, @@ -154,13 +154,13 @@ fn static_nodes() -> Vec { DocumentInputType { name: "In", data_type: FrontendGraphDataType::General, - default: NodeInput::Network(concrete!(Image)), + default: NodeInput::Network(concrete!(ImageFrame)), }, DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false), ], outputs: vec![ DocumentOutputType { - name: "Image", + name: "Image Frame", data_type: FrontendGraphDataType::Raster, }, DocumentOutputType { @@ -585,8 +585,8 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentNodeTyp category: "Image Synthesis", identifier: NodeImplementation::proto("graphene_std::raster::ImaginateNode<_>"), inputs: vec![ - DocumentInputType::value("Input Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), true), + DocumentInputType::value("Input Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), + DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false), DocumentInputType::value("Seed", TaggedValue::F64(0.), false), // Remember to keep index used in `NodeGraphFrameImaginateRandom` updated with this entry's index DocumentInputType::value("Resolution", TaggedValue::OptionalDVec2(None), false), DocumentInputType::value("Samples", TaggedValue::F64(30.), false), @@ -674,10 +674,7 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw ), resolve_document_node_type("Output") .expect("Output node does not exist") - .to_document_node([NodeInput::node(2, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))), - resolve_document_node_type("Image Frame") - .expect("Image frame node does not exist") - .to_document_node([NodeInput::node(output_node_id, 0), NodeInput::node(0, 1)], DocumentNodeMetadata::position((output_offset, 4))), + .to_document_node([NodeInput::node(output_node_id, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))), ] .into_iter() .enumerate() diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 2d6de6ffb..4b2e4fc74 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -19,17 +19,8 @@ pub struct NodeGraphExecutor { } impl NodeGraphExecutor { - /// Sets the transform property on the input node - fn set_input_transform(network: &mut NodeNetwork, transform: DAffine2) { - let Some(input_node) = network.nodes.get_mut(&network.inputs[0]) else { - return; - }; - input_node.inputs[1] = NodeInput::value(TaggedValue::DAffine2(transform), false); - } - /// Execute the network by flattening it and creating a borrow stack. Casts the output to the generic `T`. fn execute_network(&mut self, mut network: NodeNetwork, image_frame: ImageFrame) -> Result { - Self::set_input_transform(&mut network, image_frame.transform); network.duplicate_outputs(&mut generate_uuid); network.remove_dead_nodes(); @@ -49,7 +40,7 @@ impl NodeGraphExecutor { use dyn_any::IntoDynAny; use graph_craft::executor::Executor; - let boxed = self.executor.execute(image_frame.image.into_dyn()).map_err(|e| e.to_string())?; + let boxed = self.executor.execute(image_frame.into_dyn()).map_err(|e| e.to_string())?; dyn_any::downcast::(boxed).map(|v| *v) } diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 4d3956ed0..9a3a0148e 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -291,6 +291,7 @@ mod image { use super::{Color, ImageSlice}; use crate::Node; use alloc::vec::Vec; + use core::hash::{Hash, Hasher}; use dyn_any::{DynAny, StaticType}; use glam::DAffine2; @@ -389,6 +390,13 @@ mod image { } } } + + impl Hash for ImageFrame { + fn hash(&self, state: &mut H) { + self.image.hash(state); + self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)) + } + } } #[cfg(test)] diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 60fb3d6a3..ce3d238dc 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -1,7 +1,7 @@ use dyn_any::{DynAny, StaticType}; use glam::DAffine2; -use graphene_core::raster::{Color, Image}; +use graphene_core::raster::{Color, Image, ImageFrame}; use graphene_core::Node; use std::path::Path; @@ -107,6 +107,24 @@ where image } +#[derive(Debug, Clone, Copy)] +pub struct MapImageFrameNode { + map_fn: MapFn, +} + +#[node_macro::node_fn(MapImageFrameNode)] +fn map_image(mut image_frame: ImageFrame, map_fn: &'any_input MapFn) -> ImageFrame +where + MapFn: for<'any_input> Node<'any_input, Color, Output = Color> + 'input, +{ + let mut image_frame = image_frame; + for pixel in &mut image_frame.image.data { + *pixel = map_fn.eval(*pixel); + } + + image_frame +} + #[derive(Debug, Clone, Copy)] pub struct BlendImageNode { second: Second, @@ -131,9 +149,13 @@ pub struct ImaginateNode { } #[node_macro::node_fn(ImaginateNode)] -fn imaginate(image: Image, cached: Option>) -> Image { - info!("Imaginating image with {} pixels", image.data.len()); - cached.map(|mut x| std::sync::Arc::make_mut(&mut x).clone()).unwrap_or(image) +fn imaginate(image_frame: ImageFrame, cached: Option>) -> ImageFrame { + info!("Imaginating image with {} pixels", image_frame.image.data.len()); + let cached_image = cached.map(|mut x| std::sync::Arc::make_mut(&mut x).clone()).unwrap_or(image_frame.image); + ImageFrame { + image: cached_image, + transform: image_frame.transform, + } } #[derive(Debug, Clone, Copy)] diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index b8c03c90c..ee418663e 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -10,7 +10,7 @@ use graphene_core::structural::Then; use graphene_core::value::{ClonedNode, ForgetNode, ValueNode}; use graphene_core::{Node, NodeIO, NodeIOTypes}; -use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DowncastBothRefNode, DynAnyNode, IntoTypeErasedNode}; +use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DowncastBothRefNode, DynAnyNode, IntoTypeErasedNode, TypeErasedPinnedRef}; use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor}; @@ -23,18 +23,25 @@ use crate::executor::NodeContainer; use dyn_any::StaticType; +macro_rules! construct_node { + ($args: ident, $path:ty, [$($type:tt),*]) => {{ + let mut args: Vec> = $args.clone(); + args.reverse(); + <$path>::new($(graphene_core::value::ClonedNode::new( + graphene_std::any::input_node::<$type>(args.pop() + .expect("Not enough arguments provided to construct node"))).eval(()) + ),* + ) + }} +} + macro_rules! register_node { ($path:ty, input: $input:ty, params: [$($type:ty),*]) => { + vec![ ( NodeIdentifier::new(stringify!($path)), |args| { - let mut args = args.clone(); - args.reverse(); - let node = <$path>::new($( - graphene_std::any::input_node::<$type>( - args.pop().expect("not enough arguments provided to construct node") - ) - ),*); + let node = construct_node!(args, $path, [$($type),*]); let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); Box::pin(any) }, @@ -51,22 +58,28 @@ macro_rules! register_node { node_io }, ) + ] }; } macro_rules! raster_node { ($path:ty, params: [$($type:ty),*]) => { + vec![ ( NodeIdentifier::new(stringify!($path)), |args| { - let mut args = args.clone(); - args.reverse(); - let node = <$path>::new($( - graphene_core::value::ClonedNode::new( - graphene_std::any::input_node::<$type>( - args.pop().expect("Not enough arguments provided to construct node") - ).eval(()) - ) - ),*); + let node = construct_node!(args, $path, [$($type),*]); + let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); + Box::pin(any) + }, + { + let params = vec![$((concrete!(()), concrete!($type))),*]; + NodeIOTypes::new(concrete!(Color), concrete!(Color), params) + }, + ), + ( + NodeIdentifier::new(stringify!($path)), + |args| { + let node = construct_node!(args, $path, [$($type),*]); let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); Box::pin(any) @@ -75,19 +88,33 @@ macro_rules! raster_node { let params = vec![$((concrete!(()), concrete!($type))),*]; NodeIOTypes::new(concrete!(Image), concrete!(Image), params) }, + ), + ( + NodeIdentifier::new(stringify!($path)), + |args| { + let node = construct_node!(args, $path, [$($type),*]); + let map_node = graphene_std::raster::MapImageFrameNode::new(graphene_core::value::ValueNode::new(node)); + let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); + Box::pin(any) + }, + { + let params = vec![$((concrete!(()), concrete!($type))),*]; + NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), params) + }, ) - }; + ] + } } //TODO: turn into hashmap fn node_registry() -> HashMap> { - let node_types: Vec<(NodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![ + let node_types: Vec> = vec![ //register_node!(graphene_core::ops::IdNode, input: Any<'_>, params: []), - ( + vec![( NodeIdentifier::new("graphene_core::ops::IdNode"), |_| IdNode::new().into_type_erased(), NodeIOTypes::new(generic!(I), generic!(I), vec![]), - ), + )], // TODO: create macro to impl for all types register_node!(graphene_core::structural::ConsNode<_, _>, input: u32, params: [u32]), register_node!(graphene_core::structural::ConsNode<_, _>, input: u32, params: [&u32]), @@ -104,18 +131,18 @@ fn node_registry() -> HashMap, input: &f64, params: [f64]), register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [&f64]), register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [&f64]), - ( + vec![( NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"), |args| { let node = ComposeTypeErased::new(args[0], args[1]); node.into_type_erased() }, NodeIOTypes::new(generic!(T), generic!(U), vec![(generic!(T), generic!(V)), (generic!(V), generic!(U))]), - ), + )], // Filters raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]), raster_node!(graphene_core::raster::LevelsNode<_, _, _, _, _>, params: [f64, f64, f64, f64, f64]), - ( + vec![( NodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"), |args| { use graphene_core::Node; @@ -133,7 +160,7 @@ fn node_registry() -> HashMap, 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: []), @@ -143,97 +170,99 @@ fn node_registry() -> HashMap, params: [f64]), raster_node!(graphene_core::raster::PosterizeNode<_>, params: [f64]), raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f64, f64, f64]), - ( - NodeIdentifier::new("graphene_core::structural::MapImageNode"), - |args| { - let map_fn: DowncastBothNode = DowncastBothNode::new(args[0]); - let node = graphene_std::raster::MapImageNode::new(ValueNode::new(map_fn)); - 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![]), - ), - ( - NodeIdentifier::new("graphene_std::raster::ImaginateNode<_>"), - |args| { - let cached = graphene_std::any::input_node::>>(args[16]); - let node = graphene_std::raster::ImaginateNode::new(cached); - let any = DynAnyNode::new(ValueNode::new(node)); - any.into_type_erased() - }, - NodeIOTypes::new( - concrete!(Image), - concrete!(Image), - vec![ - (concrete!(()), concrete!(DAffine2)), - (concrete!(()), concrete!(f64)), - (concrete!(()), concrete!(Option)), - (concrete!(()), concrete!(f64)), - (concrete!(()), concrete!(ImaginateSamplingMethod)), - (concrete!(()), concrete!(f64)), - (concrete!(()), concrete!(String)), - (concrete!(()), concrete!(String)), - (concrete!(()), concrete!(bool)), - (concrete!(()), concrete!(f64)), - (concrete!(()), concrete!(Option>)), - (concrete!(()), concrete!(bool)), - (concrete!(()), concrete!(f64)), - (concrete!(()), concrete!(ImaginateMaskStartingFill)), - (concrete!(()), concrete!(bool)), - (concrete!(()), concrete!(bool)), - (concrete!(()), concrete!(Option>)), - (concrete!(()), concrete!(f64)), - (concrete!(()), concrete!(ImaginateStatus)), - ], + vec![ + ( + NodeIdentifier::new("graphene_core::structural::MapImageNode"), + |args| { + let map_fn: DowncastBothNode = DowncastBothNode::new(args[0]); + let node = graphene_std::raster::MapImageNode::new(ValueNode::new(map_fn)); + 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![]), ), - ), - ( - NodeIdentifier::new("graphene_core::raster::BlurNode"), - |args| { - let radius = DowncastBothNode::<(), u32>::new(args[0]); - let sigma = DowncastBothNode::<(), f64>::new(args[1]); - let image = DowncastBothRefNode::::new(args[2]); - let empty_image: ValueNode = ValueNode::new(Image::empty()); - let empty: TypeNode<_, (), Image> = TypeNode::new(empty_image.then(CloneNode::new())); - use graphene_core::Node; - let radius = ClonedNode::new(radius.eval(())); - let sigma = ClonedNode::new(sigma.eval(())); + ( + NodeIdentifier::new("graphene_std::raster::ImaginateNode<_>"), + |args| { + let cached = graphene_std::any::input_node::>>(args[16]); + let node = graphene_std::raster::ImaginateNode::new(cached); + let any = DynAnyNode::new(ValueNode::new(node)); + any.into_type_erased() + }, + NodeIOTypes::new( + concrete!(ImageFrame), + concrete!(ImageFrame), + vec![ + (concrete!(()), concrete!(DAffine2)), + (concrete!(()), concrete!(f64)), + (concrete!(()), concrete!(Option)), + (concrete!(()), concrete!(f64)), + (concrete!(()), concrete!(ImaginateSamplingMethod)), + (concrete!(()), concrete!(f64)), + (concrete!(()), concrete!(String)), + (concrete!(()), concrete!(String)), + (concrete!(()), concrete!(bool)), + (concrete!(()), concrete!(f64)), + (concrete!(()), concrete!(Option>)), + (concrete!(()), concrete!(bool)), + (concrete!(()), concrete!(f64)), + (concrete!(()), concrete!(ImaginateMaskStartingFill)), + (concrete!(()), concrete!(bool)), + (concrete!(()), concrete!(bool)), + (concrete!(()), concrete!(Option>)), + (concrete!(()), concrete!(f64)), + (concrete!(()), concrete!(ImaginateStatus)), + ], + ), + ), + ( + NodeIdentifier::new("graphene_core::raster::BlurNode"), + |args| { + let radius = DowncastBothNode::<(), u32>::new(args[0]); + let sigma = DowncastBothNode::<(), f64>::new(args[1]); + let image = DowncastBothRefNode::::new(args[2]); + let empty_image: ValueNode = ValueNode::new(Image::empty()); + let empty: TypeNode<_, (), Image> = TypeNode::new(empty_image.then(CloneNode::new())); + use graphene_core::Node; + let radius = ClonedNode::new(radius.eval(())); + let sigma = ClonedNode::new(sigma.eval(())); - //let image = &image as &dyn for<'a> Node<'a, (), Output = &'a Image>; - // dirty hack: we abuse that the cache node will ignore the input if it is evaluated a second time - let image = empty.then(image).then(ImageRefNode::new()); + //let image = &image as &dyn for<'a> Node<'a, (), Output = &'a Image>; + // dirty hack: we abuse that the cache node will ignore the input if it is evaluated a second time + let image = empty.then(image).then(ImageRefNode::new()); - let window = WindowNode::new(radius, image.clone()); - let map_gaussian = MapSndNode::new(ValueNode::new(DistanceNode.then(GaussianNode::new(sigma)))); - let map_distances = MapNode::new(ValueNode::new(map_gaussian)); - let gaussian_iter = window.then(map_distances); - let avg = gaussian_iter.then(WeightedAvgNode::new()); - let avg: TypeNode<_, u32, Color> = TypeNode::new(avg); - let blur_iter = MapNode::new(ValueNode::new(avg)); - let pixel_iter = image.clone().then(ImageIndexIterNode::new()); - let blur = pixel_iter.then(blur_iter); - let collect = CollectNode {}; - let vec = blur.then(collect); - let new_image = MapImageSliceNode::new(vec); - let dimensions = image.then(ImageDimensionsNode::new()); - let dimensions: TypeNode<_, (), (u32, u32)> = TypeNode::new(dimensions); - let new_image = dimensions.then(new_image); - let new_image = ForgetNode::new().then(new_image); - let node: DynAnyNode<&Image, _, _> = DynAnyNode::new(ValueNode::new(new_image)); - node.into_type_erased() - }, - NodeIOTypes::new(concrete!(Image), concrete!(Image), vec![(concrete!(()), concrete!(u32)), (concrete!(()), concrete!(f64))]), - ), - //register_node!(graphene_std::memo::CacheNode<_>, input: Image, params: []), - ( - NodeIdentifier::new("graphene_std::memo::CacheNode"), - |_| { - let node: CacheNode = graphene_std::memo::CacheNode::new(); - let any = graphene_std::any::DynAnyRefNode::new(node); - any.into_type_erased() - }, - NodeIOTypes::new(concrete!(Image), concrete!(&Image), vec![]), - ), + let window = WindowNode::new(radius, image.clone()); + let map_gaussian = MapSndNode::new(ValueNode::new(DistanceNode.then(GaussianNode::new(sigma)))); + let map_distances = MapNode::new(ValueNode::new(map_gaussian)); + let gaussian_iter = window.then(map_distances); + let avg = gaussian_iter.then(WeightedAvgNode::new()); + let avg: TypeNode<_, u32, Color> = TypeNode::new(avg); + let blur_iter = MapNode::new(ValueNode::new(avg)); + let pixel_iter = image.clone().then(ImageIndexIterNode::new()); + let blur = pixel_iter.then(blur_iter); + let collect = CollectNode {}; + let vec = blur.then(collect); + let new_image = MapImageSliceNode::new(vec); + let dimensions = image.then(ImageDimensionsNode::new()); + let dimensions: TypeNode<_, (), (u32, u32)> = TypeNode::new(dimensions); + let new_image = dimensions.then(new_image); + let new_image = ForgetNode::new().then(new_image); + let node: DynAnyNode<&Image, _, _> = DynAnyNode::new(ValueNode::new(new_image)); + node.into_type_erased() + }, + NodeIOTypes::new(concrete!(Image), concrete!(Image), vec![(concrete!(()), concrete!(u32)), (concrete!(()), concrete!(f64))]), + ), + //register_node!(graphene_std::memo::CacheNode<_>, input: Image, params: []), + ( + NodeIdentifier::new("graphene_std::memo::CacheNode"), + |_| { + let node: CacheNode = graphene_std::memo::CacheNode::new(); + let any = graphene_std::any::DynAnyRefNode::new(node); + any.into_type_erased() + }, + NodeIOTypes::new(concrete!(Image), concrete!(&Image), vec![]), + ), + ], register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), register_node!(graphene_std::raster::ImageFrameNode<_>, input: Image, params: [DAffine2]), /* @@ -390,7 +419,7 @@ fn node_registry() -> HashMap> = HashMap::new(); - for (id, c, types) in node_types { + for (id, c, types) in node_types.into_iter().flatten() { map.entry(id).or_default().insert(types.clone(), c); } map