From fcf5b2f8020473fb600a989e5000be04902ed0ae Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Thu, 19 Jun 2025 10:10:43 +0530 Subject: [PATCH] Add masking --- .../node_graph/document_node_definitions.rs | 2 +- node-graph/gcore/src/graphic_element.rs | 7 ++++ .../gcore/src/graphic_element/renderer.rs | 35 ++++++++++++++++++ node-graph/gcore/src/instances.rs | 36 ++++++++++++++++--- node-graph/gcore/src/raster/image.rs | 2 ++ node-graph/gcore/src/vector/vector_nodes.rs | 4 +++ node-graph/gstd/src/raster.rs | 32 ++++++++++++++--- node-graph/gstd/src/vector.rs | 1 + 8 files changed, 109 insertions(+), 10 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 239e4e894e..e926bf3fb1 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -16,7 +16,7 @@ use graph_craft::document::value::*; use graph_craft::document::*; use graphene_std::ops::XY; use graphene_std::raster::brush_cache::BrushCache; -use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha}; +use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha}; use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::transform::Footprint; diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 4f21cdfc0c..7b4fd6e067 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -101,6 +101,7 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) for (graphic_element, source_node_id) in old.elements { graphic_group_table.push(Instance { instance: graphic_element, + mask: None, transform: old.transform, alpha_blending: old.alpha_blending, source_node_id, @@ -116,6 +117,7 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) for (graphic_element, source_node_id) in &instance.instance.elements { graphic_group_table.push(Instance { instance: graphic_element.clone(), + mask: None, transform: *instance.transform, alpha_blending: *instance.alpha_blending, source_node_id: *source_node_id, @@ -332,6 +334,7 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D) for (artboard, source_node_id) in artboard_group.artboards { table.push(Instance { instance: artboard, + mask: None, transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::default(), source_node_id, @@ -352,6 +355,7 @@ async fn layer(_: impl Ctx, mut stack: GraphicGroupTable, element: GraphicElemen stack.push(Instance { instance: element, + mask: None, transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::default(), source_node_id, @@ -412,6 +416,7 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo _ => { output_group_table.push(Instance { instance: current_element, + mask: None, transform: *current_instance.transform, alpha_blending: *current_instance.alpha_blending, source_node_id: reference, @@ -450,6 +455,7 @@ async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTabl for current_element in vector_instance.instance_ref_iter() { output_group_table.push(Instance { instance: current_element.instance.clone(), + mask: None, transform: *current_instance.transform * *current_element.transform, alpha_blending: AlphaBlending { blend_mode: current_element.alpha_blending.blend_mode, @@ -514,6 +520,7 @@ async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artb artboards.push(Instance { instance: artboard, + mask: None, transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::default(), source_node_id: encapsulating_node_id, diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index 52c007ac6c..fc49b1c6f8 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -432,6 +432,20 @@ impl GraphicElementRendered for GraphicGroupTable { attributes.push(mask_type.to_attribute(), selector); } + + if let Some(mask) = instance.mask { + let uuid = generate_uuid(); + let mask_type = if mask.can_reduce_to_clip_path() { MaskType::Clip } else { MaskType::Mask }; + let mut svg = SvgRender::new(); + mask.render_svg(&mut svg, &render_params.for_clipper()); + + write!(&mut attributes.0.svg_defs, r##"{}"##, svg.svg_defs).unwrap(); + mask_type.write_to_defs(&mut attributes.0.svg_defs, uuid, svg.svg.to_svg_string()); + let id = format!("mask-{}", uuid); + let selector = format!("url(#{id})"); + + attributes.push(mask_type.to_attribute(), selector); + } }, |render| { instance.instance.render_svg(render, render_params); @@ -474,6 +488,20 @@ impl GraphicElementRendered for GraphicGroupTable { } } + let mut masked = false; + if let Some(mask) = instance.mask { + let bounds = mask.bounding_box(transform, true); + + if let Some(bounds) = bounds { + masked = true; + let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); + + scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect); + mask.render_to_vello(scene, transform, context, &render_params.for_clipper()); + scene.push_layer(peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcIn), 1., kurbo::Affine::IDENTITY, &rect); + } + } + let next_clips = iter.peek().is_some_and(|next_instance| next_instance.instance.had_clip_enabled()); if next_clips && mask_instance_state.is_none() { mask_instance_state = Some((instance.instance, transform)); @@ -502,6 +530,11 @@ impl GraphicElementRendered for GraphicGroupTable { instance.instance.render_to_vello(scene, transform, context, render_params); } + if masked { + scene.pop_layer(); + scene.pop_layer(); + } + if layer { scene.pop_layer(); } @@ -614,6 +647,7 @@ impl GraphicElementRendered for VectorDataTable { vector_row.push(Instance { instance: fill_instance, + mask: None, alpha_blending: *instance.alpha_blending, transform: *instance.transform, source_node_id: None, @@ -734,6 +768,7 @@ impl GraphicElementRendered for VectorDataTable { vector_data.push(Instance { instance: fill_instance, + mask: None, alpha_blending: *instance.alpha_blending, transform: *instance.transform, source_node_id: None, diff --git a/node-graph/gcore/src/instances.rs b/node-graph/gcore/src/instances.rs index 6a6918c713..5063cd61be 100644 --- a/node-graph/gcore/src/instances.rs +++ b/node-graph/gcore/src/instances.rs @@ -1,13 +1,17 @@ -use crate::AlphaBlending; +use crate::{AlphaBlending, GraphicElement}; use crate::uuid::NodeId; use dyn_any::StaticType; use glam::DAffine2; use std::hash::Hash; +pub type Mask = Option; + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct Instances { #[serde(alias = "instances")] instance: Vec, + #[serde(default = "one_mask_default")] + mask: Vec, #[serde(default = "one_daffine2_default")] transform: Vec, #[serde(default = "one_alpha_blending_default")] @@ -20,6 +24,7 @@ impl Instances { pub fn new(instance: T) -> Self { Self { instance: vec![instance], + mask: vec![None], transform: vec![DAffine2::IDENTITY], alpha_blending: vec![AlphaBlending::default()], source_node_id: vec![None], @@ -28,6 +33,7 @@ impl Instances { pub fn push(&mut self, instance: Instance) { self.instance.push(instance.instance); + self.mask.push(instance.mask); self.transform.push(instance.transform); self.alpha_blending.push(instance.alpha_blending); self.source_node_id.push(instance.source_node_id); @@ -43,11 +49,13 @@ impl Instances { pub fn instance_iter(self) -> impl DoubleEndedIterator> { self.instance .into_iter() + .zip(self.mask) .zip(self.transform) .zip(self.alpha_blending) .zip(self.source_node_id) - .map(|(((instance, transform), alpha_blending), source_node_id)| Instance { + .map(|((((instance, mask), transform), alpha_blending), source_node_id)| Instance { instance, + mask, transform, alpha_blending, source_node_id, @@ -57,11 +65,13 @@ impl Instances { pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator> + Clone { self.instance .iter() + .zip(self.mask.iter()) .zip(self.transform.iter()) .zip(self.alpha_blending.iter()) .zip(self.source_node_id.iter()) - .map(|(((instance, transform), alpha_blending), source_node_id)| InstanceRef { + .map(|((((instance, mask), transform), alpha_blending), source_node_id)| InstanceRef { instance, + mask, transform, alpha_blending, source_node_id, @@ -71,11 +81,13 @@ impl Instances { pub fn instance_mut_iter(&mut self) -> impl DoubleEndedIterator> { self.instance .iter_mut() + .zip(self.mask.iter_mut()) .zip(self.transform.iter_mut()) .zip(self.alpha_blending.iter_mut()) .zip(self.source_node_id.iter_mut()) - .map(|(((instance, transform), alpha_blending), source_node_id)| InstanceMut { + .map(|((((instance, mask), transform), alpha_blending), source_node_id)| InstanceMut { instance, + mask, transform, alpha_blending, source_node_id, @@ -89,6 +101,7 @@ impl Instances { Some(InstanceRef { instance: &self.instance[index], + mask: &self.mask[index], transform: &self.transform[index], alpha_blending: &self.alpha_blending[index], source_node_id: &self.source_node_id[index], @@ -102,6 +115,7 @@ impl Instances { Some(InstanceMut { instance: &mut self.instance[index], + mask: &mut self.mask[index], transform: &mut self.transform[index], alpha_blending: &mut self.alpha_blending[index], source_node_id: &mut self.source_node_id[index], @@ -121,6 +135,7 @@ impl Default for Instances { fn default() -> Self { Self { instance: Vec::new(), + mask: Vec::new(), transform: Vec::new(), alpha_blending: Vec::new(), source_node_id: Vec::new(), @@ -146,6 +161,9 @@ unsafe impl StaticType for Instances { type Static = Instances; } +fn one_mask_default() -> Vec { + vec![None] +} fn one_daffine2_default() -> Vec { vec![DAffine2::IDENTITY] } @@ -159,6 +177,7 @@ fn one_source_node_id_default() -> Vec> { #[derive(Copy, Clone, Debug, PartialEq)] pub struct InstanceRef<'a, T> { pub instance: &'a T, + pub mask: &'a Mask, pub transform: &'a DAffine2, pub alpha_blending: &'a AlphaBlending, pub source_node_id: &'a Option, @@ -171,6 +190,7 @@ impl InstanceRef<'_, T> { { Instance { instance: self.instance.clone(), + mask: self.mask.clone(), transform: *self.transform, alpha_blending: *self.alpha_blending, source_node_id: *self.source_node_id, @@ -181,14 +201,16 @@ impl InstanceRef<'_, T> { #[derive(Debug)] pub struct InstanceMut<'a, T> { pub instance: &'a mut T, + pub mask: &'a mut Mask, pub transform: &'a mut DAffine2, pub alpha_blending: &'a mut AlphaBlending, pub source_node_id: &'a mut Option, } -#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct Instance { pub instance: T, + pub mask: Mask, pub transform: DAffine2, pub alpha_blending: AlphaBlending, pub source_node_id: Option, @@ -201,6 +223,7 @@ impl Instance { { Instance { instance: self.instance.into(), + mask: self.mask, transform: self.transform, alpha_blending: self.alpha_blending, source_node_id: self.source_node_id, @@ -210,6 +233,7 @@ impl Instance { pub fn to_instance_ref(&self) -> InstanceRef { InstanceRef { instance: &self.instance, + mask: &self.mask, transform: &self.transform, alpha_blending: &self.alpha_blending, source_node_id: &self.source_node_id, @@ -219,6 +243,7 @@ impl Instance { pub fn to_instance_mut(&mut self) -> InstanceMut { InstanceMut { instance: &mut self.instance, + mask: &mut self.mask, transform: &mut self.transform, alpha_blending: &mut self.alpha_blending, source_node_id: &mut self.source_node_id, @@ -228,6 +253,7 @@ impl Instance { pub fn to_table(self) -> Instances { Instances { instance: vec![self.instance], + mask: vec![self.mask], transform: vec![self.transform], alpha_blending: vec![self.alpha_blending], source_node_id: vec![self.source_node_id], diff --git a/node-graph/gcore/src/raster/image.rs b/node-graph/gcore/src/raster/image.rs index 36254461ad..4f8c8d56d0 100644 --- a/node-graph/gcore/src/raster/image.rs +++ b/node-graph/gcore/src/raster/image.rs @@ -393,6 +393,7 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ }, FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => Instance { instance: Raster::new_cpu(image_frame_with_transform_and_blending.image), + mask: None, transform: image_frame_with_transform_and_blending.transform, alpha_blending: image_frame_with_transform_and_blending.alpha_blending, source_node_id: None, @@ -461,6 +462,7 @@ impl From> for Image { // for image_frame_instance in image_frame_table.instance_iter() { // result_table.push(Instance { // instance: image_frame_instance.instance, +// mask: image_frame_instance.mask, // transform: image_frame_instance.transform, // alpha_blending: image_frame_instance.alpha_blending, // source_node_id: image_frame_instance.source_node_id, diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 79d768ff15..e8c30f2d68 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -448,6 +448,7 @@ async fn round_corners( for source in source.instance_ref_iter() { let source_transform = *source.transform; let source_transform_inverse = source_transform.inverse(); + let source_mask = source.mask; let source = source.instance; let upstream_graphic_group = source.upstream_graphic_group.clone(); @@ -540,6 +541,7 @@ async fn round_corners( result_table.push(Instance { instance: result, + mask: source_mask.clone(), transform: source_transform, alpha_blending: Default::default(), source_node_id: None, @@ -788,6 +790,7 @@ async fn auto_tangents( for source in source.instance_ref_iter() { let transform = *source.transform; + let mask = source.mask.clone(); let alpha_blending = *source.alpha_blending; let source_node_id = *source.source_node_id; let source = source.instance; @@ -886,6 +889,7 @@ async fn auto_tangents( result_table.push(Instance { instance: result, + mask, transform, alpha_blending, source_node_id, diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 55f2792ce1..00941bf9bc 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -1,3 +1,4 @@ +use crate::instances::Instances; use dyn_any::DynAny; use fastnoise_lite; use glam::{DAffine2, DVec2, Vec2}; @@ -6,7 +7,9 @@ use graphene_core::raster::bbox::Bbox; pub use graphene_core::raster::*; use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::transform::Transform; +use graphene_core::vector::VectorDataTable; use graphene_core::{AlphaBlending, Ctx, ExtractFootprint}; +use graphene_core::{GraphicElement, GraphicGroupTable}; use rand::prelude::*; use rand_chacha::ChaCha8Rng; use std::fmt::Debug; @@ -171,6 +174,7 @@ fn combine_channels( // Add this instance to the result table result_table.push(Instance { instance: Raster::new_cpu(image), + mask: None, transform, alpha_blending, source_node_id: None, @@ -181,14 +185,34 @@ fn combine_channels( } #[node_macro::node(category("Raster"))] -fn mask( +fn mask( _: impl Ctx, /// The image to be masked. - image: RasterDataTable, + #[implementations( + VectorDataTable, + RasterDataTable, + GraphicGroupTable + )] + mut image: Instances, /// The stencil to be used for masking. #[expose] - stencil: RasterDataTable, -) -> RasterDataTable { + #[implementations( + VectorDataTable, + RasterDataTable, + GraphicGroupTable + )] + stencil: Instances, +) -> Instances +where + Instances: Into + Clone, +{ + for instance in image.instance_mut_iter() { + *instance.mask = Some(stencil.clone().into()); + } + image +} + +fn mask_lambda(image: RasterDataTable, stencil: RasterDataTable) -> RasterDataTable { // TODO: Support multiple stencil instances let Some(stencil_instance) = stencil.instance_iter().next() else { // No stencil provided so we return the original image diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index 4fbf3a664c..cbf4f48e0b 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -178,6 +178,7 @@ fn difference<'a>(vector_data: impl DoubleEndedIterator