diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index a426791e9..43691f39a 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -28,7 +28,7 @@ use crate::node_graph_executor::NodeGraphExecutor; use bezier_rs::Subpath; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork}; -use graphene_core::raster::image::ImageFrame; +use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::BlendMode; use graphene_core::vector::style::ViewMode; use graphene_std::renderer::{ClickTarget, Quad}; @@ -818,12 +818,7 @@ impl MessageHandler> for DocumentMessag responses.add(DocumentMessage::AddTransaction); - let image_frame = ImageFrame { - image, - transform: DAffine2::IDENTITY, - alpha_blending: Default::default(), - }; - let layer = graph_modification_utils::new_image_layer(image_frame, layer_node_id, self.new_layer_parent(true), responses); + let layer = graph_modification_utils::new_image_layer(ImageFrameTable::new(ImageFrame { image }), layer_node_id, self.new_layer_parent(true), responses); if let Some(name) = name { responses.add(NodeGraphMessage::SetDisplayName { diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs index db2d230b4..7b04e9f98 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs @@ -5,7 +5,7 @@ use crate::messages::prelude::*; use bezier_rs::Subpath; use graph_craft::document::NodeId; -use graphene_core::raster::image::ImageFrame; +use graphene_core::raster::image::ImageFrameTable; use graphene_core::raster::BlendMode; use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::vector::brush_stroke::BrushStroke; @@ -68,7 +68,7 @@ pub enum GraphOperationMessage { }, NewBitmapLayer { id: NodeId, - image_frame: ImageFrame, + image_frame: ImageFrameTable, parent: LayerNodeIdentifier, insert_index: usize, }, diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 1e352454f..9a45e0d08 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -8,7 +8,7 @@ use bezier_rs::Subpath; use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; -use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; +use graphene_core::raster::image::ImageFrameTable; use graphene_core::raster::BlendMode; use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::vector::brush_stroke::BrushStroke; @@ -212,12 +212,11 @@ impl<'a> ModifyInputsContext<'a> { self.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]); } - pub fn insert_image_data(&mut self, image_frame: ImageFrame, layer: LayerNodeIdentifier) { + pub fn insert_image_data(&mut self, image_frame: ImageFrameTable, layer: LayerNodeIdentifier) { let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template(); - let image = resolve_document_node_type("Image").expect("Image node does not exist").node_template_input_override([ - Some(NodeInput::value(TaggedValue::None, false)), - Some(NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::new(image_frame)), false)), - ]); + let image = resolve_document_node_type("Image") + .expect("Image node does not exist") + .node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::ImageFrame(image_frame), false))]); let image_id = NodeId::new(); self.network_interface.insert_node(image_id, image, &[]); 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 848807322..2894e446b 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 @@ -571,7 +571,7 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], + inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -809,8 +809,8 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_std::raster::MaskImageNode"), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), ], ..Default::default() }, @@ -832,8 +832,8 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_std::raster::InsertChannelNode"), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::default()), false), ], ..Default::default() @@ -856,10 +856,10 @@ fn static_nodes() -> Vec { implementation: DocumentNodeImplementation::proto("graphene_std::raster::CombineChannelsNode"), inputs: vec![ NodeInput::value(TaggedValue::None, false), - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), ], ..Default::default() }, @@ -929,7 +929,7 @@ fn static_nodes() -> Vec { ..Default::default() }), - inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], + inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -1011,8 +1011,8 @@ fn static_nodes() -> Vec { ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false), NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false), ], @@ -1061,7 +1061,7 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"), - inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], + inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)], manual_composition: Some(concrete!(Context)), ..Default::default() }, @@ -1080,7 +1080,7 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"), - inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], + inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)], manual_composition: Some(concrete!(Context)), ..Default::default() }, @@ -1112,7 +1112,7 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), false)], + inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), false)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -1825,7 +1825,7 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], + inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -1881,7 +1881,7 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true), ], ..Default::default() @@ -1923,7 +1923,7 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_core::raster::BrightnessContrastNode"), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::Bool(false), false), @@ -1955,7 +1955,7 @@ fn static_nodes() -> Vec { // document_node: DocumentNode { // implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"), // inputs: vec![ - // NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + // NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), // NodeInput::value(TaggedValue::Curve(Default::default()), false), // ], // ..Default::default() @@ -2799,7 +2799,7 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::scope("editor-api"), NodeInput::value(TaggedValue::ImaginateController(Default::default()), false), NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 8828dbc91..d7aff75c5 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -457,7 +457,7 @@ impl MessageHandler> for PortfolioMes } }; - const REPLACEMENTS: [(&str, &str); 35] = [ + const REPLACEMENTS: [(&str, &str); 34] = [ ("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"), ("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"), ("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"), @@ -488,7 +488,6 @@ impl MessageHandler> for PortfolioMes ("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"), ("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"), ("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"), - ("graphene_core::vector::generator_nodes::PathGenerator", "graphene_core::vector::generator_nodes::PathNode"), ("graphene_core::vector::generator_nodes::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"), ( "graphene_core::vector::generator_nodes::RegularPolygonGenerator", diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 82f14d0a2..f26377422 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -6,7 +6,7 @@ use crate::messages::prelude::*; use bezier_rs::Subpath; use graph_craft::document::{value::TaggedValue, NodeId, NodeInput}; -use graphene_core::raster::image::ImageFrame; +use graphene_core::raster::image::ImageFrameTable; use graphene_core::raster::BlendMode; use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::vector::style::Gradient; @@ -207,7 +207,7 @@ pub fn new_vector_layer(subpaths: Vec>, id: NodeId, parent: Lay } /// Create a new bitmap layer. -pub fn new_image_layer(image_frame: ImageFrame, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque) -> LayerNodeIdentifier { +pub fn new_image_layer(image_frame: ImageFrameTable, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque) -> LayerNodeIdentifier { let insert_index = 0; responses.add(GraphOperationMessage::NewBitmapLayer { id, diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 00269dd47..b0b84e4d5 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -298,9 +298,9 @@ impl NodeRuntime { } // Insert the vector modify if we are dealing with vector data else if let Some(record) = introspected_data.downcast_ref::>() { - self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone()); + self.vector_modify.insert(parent_network_node_id, record.output.one_instance().instance.clone()); } else if let Some(record) = introspected_data.downcast_ref::>() { - self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone()); + self.vector_modify.insert(parent_network_node_id, record.output.one_instance().instance.clone()); } } } diff --git a/node-graph/gcore/src/application_io.rs b/node-graph/gcore/src/application_io.rs index 43a1af464..88a256a80 100644 --- a/node-graph/gcore/src/application_io.rs +++ b/node-graph/gcore/src/application_io.rs @@ -2,7 +2,6 @@ use crate::instances::Instances; use crate::text::FontCache; use crate::transform::{Footprint, Transform, TransformMut}; use crate::vector::style::ViewMode; -use crate::AlphaBlending; use dyn_any::{DynAny, StaticType, StaticTypeSized}; @@ -73,36 +72,20 @@ pub struct TextureFrame { pub texture: Arc, #[cfg(not(feature = "wgpu"))] pub texture: (), - pub transform: DAffine2, - pub alpha_blend: AlphaBlending, } impl Hash for TextureFrame { + #[cfg(feature = "wgpu")] fn hash(&self, state: &mut H) { - self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)); - #[cfg(feature = "wgpu")] self.texture.hash(state); } + #[cfg(not(feature = "wgpu"))] + fn hash(&self, _state: &mut H) {} } impl PartialEq for TextureFrame { fn eq(&self, other: &Self) -> bool { - #[cfg(feature = "wgpu")] - return self.transform.eq(&other.transform) && self.texture == other.texture; - - #[cfg(not(feature = "wgpu"))] - self.transform.eq(&other.transform) - } -} - -impl Transform for TextureFrame { - fn transform(&self) -> DAffine2 { - self.transform - } -} -impl TransformMut for TextureFrame { - fn transform_mut(&mut self) -> &mut DAffine2 { - &mut self.transform + self.texture == other.texture } } diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 4ffd54548..2ef32a53f 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -45,15 +45,30 @@ impl AlphaBlending { pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { use serde::Deserialize; + #[derive(Clone, Debug, PartialEq, DynAny, Default)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct OldGraphicGroup { + elements: Vec<(GraphicElement, Option)>, + transform: DAffine2, + alpha_blending: AlphaBlending, + } + #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] enum EitherFormat { GraphicGroup(GraphicGroup), + OldGraphicGroup(OldGraphicGroup), GraphicGroupTable(GraphicGroupTable), } Ok(match EitherFormat::deserialize(deserializer)? { EitherFormat::GraphicGroup(graphic_group) => GraphicGroupTable::new(graphic_group), + EitherFormat::OldGraphicGroup(old) => { + let mut graphic_group_table = GraphicGroupTable::new(GraphicGroup { elements: old.elements }); + *graphic_group_table.one_instance_mut().transform = old.transform; + *graphic_group_table.one_instance_mut().alpha_blending = old.alpha_blending; + graphic_group_table + } EitherFormat::GraphicGroupTable(graphic_group_table) => graphic_group_table, }) } @@ -65,15 +80,11 @@ pub type GraphicGroupTable = Instances; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GraphicGroup { elements: Vec<(GraphicElement, Option)>, - pub transform: DAffine2, - pub alpha_blending: AlphaBlending, } impl core::hash::Hash for GraphicGroup { fn hash(&self, state: &mut H) { - self.transform.to_cols_array().iter().for_each(|element| element.to_bits().hash(state)); self.elements.hash(state); - self.alpha_blending.hash(state); } } @@ -81,8 +92,6 @@ impl GraphicGroup { pub fn new(elements: Vec) -> Self { Self { elements: elements.into_iter().map(|element| (element, None)).collect(), - transform: DAffine2::IDENTITY, - alpha_blending: AlphaBlending::new(), } } } @@ -214,29 +223,6 @@ impl serde::Serialize for RasterFrame { } } -impl Transform for RasterFrame { - fn transform(&self) -> DAffine2 { - match self { - RasterFrame::ImageFrame(frame) => frame.transform(), - RasterFrame::TextureFrame(frame) => frame.transform(), - } - } - fn local_pivot(&self, pivot: glam::DVec2) -> glam::DVec2 { - match self { - RasterFrame::ImageFrame(frame) => frame.local_pivot(pivot), - RasterFrame::TextureFrame(frame) => frame.local_pivot(pivot), - } - } -} -impl TransformMut for RasterFrame { - fn transform_mut(&mut self) -> &mut DAffine2 { - match self { - RasterFrame::ImageFrame(frame) => frame.transform_mut(), - RasterFrame::TextureFrame(frame) => frame.transform_mut(), - } - } -} - /// Some [`ArtboardData`] with some optional clipping bounds that can be exported. #[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -281,20 +267,20 @@ impl ArtboardGroup { #[node_macro::node(category(""))] async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec) -> GraphicGroupTable { - let mut stack = stack.one_item().clone(); + let mut stack = stack; - if stack.transform.matrix2.determinant() != 0. { - *element.transform_mut() = stack.transform.inverse() * element.transform(); + if stack.transform().matrix2.determinant() != 0. { + *element.transform_mut() = stack.transform().inverse() * element.transform(); } else { - stack.clear(); - stack.transform = DAffine2::IDENTITY; + stack.one_instance_mut().instance.clear(); + *stack.transform_mut() = DAffine2::IDENTITY; } // Get the penultimate element of the node path, or None if the path is too short let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); - stack.push((element, encapsulating_node_id)); + stack.one_instance_mut().instance.push((element, encapsulating_node_id)); - GraphicGroupTable::new(stack) + stack } #[node_macro::node(category("Debug"))] @@ -327,40 +313,38 @@ async fn to_group + 'n>( #[node_macro::node(category("General"))] async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable { - let nested_group = group.one_item().clone(); - - let mut flat_group = GraphicGroup::default(); - - fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) { + fn flatten_group(result_group: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool) { let mut collection_group = GraphicGroup::default(); - for (element, reference) in current_group.elements { - if let GraphicElement::GraphicGroup(nested_group) = element { - let nested_group = nested_group.one_item(); - let mut nested_group = nested_group.clone(); + let current_group_elements = current_group_table.one_instance().instance.elements.clone(); - *nested_group.transform_mut() = nested_group.transform() * current_group.transform; + for (element, reference) in current_group_elements { + if let GraphicElement::GraphicGroup(mut nested_group_table) = element { + *nested_group_table.transform_mut() = nested_group_table.transform() * current_group_table.transform(); - let mut sub_group = GraphicGroup::default(); + let mut sub_group_table = GraphicGroupTable::default(); if fully_flatten { - flatten_group(&mut sub_group, nested_group, fully_flatten); + flatten_group(&mut sub_group_table, nested_group_table, fully_flatten); } else { - for (collection_element, _) in &mut nested_group.elements { - *collection_element.transform_mut() = nested_group.transform * collection_element.transform(); + let nested_group_table_transform = nested_group_table.transform(); + for (collection_element, _) in &mut nested_group_table.one_instance_mut().instance.elements { + *collection_element.transform_mut() = nested_group_table_transform * collection_element.transform(); } - sub_group = nested_group; + sub_group_table = nested_group_table; } - collection_group.append(&mut sub_group.elements); + + collection_group.append(&mut sub_group_table.one_instance_mut().instance.elements); } else { collection_group.push((element, reference)); } } - result_group.append(&mut collection_group.elements); + result_group.one_instance_mut().instance.append(&mut collection_group.elements); } - flatten_group(&mut flat_group, nested_group, fully_flatten); + let mut flat_group = GraphicGroupTable::default(); + flatten_group(&mut flat_group, group, fully_flatten); - GraphicGroupTable::new(flat_group) + flat_group } #[node_macro::node(category(""))] @@ -487,8 +471,6 @@ where fn from(value: T) -> Self { Self { elements: (vec![(value.into(), None)]), - transform: DAffine2::IDENTITY, - alpha_blending: AlphaBlending::default(), } } } diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index 79427e112..2888b8971 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -9,7 +9,7 @@ use crate::transform::{Footprint, Transform}; use crate::uuid::{generate_uuid, NodeId}; use crate::vector::style::{Fill, Stroke, ViewMode}; use crate::vector::{PointId, VectorDataTable}; -use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame}; +use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroupTable, RasterFrame}; use bezier_rs::Subpath; use dyn_any::DynAny; @@ -291,14 +291,7 @@ pub trait GraphicElementRendered { fn collect_metadata(&self, _metadata: &mut RenderMetadata, _footprint: Footprint, _element_id: Option) {} #[cfg(feature = "vello")] - fn to_vello_scene(&self, transform: DAffine2, context: &mut RenderContext) -> Scene { - let mut scene = vello::Scene::new(); - self.render_to_vello(&mut scene, transform, context); - scene - } - - #[cfg(feature = "vello")] - fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _render_condext: &mut RenderContext) {} + fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _render_context: &mut RenderContext) {} fn contains_artboard(&self) -> bool { false @@ -311,40 +304,54 @@ pub trait GraphicElementRendered { } } -impl GraphicElementRendered for GraphicGroup { +impl GraphicElementRendered for GraphicGroupTable { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { - render.parent_tag( - "g", - |attributes| { - let matrix = format_transform_matrix(self.transform); - if !matrix.is_empty() { - attributes.push("transform", matrix); - } + for instance in self.instances() { + render.parent_tag( + "g", + |attributes| { + let matrix = format_transform_matrix(instance.transform()); + if !matrix.is_empty() { + attributes.push("transform", matrix); + } - if self.alpha_blending.opacity < 1. { - attributes.push("opacity", self.alpha_blending.opacity.to_string()); - } + if instance.alpha_blending.opacity < 1. { + attributes.push("opacity", instance.alpha_blending.opacity.to_string()); + } - if self.alpha_blending.blend_mode != BlendMode::default() { - attributes.push("style", self.alpha_blending.blend_mode.render()); - } - }, - |render| { - for (element, _) in self.iter() { - element.render_svg(render, render_params); - } - }, - ); + if instance.alpha_blending.blend_mode != BlendMode::default() { + attributes.push("style", instance.alpha_blending.blend_mode.render()); + } + }, + |render| { + for (element, _) in instance.instance.iter() { + element.render_svg(render, render_params); + } + }, + ); + } } fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { - self.iter().filter_map(|(element, _)| element.bounding_box(transform * self.transform)).reduce(Quad::combine_bounds) + self.instances() + .flat_map(|instance| { + instance + .instance + .iter() + .filter_map(|(element, _)| element.bounding_box(transform * instance.transform())) + .reduce(Quad::combine_bounds) + }) + .reduce(Quad::combine_bounds) } - fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option) { - footprint.transform *= self.transform; + fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { + let instance_transform = self.transform(); + let instance = self.one_instance().instance; - for (element, element_id) in self.elements.iter() { + let mut footprint = footprint; + footprint.transform *= instance_transform; + + for (element, element_id) in instance.elements.iter() { if let Some(element_id) = element_id { element.collect_metadata(metadata, footprint, Some(*element_id)); } @@ -352,104 +359,79 @@ impl GraphicElementRendered for GraphicGroup { if let Some(graphic_group_id) = element_id { let mut all_upstream_click_targets = Vec::new(); - self.add_upstream_click_targets(&mut all_upstream_click_targets); + + for (element, _) in instance.elements.iter() { + let mut new_click_targets = Vec::new(); + + element.add_upstream_click_targets(&mut new_click_targets); + + for click_target in new_click_targets.iter_mut() { + click_target.apply_transform(element.transform()) + } + + all_upstream_click_targets.extend(new_click_targets); + } + metadata.click_targets.insert(graphic_group_id, all_upstream_click_targets); } } fn add_upstream_click_targets(&self, click_targets: &mut Vec) { - for (element, _) in self.elements.iter() { - let mut new_click_targets = Vec::new(); + for instance in self.instances() { + for (element, _) in instance.instance.elements.iter() { + let mut new_click_targets = Vec::new(); - element.add_upstream_click_targets(&mut new_click_targets); + element.add_upstream_click_targets(&mut new_click_targets); - for click_target in new_click_targets.iter_mut() { - click_target.apply_transform(element.transform()) + for click_target in new_click_targets.iter_mut() { + click_target.apply_transform(element.transform()) + } + + click_targets.extend(new_click_targets); + } + } + } + + #[cfg(feature = "vello")] + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) { + for instance in self.instances() { + let transform = transform * instance.transform(); + let alpha_blending = *instance.alpha_blending; + + let blending = vello::peniko::BlendMode::new(alpha_blending.blend_mode.into(), vello::peniko::Compose::SrcOver); + let mut layer = false; + + if alpha_blending.opacity < 1. || alpha_blending.blend_mode != BlendMode::default() { + if let Some(bounds) = instance.instance.iter().filter_map(|(element, _)| element.bounding_box(transform)).reduce(Quad::combine_bounds) { + scene.push_layer( + blending, + alpha_blending.opacity, + kurbo::Affine::IDENTITY, + &vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y), + ); + layer = true; + } } - click_targets.extend(new_click_targets); - } - } + for (element, _) in instance.instance.iter() { + element.render_to_vello(scene, transform, context); + } - #[cfg(feature = "vello")] - fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) { - let child_transform = transform * self.transform; - - let Some(bounds) = self.bounding_box(transform) else { return }; - let blending = vello::peniko::BlendMode::new(self.alpha_blending.blend_mode.into(), vello::peniko::Compose::SrcOver); - let mut layer = false; - - if self.alpha_blending.opacity < 1. || self.alpha_blending.blend_mode != BlendMode::default() { - layer = true; - scene.push_layer( - blending, - self.alpha_blending.opacity, - kurbo::Affine::IDENTITY, - &vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y), - ); - } - - for (element, _) in self.iter() { - element.render_to_vello(scene, child_transform, context); - } - - if layer { - scene.pop_layer(); + if layer { + scene.pop_layer(); + } } } fn contains_artboard(&self) -> bool { - self.iter().any(|(element, _)| element.contains_artboard()) - } - - fn new_ids_from_hash(&mut self, _reference: Option) { - for (element, node_id) in self.elements.iter_mut() { - element.new_ids_from_hash(*node_id); - } - } - - fn to_graphic_element(&self) -> GraphicElement { - GraphicElement::GraphicGroup(GraphicGroupTable::new(self.clone())) - } -} - -impl GraphicElementRendered for GraphicGroupTable { - fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { - for instance in self.instances() { - instance.render_svg(render, render_params); - } - } - - fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { - self.instances().flat_map(|instance| instance.bounding_box(transform)).reduce(Quad::combine_bounds) - } - - fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { - let instance = self.one_item(); - - instance.collect_metadata(metadata, footprint, element_id); - } - - fn add_upstream_click_targets(&self, click_targets: &mut Vec) { - for instance in self.instances() { - instance.add_upstream_click_targets(click_targets); - } - } - - #[cfg(feature = "vello")] - fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) { - for instance in self.instances() { - instance.render_to_vello(scene, transform, context); - } - } - - fn contains_artboard(&self) -> bool { - self.instances().any(|instance| instance.contains_artboard()) + self.instances().any(|instance| instance.instance.iter().any(|(element, _)| element.contains_artboard())) } fn new_ids_from_hash(&mut self, _reference: Option) { for instance in self.instances_mut() { - instance.new_ids_from_hash(None); + for (element, node_id) in instance.instance.elements.iter_mut() { + element.new_ids_from_hash(*node_id); + } } } @@ -461,16 +443,21 @@ impl GraphicElementRendered for GraphicGroupTable { impl GraphicElementRendered for VectorDataTable { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { for instance in self.instances() { - let multiplied_transform = render.transform * instance.transform; - let set_stroke_transform = instance.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.); - let applied_stroke_transform = set_stroke_transform.unwrap_or(instance.transform); + let multiplied_transform = render.transform * instance.transform(); + let set_stroke_transform = instance + .instance + .style + .stroke() + .map(|stroke| stroke.transform) + .filter(|transform| transform.matrix2.determinant() != 0.); + let applied_stroke_transform = set_stroke_transform.unwrap_or(instance.transform()); let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse()); let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY); - let layer_bounds = instance.bounding_box().unwrap_or_default(); - let transformed_bounds = instance.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default(); + let layer_bounds = instance.instance.bounding_box().unwrap_or_default(); + let transformed_bounds = instance.instance.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default(); let mut path = String::new(); - for subpath in instance.stroke_bezier_paths() { + for subpath in instance.instance.stroke_bezier_paths() { let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform); } @@ -481,6 +468,7 @@ impl GraphicElementRendered for VectorDataTable { let defs = &mut attributes.0.svg_defs; let fill_and_stroke = instance + .instance .style .render(render_params.view_mode, defs, element_transform, applied_stroke_transform, layer_bounds, transformed_bounds); attributes.push_val(fill_and_stroke); @@ -499,22 +487,23 @@ impl GraphicElementRendered for VectorDataTable { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { self.instances() .flat_map(|instance| { - let stroke_width = instance.style.stroke().map(|s| s.weight()).unwrap_or_default(); + let stroke_width = instance.instance.style.stroke().map(|s| s.weight()).unwrap_or_default(); - let miter_limit = instance.style.stroke().map(|s| s.line_join_miter_limit).unwrap_or(1.); + let miter_limit = instance.instance.style.stroke().map(|s| s.line_join_miter_limit).unwrap_or(1.); let scale = transform.decompose_scale(); // We use the full line width here to account for different styles of line caps let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit); - instance.bounding_box_with_transform(transform * instance.transform).map(|[a, b]| [a - offset, b + offset]) + instance.instance.bounding_box_with_transform(transform * instance.transform()).map(|[a, b]| [a - offset, b + offset]) }) .reduce(Quad::combine_bounds) } fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option) { - let instance = self.one_item(); + let instance_transform = self.transform(); + let instance = self.one_instance().instance; if let Some(element_id) = element_id { let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight); @@ -536,15 +525,15 @@ impl GraphicElementRendered for VectorDataTable { } if let Some(upstream_graphic_group) = &instance.upstream_graphic_group { - footprint.transform *= instance.transform; + footprint.transform *= instance_transform; upstream_graphic_group.collect_metadata(metadata, footprint, None); } } fn add_upstream_click_targets(&self, click_targets: &mut Vec) { for instance in self.instances() { - let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight); - let filled = instance.style.fill() != &Fill::None; + let stroke_width = instance.instance.style.stroke().as_ref().map_or(0., Stroke::weight); + let filled = instance.instance.style.fill() != &Fill::None; let fill = |mut subpath: bezier_rs::Subpath<_>| { if filled { subpath.set_closed(true); @@ -552,7 +541,7 @@ impl GraphicElementRendered for VectorDataTable { subpath }; - click_targets.extend(instance.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width))); + click_targets.extend(instance.instance.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width))); } } @@ -564,12 +553,17 @@ impl GraphicElementRendered for VectorDataTable { for instance in self.instances() { let mut layer = false; - let multiplied_transform = parent_transform * instance.transform; - let set_stroke_transform = instance.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.); + let multiplied_transform = parent_transform * instance.transform(); + let set_stroke_transform = instance + .instance + .style + .stroke() + .map(|stroke| stroke.transform) + .filter(|transform| transform.matrix2.determinant() != 0.); let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform); let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse()); let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY); - let layer_bounds = instance.bounding_box().unwrap_or_default(); + let layer_bounds = instance.instance.bounding_box().unwrap_or_default(); if instance.alpha_blending.opacity < 1. || instance.alpha_blending.blend_mode != BlendMode::default() { layer = true; @@ -583,11 +577,11 @@ impl GraphicElementRendered for VectorDataTable { let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y); let mut path = kurbo::BezPath::new(); - for subpath in instance.stroke_bezier_paths() { + for subpath in instance.instance.stroke_bezier_paths() { subpath.to_vello_path(applied_stroke_transform, &mut path); } - match instance.style.fill() { + match instance.instance.style.fill() { Fill::Solid(color) => { let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])); scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path); @@ -601,7 +595,7 @@ impl GraphicElementRendered for VectorDataTable { }); } // Compute bounding box of the shape to determine the gradient start and end points - let bounds = instance.nonzero_bounding_box(); + let bounds = instance.instance.nonzero_bounding_box(); let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]); let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default(); @@ -638,7 +632,7 @@ impl GraphicElementRendered for VectorDataTable { Fill::None => (), }; - if let Some(stroke) = instance.style.stroke() { + if let Some(stroke) = instance.instance.style.stroke() { let color = match stroke.color { Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]), None => peniko::Color::TRANSPARENT, @@ -676,12 +670,12 @@ impl GraphicElementRendered for VectorDataTable { fn new_ids_from_hash(&mut self, reference: Option) { for instance in self.instances_mut() { - instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default()); + instance.instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default()); } } fn to_graphic_element(&self) -> GraphicElement { - let instance = self.one_item(); + let instance = self.one_instance().instance; GraphicElement::VectorData(VectorDataTable::new(instance.clone())) } @@ -833,11 +827,11 @@ impl GraphicElementRendered for ArtboardGroup { impl GraphicElementRendered for ImageFrameTable { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { for instance in self.instances() { - let transform = instance.transform * render.transform; + let transform = instance.transform() * render.transform; match render_params.image_render_mode { ImageRenderMode::Base64 => { - let image = &instance.image; + let image = &instance.instance.image; if image.data.is_empty() { return; } @@ -874,20 +868,20 @@ impl GraphicElementRendered for ImageFrameTable { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { self.instances() .flat_map(|instance| { - let transform = transform * instance.transform; + let transform = transform * instance.transform(); (transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box()) }) .reduce(Quad::combine_bounds) } fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { - let instance = self.one_item(); + let instance_transform = self.transform(); let Some(element_id) = element_id else { return }; let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]); - metadata.footprints.insert(element_id, (footprint, instance.transform)); + metadata.footprints.insert(element_id, (footprint, instance_transform)); } fn add_upstream_click_targets(&self, click_targets: &mut Vec) { @@ -900,12 +894,12 @@ impl GraphicElementRendered for ImageFrameTable { use vello::peniko; for instance in self.instances() { - let image = &instance.image; + let image = &instance.instance.image; if image.data.is_empty() { return; } let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat); - let transform = transform * instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); + let transform = transform * instance.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array())); } @@ -923,8 +917,8 @@ impl GraphicElementRendered for RasterFrame { RasterFrame::TextureFrame(_) => return, }; - for image in image.instances() { - let (image, blending) = (&image.image, image.alpha_blending); + for instance in image.instances() { + let (image, blending) = (&instance.instance.image, instance.alpha_blending); if image.data.is_empty() { return; } @@ -999,25 +993,26 @@ impl GraphicElementRendered for RasterFrame { match self { RasterFrame::ImageFrame(image_frame) => { - for image_frame in image_frame.instances() { - let image = &image_frame.image; + for instance in image_frame.instances() { + let image = &instance.instance.image; if image.data.is_empty() { return; } let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat); - render_stuff(image, image_frame.alpha_blending); + render_stuff(image, *instance.alpha_blending); } } RasterFrame::TextureFrame(texture) => { - for texture in texture.instances() { - let image = vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, texture.texture.width(), texture.texture.height()).with_extend(peniko::Extend::Repeat); + for instance in texture.instances() { + let image = + vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, instance.instance.texture.width(), instance.instance.texture.height()).with_extend(peniko::Extend::Repeat); let id = image.data.id(); - context.resource_overrides.insert(id, texture.texture.clone()); + context.resource_overrides.insert(id, instance.instance.texture.clone()); - render_stuff(image, texture.alpha_blend); + render_stuff(image, *instance.alpha_blending); } } } diff --git a/node-graph/gcore/src/instances.rs b/node-graph/gcore/src/instances.rs index 1eb1325dc..9260b6563 100644 --- a/node-graph/gcore/src/instances.rs +++ b/node-graph/gcore/src/instances.rs @@ -1,8 +1,12 @@ -use crate::vector::InstanceId; -use crate::GraphicElement; +use crate::application_io::{TextureFrame, TextureFrameTable}; +use crate::raster::image::{ImageFrame, ImageFrameTable}; +use crate::raster::Pixel; +use crate::transform::{Transform, TransformMut}; +use crate::vector::{InstanceId, VectorData, VectorDataTable}; +use crate::{AlphaBlending, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame}; use dyn_any::StaticType; - +use glam::{DAffine2, DVec2}; use std::hash::Hash; #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] @@ -11,54 +15,73 @@ where T: Into + StaticType + 'static, { id: Vec, - instances: Vec, + #[serde(alias = "instances")] + instance: Vec, + #[serde(default = "one_daffine2_default")] + transform: Vec, + #[serde(default = "one_alpha_blending_default")] + alpha_blending: Vec, } impl + StaticType + 'static> Instances { pub fn new(instance: T) -> Self { Self { id: vec![InstanceId::generate()], - instances: vec![instance], + instance: vec![instance], + transform: vec![DAffine2::IDENTITY], + alpha_blending: vec![AlphaBlending::default()], } } - pub fn one_item(&self) -> &T { - self.instances.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {} (one_item)", self.instances.len())) + pub fn one_instance(&self) -> Instance { + Instance { + id: self.id.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())), + instance: self.instance.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())), + transform: self.transform.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())), + alpha_blending: self.alpha_blending.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())), + } } - pub fn one_item_mut(&mut self) -> &mut T { - let length = self.instances.len(); - self.instances.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {} (one_item_mut)", length)) + pub fn one_instance_mut(&mut self) -> InstanceMut { + let length = self.instance.len(); + + InstanceMut { + id: self.id.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)), + instance: self.instance.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)), + transform: self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)), + alpha_blending: self.alpha_blending.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)), + } } - pub fn instances(&self) -> impl Iterator { - assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instances.len()); - self.instances.iter() + pub fn instances(&self) -> impl Iterator> { + assert!(self.instance.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instance.len()); + self.id + .iter() + .zip(self.instance.iter()) + .zip(self.transform.iter()) + .zip(self.alpha_blending.iter()) + .map(|(((id, instance), transform), alpha_blending)| Instance { + id, + instance, + transform, + alpha_blending, + }) } - pub fn instances_mut(&mut self) -> impl Iterator { - assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances_mut)", self.instances.len()); - self.instances.iter_mut() + pub fn instances_mut(&mut self) -> impl Iterator> { + assert!(self.instance.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances_mut)", self.instance.len()); + self.id + .iter_mut() + .zip(self.instance.iter_mut()) + .zip(self.transform.iter_mut()) + .zip(self.alpha_blending.iter_mut()) + .map(|(((id, instance), transform), alpha_blending)| InstanceMut { + id, + instance, + transform, + alpha_blending, + }) } - - // pub fn id(&self) -> impl Iterator + '_ { - // self.id.iter().copied() - // } - - // pub fn push(&mut self, id: InstanceId, instance: T) { - // self.id.push(id); - // self.instances.push(instance); - // } - - // pub fn replace_all(&mut self, id: InstanceId, instance: T) { - // let mut instance = instance; - - // for (old_id, old_instance) in self.id.iter_mut().zip(self.instances.iter_mut()) { - // let mut new_id = id; - // std::mem::swap(old_id, &mut new_id); - // std::mem::swap(&mut instance, old_instance); - // } - // } } impl + Default + Hash + StaticType + 'static> Default for Instances { @@ -70,7 +93,7 @@ impl + Default + Hash + StaticType + 'static> Default fo impl + Hash + StaticType + 'static> core::hash::Hash for Instances { fn hash(&self, state: &mut H) { self.id.hash(state); - for instance in &self.instances { + for instance in &self.instance { instance.hash(state); } } @@ -78,10 +101,208 @@ impl + Hash + StaticType + 'static> core::hash::Hash for impl + PartialEq + StaticType + 'static> PartialEq for Instances { fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.instances.len() == other.instances.len() && { self.instances.iter().zip(other.instances.iter()).all(|(a, b)| a == b) } + self.id == other.id && self.instance.len() == other.instance.len() && { self.instance.iter().zip(other.instance.iter()).all(|(a, b)| a == b) } } } unsafe impl + StaticType + 'static> dyn_any::StaticType for Instances { type Static = Instances; } + +fn one_daffine2_default() -> Vec { + vec![DAffine2::IDENTITY] +} +fn one_alpha_blending_default() -> Vec { + vec![AlphaBlending::default()] +} + +#[derive(Copy, Clone, Debug)] +pub struct Instance<'a, T> { + pub id: &'a InstanceId, + pub instance: &'a T, + pub transform: &'a DAffine2, + pub alpha_blending: &'a AlphaBlending, +} +#[derive(Debug)] +pub struct InstanceMut<'a, T> { + pub id: &'a mut InstanceId, + pub instance: &'a mut T, + pub transform: &'a mut DAffine2, + pub alpha_blending: &'a mut AlphaBlending, +} + +// GRAPHIC ELEMENT +impl Transform for GraphicElement { + fn transform(&self) -> DAffine2 { + match self { + GraphicElement::GraphicGroup(group) => group.transform(), + GraphicElement::VectorData(vector_data) => vector_data.transform(), + GraphicElement::RasterFrame(frame) => frame.transform(), + } + } +} +impl TransformMut for GraphicElement { + fn transform_mut(&mut self) -> &mut DAffine2 { + match self { + GraphicElement::GraphicGroup(group) => group.transform_mut(), + GraphicElement::VectorData(vector_data) => vector_data.transform_mut(), + GraphicElement::RasterFrame(frame) => frame.transform_mut(), + } + } +} + +// GRAPHIC GROUP +impl Transform for Instance<'_, GraphicGroup> { + fn transform(&self) -> DAffine2 { + *self.transform + } +} +impl Transform for InstanceMut<'_, GraphicGroup> { + fn transform(&self) -> DAffine2 { + *self.transform + } +} +impl TransformMut for InstanceMut<'_, GraphicGroup> { + fn transform_mut(&mut self) -> &mut DAffine2 { + self.transform + } +} + +// GRAPHIC GROUP TABLE +impl Transform for GraphicGroupTable { + fn transform(&self) -> DAffine2 { + self.one_instance().transform() + } +} +impl TransformMut for GraphicGroupTable { + fn transform_mut(&mut self) -> &mut DAffine2 { + self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED")) + } +} + +// TEXTURE FRAME +impl Transform for Instance<'_, TextureFrame> { + fn transform(&self) -> DAffine2 { + *self.transform + } +} +impl Transform for InstanceMut<'_, TextureFrame> { + fn transform(&self) -> DAffine2 { + *self.transform + } +} +impl TransformMut for InstanceMut<'_, TextureFrame> { + fn transform_mut(&mut self) -> &mut DAffine2 { + self.transform + } +} + +// TEXTURE FRAME TABLE +impl Transform for TextureFrameTable { + fn transform(&self) -> DAffine2 { + self.one_instance().transform() + } +} +impl TransformMut for TextureFrameTable { + fn transform_mut(&mut self) -> &mut DAffine2 { + self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED")) + } +} + +// IMAGE FRAME +impl Transform for Instance<'_, ImageFrame

> { + fn transform(&self) -> DAffine2 { + *self.transform + } + fn local_pivot(&self, pivot: DVec2) -> DVec2 { + self.transform.transform_point2(pivot) + } +} +impl Transform for InstanceMut<'_, ImageFrame

> { + fn transform(&self) -> DAffine2 { + *self.transform + } + fn local_pivot(&self, pivot: DVec2) -> DVec2 { + self.transform.transform_point2(pivot) + } +} +impl TransformMut for InstanceMut<'_, ImageFrame

> { + fn transform_mut(&mut self) -> &mut DAffine2 { + self.transform + } +} + +// IMAGE FRAME TABLE +impl Transform for ImageFrameTable

+where + P: dyn_any::StaticType, + P::Static: Pixel, + GraphicElement: From>, +{ + fn transform(&self) -> DAffine2 { + self.one_instance().transform() + } +} +impl TransformMut for ImageFrameTable

+where + P: dyn_any::StaticType, + P::Static: Pixel, + GraphicElement: From>, +{ + fn transform_mut(&mut self) -> &mut DAffine2 { + self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED")) + } +} + +// VECTOR DATA +impl Transform for Instance<'_, VectorData> { + fn transform(&self) -> DAffine2 { + *self.transform + } + fn local_pivot(&self, pivot: DVec2) -> DVec2 { + self.transform.transform_point2(self.instance.layerspace_pivot(pivot)) + } +} +impl Transform for InstanceMut<'_, VectorData> { + fn transform(&self) -> DAffine2 { + *self.transform + } + fn local_pivot(&self, pivot: DVec2) -> DVec2 { + self.transform.transform_point2(self.instance.layerspace_pivot(pivot)) + } +} +impl TransformMut for InstanceMut<'_, VectorData> { + fn transform_mut(&mut self) -> &mut DAffine2 { + self.transform + } +} + +// VECTOR DATA TABLE +impl Transform for VectorDataTable { + fn transform(&self) -> DAffine2 { + self.one_instance().transform() + } +} +impl TransformMut for VectorDataTable { + fn transform_mut(&mut self) -> &mut DAffine2 { + self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED")) + } +} + +// RASTER FRAME +impl Transform for RasterFrame { + fn transform(&self) -> DAffine2 { + match self { + RasterFrame::ImageFrame(image_frame) => image_frame.transform(), + RasterFrame::TextureFrame(texture_frame) => texture_frame.transform(), + } + } +} +impl TransformMut for RasterFrame { + fn transform_mut(&mut self) -> &mut DAffine2 { + match self { + RasterFrame::ImageFrame(image_frame) => image_frame.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED")), + RasterFrame::TextureFrame(texture_frame) => texture_frame.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED")), + } + } +} diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 3e35afbd0..60e870362 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -129,7 +129,7 @@ impl serde::Deserialize<'a>> Serde for T {} impl Serde for T {} // TODO: Come up with a better name for this trait -pub trait Pixel: Clone + Pod + Zeroable { +pub trait Pixel: Clone + Pod + Zeroable + Default { #[cfg(not(target_arch = "spirv"))] fn to_bytes(&self) -> Vec { bytemuck::bytes_of(self).to_vec() diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 9e875a8f5..845446cc8 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -605,17 +605,15 @@ impl Blend for ImageFrameTable { let mut result = self.clone(); for (over, under) in result.instances_mut().zip(under.instances()) { - let data = over.image.data.iter().zip(under.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect(); + let data = over.instance.image.data.iter().zip(under.instance.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect(); - *over = ImageFrame { + *over.instance = ImageFrame { image: super::Image { data, - width: over.image.width, - height: over.image.height, + width: over.instance.image.width, + height: over.instance.image.height, base64_string: None, }, - transform: over.transform, - alpha_blending: over.alpha_blending, }; } @@ -744,7 +742,7 @@ where { fn adjust(&mut self, map_fn: impl Fn(&P) -> P) { for instance in self.instances_mut() { - for c in instance.image.data.iter_mut() { + for c in instance.instance.image.data.iter_mut() { *c = map_fn(c); } } @@ -1582,10 +1580,7 @@ mod test { #[tokio::test] async fn color_overlay_multiply() { let image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4); - let image = ImageFrame { - image: Image::new(1, 1, image_color), - ..Default::default() - }; + let image = ImageFrame { image: Image::new(1, 1, image_color) }; // Color { red: 0., green: 1., blue: 0., alpha: 1. } let overlay_color = Color::GREEN; @@ -1594,7 +1589,7 @@ mod test { let opacity = 100_f64; let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity); - let result = result.one_item(); + let result = result.one_instance().instance; // The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) assert_eq!(result.image.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a())); diff --git a/node-graph/gcore/src/raster/brush_cache.rs b/node-graph/gcore/src/raster/brush_cache.rs index 38d007641..f4b08324e 100644 --- a/node-graph/gcore/src/raster/brush_cache.rs +++ b/node-graph/gcore/src/raster/brush_cache.rs @@ -1,16 +1,15 @@ -use core::hash::Hash; -use std::collections::HashMap; -use std::sync::Arc; -use std::sync::Mutex; - -use dyn_any::DynAny; - -use crate::raster::image::ImageFrame; +use crate::graphene_core::raster::image::ImageFrameTable; use crate::raster::Image; use crate::vector::brush_stroke::BrushStroke; use crate::vector::brush_stroke::BrushStyle; use crate::Color; +use core::hash::Hash; +use dyn_any::DynAny; +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::Mutex; + #[derive(Clone, Debug, PartialEq, DynAny, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] struct BrushCacheImpl { @@ -18,9 +17,12 @@ struct BrushCacheImpl { prev_input: Vec, // The strokes that have been fully processed and blended into the background. - background: ImageFrame, - blended_image: ImageFrame, - last_stroke_texture: ImageFrame, + #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))] + background: ImageFrameTable, + #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))] + blended_image: ImageFrameTable, + #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))] + last_stroke_texture: ImageFrameTable, // A cache for brush textures. #[cfg_attr(feature = "serde", serde(skip))] @@ -28,9 +30,9 @@ struct BrushCacheImpl { } impl BrushCacheImpl { - fn compute_brush_plan(&mut self, mut background: ImageFrame, input: &[BrushStroke]) -> BrushPlan { + fn compute_brush_plan(&mut self, mut background: ImageFrameTable, input: &[BrushStroke]) -> BrushPlan { // Do background invalidation. - if background.transform != self.background.transform || background.image != self.background.image { + if background.one_instance().instance.image != self.background.one_instance().instance.image { self.background = background.clone(); return BrushPlan { strokes: input.to_vec(), @@ -55,7 +57,7 @@ impl BrushCacheImpl { background = core::mem::take(&mut self.blended_image); // Check if the first non-blended stroke is an extension of the last one. - let mut first_stroke_texture = ImageFrame::default(); + let mut first_stroke_texture = ImageFrameTable::empty(); let mut first_stroke_point_skip = 0; let strokes = input[num_blended_strokes..].to_vec(); if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes { @@ -79,7 +81,7 @@ impl BrushCacheImpl { } } - pub fn cache_results(&mut self, input: Vec, blended_image: ImageFrame, last_stroke_texture: ImageFrame) { + pub fn cache_results(&mut self, input: Vec, blended_image: ImageFrameTable, last_stroke_texture: ImageFrameTable) { self.prev_input = input; self.blended_image = blended_image; self.last_stroke_texture = last_stroke_texture; @@ -94,8 +96,8 @@ impl Hash for BrushCacheImpl { #[derive(Clone, Debug, Default)] pub struct BrushPlan { pub strokes: Vec, - pub background: ImageFrame, - pub first_stroke_texture: ImageFrame, + pub background: ImageFrameTable, + pub first_stroke_texture: ImageFrameTable, pub first_stroke_point_skip: usize, } @@ -159,12 +161,12 @@ impl BrushCache { } } - pub fn compute_brush_plan(&self, background: ImageFrame, input: &[BrushStroke]) -> BrushPlan { + pub fn compute_brush_plan(&self, background: ImageFrameTable, input: &[BrushStroke]) -> BrushPlan { let mut inner = self.inner.lock().unwrap(); inner.compute_brush_plan(background, input) } - pub fn cache_results(&self, input: Vec, blended_image: ImageFrame, last_stroke_texture: ImageFrame) { + pub fn cache_results(&self, input: Vec, blended_image: ImageFrameTable, last_stroke_texture: ImageFrameTable) { let mut inner = self.inner.lock().unwrap(); inner.cache_results(input, blended_image, last_stroke_texture) } diff --git a/node-graph/gcore/src/raster/image.rs b/node-graph/gcore/src/raster/image.rs index f4f89e060..34eb5148c 100644 --- a/node-graph/gcore/src/raster/image.rs +++ b/node-graph/gcore/src/raster/image.rs @@ -1,6 +1,6 @@ use super::discrete_srgb::float_to_srgb_u8; use super::Color; -use crate::instances::Instances; +use crate::{instances::Instances, transform::TransformMut}; use crate::{AlphaBlending, GraphicElement}; use alloc::vec::Vec; use core::hash::{Hash, Hasher}; @@ -110,15 +110,6 @@ impl Hash for Image

{ } impl Image

{ - pub const fn empty() -> Self { - Self { - width: 0, - height: 0, - data: Vec::new(), - base64_string: None, - } - } - pub fn new(width: u32, height: u32, color: P) -> Self { Self { width, @@ -221,47 +212,50 @@ impl IntoIterator for Image

{ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { use serde::Deserialize; + #[derive(Clone, Default, Debug, PartialEq, specta::Type)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct OldImageFrame { + image: Image

, + transform: DAffine2, + alpha_blending: AlphaBlending, + } + #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] enum EitherFormat { ImageFrame(ImageFrame), + OldImageFrame(OldImageFrame), ImageFrameTable(ImageFrameTable), } Ok(match EitherFormat::deserialize(deserializer)? { EitherFormat::ImageFrame(image_frame) => ImageFrameTable::::new(image_frame), + EitherFormat::OldImageFrame(image_frame_with_transform_and_blending) => { + let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending; + let mut image_frame_table = ImageFrameTable::new(ImageFrame { image }); + *image_frame_table.one_instance_mut().transform = transform; + *image_frame_table.one_instance_mut().alpha_blending = alpha_blending; + image_frame_table + } EitherFormat::ImageFrameTable(image_frame_table) => image_frame_table, }) } pub type ImageFrameTable

= Instances>; -#[derive(Clone, Debug, PartialEq, specta::Type)] +/// Construct a 0x0 image frame table. This is useful because ImageFrameTable::default() will return a 1x1 image frame table. +impl ImageFrameTable { + pub fn empty() -> Self { + let mut result = Self::new(ImageFrame::default()); + *result.transform_mut() = DAffine2::ZERO; + result + } +} + +#[derive(Clone, Default, Debug, PartialEq, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ImageFrame { pub image: Image

, - // The transform that maps image space to layer space. - // - // Image space is unitless [0, 1] for both axes, with x axis positive - // going right and y axis positive going down, with the origin lying at - // the topleft of the image and (1, 1) lying at the bottom right of the image. - // - // Layer space has pixels as its units for both axes, with the x axis - // positive going right and y axis positive going down, with the origin - // being an unspecified quantity. - pub transform: DAffine2, - pub alpha_blending: AlphaBlending, -} - -impl Default for ImageFrame

{ - fn default() -> Self { - Self { - image: Image::empty(), - alpha_blending: AlphaBlending::new(), - // Different from DAffine2::default() which is IDENTITY - transform: DAffine2::ZERO, - } - } } impl Sample for ImageFrame

{ @@ -271,7 +265,6 @@ impl Sample for ImageFrame

{ #[inline(always)] fn sample(&self, pos: DVec2, _area: DVec2) -> Option { let image_size = DVec2::new(self.image.width() as f64, self.image.height() as f64); - let pos = (DAffine2::from_scale(image_size) * self.transform.inverse()).transform_point2(pos); if pos.x < 0. || pos.y < 0. || pos.x >= image_size.x || pos.y >= image_size.y { return None; } @@ -289,7 +282,11 @@ where // TODO: Improve sampling logic #[inline(always)] fn sample(&self, pos: DVec2, area: DVec2) -> Option { - let image = self.one_item(); + let image_transform = self.one_instance().transform; + let image = self.one_instance().instance; + + let image_size = DVec2::new(image.width() as f64, image.height() as f64); + let pos = (DAffine2::from_scale(image_size) * image_transform.inverse()).transform_point2(pos); Sample::sample(image, pos, area) } @@ -319,19 +316,19 @@ where type Pixel = P; fn width(&self) -> u32 { - let image = self.one_item(); + let image = self.one_instance().instance; image.width() } fn height(&self) -> u32 { - let image = self.one_item(); + let image = self.one_instance().instance; image.height() } fn get_pixel(&self, x: u32, y: u32) -> Option { - let image = self.one_item(); + let image = self.one_instance().instance; image.get_pixel(x, y) } @@ -349,7 +346,7 @@ where P::Static: Pixel, { fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> { - let image = self.one_item_mut(); + let image = self.one_instance_mut().instance; BitmapMut::get_pixel_mut(image, x, y) } @@ -384,19 +381,11 @@ impl AsRef> for ImageFrame

{ impl Hash for ImageFrame

{ fn hash(&self, state: &mut H) { - self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)); 0.hash(state); self.image.hash(state); } } -impl ImageFrame

{ - /// Compute the pivot in local space with the current transform applied - pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 { - self.transform.transform_point2(normalized_pivot) - } -} - /* This does not work because of missing specialization * so we have to manually implement this for now impl + Pixel, P: Pixel> From> for Image

{ @@ -420,8 +409,6 @@ impl From> for ImageFrame { height: image.image.height, base64_string: None, }, - transform: image.transform, - alpha_blending: image.alpha_blending, } } } @@ -436,8 +423,6 @@ impl From> for ImageFrame { height: image.image.height, base64_string: None, }, - transform: image.transform, - alpha_blending: image.alpha_blending, } } } diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 31ea15387..dbab82213 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -1,9 +1,8 @@ use crate::application_io::TextureFrameTable; use crate::raster::bbox::AxisAlignedBbox; -use crate::raster::image::{ImageFrame, ImageFrameTable}; -use crate::raster::Pixel; -use crate::vector::{VectorData, VectorDataTable}; -use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl}; +use crate::raster::image::ImageFrameTable; +use crate::vector::VectorDataTable; +use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedContextImpl}; use glam::{DAffine2, DVec2}; @@ -34,153 +33,6 @@ impl Transform for &T { } } -// Implementations for ImageFrame

-impl Transform for ImageFrame

{ - fn transform(&self) -> DAffine2 { - self.transform - } - fn local_pivot(&self, pivot: DVec2) -> DVec2 { - self.local_pivot(pivot) - } -} -impl TransformMut for ImageFrame

{ - fn transform_mut(&mut self) -> &mut DAffine2 { - &mut self.transform - } -} - -// Implementations for ImageFrameTable

-impl Transform for ImageFrameTable

-where - P: dyn_any::StaticType, - P::Static: Pixel, - GraphicElement: From>, -{ - fn transform(&self) -> DAffine2 { - let image_frame = self.one_item(); - image_frame.transform - } - fn local_pivot(&self, pivot: DVec2) -> DVec2 { - let image_frame = self.one_item(); - image_frame.local_pivot(pivot) - } -} -impl TransformMut for ImageFrameTable

-where - P: dyn_any::StaticType, - P::Static: Pixel, - GraphicElement: From>, -{ - fn transform_mut(&mut self) -> &mut DAffine2 { - let image_frame = self.one_item_mut(); - &mut image_frame.transform - } -} - -// Implementations for TextureTable -impl Transform for TextureFrameTable { - fn transform(&self) -> DAffine2 { - let image_frame = self.one_item(); - image_frame.transform - } - fn local_pivot(&self, pivot: DVec2) -> DVec2 { - let image_frame = self.one_item(); - image_frame.local_pivot(pivot) - } -} -impl TransformMut for TextureFrameTable { - fn transform_mut(&mut self) -> &mut DAffine2 { - let image_frame = self.one_item_mut(); - &mut image_frame.transform - } -} - -// Implementations for GraphicGroup -impl Transform for GraphicGroup { - fn transform(&self) -> DAffine2 { - self.transform - } -} -impl TransformMut for GraphicGroup { - fn transform_mut(&mut self) -> &mut DAffine2 { - &mut self.transform - } -} - -// Implementations for GraphicGroupTable -impl Transform for GraphicGroupTable { - fn transform(&self) -> DAffine2 { - let graphic_group = self.one_item(); - graphic_group.transform - } -} -impl TransformMut for GraphicGroupTable { - fn transform_mut(&mut self) -> &mut DAffine2 { - let graphic_group = self.one_item_mut(); - &mut graphic_group.transform - } -} - -// Implementations for GraphicElement -impl Transform for GraphicElement { - fn transform(&self) -> DAffine2 { - match self { - GraphicElement::VectorData(vector_shape) => vector_shape.transform(), - GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(), - GraphicElement::RasterFrame(raster) => raster.transform(), - } - } - fn local_pivot(&self, pivot: DVec2) -> DVec2 { - match self { - GraphicElement::VectorData(vector_shape) => vector_shape.local_pivot(pivot), - GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot), - GraphicElement::RasterFrame(raster) => raster.local_pivot(pivot), - } - } -} -impl TransformMut for GraphicElement { - fn transform_mut(&mut self) -> &mut DAffine2 { - match self { - GraphicElement::VectorData(vector_shape) => vector_shape.transform_mut(), - GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(), - GraphicElement::RasterFrame(raster) => raster.transform_mut(), - } - } -} - -// Implementations for VectorData -impl Transform for VectorData { - fn transform(&self) -> DAffine2 { - self.transform - } - fn local_pivot(&self, pivot: DVec2) -> DVec2 { - self.local_pivot(pivot) - } -} -impl TransformMut for VectorData { - fn transform_mut(&mut self) -> &mut DAffine2 { - &mut self.transform - } -} - -// Implementations for VectorDataTable -impl Transform for VectorDataTable { - fn transform(&self) -> DAffine2 { - let vector_data = self.one_item(); - vector_data.transform - } - fn local_pivot(&self, pivot: DVec2) -> DVec2 { - let vector_data = self.one_item(); - vector_data.local_pivot(pivot) - } -} -impl TransformMut for VectorDataTable { - fn transform_mut(&mut self) -> &mut DAffine2 { - let vector_data = self.one_item_mut(); - &mut vector_data.transform - } -} - // Implementations for Artboard impl Transform for Artboard { fn transform(&self) -> DAffine2 { diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index bcca892b3..b4a528702 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -1,4 +1,4 @@ -use crate::vector::{HandleId, PointId, VectorData, VectorDataTable}; +use crate::vector::{HandleId, VectorData, VectorDataTable}; use crate::Ctx; use bezier_rs::Subpath; @@ -105,15 +105,3 @@ fn star( fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable { VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end))) } - -// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node -#[node_macro::node(category(""))] -fn path(_: impl Ctx, path_data: Vec>, colinear_manipulators: Vec) -> VectorDataTable { - let mut vector_data = VectorData::from_subpaths(path_data, false); - vector_data.colinear_manipulators = colinear_manipulators - .iter() - .filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data)) - .collect(); - - VectorDataTable::new(vector_data) -} diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index 8502b7178..2134ac40f 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -17,16 +17,50 @@ use glam::{DAffine2, DVec2}; pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { use serde::Deserialize; + #[derive(Clone, Debug, PartialEq, DynAny)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct OldVectorData { + pub transform: DAffine2, + pub alpha_blending: AlphaBlending, + + pub style: PathStyle, + + /// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another). + /// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it). + pub colinear_manipulators: Vec<[HandleId; 2]>, + + pub point_domain: PointDomain, + pub segment_domain: SegmentDomain, + pub region_domain: RegionDomain, + + // Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved. + pub upstream_graphic_group: Option, + } + #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] #[allow(clippy::large_enum_variant)] enum EitherFormat { VectorData(VectorData), + OldVectorData(OldVectorData), VectorDataTable(VectorDataTable), } Ok(match EitherFormat::deserialize(deserializer)? { EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data), + EitherFormat::OldVectorData(old) => { + let mut vector_data_table = VectorDataTable::new(VectorData { + style: old.style, + colinear_manipulators: old.colinear_manipulators, + point_domain: old.point_domain, + segment_domain: old.segment_domain, + region_domain: old.region_domain, + upstream_graphic_group: old.upstream_graphic_group, + }); + *vector_data_table.one_instance_mut().transform = old.transform; + *vector_data_table.one_instance_mut().alpha_blending = old.alpha_blending; + vector_data_table + } EitherFormat::VectorDataTable(vector_data_table) => vector_data_table, }) } @@ -38,9 +72,8 @@ pub type VectorDataTable = Instances; #[derive(Clone, Debug, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct VectorData { - pub transform: DAffine2, pub style: PathStyle, - pub alpha_blending: AlphaBlending, + /// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another). /// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it). pub colinear_manipulators: Vec<[HandleId; 2]>, @@ -58,20 +91,17 @@ impl core::hash::Hash for VectorData { self.point_domain.hash(state); self.segment_domain.hash(state); self.region_domain.hash(state); - self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)); self.style.hash(state); - self.alpha_blending.hash(state); self.colinear_manipulators.hash(state); } } impl VectorData { /// An empty subpath with no data, an identity transform, and a black fill. + // TODO: Replace with just `Default` pub const fn empty() -> Self { Self { - transform: DAffine2::IDENTITY, style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None), - alpha_blending: AlphaBlending::new(), colinear_manipulators: Vec::new(), point_domain: PointDomain::new(), segment_domain: SegmentDomain::new(), @@ -190,11 +220,6 @@ impl VectorData { bounds_min + bounds_size * normalized_pivot } - /// Compute the pivot in local space with the current transform applied - pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 { - self.transform.transform_point2(self.layerspace_pivot(normalized_pivot)) - } - pub fn start_point(&self) -> impl Iterator + '_ { self.segment_domain.start_point().iter().map(|&index| self.point_domain.ids()[index]) } diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index b164b09e7..5134802de 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -1,3 +1,4 @@ +use crate::transform::Transform; use crate::vector::vector_data::{HandleId, VectorData, VectorDataTable}; use crate::vector::ConcatElement; @@ -82,32 +83,30 @@ impl core::hash::BuildHasher for NoHashBuilder { /// Stores data which is per-point. Each point is merely a position and can be used in a point cloud or to for a bézier path. In future this will be extendable at runtime with custom attributes. pub struct PointDomain { id: Vec, - positions: Vec, + #[serde(alias = "positions")] + position: Vec, } impl core::hash::Hash for PointDomain { fn hash(&self, state: &mut H) { self.id.hash(state); - self.positions.iter().for_each(|pos| pos.to_array().map(|v| v.to_bits()).hash(state)); + self.position.iter().for_each(|pos| pos.to_array().map(|v| v.to_bits()).hash(state)); } } impl PointDomain { pub const fn new() -> Self { - Self { - id: Vec::new(), - positions: Vec::new(), - } + Self { id: Vec::new(), position: Vec::new() } } pub fn clear(&mut self) { self.id.clear(); - self.positions.clear(); + self.position.clear(); } pub fn retain(&mut self, segment_domain: &mut SegmentDomain, f: impl Fn(&PointId) -> bool) { let mut keep = self.id.iter().map(&f); - self.positions.retain(|_| keep.next().unwrap_or_default()); + self.position.retain(|_| keep.next().unwrap_or_default()); // TODO(TrueDoctor): Consider using a prefix sum to avoid this Vec allocation (https://github.com/GraphiteEditor/Graphite/pull/1949#discussion_r1741711562) let mut id_map = Vec::with_capacity(self.ids().len()); @@ -131,19 +130,19 @@ impl PointDomain { pub fn push(&mut self, id: PointId, position: DVec2) { debug_assert!(!self.id.contains(&id)); self.id.push(id); - self.positions.push(position); + self.position.push(position); } pub fn positions(&self) -> &[DVec2] { - &self.positions + &self.position } pub fn positions_mut(&mut self) -> impl Iterator { - self.id.iter().copied().zip(self.positions.iter_mut()) + self.id.iter().copied().zip(self.position.iter_mut()) } pub fn set_position(&mut self, index: usize, position: DVec2) { - self.positions[index] = position; + self.position[index] = position; } pub fn ids(&self) -> &[PointId] { @@ -156,7 +155,7 @@ impl PointDomain { #[track_caller] pub fn position_from_id(&self, id: PointId) -> Option { - let pos = self.resolve_id(id).map(|index| self.positions[index]); + let pos = self.resolve_id(id).map(|index| self.position[index]); if pos.is_none() { warn!("Resolving pos of invalid id"); } @@ -169,7 +168,7 @@ impl PointDomain { fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) { self.id.extend(other.id.iter().map(|id| *id_map.point_map.get(id).unwrap_or(id))); - self.positions.extend(other.positions.iter().map(|&pos| transform.transform_point2(pos))); + self.position.extend(other.position.iter().map(|&pos| transform.transform_point2(pos))); } fn map_ids(&mut self, id_map: &IdMap) { @@ -177,7 +176,7 @@ impl PointDomain { } fn transform(&mut self, transform: DAffine2) { - for pos in &mut self.positions { + for pos in &mut self.position { *pos = transform.transform_point2(*pos); } } @@ -187,7 +186,8 @@ impl PointDomain { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] /// Stores data which is per-segment. A segment is a bézier curve between two end points with a stroke. In future this will be extendable at runtime with custom attributes. pub struct SegmentDomain { - ids: Vec, + #[serde(alias = "ids")] + id: Vec, start_point: Vec, end_point: Vec, handles: Vec, @@ -197,7 +197,7 @@ pub struct SegmentDomain { impl SegmentDomain { pub const fn new() -> Self { Self { - ids: Vec::new(), + id: Vec::new(), start_point: Vec::new(), end_point: Vec::new(), handles: Vec::new(), @@ -206,7 +206,7 @@ impl SegmentDomain { } pub fn clear(&mut self) { - self.ids.clear(); + self.id.clear(); self.start_point.clear(); self.end_point.clear(); self.handles.clear(); @@ -215,7 +215,7 @@ impl SegmentDomain { pub fn retain(&mut self, f: impl Fn(&SegmentId) -> bool, points_length: usize) { let additional_delete_ids = self - .ids + .id .iter() .zip(&self.start_point) .zip(&self.end_point) @@ -236,17 +236,17 @@ impl SegmentDomain { } }; - let mut keep = self.ids.iter().map(can_delete()); + let mut keep = self.id.iter().map(can_delete()); self.start_point.retain(|_| keep.next().unwrap_or_default()); - let mut keep = self.ids.iter().map(can_delete()); + let mut keep = self.id.iter().map(can_delete()); self.end_point.retain(|_| keep.next().unwrap_or_default()); - let mut keep = self.ids.iter().map(can_delete()); + let mut keep = self.id.iter().map(can_delete()); self.handles.retain(|_| keep.next().unwrap_or_default()); - let mut keep = self.ids.iter().map(can_delete()); + let mut keep = self.id.iter().map(can_delete()); self.stroke.retain(|_| keep.next().unwrap_or_default()); let mut delete_iter = additional_delete_ids.iter().peekable(); - self.ids.retain(move |id| { + self.id.retain(move |id| { if delete_iter.peek() == Some(&id) { delete_iter.next(); false @@ -257,7 +257,7 @@ impl SegmentDomain { } pub fn ids(&self) -> &[SegmentId] { - &self.ids + &self.id } pub fn next_id(&self) -> SegmentId { @@ -289,9 +289,9 @@ impl SegmentDomain { } pub(crate) fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: bezier_rs::BezierHandles, stroke: StrokeId) { - debug_assert!(!self.ids.contains(&id), "Tried to push an existing point to a point domain"); + debug_assert!(!self.id.contains(&id), "Tried to push an existing point to a point domain"); - self.ids.push(id); + self.id.push(id); self.start_point.push(start); self.end_point.push(end); self.handles.push(handles); @@ -299,15 +299,15 @@ impl SegmentDomain { } pub(crate) fn start_point_mut(&mut self) -> impl Iterator { - self.ids.iter().copied().zip(self.start_point.iter_mut()) + self.id.iter().copied().zip(self.start_point.iter_mut()) } pub(crate) fn end_point_mut(&mut self) -> impl Iterator { - self.ids.iter().copied().zip(self.end_point.iter_mut()) + self.id.iter().copied().zip(self.end_point.iter_mut()) } pub(crate) fn handles_mut(&mut self) -> impl Iterator { - let nested = self.ids.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point); + let nested = self.id.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point); nested.map(|(((&a, b), &c), &d)| (a, b, c, d)) } @@ -317,7 +317,7 @@ impl SegmentDomain { } pub fn stroke_mut(&mut self) -> impl Iterator { - self.ids.iter().copied().zip(self.stroke.iter_mut()) + self.id.iter().copied().zip(self.stroke.iter_mut()) } pub(crate) fn segment_start_from_id(&self, segment: SegmentId) -> Option { @@ -348,15 +348,15 @@ impl SegmentDomain { } fn id_to_index(&self, id: SegmentId) -> Option { - debug_assert_eq!(self.ids.len(), self.handles.len()); - debug_assert_eq!(self.ids.len(), self.start_point.len()); - debug_assert_eq!(self.ids.len(), self.end_point.len()); - self.ids.iter().position(|&check_id| check_id == id) + debug_assert_eq!(self.id.len(), self.handles.len()); + debug_assert_eq!(self.id.len(), self.start_point.len()); + debug_assert_eq!(self.id.len(), self.end_point.len()); + self.id.iter().position(|&check_id| check_id == id) } fn resolve_range(&self, range: &core::ops::RangeInclusive) -> Option> { match (self.id_to_index(*range.start()), self.id_to_index(*range.end())) { - (Some(start), Some(end)) if start.max(end) < self.handles.len().min(self.ids.len()).min(self.start_point.len()).min(self.end_point.len()) => Some(start..=end), + (Some(start), Some(end)) if start.max(end) < self.handles.len().min(self.id.len()).min(self.start_point.len()).min(self.end_point.len()) => Some(start..=end), _ => { warn!("Resolving range with invalid id"); None @@ -365,7 +365,7 @@ impl SegmentDomain { } fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) { - self.ids.extend(other.ids.iter().map(|id| *id_map.segment_map.get(id).unwrap_or(id))); + self.id.extend(other.id.iter().map(|id| *id_map.segment_map.get(id).unwrap_or(id))); self.start_point.extend(other.start_point.iter().map(|&index| id_map.point_offset + index)); self.end_point.extend(other.end_point.iter().map(|&index| id_map.point_offset + index)); self.handles.extend(other.handles.iter().map(|handles| handles.apply_transformation(|p| transform.transform_point2(p)))); @@ -373,7 +373,7 @@ impl SegmentDomain { } fn map_ids(&mut self, id_map: &IdMap) { - self.ids.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id)); + self.id.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id)); } fn transform(&mut self, transform: DAffine2) { @@ -384,12 +384,12 @@ impl SegmentDomain { /// Enumerate all segments that start at the point. pub(crate) fn start_connected(&self, point: usize) -> impl Iterator + '_ { - self.start_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg) + self.start_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg) } /// Enumerate all segments that end at the point. pub(crate) fn end_connected(&self, point: usize) -> impl Iterator + '_ { - self.end_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg) + self.end_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg) } /// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment. @@ -407,7 +407,8 @@ impl SegmentDomain { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] /// Stores data which is per-region. A region is an enclosed area composed of a range of segments from the [`SegmentDomain`] that can be given a fill. In future this will be extendable at runtime with custom attributes. pub struct RegionDomain { - ids: Vec, + #[serde(alias = "ids")] + id: Vec, segment_range: Vec>, fill: Vec, } @@ -415,54 +416,54 @@ pub struct RegionDomain { impl RegionDomain { pub const fn new() -> Self { Self { - ids: Vec::new(), + id: Vec::new(), segment_range: Vec::new(), fill: Vec::new(), } } pub fn clear(&mut self) { - self.ids.clear(); + self.id.clear(); self.segment_range.clear(); self.fill.clear(); } pub fn retain(&mut self, f: impl Fn(&RegionId) -> bool) { - let mut keep = self.ids.iter().map(&f); + let mut keep = self.id.iter().map(&f); self.segment_range.retain(|_| keep.next().unwrap_or_default()); - let mut keep = self.ids.iter().map(&f); + let mut keep = self.id.iter().map(&f); self.fill.retain(|_| keep.next().unwrap_or_default()); - self.ids.retain(&f); + self.id.retain(&f); } pub fn push(&mut self, id: RegionId, segment_range: core::ops::RangeInclusive, fill: FillId) { - if self.ids.contains(&id) { + if self.id.contains(&id) { warn!("Duplicate region"); return; } - self.ids.push(id); + self.id.push(id); self.segment_range.push(segment_range); self.fill.push(fill); } fn _resolve_id(&self, id: RegionId) -> Option { - self.ids.iter().position(|&check_id| check_id == id) + self.id.iter().position(|&check_id| check_id == id) } pub fn next_id(&self) -> RegionId { - self.ids.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO) + self.id.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO) } pub fn segment_range_mut(&mut self) -> impl Iterator)> { - self.ids.iter().copied().zip(self.segment_range.iter_mut()) + self.id.iter().copied().zip(self.segment_range.iter_mut()) } pub fn fill_mut(&mut self) -> impl Iterator { - self.ids.iter().copied().zip(self.fill.iter_mut()) + self.id.iter().copied().zip(self.fill.iter_mut()) } pub fn ids(&self) -> &[RegionId] { - &self.ids + &self.id } pub fn segment_range(&self) -> &[core::ops::RangeInclusive] { @@ -474,7 +475,7 @@ impl RegionDomain { } fn concat(&mut self, other: &Self, _transform: DAffine2, id_map: &IdMap) { - self.ids.extend(other.ids.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id))); + self.id.extend(other.id.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id))); self.segment_range.extend( other .segment_range @@ -485,7 +486,7 @@ impl RegionDomain { } fn map_ids(&mut self, id_map: &IdMap) { - self.ids.iter_mut().for_each(|id| *id = *id_map.region_map.get(id).unwrap_or(id)); + self.id.iter_mut().for_each(|id| *id = *id_map.region_map.get(id).unwrap_or(id)); self.segment_range .iter_mut() .for_each(|range| *range = *id_map.segment_map.get(range.start()).unwrap_or(range.start())..=*id_map.segment_map.get(range.end()).unwrap_or(range.end())); @@ -525,7 +526,7 @@ impl VectorData { self.segment_domain .handles .iter() - .zip(&self.segment_domain.ids) + .zip(&self.segment_domain.id) .zip(self.segment_domain.start_point()) .zip(self.segment_domain.end_point()) .map(to_bezier) @@ -574,7 +575,7 @@ impl VectorData { /// Construct a [`bezier_rs::Bezier`] curve for each region, skipping invalid regions. pub fn region_bezier_paths(&self) -> impl Iterator)> + '_ { self.region_domain - .ids + .id .iter() .zip(&self.region_domain.segment_range) .filter_map(|(&id, segment_range)| self.segment_domain.resolve_range(segment_range).map(|range| (id, range))) @@ -774,16 +775,16 @@ impl ConcatElement for VectorData { let point_map = new_ids.collect::>(); let new_ids = other .segment_domain - .ids + .id .iter() - .filter(|id| self.segment_domain.ids.contains(id)) + .filter(|id| self.segment_domain.id.contains(id)) .map(|&old| (old, old.generate_from_hash(node_id))); let segment_map = new_ids.collect::>(); let new_ids = other .region_domain - .ids + .id .iter() - .filter(|id| self.region_domain.ids.contains(id)) + .filter(|id| self.region_domain.id.contains(id)) .map(|&old| (old, old.generate_from_hash(node_id))); let region_map = new_ids.collect::>(); let id_map = IdMap { @@ -792,20 +793,20 @@ impl ConcatElement for VectorData { segment_map, region_map, }; - self.point_domain.concat(&other.point_domain, transform * other.transform, &id_map); - self.segment_domain.concat(&other.segment_domain, transform * other.transform, &id_map); - self.region_domain.concat(&other.region_domain, transform * other.transform, &id_map); + self.point_domain.concat(&other.point_domain, transform, &id_map); + self.segment_domain.concat(&other.segment_domain, transform, &id_map); + self.region_domain.concat(&other.region_domain, transform, &id_map); // TODO: properly deal with fills such as gradients self.style = other.style.clone(); self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied()); - self.alpha_blending = other.alpha_blending; } } impl ConcatElement for VectorDataTable { fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) { for (instance, other_instance) in self.instances_mut().zip(other.instances()) { - instance.concat(other_instance, transform, node_id); + *instance.alpha_blending = *other_instance.alpha_blending; + instance.instance.concat(other_instance.instance, transform * other_instance.transform(), node_id); } } } diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 221e41ad3..e5060d196 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -1,4 +1,5 @@ use super::*; +use crate::transform::TransformMut; use crate::uuid::generate_uuid; use crate::Ctx; @@ -425,11 +426,14 @@ impl core::hash::Hash for VectorModification { /// A node that applies a procedural modification to some [`VectorData`]. #[node_macro::node(category(""))] async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box) -> VectorDataTable { - let vector_data = vector_data.one_item_mut(); + let vector_data_transform = *vector_data.one_instance().transform; + let vector_data = vector_data.one_instance_mut().instance; modification.apply(vector_data); - VectorDataTable::new(vector_data.clone()) + let mut result = VectorDataTable::new(vector_data.clone()); + *result.transform_mut() = vector_data_transform; + result } #[test] diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 1201e8818..4c1a42b0f 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -1,12 +1,13 @@ use super::misc::CentroidType; use super::style::{Fill, Gradient, GradientStops, Stroke}; use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable}; +use crate::instances::InstanceMut; use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue}; use crate::renderer::GraphicElementRendered; use crate::transform::{Footprint, Transform, TransformMut}; use crate::vector::style::LineJoin; use crate::vector::PointDomain; -use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl}; +use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl}; use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue}; use glam::{DAffine2, DVec2}; @@ -20,15 +21,16 @@ trait VectorIterMut { impl VectorIterMut for GraphicGroupTable { fn vector_iter_mut(&mut self) -> impl Iterator { - let instance = self.one_item_mut(); - - let parent_transform = instance.transform; + let parent_transform = self.transform(); + let instance = self.one_instance_mut().instance; // Grab only the direct children instance.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector_data| { - let vector_data = vector_data.one_item_mut(); - let transform = parent_transform * vector_data.transform; - (vector_data, transform) + let transform = parent_transform * vector_data.transform(); + + let vector_data_instance = vector_data.one_instance_mut().instance; + + (vector_data_instance, transform) }) } } @@ -36,8 +38,8 @@ impl VectorIterMut for GraphicGroupTable { impl VectorIterMut for VectorDataTable { fn vector_iter_mut(&mut self) -> impl Iterator { self.instances_mut().map(|instance| { - let transform = instance.transform; - (instance, transform) + let transform = instance.transform(); + (instance.instance, transform) }) } } @@ -176,11 +178,10 @@ async fn repeat( random_rotation: Angle, random_rotation_seed: SeedValue, ) -> GraphicGroupTable { - let points = points.one_item(); + let points_transform = points.transform(); + let points = points.one_instance().instance; let instance_transform = instance.transform(); @@ -266,11 +267,13 @@ async fn copy_to_points( let do_scale = random_scale_difference.abs() > 1e-6; let do_rotation = random_rotation.abs() > 1e-6; - let mut result = GraphicGroup::default(); + let mut result_table = GraphicGroupTable::default(); + let result = result_table.one_instance_mut().instance; + for &point in points_list { let center_transform = DAffine2::from_translation(instance_center); - let translation = points.transform.transform_point2(point); + let translation = points_transform.transform_point2(point); let rotation = if do_rotation { let degrees = (rotation_rng.random::() - 0.5) * random_rotation; @@ -301,14 +304,15 @@ async fn copy_to_points( result.push((new_instance, None)); } - GraphicGroupTable::new(result) + result_table } #[node_macro::node(category("Vector"), path(graphene_core::vector))] async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { - let vector_data = vector_data.one_item(); + let vector_data_transform = vector_data.transform(); + let vector_data = vector_data.one_instance().instance; - let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap(); + let bounding_box = vector_data.bounding_box_with_transform(vector_data_transform).unwrap(); let mut result = VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1])); result.style = vector_data.style.clone(); result.style.set_stroke_transform(DAffine2::IDENTITY); @@ -318,7 +322,8 @@ async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTa #[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))] async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable { - let vector_data = vector_data.one_item(); + let vector_data_transform = vector_data.transform(); + let vector_data = vector_data.one_instance().instance; let subpaths = vector_data.stroke_bezier_paths(); let mut result = VectorData::empty(); @@ -327,7 +332,7 @@ async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, l // Perform operation on all subpaths in this shape. for mut subpath in subpaths { - subpath.apply_transform(vector_data.transform); + subpath.apply_transform(vector_data_transform); // Taking the existing stroke data and passing it to Bezier-rs to generate new paths. let subpath_out = subpath.offset( @@ -348,9 +353,9 @@ async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, l #[node_macro::node(category("Vector"), path(graphene_core::vector))] async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { - let vector_data = vector_data.one_item(); + let vector_data_transform = vector_data.transform(); + let vector_data = vector_data.one_instance().instance; - let transform = &vector_data.transform; let style = &vector_data.style; let subpaths = vector_data.stroke_bezier_paths(); @@ -359,7 +364,7 @@ async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDat // Perform operation on all subpaths in this shape. for mut subpath in subpaths { let stroke = style.stroke().unwrap(); - subpath.apply_transform(*transform); + subpath.apply_transform(vector_data_transform); // Taking the existing stroke data and passing it to Bezier-rs to generate new paths. let subpath_out = subpath.outline( @@ -398,34 +403,35 @@ async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDat #[node_macro::node(category("Vector"), path(graphene_core::vector))] async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupTable) -> VectorDataTable { - let graphic_group_input = graphic_group_input.one_item(); - // A node based solution to support passing through vector data could be a network node with a cache node connected to // a flatten vector elements connected to an if else node, another connection from the cache directly // To the if else node, and another connection from the cache to a matches type node connected to the if else node. - fn concat_group(graphic_group: &GraphicGroup, current_transform: DAffine2, result: &mut VectorData) { - for (element, reference) in graphic_group.iter() { + fn concat_group(graphic_group_table: &GraphicGroupTable, current_transform: DAffine2, result: &mut InstanceMut) { + for (element, reference) in graphic_group_table.one_instance().instance.iter() { match element { GraphicElement::VectorData(vector_data) => { for instance in vector_data.instances() { - result.concat(instance, current_transform, reference.map(|node_id| node_id.0).unwrap_or_default()); + *result.alpha_blending = *instance.alpha_blending; + result + .instance + .concat(instance.instance, current_transform * instance.transform(), reference.map(|node_id| node_id.0).unwrap_or_default()); } } GraphicElement::GraphicGroup(graphic_group) => { - let graphic_group = graphic_group.one_item(); - concat_group(graphic_group, current_transform * graphic_group.transform, result); + concat_group(graphic_group, current_transform * graphic_group.transform(), result); } _ => {} } } } - let mut result = VectorData::empty(); - concat_group(graphic_group_input, DAffine2::IDENTITY, &mut result); + let mut result_table = VectorDataTable::default(); + let mut result_instance = result_table.one_instance_mut(); // TODO: This leads to incorrect stroke widths when flattening groups with different transforms. - result.style.set_stroke_transform(DAffine2::IDENTITY); + result_instance.instance.style.set_stroke_transform(DAffine2::IDENTITY); + concat_group(&graphic_group_input, DAffine2::IDENTITY, &mut result_instance); - VectorDataTable::new(result) + result_table } pub trait ConcatElement { @@ -434,16 +440,18 @@ pub trait ConcatElement { impl ConcatElement for GraphicGroupTable { fn concat(&mut self, other: &Self, transform: DAffine2, _node_id: u64) { - let own = self.one_item_mut(); - let other = other.one_item(); + let other_transform = other.transform(); + + let self_group = self.one_instance_mut().instance; + let other_group = other.one_instance().instance; // TODO: Decide if we want to keep this behavior whereby the layers are flattened - for (mut element, footprint_mapping) in other.iter().cloned() { - *element.transform_mut() = transform * element.transform() * other.transform(); - own.push((element, footprint_mapping)); + for (mut element, footprint_mapping) in other_group.iter().cloned() { + *element.transform_mut() = transform * element.transform() * other_transform; + self_group.push((element, footprint_mapping)); } - own.alpha_blending = other.alpha_blending; + *self.one_instance_mut().alpha_blending = *other.one_instance().alpha_blending; } } @@ -451,14 +459,16 @@ impl ConcatElement for GraphicGroupTable { async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, subpath_segment_lengths: Vec) -> VectorDataTable { // Limit the smallest spacing to something sensible to avoid freezing the application. let spacing = spacing.max(0.01); - let vector_data = vector_data.one_item(); + + let vector_data_transform = vector_data.transform(); + let vector_data = vector_data.one_instance().instance; // Create an iterator over the bezier segments with enumeration and peeking capability. let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable(); // Initialize the result VectorData with the same transformation as the input. - let mut result = VectorData::empty(); - result.transform = vector_data.transform; + let mut result = VectorDataTable::default(); + *result.transform_mut() = vector_data_transform; // Iterate over each segment in the bezier iterator. while let Some((index, (segment_id, _, start_point_index, mut last_end))) = bezier.next() { @@ -537,7 +547,7 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, // Retrieve the segment and apply transformation. let Some(segment) = vector_data.segment_from_id(current_segment_id) else { continue }; - let segment = segment.apply_transformation(|point| vector_data.transform.transform_point2(point)); + let segment = segment.apply_transformation(|point| vector_data_transform.transform_point2(point)); // Calculate the position on the segment. let parametric_t = segment.euclidean_to_parametric_with_total_length((total_distance - total_length_before) / length, 0.001, length); @@ -545,10 +555,10 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, // Generate a new PointId and add the point to result.point_domain. let point_id = PointId::generate(); - result.point_domain.push(point_id, vector_data.transform.inverse().transform_point2(point)); + result.one_instance_mut().instance.point_domain.push(point_id, vector_data_transform.inverse().transform_point2(point)); // Store the index of the point. - let point_index = result.point_domain.ids().len() - 1; + let point_index = result.one_instance_mut().instance.point_domain.ids().len() - 1; point_indices.push(point_index); } @@ -565,7 +575,7 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, let stroke_id = StrokeId::generate(); // Add the segment to result.segment_domain. - result.segment_domain.push(segment_id, start_index, end_index, handles, stroke_id); + result.one_instance_mut().instance.segment_domain.push(segment_id, start_index, end_index, handles, stroke_id); } } @@ -582,17 +592,17 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, let stroke_id = StrokeId::generate(); // Add the closing segment to result.segment_domain. - result.segment_domain.push(segment_id, last_index, first_index, handles, stroke_id); + result.one_instance_mut().instance.segment_domain.push(segment_id, last_index, first_index, handles, stroke_id); } } } // Transfer the style from the input vector data to the result. - result.style = vector_data.style.clone(); - result.style.set_stroke_transform(vector_data.transform); + result.one_instance_mut().instance.style = vector_data.style.clone(); + result.one_instance_mut().instance.style.set_stroke_transform(vector_data_transform); // Return the resulting vector data with newly generated points and segments. - VectorDataTable::new(result) + result } #[node_macro::node(category(""), path(graphene_core::vector))] @@ -604,7 +614,8 @@ async fn poisson_disk_points( separation_disk_diameter: f64, seed: SeedValue, ) -> VectorDataTable { - let vector_data = vector_data.one_item(); + let vector_data_transform = vector_data.transform(); + let vector_data = vector_data.one_instance().instance; let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let mut result = VectorData::empty(); @@ -618,7 +629,7 @@ async fn poisson_disk_points( continue; } - subpath.apply_transform(vector_data.transform); + subpath.apply_transform(vector_data_transform); let mut previous_point_index: Option = None; @@ -648,17 +659,18 @@ async fn poisson_disk_points( #[node_macro::node(category(""), path(graphene_core::vector))] async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec { - let vector_data = vector_data.one_item(); + let vector_data_transform = vector_data.transform(); + let vector_data = vector_data.one_instance().instance; vector_data .segment_bezier_iter() - .map(|(_id, bezier, _, _)| bezier.apply_transformation(|point| vector_data.transform.transform_point2(point)).length(None)) + .map(|(_id, bezier, _, _)| bezier.apply_transformation(|point| vector_data_transform.transform_point2(point)).length(None)) .collect() } #[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))] async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable { - let vector_data = vector_data.one_item_mut(); + let vector_data = vector_data.one_instance_mut().instance; // Exit early if there are no points to generate splines from. if vector_data.point_domain.positions().is_empty() { @@ -700,7 +712,8 @@ async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTabl #[node_macro::node(category("Vector"), path(graphene_core::vector))] async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable { - let mut vector_data = vector_data.one_item().clone(); + let vector_data_transform = vector_data.transform(); + let mut vector_data = vector_data.one_instance().instance.clone(); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); @@ -718,30 +731,29 @@ async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] if !already_applied[*start] { let start_position = vector_data.point_domain.positions()[*start]; - let start_position = vector_data.transform.transform_point2(start_position); + let start_position = vector_data_transform.transform_point2(start_position); vector_data.point_domain.set_position(*start, start_position + start_delta); already_applied[*start] = true; } if !already_applied[*end] { let end_position = vector_data.point_domain.positions()[*end]; - let end_position = vector_data.transform.transform_point2(end_position); + let end_position = vector_data_transform.transform_point2(end_position); vector_data.point_domain.set_position(*end, end_position + end_delta); already_applied[*end] = true; } match handles { bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { - *handle_start = vector_data.transform.transform_point2(*handle_start) + start_delta; - *handle_end = vector_data.transform.transform_point2(*handle_end) + end_delta; + *handle_start = vector_data_transform.transform_point2(*handle_start) + start_delta; + *handle_end = vector_data_transform.transform_point2(*handle_end) + end_delta; } bezier_rs::BezierHandles::Quadratic { handle } => { - *handle = vector_data.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.; + *handle = vector_data_transform.transform_point2(*handle) + (start_delta + end_delta) / 2.; } bezier_rs::BezierHandles::Linear => {} } } - vector_data.transform = DAffine2::IDENTITY; vector_data.style.set_stroke_transform(DAffine2::IDENTITY); VectorDataTable::new(vector_data) @@ -757,23 +769,29 @@ async fn morph( time: Fraction, #[min(0.)] start_index: IntegerCount, ) -> VectorDataTable { - let source = source.one_item(); - let target = target.one_item(); - - let mut result = VectorData::empty(); - let time = time.clamp(0., 1.); + let source_alpha_blending = source.one_instance().alpha_blending; + let target_alpha_blending = target.one_instance().alpha_blending; + + let source_transform = source.transform(); + let target_transform = target.transform(); + + let source = source.one_instance().instance; + let target = target.one_instance().instance; + + let mut result = VectorDataTable::default(); + // Lerp styles - result.alpha_blending = if time < 0.5 { source.alpha_blending } else { target.alpha_blending }; - result.style = source.style.lerp(&target.style, time); + *result.one_instance_mut().alpha_blending = if time < 0.5 { *source_alpha_blending } else { *target_alpha_blending }; + result.one_instance_mut().instance.style = source.style.lerp(&target.style, time); let mut source_paths = source.stroke_bezier_paths(); let mut target_paths = target.stroke_bezier_paths(); for (mut source_path, mut target_path) in (&mut source_paths).zip(&mut target_paths) { // Deal with mismatched transforms - source_path.apply_transform(source.transform); - target_path.apply_transform(target.transform); + source_path.apply_transform(source_transform); + target_path.apply_transform(target_transform); // Deal with mismatched start index for _ in 0..start_index { @@ -816,11 +834,12 @@ async fn morph( manipulator.anchor = manipulator.anchor.lerp(target.anchor, time); } - result.append_subpath(source_path, true); + result.one_instance_mut().instance.append_subpath(source_path, true); } + // Mismatched subpath count for mut source_path in source_paths { - source_path.apply_transform(source.transform); + source_path.apply_transform(source_transform); let end = source_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default(); for group in source_path.manipulator_groups_mut() { group.anchor = group.anchor.lerp(end, time); @@ -829,7 +848,7 @@ async fn morph( } } for mut target_path in target_paths { - target_path.apply_transform(target.transform); + target_path.apply_transform(target_transform); let start = target_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default(); for group in target_path.manipulator_groups_mut() { group.anchor = start.lerp(group.anchor, time); @@ -838,10 +857,10 @@ async fn morph( } } - VectorDataTable::new(result) + result } -fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData { +fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, distance: f64) -> VectorData { // Splits a bézier curve based on a distance measurement fn split_distance(bezier: bezier_rs::Bezier, distance: f64, length: f64) -> bezier_rs::Bezier { const EUCLIDEAN_ERROR: f64 = 0.001; @@ -885,7 +904,7 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData { } } - fn update_existing_segments(vector_data: &mut VectorData, distance: f64, segments_connected: &mut [u8]) -> Vec<[usize; 2]> { + fn update_existing_segments(vector_data: &mut VectorData, vector_data_transform: DAffine2, distance: f64, segments_connected: &mut [u8]) -> Vec<[usize; 2]> { let mut next_id = vector_data.point_domain.next_id(); let mut new_segments = Vec::new(); @@ -900,8 +919,8 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData { if bezier.is_linear() { bezier.handles = bezier_rs::BezierHandles::Linear; } - bezier = bezier.apply_transformation(|p| vector_data.transform.transform_point2(p)); - let inverse_transform = (vector_data.transform.matrix2.determinant() != 0.).then(|| vector_data.transform.inverse()).unwrap_or_default(); + bezier = bezier.apply_transformation(|p| vector_data_transform.transform_point2(p)); + let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default(); let original_length = bezier.length(None); let mut length = original_length; @@ -942,7 +961,7 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData { } let mut segments_connected = segments_connected_count(&vector_data); - let new_segments = update_existing_segments(&mut vector_data, distance, &mut segments_connected); + let new_segments = update_existing_segments(&mut vector_data, vector_data_transform, distance, &mut segments_connected); insert_new_segments(&mut vector_data, &new_segments); vector_data @@ -950,22 +969,28 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData { #[node_macro::node(category("Vector"), path(graphene_core::vector))] fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable { - let source = source.one_item(); + let source_transform = source.transform(); + let source = source.one_instance().instance; - VectorDataTable::new(bevel_algorithm(source.clone(), distance)) + let mut result = VectorDataTable::new(bevel_algorithm(source.clone(), source_transform, distance)); + *result.transform_mut() = source_transform; + result } #[node_macro::node(category("Vector"), path(graphene_core::vector))] async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node, Output = VectorDataTable>) -> f64 { let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); let vector_data = vector_data.eval(new_ctx).await; - let vector_data = vector_data.one_item(); + + let vector_data_transform = vector_data.transform(); + let vector_data = vector_data.one_instance().instance; let mut area = 0.; - let scale = vector_data.transform.decompose_scale(); + let scale = vector_data_transform.decompose_scale(); for subpath in vector_data.stroke_bezier_paths() { area += subpath.area(Some(1e-3), Some(1e-3)); } + area * scale[0] * scale[1] } @@ -973,7 +998,9 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node< async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 { let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); let vector_data = vector_data.eval(new_ctx).await; - let vector_data = vector_data.one_item(); + + let vector_data_transform = vector_data.transform(); + let vector_data = vector_data.one_instance().instance; if centroid_type == CentroidType::Area { let mut area = 0.; @@ -990,7 +1017,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N if area != 0. { centroid /= area; - return vector_data.transform().transform_point2(centroid); + return vector_data_transform.transform_point2(centroid); } } @@ -1005,13 +1032,13 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N if length != 0. { centroid /= length; - return vector_data.transform().transform_point2(centroid); + return vector_data_transform.transform_point2(centroid); } let positions = vector_data.point_domain.positions(); if !positions.is_empty() { let centroid = positions.iter().sum::() / (positions.len() as f64); - return vector_data.transform().transform_point2(centroid); + return vector_data_transform.transform_point2(centroid); } DVec2::ZERO @@ -1047,7 +1074,7 @@ mod test { let instances = 3; let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; - let vector_data = vector_data.one_item(); + let vector_data = vector_data.one_instance().instance; assert_eq!(vector_data.region_bezier_paths().count(), 3); for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5); @@ -1059,7 +1086,7 @@ mod test { let instances = 8; let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; - let vector_data = vector_data.one_item(); + let vector_data = vector_data.one_instance().instance; assert_eq!(vector_data.region_bezier_paths().count(), 8); for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5); @@ -1069,7 +1096,7 @@ mod test { async fn circle_repeat() { let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await; let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; - let vector_data = vector_data.one_item(); + let vector_data = vector_data.one_instance().instance; assert_eq!(vector_data.region_bezier_paths().count(), 8); for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { let expected_angle = (index as f64 + 1.) * 45.; @@ -1081,20 +1108,21 @@ mod test { #[tokio::test] async fn bounding_box() { let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await; - let bounding_box = bounding_box.one_item(); + let bounding_box = bounding_box.one_instance().instance; assert_eq!(bounding_box.region_bezier_paths().count(), 1); let subpath = bounding_box.region_bezier_paths().next().unwrap().1; assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]); // Test a VectorData with non-zero rotation - let mut square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)); - square.transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4); + let square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)); + let mut square = VectorDataTable::new(square); + *square.one_instance_mut().transform_mut() *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4); let bounding_box = BoundingBoxNode { - vector_data: FutureWrapperNode(VectorDataTable::new(square)), + vector_data: FutureWrapperNode(square), } .eval(Footprint::default()) .await; - let bounding_box = bounding_box.one_item(); + let bounding_box = bounding_box.one_instance().instance; assert_eq!(bounding_box.region_bezier_paths().count(), 1); let subpath = bounding_box.region_bezier_paths().next().unwrap().1; let sqrt2 = core::f64::consts::SQRT_2; @@ -1108,7 +1136,7 @@ mod test { let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec(); let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await; let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), copy_to_points).await; - let flattened_copy_to_points = flattened_copy_to_points.one_item(); + let flattened_copy_to_points = flattened_copy_to_points.one_instance().instance; assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len()); for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() { let offset = expected_points[index]; @@ -1122,7 +1150,7 @@ mod test { async fn sample_points() { let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await; - let sample_points = sample_points.one_item(); + let sample_points = sample_points.one_instance().instance; assert_eq!(sample_points.point_domain.positions().len(), 4); for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) { assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}"); @@ -1132,7 +1160,7 @@ mod test { async fn adaptive_spacing() { let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await; - let sample_points = sample_points.one_item(); + let sample_points = sample_points.one_instance().instance; assert_eq!(sample_points.point_domain.positions().len(), 4); for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) { assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}"); @@ -1147,7 +1175,7 @@ mod test { 0, ) .await; - let sample_points = sample_points.one_item(); + let sample_points = sample_points.one_instance().instance; assert!( (20..=40).contains(&sample_points.point_domain.positions().len()), "actual len {}", @@ -1166,7 +1194,7 @@ mod test { #[tokio::test] async fn spline() { let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await; - let spline = spline.one_item(); + let spline = spline.one_instance().instance; assert_eq!(spline.stroke_bezier_paths().count(), 1); assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]); } @@ -1175,7 +1203,7 @@ mod test { let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.); let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO); let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await; - let sample_points = sample_points.one_item(); + let sample_points = sample_points.one_instance().instance; assert_eq!( &sample_points.point_domain.positions()[..4], vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)] @@ -1186,14 +1214,19 @@ mod test { fn contains_segment(vector: VectorData, target: bezier_rs::Bezier) { let segments = vector.segment_bezier_iter().map(|x| x.1); let count = segments.filter(|bezier| bezier.abs_diff_eq(&target, 0.01) || bezier.reversed().abs_diff_eq(&target, 0.01)).count(); - assert_eq!(count, 1, "Incorrect number of {target:#?} in {:#?}", vector.segment_bezier_iter().collect::>()); + assert_eq!( + count, + 1, + "Expected exactly one matching segment for {target:?}, but found {count}. The given segments are: {:#?}", + vector.segment_bezier_iter().collect::>() + ); } #[tokio::test] async fn bevel_rect() { let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.); let beveled = super::bevel(Footprint::default(), vector_node(source), 5.); - let beveled = beveled.one_item(); + let beveled = beveled.one_instance().instance; assert_eq!(beveled.point_domain.positions().len(), 8); assert_eq!(beveled.segment_domain.ids().len(), 8); @@ -1216,7 +1249,7 @@ mod test { let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.); let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false); let beveled = super::bevel((), vector_node(source), 5.); - let beveled = beveled.one_item(); + let beveled = beveled.one_instance().instance; assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.segment_domain.ids().len(), 3); @@ -1232,32 +1265,34 @@ mod test { #[tokio::test] async fn bevel_with_transform() { - let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(1., 0.), DVec2::new(1., 10.), DVec2::X * 10.); - let source = Subpath::::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -10., DVec2::ZERO), curve], false); - let mut vector_data = VectorData::from_subpath(source); + let curve = Bezier::from_cubic_dvec2(DVec2::new(0., 0.), DVec2::new(1., 0.), DVec2::new(1., 10.), DVec2::new(10., 0.)); + let source = Subpath::::from_beziers(&[Bezier::from_linear_dvec2(DVec2::new(-10., 0.), DVec2::ZERO), curve], false); + let vector_data = VectorData::from_subpath(source); + let mut vector_data_table = VectorDataTable::new(vector_data.clone()); + let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)); - vector_data.transform = transform; + *vector_data_table.one_instance_mut().transform_mut() = transform; + let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.); - let beveled = beveled.one_item(); + let beveled = beveled.one_instance().instance; assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.segment_domain.ids().len(), 3); - assert_eq!(beveled.transform, transform); // Segments - contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.))); - let trimmed = curve.trim(bezier_rs::TValue::Euclidean(0.5 / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.)); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-10., 0.))); + let trimmed = curve.trim(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.)); contains_segment(beveled.clone(), trimmed); // Join - contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start)); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start)); } #[tokio::test] async fn bevel_too_high() { let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false); let beveled = super::bevel(Footprint::default(), vector_node(source), 999.); - let beveled = beveled.one_item(); + let beveled = beveled.one_instance().instance; assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.segment_domain.ids().len(), 5); @@ -1278,7 +1313,7 @@ mod test { let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO); let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false); let beveled = super::bevel(Footprint::default(), vector_node(source), 5.); - let beveled = beveled.one_item(); + let beveled = beveled.one_instance().instance; assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.segment_domain.ids().len(), 5); diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index d2b83c83d..0981477d2 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -118,7 +118,8 @@ tagged_value! { String(String), U32(u32), U64(u64), - #[cfg_attr(feature = "serde", serde(alias = "F32"))] // TODO: Eventually remove this alias document upgrade code + // TODO: Eventually remove this alias document upgrade code + #[cfg_attr(feature = "serde", serde(alias = "F32"))] F64(f64), OptionalF64(Option), Bool(bool), @@ -129,7 +130,8 @@ tagged_value! { DAffine2(DAffine2), Image(graphene_core::raster::Image), ImaginateCache(ImaginateCache), - #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code + // TODO: Eventually remove this migration document upgrade code + #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] ImageFrame(graphene_core::raster::image::ImageFrameTable), Color(graphene_core::raster::color::Color), Subpaths(Vec>), @@ -138,12 +140,14 @@ tagged_value! { ImaginateSamplingMethod(ImaginateSamplingMethod), ImaginateMaskStartingFill(ImaginateMaskStartingFill), ImaginateController(ImaginateController), - #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] // TODO: Eventually remove this migration document upgrade code + // TODO: Eventually remove this migration document upgrade code + #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] VectorData(graphene_core::vector::VectorDataTable), Fill(graphene_core::vector::style::Fill), Stroke(graphene_core::vector::style::Stroke), F64Array4([f64; 4]), - #[cfg_attr(feature = "serde", serde(alias = "VecF32"))] // TODO: Eventually remove this alias document upgrade code + // TODO: Eventually remove this alias document upgrade code + #[cfg_attr(feature = "serde", serde(alias = "VecF32"))] VecF64(Vec), VecU64(Vec), NodePath(Vec), @@ -163,16 +167,19 @@ tagged_value! { FillChoice(graphene_core::vector::style::FillChoice), Gradient(graphene_core::vector::style::Gradient), GradientType(graphene_core::vector::style::GradientType), - #[cfg_attr(feature = "serde", serde(alias = "GradientPositions"))] // TODO: Eventually remove this alias document upgrade code + // TODO: Eventually remove this alias document upgrade code + #[cfg_attr(feature = "serde", serde(alias = "GradientPositions"))] GradientStops(graphene_core::vector::style::GradientStops), OptionalColor(Option), - #[cfg_attr(feature = "serde", serde(alias = "ManipulatorGroupIds"))] // TODO: Eventually remove this alias document upgrade code + // TODO: Eventually remove this alias document upgrade code + #[cfg_attr(feature = "serde", serde(alias = "ManipulatorGroupIds"))] PointIds(Vec), Font(graphene_core::text::Font), BrushStrokes(Vec), BrushCache(BrushCache), DocumentNode(DocumentNode), - #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code + // TODO: Eventually remove this migration document upgrade code + #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] GraphicGroup(graphene_core::GraphicGroupTable), GraphicElement(graphene_core::GraphicElement), ArtboardGroup(graphene_core::ArtboardGroup), diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index 393c09572..cdc6ae761 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -89,3 +89,6 @@ web-sys = { workspace = true, optional = true, features = [ # Optional dependencies image-compare = { version = "0.4.1", optional = true } ndarray = "0.16.1" + +[dev-dependencies] +tokio = { workspace = true, features = ["macros"] } diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index 7017b73c9..91981e451 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -6,19 +6,18 @@ use graphene_core::raster::adjustments::blend_colors; use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; -use graphene_core::raster::BlendMode; -use graphene_core::raster::{Alpha, Color, Image, Pixel, Sample}; +use graphene_core::raster::{Alpha, Bitmap, BlendMode, Color, Image, Pixel, Sample}; use graphene_core::transform::{Transform, TransformMut}; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle}; use graphene_core::vector::VectorDataTable; -use graphene_core::{Ctx, Node}; +use graphene_core::{Ctx, GraphicElement, Node}; use glam::{DAffine2, DVec2}; #[node_macro::node(category("Debug"))] fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec { - let vector_data = vector_data.one_item(); + let vector_data = vector_data.one_instance().instance; vector_data.point_domain.positions().to_vec() } @@ -89,17 +88,24 @@ fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64) } #[node_macro::node(skip_impl)] -fn blit(mut target: ImageFrame

, texture: Image

, positions: Vec, blend_mode: BlendFn) -> ImageFrame

+fn blit(mut target: ImageFrameTable

, texture: Image

, positions: Vec, blend_mode: BlendFn) -> ImageFrameTable

where + P: Pixel + Alpha + std::fmt::Debug + dyn_any::StaticType, + P::Static: Pixel, BlendFn: for<'any_input> Node<'any_input, (P, P), Output = P>, + GraphicElement: From>, { if positions.is_empty() { return target; } - let target_size = DVec2::new(target.image.width as f64, target.image.height as f64); + let target_width = target.one_instance().instance.image.width; + let target_height = target.one_instance().instance.image.height; + let target_size = DVec2::new(target_width as f64, target_height as f64); + let texture_size = DVec2::new(texture.width as f64, texture.height as f64); - let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * target.transform.inverse(); + + let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * target.transform().inverse(); for position in positions { let start = document_to_target.transform_point2(position).round(); @@ -114,17 +120,17 @@ where // Tight blitting loop. Eagerly assert bounds to hopefully eliminate bounds check inside loop. let texture_index = |x: u32, y: u32| -> usize { (y as usize * texture.width as usize) + (x as usize) }; - let target_index = |x: u32, y: u32| -> usize { (y as usize * target.image.width as usize) + (x as usize) }; + let target_index = |x: u32, y: u32| -> usize { (y as usize * target_width as usize) + (x as usize) }; let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1); let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1); assert!(texture_index(max_x, max_y) < texture.data.len()); - assert!(target_index(max_x, max_y) < target.image.data.len()); + assert!(target_index(max_x, max_y) < target.one_instance().instance.image.data.len()); for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y { for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x { let src_pixel = texture.data[texture_index(x, y)]; - let dst_pixel = &mut target.image.data[target_index(x + clamp_start.x, y + clamp_start.y)]; + let dst_pixel = &mut target.one_instance_mut().instance.image.data[target_index(x + clamp_start.x, y + clamp_start.y)]; *dst_pixel = blend_mode.eval((src_pixel, *dst_pixel)); } } @@ -138,16 +144,9 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image { let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.)); use crate::raster::empty_image; let blank_texture = empty_image((), transform, Color::TRANSPARENT); - // let normal_blend = BlendColorPairNode::new( - // FutureWrapperNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal))), - // FutureWrapperNode::new(ValueNode::new(CopiedNode::new(100.))), - // ); - // normal_blend.eval((Color::default(), Color::default())); - // use crate::raster::blend_image_tuple; - // blend_image_tuple((blank_texture, stamp), &normal_blend).await.image; - crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.)).image - // let blend_executoc = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(normal_blend))); - // blend_executor.eval((blank_texture, stamp)).image + let image = crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.)); + + image.one_instance().instance.image.clone() } macro_rules! inline_blend_funcs { @@ -162,7 +161,7 @@ macro_rules! inline_blend_funcs { }; } -pub fn blend_with_mode(background: ImageFrame, foreground: ImageFrame, blend_mode: BlendMode, opacity: f64) -> ImageFrame { +pub fn blend_with_mode(background: ImageFrameTable, foreground: ImageFrameTable, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable { let opacity = opacity / 100.; inline_blend_funcs!( background, @@ -211,21 +210,21 @@ pub fn blend_with_mode(background: ImageFrame, foreground: ImageFrame, bounds: ImageFrameTable, strokes: Vec, cache: BrushCache) -> ImageFrameTable { - let image = image.one_item().clone(); - +async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable, bounds: ImageFrameTable, strokes: Vec, cache: BrushCache) -> ImageFrameTable { let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); - let image_bbox = Bbox::from_transform(image.transform).to_axis_aligned_bbox(); + let image_bbox = Bbox::from_transform(image_frame_table.transform()).to_axis_aligned_bbox(); let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) }; let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect(); let erase_restore_strokes: Vec<_> = strokes.iter().filter(|&s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect(); - let mut brush_plan = cache.compute_brush_plan(image, &draw_strokes); + let mut brush_plan = cache.compute_brush_plan(image_frame_table, &draw_strokes); let mut background_bounds = bbox.to_transform(); - if bounds.transform() != DAffine2::ZERO { + // If the bounds are empty (no size on images or det(transform) = 0), keep the target bounds + let bounds_empty = bounds.instances().all(|bounds| bounds.instance.width() == 0 || bounds.instance.height() == 0); + if bounds.transform().matrix2.determinant() != 0. && !bounds_empty { background_bounds = bounds.transform(); } @@ -289,10 +288,10 @@ async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTab if has_erase_strokes { let opaque_image = ImageFrame { image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE), - transform: background_bounds, - alpha_blending: Default::default(), }; - let mut erase_restore_mask = opaque_image; + let mut erase_restore_mask = ImageFrameTable::new(opaque_image); + *erase_restore_mask.transform_mut() = background_bounds; + *erase_restore_mask.one_instance_mut().alpha_blending = Default::default(); for stroke in erase_restore_strokes { let mut brush_texture = cache.get_cached_brush(&stroke.style); @@ -314,7 +313,6 @@ async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTab ); erase_restore_mask = blit_node.eval(erase_restore_mask).await; } - // Yes, this is essentially the same as the above, but we duplicate to inline the blend mode. BlendMode::Restore => { let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 1.)); @@ -325,7 +323,6 @@ async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTab ); erase_restore_mask = blit_node.eval(erase_restore_mask).await; } - _ => unreachable!(), } } @@ -335,13 +332,14 @@ async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTab actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await; } - ImageFrameTable::new(actual_image) + actual_image } #[cfg(test)] mod test { use super::*; + use graphene_core::raster::Bitmap; use graphene_core::transform::Transform; use glam::DAffine2; @@ -354,4 +352,27 @@ mod test { // center pixel should be BLACK assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK)); } + + #[tokio::test] + async fn test_brush_output_size() { + let image = brush( + (), + ImageFrameTable::::default(), + ImageFrameTable::::default(), + vec![BrushStroke { + trace: vec![crate::vector::brush_stroke::BrushInputSample { position: DVec2::ZERO }], + style: BrushStyle { + color: Color::BLACK, + diameter: 20., + hardness: 20., + flow: 20., + spacing: 20., + blend_mode: BlendMode::Normal, + }, + }], + BrushCache::new_proto(), + ) + .await; + assert_eq!(image.width(), 20); + } } diff --git a/node-graph/gstd/src/dehaze.rs b/node-graph/gstd/src/dehaze.rs index 20d942d2f..6b1d9c586 100644 --- a/node-graph/gstd/src/dehaze.rs +++ b/node-graph/gstd/src/dehaze.rs @@ -1,6 +1,7 @@ use graph_craft::proto::types::Percentage; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::Image; +use graphene_core::transform::{Transform, TransformMut}; use graphene_core::{Color, Ctx}; use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage}; @@ -9,7 +10,10 @@ use std::cmp::{max, min}; #[node_macro::node(category("Raster"))] async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable, strength: Percentage) -> ImageFrameTable { - let image_frame = image_frame.one_item(); + let image_frame_transform = image_frame.transform(); + let image_frame_alpha_blending = image_frame.one_instance().alpha_blending; + + let image_frame = image_frame.one_instance().instance; // Prepare the image data for processing let image = &image_frame.image; @@ -30,13 +34,11 @@ async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable, strength: Perc base64_string: None, }; - let result = ImageFrame { - image: dehazed_image, - transform: image_frame.transform, - alpha_blending: image_frame.alpha_blending, - }; + let mut result = ImageFrameTable::new(ImageFrame { image: dehazed_image }); + *result.transform_mut() = image_frame_transform; + *result.one_instance_mut().alpha_blending = *image_frame_alpha_blending; - ImageFrameTable::new(result) + result } // There is no real point in modifying these values because they do not change the final result all that much. diff --git a/node-graph/gstd/src/gpu_nodes.rs b/node-graph/gstd/src/gpu_nodes.rs index 4e2233f33..5b4a44686 100644 --- a/node-graph/gstd/src/gpu_nodes.rs +++ b/node-graph/gstd/src/gpu_nodes.rs @@ -6,6 +6,8 @@ use graph_craft::proto::*; use graphene_core::application_io::ApplicationIo; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::{BlendMode, Image, Pixel}; +use graphene_core::transform::Transform; +use graphene_core::transform::TransformMut; use graphene_core::*; use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput}; @@ -64,7 +66,7 @@ impl Clone for ComputePass { #[node_macro::old_node_impl(MapGpuNode)] async fn map_gpu<'a: 'input>(image: ImageFrameTable, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi) -> ImageFrameTable { let image_frame_table = ℑ - let image = image.one_item(); + let image = image.one_instance().instance; log::debug!("Executing gpu node"); let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap(); @@ -81,7 +83,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable, node: DocumentNode, let name = "placeholder".to_string(); let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else { log::error!("Error creating compute pass descriptor in 'map_gpu()"); - return ImageFrameTable::default(); + return ImageFrameTable::empty(); }; self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone()); log::error!("created compute pass"); @@ -109,18 +111,17 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable, node: DocumentNode, #[cfg(feature = "image-compare")] log::debug!("score: {:?}", score.score); - let result = ImageFrame { - image: Image { - data: colors, - width: image.image.width, - height: image.image.height, - ..Default::default() - }, - transform: image.transform, - alpha_blending: image.alpha_blending, + let new_image = Image { + data: colors, + width: image.image.width, + height: image.image.height, + ..Default::default() }; + let mut result = ImageFrameTable::new(ImageFrame { image: new_image }); + *result.transform_mut() = image_frame_table.transform(); + *result.one_instance_mut().alpha_blending = *image_frame_table.one_instance().alpha_blending; - ImageFrameTable::new(result) + result } impl MapGpuNode { @@ -138,7 +139,7 @@ where GraphicElement: From>, T::Static: Pixel, { - let image = image.one_item(); + let image = image.one_instance().instance; let compiler = graph_craft::graphene_compiler::Compiler {}; let inner_network = NodeNetwork::value_network(node); @@ -280,14 +281,19 @@ where #[node_macro::node(category("Debug: GPU"))] async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable, background: ImageFrameTable, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable { - let foreground = foreground.one_item(); - let background = background.one_item(); + let foreground_transform = foreground.transform(); + let background_transform = background.transform(); + + let background_alpha_blending = background.one_instance().alpha_blending; + + let foreground = foreground.one_instance().instance; + let background = background.one_instance().instance; let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64); let background_size = DVec2::new(background.image.width as f64, background.image.height as f64); // Transforms a point from the background image to the foreground image - let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size); + let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground_transform.inverse() * background_transform * DAffine2::from_scale(1. / background_size); let transform_matrix: Mat2 = bg_to_fg.matrix2.as_mat2(); let translation: Vec2 = bg_to_fg.translation.as_vec2(); @@ -334,7 +340,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable, backgr let proto_networks: Result, _> = compiler.compile(network.clone()).collect(); let Ok(proto_networks_result) = proto_networks else { log::error!("Error compiling network in 'blend_gpu_image()"); - return ImageFrameTable::default(); + return ImageFrameTable::empty(); }; let proto_networks = proto_networks_result; log::debug!("compiling shader"); @@ -444,16 +450,16 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable, backgr let result = executor.read_output_buffer(readback_buffer).await.unwrap(); let colors = bytemuck::pod_collect_to_vec::(result.as_slice()); - let result = ImageFrame { - image: Image { - data: colors, - width: background.image.width, - height: background.image.height, - ..Default::default() - }, - transform: background.transform, - alpha_blending: background.alpha_blending, + let created_image = Image { + data: colors, + width: background.image.width, + height: background.image.height, + ..Default::default() }; - ImageFrameTable::new(result) + let mut result = ImageFrameTable::new(ImageFrame { image: created_image }); + *result.transform_mut() = background_transform; + *result.one_instance_mut().alpha_blending = *background_alpha_blending; + + result } diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index a554c9ee1..955d5123f 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -16,7 +16,7 @@ async fn image_color_palette( let mut histogram: Vec = vec![0; (bins + 1.) as usize]; let mut colors: Vec> = vec![vec![]; (bins + 1.) as usize]; - let image = image.one_item(); + let image = image.one_instance().instance; for pixel in image.image.data.iter() { let r = pixel.r() * GRID; @@ -79,7 +79,6 @@ mod test { data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000], base64_string: None, }, - ..Default::default() }), 1, ); diff --git a/node-graph/gstd/src/imaginate.rs b/node-graph/gstd/src/imaginate.rs index e49dccc3e..39e44c355 100644 --- a/node-graph/gstd/src/imaginate.rs +++ b/node-graph/gstd/src/imaginate.rs @@ -327,7 +327,7 @@ pub async fn imaginate<'a, P: Pixel>( set_progress(ImaginateStatus::Failed(err.to_string())); } }; - Image::empty() + Image::default() }) } diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 39cbf95ef..cb629124d 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -5,8 +5,8 @@ use graphene_core::raster::{ Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Sample, }; -use graphene_core::transform::Transform; -use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node}; +use graphene_core::transform::{Transform, TransformMut}; +use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, GraphicElement, Node}; use fastnoise_lite; use glam::{DAffine2, DVec2, Vec2}; @@ -30,7 +30,10 @@ impl From for Error { #[node_macro::node(category("Debug: Raster"))] fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable) -> ImageFrameTable { - let image_frame = image_frame.one_item(); + let image_frame_transform = image_frame.transform(); + let image_frame_alpha_blending = image_frame.one_instance().alpha_blending; + + let image_frame = image_frame.one_instance().instance; // Resize the image using the image crate let image = &image_frame.image; @@ -38,7 +41,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra let footprint = ctx.footprint(); let viewport_bounds = footprint.viewport_bounds_in_local_space(); - let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox(); + let image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox(); let intersection = viewport_bounds.intersect(&image_bounds); let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64)); let size = intersection.size(); @@ -46,7 +49,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra // If the image would not be visible, return an empty image if size.x <= 0. || size.y <= 0. { - return ImageFrameTable::default(); + return ImageFrameTable::empty(); } let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type."); @@ -81,15 +84,13 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra }; // we need to adjust the offset if we truncate the offset calculation - let new_transform = image_frame.transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size); + let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size); - let result = ImageFrame { - image, - transform: new_transform, - alpha_blending: image_frame.alpha_blending, - }; + let mut result = ImageFrameTable::new(ImageFrame { image }); + *result.transform_mut() = new_transform; + *result.one_instance_mut().alpha_blending = *image_frame_alpha_blending; - ImageFrameTable::new(result) + result } #[derive(Debug, Clone, Copy)] @@ -256,33 +257,35 @@ fn mask_image< // } #[node_macro::node(skip_impl)] -async fn blend_image_tuple<_P: Alpha + Pixel + Debug + Send, MapFn, _Fg: Sample + Transform + Clone + Send + 'n>(images: (ImageFrame<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrame<_P> +async fn blend_image_tuple<_P, MapFn, _Fg>(images: (ImageFrameTable<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrameTable<_P> where + _P: Alpha + Pixel + Debug + Send + dyn_any::StaticType, + _P::Static: Pixel, MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone, + _Fg: Sample + Transform + Clone + Send + 'n, + GraphicElement: From>, { let (background, foreground) = images; blend_image(foreground, background, map_fn) } -fn blend_image<'input, _P: Alpha + Pixel + Debug, MapFn, Frame: Sample + Transform, Background: BitmapMut + Transform + Sample>( - foreground: Frame, - background: Background, - map_fn: &'input MapFn, -) -> Background +fn blend_image<'input, _P, MapFn, Frame, Background>(foreground: Frame, background: Background, map_fn: &'input MapFn) -> Background where MapFn: Node<'input, (_P, _P), Output = _P>, + _P: Pixel + Alpha + Debug, + Frame: Sample + Transform, + Background: BitmapMut + Sample + Transform, { blend_image_closure(foreground, background, |a, b| map_fn.eval((a, b))) } -pub fn blend_image_closure<_P: Alpha + Pixel + Debug, MapFn, Frame: Sample + Transform, Background: BitmapMut + Transform + Sample>( - foreground: Frame, - mut background: Background, - map_fn: MapFn, -) -> Background +pub fn blend_image_closure<_P, MapFn, Frame, Background>(foreground: Frame, mut background: Background, map_fn: MapFn) -> Background where MapFn: Fn(_P, _P) -> _P, + _P: Pixel + Alpha + Debug, + Frame: Sample + Transform, + Background: BitmapMut + Sample + Transform, { let background_size = DVec2::new(background.width() as f64, background.height() as f64); @@ -319,19 +322,20 @@ pub struct ExtendImageToBoundsNode { } #[node_macro::old_node_fn(ExtendImageToBoundsNode)] -fn extend_image_to_bounds(image: ImageFrame, bounds: DAffine2) -> ImageFrame { +fn extend_image_to_bounds(image: ImageFrameTable, bounds: DAffine2) -> ImageFrameTable { let image_aabb = Bbox::unit().affine_transform(image.transform()).to_axis_aligned_bbox(); let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox(); if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) { return image; } - if image.image.width == 0 || image.image.height == 0 { + let image_instance = image.one_instance().instance; + if image_instance.image.width == 0 || image_instance.image.height == 0 { return empty_image((), bounds, Color::TRANSPARENT); } - let orig_image_scale = DVec2::new(image.image.width as f64, image.image.height as f64); - let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform.inverse(); + let orig_image_scale = DVec2::new(image_instance.image.width as f64, image_instance.image.height as f64); + let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform().inverse(); let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox(); let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO); @@ -341,36 +345,37 @@ fn extend_image_to_bounds(image: ImageFrame, bounds: DAffine2) -> ImageFr // Copy over original image into enlarged image. let mut new_img = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT); let offset_in_new_image = (-new_start).as_uvec2(); - for y in 0..image.image.height { - let old_start = y * image.image.width; + for y in 0..image_instance.image.height { + let old_start = y * image_instance.image.width; let new_start = (y + offset_in_new_image.y) * new_img.width + offset_in_new_image.x; - let old_row = &image.image.data[old_start as usize..(old_start + image.image.width) as usize]; - let new_row = &mut new_img.data[new_start as usize..(new_start + image.image.width) as usize]; + let old_row = &image_instance.image.data[old_start as usize..(old_start + image_instance.image.width) as usize]; + let new_row = &mut new_img.data[new_start as usize..(new_start + image_instance.image.width) as usize]; new_row.copy_from_slice(old_row); } // Compute new transform. // let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse(); - let new_texture_to_layer_space = image.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale); - ImageFrame { - image: new_img, - transform: new_texture_to_layer_space, - alpha_blending: image.alpha_blending, - } + let new_texture_to_layer_space = image.transform() * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale); + + let mut result = ImageFrameTable::new(ImageFrame { image: new_img }); + *result.transform_mut() = new_texture_to_layer_space; + *result.one_instance_mut().alpha_blending = *image.one_instance().alpha_blending; + + result } #[node_macro::node(category("Debug: Raster"))] -fn empty_image(_: impl Ctx, transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame

{ +fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> ImageFrameTable { let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32; let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32; let image = Image::new(width, height, color); - ImageFrame { - image, - transform, - alpha_blending: AlphaBlending::default(), - } + let mut result = ImageFrameTable::new(ImageFrame { image }); + *result.transform_mut() = transform; + *result.one_instance_mut().alpha_blending = AlphaBlending::default(); + + result } // #[cfg(feature = "serde")] @@ -510,7 +515,7 @@ fn noise_pattern( // If the image would not be visible, return an empty image if size.x <= 0. || size.y <= 0. { - return ImageFrameTable::default(); + return ImageFrameTable::empty(); } let footprint_scale = footprint.scale(); @@ -554,13 +559,11 @@ fn noise_pattern( } } - let result = ImageFrame { - image, - transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), - alpha_blending: AlphaBlending::default(), - }; + let mut result = ImageFrameTable::new(ImageFrame { image }); + *result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size); + *result.one_instance_mut().alpha_blending = AlphaBlending::default(); - return ImageFrameTable::new(result); + return result; } }; noise.set_noise_type(Some(noise_type)); @@ -618,13 +621,11 @@ fn noise_pattern( } } - let result = ImageFrame { - image, - transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), - alpha_blending: AlphaBlending::default(), - }; + let mut result = ImageFrameTable::new(ImageFrame { image }); + *result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size); + *result.one_instance_mut().alpha_blending = AlphaBlending::default(); - ImageFrameTable::new(result) + result } #[node_macro::node(category("Raster"))] @@ -640,7 +641,7 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable { // If the image would not be visible, return an empty image if size.x <= 0. || size.y <= 0. { - return ImageFrameTable::default(); + return ImageFrameTable::empty(); } let scale = footprint.scale(); @@ -662,18 +663,17 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable { } } - let result = ImageFrame { - image: Image { - width, - height, - data, - ..Default::default() - }, - transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), - alpha_blending: Default::default(), + let image = Image { + width, + height, + data, + ..Default::default() }; + let mut result = ImageFrameTable::new(ImageFrame { image }); + *result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size); + *result.one_instance_mut().alpha_blending = Default::default(); - ImageFrameTable::new(result) + result } #[inline(always)] diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index aab0fffa7..e41b9d00b 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -1,8 +1,9 @@ use bezier_rs::{ManipulatorGroup, Subpath}; +use graphene_core::transform::Transform; +use graphene_core::transform::TransformMut; use graphene_core::vector::misc::BooleanOperation; use graphene_core::vector::style::Fill; pub use graphene_core::vector::*; -use graphene_core::{transform::Transform, GraphicGroup}; use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable}; pub use path_bool as path_bool_lib; use path_bool::{FillRule, PathBooleanOperation}; @@ -12,7 +13,7 @@ use std::ops::Mul; #[node_macro::node(category(""))] async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable { - fn vector_from_image(image_frame: T) -> VectorData { + fn vector_from_image(image_frame: T) -> VectorDataTable { let corner1 = DVec2::ZERO; let corner2 = DVec2::new(1., 1.); @@ -22,18 +23,14 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera let mut vector_data = VectorData::from_subpath(subpath); vector_data.style.set_fill(Fill::Solid(Color::from_rgb_str("777777").unwrap().to_gamma_srgb())); - vector_data + VectorDataTable::new(vector_data) } - fn union_vector_data(graphic_element: &GraphicElement) -> VectorData { + fn union_vector_data(graphic_element: &GraphicElement) -> VectorDataTable { match graphic_element { - GraphicElement::VectorData(vector_data) => { - let vector_data = vector_data.one_item(); - vector_data.clone() - } + GraphicElement::VectorData(vector_data) => vector_data.clone(), // Union all vector data in the graphic group into a single vector GraphicElement::GraphicGroup(graphic_group) => { - let graphic_group = graphic_group.one_item(); let vector_data = collect_vector_data(graphic_group); boolean_operation_on_vector_data(&vector_data, BooleanOperation::Union) @@ -42,28 +39,32 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera } } - fn collect_vector_data(graphic_group: &GraphicGroup) -> Vec { + fn collect_vector_data(graphic_group_table: &GraphicGroupTable) -> Vec { + let graphic_group = graphic_group_table.one_instance(); + // Ensure all non vector data in the graphic group is converted to vector data - let vector_data = graphic_group.iter().map(|(element, _)| union_vector_data(element)); + let vector_data_tables = graphic_group.instance.iter().map(|(element, _)| union_vector_data(element)); // Apply the transform from the parent graphic group - let transformed_vector_data = vector_data.map(|mut vector_data| { - vector_data.transform = graphic_group.transform * vector_data.transform; - vector_data + let transformed_vector_data = vector_data_tables.map(|mut vector_data_table| { + *vector_data_table.transform_mut() = graphic_group.transform() * vector_data_table.transform(); + vector_data_table }); transformed_vector_data.collect::>() } - fn subtract<'a>(vector_data: impl Iterator) -> VectorData { + fn subtract<'a>(vector_data: impl Iterator) -> VectorDataTable { let mut vector_data = vector_data.into_iter(); let mut result = vector_data.next().cloned().unwrap_or_default(); let mut next_vector_data = vector_data.next(); while let Some(lower_vector_data) = next_vector_data { - let transform_of_lower_into_space_of_upper = result.transform.inverse() * lower_vector_data.transform; + let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform(); - let upper_path_string = to_path(&result, DAffine2::IDENTITY); - let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper); + let result = result.one_instance_mut().instance; + + let upper_path_string = to_path(result, DAffine2::IDENTITY); + let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper); #[allow(unused_unsafe)] let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) }; @@ -76,49 +77,58 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera next_vector_data = vector_data.next(); } + result } - fn boolean_operation_on_vector_data(vector_data: &[VectorData], boolean_operation: BooleanOperation) -> VectorData { + fn boolean_operation_on_vector_data(vector_data_table: &[VectorDataTable], boolean_operation: BooleanOperation) -> VectorDataTable { match boolean_operation { BooleanOperation::Union => { // Reverse vector data so that the result style is the style of the first vector data - let mut vector_data = vector_data.iter().rev(); - let mut result = vector_data.next().cloned().unwrap_or_default(); - let mut second_vector_data = Some(vector_data.next().unwrap_or(const { &VectorData::empty() })); + let mut vector_data_table = vector_data_table.iter().rev(); + let mut result_vector_data_table = vector_data_table.next().cloned().unwrap_or_default(); // Loop over all vector data and union it with the result + let default = VectorDataTable::default(); + let mut second_vector_data = Some(vector_data_table.next().unwrap_or(&default)); while let Some(lower_vector_data) = second_vector_data { - let transform_of_lower_into_space_of_upper = result.transform.inverse() * lower_vector_data.transform; + let transform_of_lower_into_space_of_upper = result_vector_data_table.transform().inverse() * lower_vector_data.transform(); - let upper_path_string = to_path(&result, DAffine2::IDENTITY); - let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper); + let result_vector_data = result_vector_data_table.one_instance_mut().instance; + + let upper_path_string = to_path(result_vector_data, DAffine2::IDENTITY); + let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper); #[allow(unused_unsafe)] let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) }; let boolean_operation_result = from_path(&boolean_operation_string); - result.colinear_manipulators = boolean_operation_result.colinear_manipulators; - result.point_domain = boolean_operation_result.point_domain; - result.segment_domain = boolean_operation_result.segment_domain; - result.region_domain = boolean_operation_result.region_domain; - second_vector_data = vector_data.next(); + result_vector_data.colinear_manipulators = boolean_operation_result.colinear_manipulators; + result_vector_data.point_domain = boolean_operation_result.point_domain; + result_vector_data.segment_domain = boolean_operation_result.segment_domain; + result_vector_data.region_domain = boolean_operation_result.region_domain; + + second_vector_data = vector_data_table.next(); } - result + + result_vector_data_table } - BooleanOperation::SubtractFront => subtract(vector_data.iter()), - BooleanOperation::SubtractBack => subtract(vector_data.iter().rev()), + BooleanOperation::SubtractFront => subtract(vector_data_table.iter()), + BooleanOperation::SubtractBack => subtract(vector_data_table.iter().rev()), BooleanOperation::Intersect => { - let mut vector_data = vector_data.iter().rev(); + let mut vector_data = vector_data_table.iter().rev(); let mut result = vector_data.next().cloned().unwrap_or_default(); - let mut second_vector_data = Some(vector_data.next().unwrap_or(const { &VectorData::empty() })); + let default = VectorDataTable::default(); + let mut second_vector_data = Some(vector_data.next().unwrap_or(&default)); // For each vector data, set the result to the intersection of that data and the result while let Some(lower_vector_data) = second_vector_data { - let transform_of_lower_into_space_of_upper = result.transform.inverse() * lower_vector_data.transform; + let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform(); - let upper_path_string = to_path(&result, DAffine2::IDENTITY); - let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper); + let result = result.one_instance_mut().instance; + + let upper_path_string = to_path(result, DAffine2::IDENTITY); + let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper); #[allow(unused_unsafe)] let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) }; @@ -130,63 +140,67 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera result.region_domain = boolean_operation_result.region_domain; second_vector_data = vector_data.next(); } + result } BooleanOperation::Difference => { - let mut vector_data_iter = vector_data.iter().rev(); - let mut any_intersection = VectorData::empty(); - let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(const { &VectorData::empty() })); + let mut vector_data_iter = vector_data_table.iter().rev(); + let mut any_intersection = VectorDataTable::default(); + let default = VectorDataTable::default(); + let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(&default)); // Find where all vector data intersect at least once while let Some(lower_vector_data) = second_vector_data { - let all_other_vector_data = boolean_operation_on_vector_data(&vector_data.iter().filter(|v| v != &lower_vector_data).cloned().collect::>(), BooleanOperation::Union); + let all_other_vector_data = boolean_operation_on_vector_data(&vector_data_table.iter().filter(|v| v != &lower_vector_data).cloned().collect::>(), BooleanOperation::Union); + let all_other_vector_data_instance = all_other_vector_data.one_instance(); - let transform_of_lower_into_space_of_upper = all_other_vector_data.transform.inverse() * lower_vector_data.transform; + let transform_of_lower_into_space_of_upper = all_other_vector_data.transform().inverse() * lower_vector_data.transform(); - let upper_path_string = to_path(&all_other_vector_data, DAffine2::IDENTITY); - let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper); + let upper_path_string = to_path(all_other_vector_data_instance.instance, DAffine2::IDENTITY); + let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper); #[allow(unused_unsafe)] let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) }; - let mut boolean_intersection_result = from_path(&boolean_intersection_string); + let mut boolean_intersection_result = VectorDataTable::new(from_path(&boolean_intersection_string)); + *boolean_intersection_result.transform_mut() = all_other_vector_data_instance.transform(); - boolean_intersection_result.transform = all_other_vector_data.transform; - boolean_intersection_result.style = all_other_vector_data.style.clone(); - boolean_intersection_result.alpha_blending = all_other_vector_data.alpha_blending; + boolean_intersection_result.one_instance_mut().instance.style = all_other_vector_data_instance.instance.style.clone(); + *boolean_intersection_result.one_instance_mut().alpha_blending = *all_other_vector_data_instance.alpha_blending; - let transform_of_lower_into_space_of_upper = boolean_intersection_result.transform.inverse() * any_intersection.transform; + let transform_of_lower_into_space_of_upper = boolean_intersection_result.one_instance_mut().transform().inverse() * any_intersection.transform(); - let upper_path_string = to_path(&boolean_intersection_result, DAffine2::IDENTITY); - let lower_path_string = to_path(&any_intersection, transform_of_lower_into_space_of_upper); + let upper_path_string = to_path(boolean_intersection_result.one_instance_mut().instance, DAffine2::IDENTITY); + let lower_path_string = to_path(any_intersection.one_instance_mut().instance, transform_of_lower_into_space_of_upper); #[allow(unused_unsafe)] let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) }); - any_intersection = union_result; + *any_intersection.one_instance_mut().instance = union_result; - any_intersection.transform = boolean_intersection_result.transform; - any_intersection.style = boolean_intersection_result.style.clone(); - any_intersection.alpha_blending = boolean_intersection_result.alpha_blending; + *any_intersection.transform_mut() = boolean_intersection_result.transform(); + any_intersection.one_instance_mut().instance.style = boolean_intersection_result.one_instance_mut().instance.style.clone(); + any_intersection.one_instance_mut().alpha_blending = boolean_intersection_result.one_instance_mut().alpha_blending; second_vector_data = vector_data_iter.next(); } // Subtract the area where they intersect at least once from the union of all vector data - let union = boolean_operation_on_vector_data(vector_data, BooleanOperation::Union); + let union = boolean_operation_on_vector_data(vector_data_table, BooleanOperation::Union); boolean_operation_on_vector_data(&[union, any_intersection], BooleanOperation::SubtractFront) } } } - let group_of_paths = group_of_paths.one_item(); // The first index is the bottom of the stack - let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(group_of_paths), operation); + let mut result_vector_data_table = boolean_operation_on_vector_data(&collect_vector_data(&group_of_paths), operation); - let transform = boolean_operation_result.transform; - VectorData::transform(&mut boolean_operation_result, transform); - boolean_operation_result.style.set_stroke_transform(DAffine2::IDENTITY); - boolean_operation_result.transform = DAffine2::IDENTITY; - boolean_operation_result.upstream_graphic_group = Some(GraphicGroupTable::new(group_of_paths.clone())); + // Replace the transformation matrix with a mutation of the vector points themselves + let result_vector_data_table_transform = result_vector_data_table.transform(); + *result_vector_data_table.transform_mut() = DAffine2::IDENTITY; + let result_vector_data = result_vector_data_table.one_instance_mut().instance; + VectorData::transform(result_vector_data, result_vector_data_table_transform); + result_vector_data.style.set_stroke_transform(DAffine2::IDENTITY); + result_vector_data.upstream_graphic_group = Some(group_of_paths.clone()); - VectorDataTable::new(boolean_operation_result) + result_vector_data_table } fn to_path(vector: &VectorData, transform: DAffine2) -> Vec { diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 967ce363c..f3e6a5077 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -11,6 +11,8 @@ use graphene_core::raster::Image; use graphene_core::renderer::RenderMetadata; use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender}; use graphene_core::transform::Footprint; +#[cfg(target_arch = "wasm32")] +use graphene_core::transform::TransformMut; use graphene_core::vector::VectorDataTable; use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend}; @@ -40,7 +42,7 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc, // surface_handle: Arc, // ) -> graphene_core::application_io::SurfaceHandleFrame { -// let image = image.one_item(); +// let image = image.one_instance().instance; // let image_data = image.image.data; // let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice())); // if image.image.width > 0 && image.image.height > 0 { @@ -76,7 +78,7 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] #[node_macro::node(category("Network"))] fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable { let Some(image) = image::load_from_memory(data.as_ref()).ok() else { - return ImageFrameTable::default(); + return ImageFrameTable::empty(); }; let image = image.to_rgba32f(); let image = ImageFrame { @@ -86,8 +88,6 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable { height: image.height(), ..Default::default() }, - transform: glam::DAffine2::IDENTITY, - alpha_blending: Default::default(), }; ImageFrameTable::new(image) @@ -130,7 +130,7 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen let mut child = Scene::new(); let mut context = wgpu_executor::RenderContext::default(); - data.render_to_vello(&mut child, glam::DAffine2::IDENTITY, &mut context); + data.render_to_vello(&mut child, Default::default(), &mut context); // TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(Nr cost scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array()))); @@ -167,7 +167,7 @@ async fn rasterize ImageFrameTable { if footprint.transform.matrix2.determinant() == 0. { log::trace!("Invalid footprint received for rasterization"); - return ImageFrameTable::default(); + return ImageFrameTable::empty(); } let mut render = SvgRender::new(); @@ -204,13 +204,12 @@ async fn rasterize( let data = data.eval(ctx.clone()).await; let editor_api = editor_api.eval(ctx.clone()).await; + #[cfg(all(feature = "vello", target_arch = "wasm32"))] let surface_handle = _surface_handle.eval(ctx.clone()).await; + let use_vello = editor_api.editor_preferences.use_vello(); #[cfg(all(feature = "vello", target_arch = "wasm32"))] let use_vello = use_vello && surface_handle.is_some(); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 85ab9d23b..67b300f72 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -15,7 +15,7 @@ use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureW use graphene_std::application_io::TextureFrame; use graphene_std::wasm_application_io::*; use graphene_std::Context; -use graphene_std::{GraphicElement, GraphicGroup}; +use graphene_std::GraphicElement; #[cfg(feature = "gpu")] use wgpu_executor::{ShaderInputFrame, WgpuExecutor}; use wgpu_executor::{WgpuSurface, WindowHandle}; @@ -261,7 +261,7 @@ fn node_registry() -> HashMap, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroup]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]), #[cfg(feature = "gpu")] diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 2a480bee2..41be958a6 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -916,7 +916,7 @@ async fn render_texture<'a: 'n>( async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable, executor: &'a WgpuExecutor) -> TextureFrame { // let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); - let input = input.one_item(); + let input = input.one_instance().instance; let new_data: Vec = input.image.data.iter().map(|x| (*x).into()).collect(); let new_image = Image { width: input.image.width, @@ -934,7 +934,8 @@ async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFram TextureFrame { texture: texture.into(), - transform: input.transform, - alpha_blend: Default::default(), + // TODO: Find an alternate way to encode the transform and alpha_blend now that these fields have been moved up out of TextureFrame + // transform: input.transform, + // alpha_blend: Default::default(), } }