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 b20382a8..1e9ede46 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 @@ -8,7 +8,7 @@ use graph_craft::document::*; use graph_craft::imaginate_input::ImaginateSamplingMethod; use graph_craft::NodeIdentifier; -use graphene_core::raster::{Color, Image, LuminanceCalculation}; +use graphene_core::raster::{Color, Image, ImageFrame, LuminanceCalculation}; use graphene_core::*; use std::collections::VecDeque; @@ -176,7 +176,7 @@ fn static_nodes() -> Vec { inputs: vec![DocumentInputType { name: "In", data_type: FrontendGraphDataType::Raster, - default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), }], outputs: vec![], properties: |_document_node, _node_id, _context| node_properties::string_properties("The graph's output is rendered into the frame"), diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index c86db4a3..dbb7c47c 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -245,34 +245,39 @@ impl NodeGraphExecutor { if let Some(imaginate_node) = imaginate_node { responses.push_back(self.generate_imaginate(network, imaginate_node, (document, document_id), layer_path, image_frame, persistent_data)?); } else { - let ImageFrame { mut image, transform } = self.execute_network(network, image_frame)?; + let ImageFrame { image, transform } = self.execute_network(network, image_frame)?; - // If no image was generated, use the input image + // If no image was generated, clear the frame if image.width == 0 || image.height == 0 { - image = graphene_core::raster::Image::from_image_data(&image_data, width, height); + responses.push_back(DocumentMessage::FrameClear.into()); + } else { + // Update the image data + let (image_data, _size) = Self::encode_img(image, None, image::ImageOutputFormat::Bmp)?; + + responses.push_back( + Operation::SetNodeGraphFrameImageData { + layer_path: layer_path.clone(), + image_data: image_data.clone(), + } + .into(), + ); + let mime = "image/bmp".to_string(); + let image_data = std::sync::Arc::new(image_data); + let image_data = vec![FrontendImageData { + path: layer_path.clone(), + image_data, + mime, + }]; + responses.push_back(FrontendMessage::UpdateImageData { document_id, image_data }.into()); } - let (image_data, _size) = Self::encode_img(image, None, image::ImageOutputFormat::Bmp)?; - - responses.push_back( - Operation::SetNodeGraphFrameImageData { - layer_path: layer_path.clone(), - image_data: image_data.clone(), - } - .into(), - ); - let mime = "image/bmp".to_string(); - let image_data = std::sync::Arc::new(image_data); - let image_data = vec![FrontendImageData { - path: layer_path.clone(), - image_data, - mime, - }]; - responses.push_back(FrontendMessage::UpdateImageData { document_id, image_data }.into()); - - // Update the transform based on the graph output - let transform = transform.to_cols_array(); - responses.push_back(Operation::SetLayerTransform { path: layer_path, transform }.into()); + // Don't update the frame's transform if the new transform is DAffine2::ZERO. + if !transform.abs_diff_eq(DAffine2::ZERO, f64::EPSILON) { + // Update the transform based on the graph output + let transform = transform.to_cols_array(); + responses.push_back(Operation::SetLayerTransform { path: layer_path.clone(), transform }.into()); + responses.push_back(Operation::SetLayerVisibility { path: layer_path, visible: true }.into()); + } } Ok(()) diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index f8ebea20..4d3956ed 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -374,11 +374,21 @@ mod image { } } - #[derive(Clone, Debug, PartialEq, DynAny, Default)] + #[derive(Clone, Debug, PartialEq, DynAny, Default, specta::Type)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ImageFrame { pub image: Image, pub transform: DAffine2, } + + impl ImageFrame { + pub const fn empty() -> Self { + Self { + image: Image::empty(), + transform: DAffine2::ZERO, + } + } + } } #[cfg(test)] diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index cf0f051c..4244a77d 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -25,6 +25,7 @@ pub enum TaggedValue { DAffine2(DAffine2), Image(graphene_core::raster::Image), RcImage(Option>), + ImageFrame(graphene_core::raster::ImageFrame), Color(graphene_core::raster::color::Color), Subpath(graphene_core::vector::subpath::Subpath), RcSubpath(Arc), @@ -113,6 +114,11 @@ impl Hash for TaggedValue { 19.hash(state); p.hash(state) } + Self::ImageFrame(i) => { + 20.hash(state); + i.image.hash(state); + i.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)) + } } } } @@ -132,6 +138,7 @@ impl<'a> TaggedValue { TaggedValue::DAffine2(x) => Box::new(x), TaggedValue::Image(x) => Box::new(x), TaggedValue::RcImage(x) => Box::new(x), + TaggedValue::ImageFrame(x) => Box::new(x), TaggedValue::Color(x) => Box::new(x), TaggedValue::Subpath(x) => Box::new(x), TaggedValue::RcSubpath(x) => Box::new(x), @@ -157,6 +164,7 @@ impl<'a> TaggedValue { TaggedValue::OptionalDVec2(_) => concrete!(Option), TaggedValue::Image(_) => concrete!(graphene_core::raster::Image), TaggedValue::RcImage(_) => concrete!(Option>), + TaggedValue::ImageFrame(_) => concrete!(graphene_core::raster::ImageFrame), TaggedValue::Color(_) => concrete!(graphene_core::raster::Color), TaggedValue::Subpath(_) => concrete!(graphene_core::vector::subpath::Subpath), TaggedValue::RcSubpath(_) => concrete!(Arc),