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 c51ab582..be234a8d 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 @@ -286,7 +286,18 @@ fn static_nodes() -> Vec { properties: |_document_node, _node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform"), }, DocumentNodeType { - name: "Blend Node", + name: "Mask", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_std::raster::MaskImageNode<_>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), + DocumentInputType::value("Stencil", TaggedValue::ImageFrame(ImageFrame::empty()), true), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::mask_properties, + }, + DocumentNodeType { + name: "Blend", category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::BlendNode<_, _, _, _>"), inputs: vec![ 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 7eef57bf..d73a8378 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 @@ -477,6 +477,12 @@ pub fn blend_properties(document_node: &DocumentNode, node_id: NodeId, _context: vec![backdrop, blend_mode, LayoutGroup::Row { widgets: opacity }] } +pub fn mask_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + let mask = color_widget(document_node, node_id, 1, "Stencil", ColorInput::default(), true); + + vec![mask] +} + pub fn luminance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { let luminance_calc = luminance_calculation(document_node, node_id, 1, "Luminance Calc", true); diff --git a/node-graph/compilation-client/src/main.rs b/node-graph/compilation-client/src/main.rs index a6c770f0..9cf9206c 100644 --- a/node-graph/compilation-client/src/main.rs +++ b/node-graph/compilation-client/src/main.rs @@ -16,7 +16,7 @@ fn main() { nodes: [( 0, DocumentNode { - name: "Inc Node".into(), + name: "Inc".into(), inputs: vec![NodeInput::Network(concrete!(u32))], implementation: DocumentNodeImplementation::Network(add_network()), metadata: DocumentNodeMetadata::default(), diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 3748cfa0..7500fa4c 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -442,7 +442,7 @@ mod image { &mut self.image.data[y * (self.image.width as usize) + x] } - /// Clamps the provided point to (0, 0) (ImageSize) and returns the closest pixel + /// Clamps the provided point to ((0, 0), (ImageSize.x, ImageSize.y)) and returns the closest pixel pub fn sample(&self, position: DVec2) -> Color { let x = position.x.clamp(0., self.image.width as f64 - 1.) as usize; let y = position.y.clamp(0., self.image.height as f64 - 1.) as usize; diff --git a/node-graph/gstd/src/executor.rs b/node-graph/gstd/src/executor.rs index bb72985e..c0495909 100644 --- a/node-graph/gstd/src/executor.rs +++ b/node-graph/gstd/src/executor.rs @@ -44,7 +44,7 @@ fn map_gpu_single_image(input: Image, node: String) -> Image { nodes: [( 0, DocumentNode { - name: "Image filter Node".into(), + name: "Image Filter".into(), inputs: vec![NodeInput::Network(concrete!(Color))], implementation: DocumentNodeImplementation::Unresolved(identifier), metadata: DocumentNodeMetadata::default(), diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 00e8bb68..5a710f47 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -194,6 +194,40 @@ fn compute_transformed_bounding_box(transform: DAffine2) -> Bbox { } } +#[derive(Debug, Clone, Copy)] +pub struct MaskImageNode { + mask: Mask, +} + +#[node_macro::node_fn(MaskImageNode)] +fn mask_image(mut image: ImageFrame, mask: ImageFrame) -> ImageFrame { + let image_size = DVec2::new(image.image.width as f64, image.image.height as f64); + let mask_size = DVec2::new(mask.image.width as f64, mask.image.height as f64); + + if mask_size == DVec2::ZERO { + return image; + } + + // Transforms a point from the background image to the forground image + let bg_to_fg = DAffine2::from_scale(mask_size) * mask.transform.inverse() * image.transform * DAffine2::from_scale(1. / image_size); + + for y in 0..image.image.height { + for x in 0..image.image.width { + let image_point = DVec2::new(x as f64, y as f64); + let mut mask_point = bg_to_fg.transform_point2(image_point); + mask_point = mask_point.clamp(DVec2::ZERO, mask_size); + + let image_pixel = image.get_mut(x as usize, y as usize); + let mask_pixel = mask.sample(mask_point); + let alpha = image_pixel.a() * mask_pixel.r(); + + *image_pixel = Color::from_rgbaf32(image_pixel.r(), image_pixel.g(), image_pixel.b(), alpha).unwrap(); + } + } + + image +} + #[derive(Debug, Clone, Copy)] pub struct BlendImageNode { background: Background, diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 72c7adfa..a7979162 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -136,6 +136,7 @@ fn node_registry() -> HashMap, input: &f64, params: [&f64]), register_node!(graphene_core::ops::SomeNode, input: ImageFrame, params: []), register_node!(graphene_std::raster::DownscaleNode, input: ImageFrame, params: []), + register_node!(graphene_std::raster::MaskImageNode<_>, input: ImageFrame, params: [ImageFrame]), #[cfg(feature = "gpu")] register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image, params: [String]), vec![(