diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index 27fb1a227f..4408947d05 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -13,7 +13,6 @@ use graphene_core::vector::style::ViewMode; use graphene_core::Color; use graphene_std::renderer::ClickTarget; use graphene_std::transform::Footprint; -use graphene_std::vector::VectorData; use glam::DAffine2; @@ -181,9 +180,6 @@ pub enum DocumentMessage { UpdateClipTargets { clip_targets: HashSet, }, - UpdateVectorModify { - vector_modify: HashMap, - }, Undo, UngroupSelectedLayers, UngroupLayer { diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index fefed2da90..90e0f49be8 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -27,7 +27,8 @@ use crate::node_graph_executor::NodeGraphExecutor; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeNetwork, OldNodeNetwork}; -use graphene_core::raster::{BlendMode, ImageFrame}; +use graphene_core::raster::image::ImageFrame; +use graphene_core::raster::BlendMode; use graphene_core::vector::style::ViewMode; use graphene_std::renderer::{ClickTarget, Quad}; use graphene_std::vector::path_bool_lib; @@ -1159,9 +1160,6 @@ impl MessageHandler> for DocumentMessag DocumentMessage::UpdateClipTargets { clip_targets } => { self.network_interface.update_clip_targets(clip_targets); } - DocumentMessage::UpdateVectorModify { vector_modify } => { - self.network_interface.update_vector_modify(vector_modify); - } DocumentMessage::Undo => { if self.network_interface.transaction_status() != TransactionStatus::Finished { return; @@ -2126,7 +2124,7 @@ impl DocumentMessageHandler { /// Create a network interface with a single export fn default_document_network_interface() -> NodeNetworkInterface { let mut network_interface = NodeNetworkInterface::default(); - network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY), -1, "", &[]); + network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::default()), -1, "", &[]); network_interface } 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 b99af14b92..257e1a783e 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,8 @@ use crate::messages::prelude::*; use bezier_rs::Subpath; use graph_craft::document::NodeId; -use graphene_core::raster::{BlendMode, ImageFrame}; +use graphene_core::raster::image::ImageFrame; +use graphene_core::raster::BlendMode; use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::style::{Fill, Stroke}; 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 a4d1e90db8..b2d9a2b16b 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -8,16 +8,17 @@ use bezier_rs::Subpath; use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; -use graphene_core::raster::{BlendMode, ImageFrame}; +use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; +use graphene_core::raster::BlendMode; use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::{PointId, VectorModificationType}; use graphene_core::{Artboard, Color}; +use graphene_std::vector::{VectorData, VectorDataTable}; +use graphene_std::GraphicGroupTable; use glam::{DAffine2, DVec2, IVec2}; -use graphene_std::vector::VectorData; -use graphene_std::GraphicGroup; #[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)] pub enum TransformIn { @@ -125,8 +126,8 @@ impl<'a> ModifyInputsContext<'a> { /// Creates an artboard as the primary export for the document network pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier { let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([ - Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroup::EMPTY), true)), - Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_core::GraphicGroup::EMPTY), true)), + Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroup::default()), true)), + Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_core::GraphicGroupTable::default()), true)), Some(NodeInput::value(TaggedValue::IVec2(artboard.location), false)), Some(NodeInput::value(TaggedValue::IVec2(artboard.dimensions), false)), Some(NodeInput::value(TaggedValue::Color(artboard.background), false)), @@ -138,7 +139,7 @@ impl<'a> ModifyInputsContext<'a> { pub fn insert_boolean_data(&mut self, operation: graphene_std::vector::misc::BooleanOperation, layer: LayerNodeIdentifier) { let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([ - Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroup::EMPTY), true)), + Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)), Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)), ]); @@ -148,7 +149,7 @@ impl<'a> ModifyInputsContext<'a> { } pub fn insert_vector_data(&mut self, subpaths: Vec>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) { - let vector_data = VectorData::from_subpaths(subpaths, true); + let vector_data = VectorDataTable::new(VectorData::from_subpaths(subpaths, true)); let shape = resolve_document_node_type("Path") .expect("Path node does not exist") @@ -213,9 +214,10 @@ impl<'a> ModifyInputsContext<'a> { pub fn insert_image_data(&mut self, image_frame: ImageFrame, 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(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(ImageFrameTable::new(image_frame)), false)), + ]); let image_id = NodeId::new(); self.network_interface.insert_node(image_id, image, &[]); @@ -289,7 +291,7 @@ impl<'a> ModifyInputsContext<'a> { // TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group. if node_definition.identifier == "Path" { let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type(); - if layer_input_type == concrete!(GraphicGroup) { + if layer_input_type == concrete!(GraphicGroupTable) { let Some(flatten_vector_elements_definition) = resolve_document_node_type("Flatten Vector Elements") else { log::error!("Flatten Vector Elements does not exist in ModifyInputsContext::existing_node_id"); return None; 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 6d5eb34bfb..264ee3c9da 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 @@ -15,13 +15,13 @@ use graph_craft::document::*; use graph_craft::imaginate_input::ImaginateSamplingMethod; use graph_craft::ProtoNodeIdentifier; use graphene_core::raster::brush_cache::BrushCache; -use graphene_core::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, ImageFrame, NoiseType, RedGreenBlue, RedGreenBlueAlpha}; +use graphene_core::raster::image::ImageFrameTable; +use graphene_core::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlue, RedGreenBlueAlpha}; use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::transform::Footprint; -use graphene_core::vector::VectorData; +use graphene_core::vector::VectorDataTable; use graphene_core::*; use graphene_std::wasm_application_io::WasmEditorApi; - #[cfg(feature = "gpu")] use wgpu_executor::{Bindgroup, CommandBuffer, PipelineLayout, ShaderHandle, ShaderInputFrame, WgpuShaderInput}; @@ -40,9 +40,7 @@ pub struct NodePropertiesContext<'a> { impl NodePropertiesContext<'_> { pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option> { - let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else { - return None; - }; + let input_properties_row = self.network_interface.input_properties_row(node_id, index, self.selection_network_path)?; if let Some(widget_override) = &input_properties_row.widget_override { let Some(widget_override_lambda) = INPUT_OVERRIDES.get(widget_override) else { log::error!("Could not get widget override lambda in call_widget_override"); @@ -204,8 +202,8 @@ fn static_nodes() -> Vec { ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), - NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), + NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true), + NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true), ], ..Default::default() }, @@ -312,8 +310,8 @@ fn static_nodes() -> Vec { ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroup::EMPTY), true), - NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), + NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroup::default()), true), + NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true), NodeInput::value(TaggedValue::IVec2(glam::IVec2::ZERO), false), NodeInput::value(TaggedValue::IVec2(glam::IVec2::new(1920, 1080)), false), NodeInput::value(TaggedValue::Color(Color::WHITE), false), @@ -545,8 +543,8 @@ fn static_nodes() -> Vec { exports: vec![NodeInput::node(NodeId(3), 0)], nodes: [ DocumentNode { - inputs: vec![NodeInput::network(concrete!(ImageFrame), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrame>")), + inputs: vec![NodeInput::network(concrete!(ImageFrameTable), 0)], + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrame>")), // TODO: Possibly change `ImageFrame` to something else ..Default::default() }, DocumentNode { @@ -573,7 +571,7 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true)], + inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -664,7 +662,7 @@ fn static_nodes() -> Vec { ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::VectorData(VectorData::default()), true), + NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true), NodeInput::value( TaggedValue::Footprint(Footprint { transform: DAffine2::from_scale_angle_translation(DVec2::new(100., 100.), 0., DVec2::new(0., 0.)), @@ -811,8 +809,8 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_std::raster::MaskImageNode"), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), ], ..Default::default() }, @@ -834,8 +832,8 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_std::raster::InsertChannelNode"), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::default()), false), ], ..Default::default() @@ -858,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(ImageFrame::empty()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), 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::default()), true), ], ..Default::default() }, @@ -889,7 +887,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![ - NodeInput::network(concrete!(ImageFrame), 0), + NodeInput::network(concrete!(ImageFrameTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), ], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), @@ -898,7 +896,7 @@ fn static_nodes() -> Vec { }, DocumentNode { inputs: vec![ - NodeInput::network(concrete!(ImageFrame), 0), + NodeInput::network(concrete!(ImageFrameTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), ], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), @@ -907,7 +905,7 @@ fn static_nodes() -> Vec { }, DocumentNode { inputs: vec![ - NodeInput::network(concrete!(ImageFrame), 0), + NodeInput::network(concrete!(ImageFrameTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), ], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), @@ -916,7 +914,7 @@ fn static_nodes() -> Vec { }, DocumentNode { inputs: vec![ - NodeInput::network(concrete!(ImageFrame), 0), + NodeInput::network(concrete!(ImageFrameTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), ], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), @@ -931,7 +929,7 @@ fn static_nodes() -> Vec { ..Default::default() }), - inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true)], + inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -997,8 +995,8 @@ fn static_nodes() -> Vec { exports: vec![NodeInput::node(NodeId(0), 0)], nodes: vec![DocumentNode { inputs: vec![ - NodeInput::network(concrete!(graphene_core::raster::ImageFrame), 0), - NodeInput::network(concrete!(graphene_core::raster::ImageFrame), 1), + NodeInput::network(concrete!(ImageFrameTable), 0), + NodeInput::network(concrete!(ImageFrameTable), 1), NodeInput::network(concrete!(Vec), 2), NodeInput::network(concrete!(BrushCache), 3), ], @@ -1013,8 +1011,8 @@ fn static_nodes() -> Vec { ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false), NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false), ], @@ -1063,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(ImageFrame::empty()), true)], + inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], manual_composition: Some(concrete!(())), ..Default::default() }, @@ -1082,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(ImageFrame::empty()), true)], + inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], manual_composition: Some(concrete!(Footprint)), ..Default::default() }, @@ -1103,7 +1101,7 @@ fn static_nodes() -> Vec { implementation: DocumentNodeImplementation::Network(NodeNetwork { exports: vec![NodeInput::node(NodeId(0), 0)], nodes: vec![DocumentNode { - inputs: vec![NodeInput::network(concrete!(ImageFrame), 1)], + inputs: vec![NodeInput::network(concrete!(ImageFrameTable), 1)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")), manual_composition: Some(concrete!(Footprint)), ..Default::default() @@ -1114,7 +1112,7 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), false)], + inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), false)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -1809,7 +1807,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - inputs: vec![NodeInput::network(concrete!(ImageFrame), 0), NodeInput::node(NodeId(0), 0)], + inputs: vec![NodeInput::network(concrete!(ImageFrameTable), 0), NodeInput::node(NodeId(0), 0)], manual_composition: Some(generic!(T)), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode")), ..Default::default() @@ -1827,7 +1825,7 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true)], + inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -1883,7 +1881,7 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true), ], ..Default::default() @@ -1925,7 +1923,7 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_core::raster::BrightnessContrastNode"), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::Bool(false), false), @@ -1956,7 +1954,7 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::Curve(Default::default()), false), ], ..Default::default() @@ -2046,7 +2044,7 @@ fn static_nodes() -> Vec { exports: vec![NodeInput::node(NodeId(1), 0)], nodes: vec![ DocumentNode { - inputs: vec![NodeInput::network(concrete!(VectorData), 0)], + inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), manual_composition: Some(generic!(T)), skip_deduplication: true, @@ -2066,7 +2064,7 @@ fn static_nodes() -> Vec { ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::VectorData(VectorData::empty()), true), + NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true), NodeInput::value(TaggedValue::VectorModification(Default::default()), false), ], ..Default::default() @@ -2189,7 +2187,7 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { inputs: vec![ - NodeInput::value(TaggedValue::VectorData(VectorData::empty()), true), + NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false), @@ -2200,7 +2198,7 @@ fn static_nodes() -> Vec { exports: vec![NodeInput::node(NodeId(1), 0)], nodes: [ DocumentNode { - inputs: vec![NodeInput::network(concrete!(VectorData), 0)], + inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), manual_composition: Some(generic!(T)), skip_deduplication: true, @@ -2296,7 +2294,7 @@ fn static_nodes() -> Vec { exports: vec![NodeInput::node(NodeId(1), 0)], nodes: vec![ DocumentNode { - inputs: vec![NodeInput::network(concrete!(VectorData), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], + inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::vector::BooleanOperationNode")), manual_composition: Some(generic!(T)), ..Default::default() @@ -2315,7 +2313,7 @@ fn static_nodes() -> Vec { ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), + NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true), NodeInput::value(TaggedValue::BooleanOperation(vector::misc::BooleanOperation::Union), false), ], ..Default::default() @@ -2366,8 +2364,8 @@ fn static_nodes() -> Vec { // TODO: Wrap this implementation with a document node that has a cache node so the output is cached? implementation: DocumentNodeImplementation::proto("graphene_core::vector::CopyToPointsNode"), inputs: vec![ - NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), - NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), + NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorDataTable::default()), true), + NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorDataTable::default()), true), NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(0.), false), @@ -2463,14 +2461,14 @@ fn static_nodes() -> Vec { exports: vec![NodeInput::node(NodeId(2), 0)], // Taken from output 0 of Sample Points nodes: [ DocumentNode { - inputs: vec![NodeInput::network(concrete!(graphene_core::vector::VectorData), 0)], + inputs: vec![NodeInput::network(concrete!(graphene_core::vector::VectorDataTable), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SubpathSegmentLengthsNode")), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![ - NodeInput::network(concrete!(graphene_core::vector::VectorData), 0), + NodeInput::network(concrete!(graphene_core::vector::VectorDataTable), 0), NodeInput::network(concrete!(f64), 1), // From the document node's parameters NodeInput::network(concrete!(f64), 2), // From the document node's parameters NodeInput::network(concrete!(f64), 3), // From the document node's parameters @@ -2495,7 +2493,7 @@ fn static_nodes() -> Vec { ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), + NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorDataTable::default()), true), NodeInput::value(TaggedValue::F64(100.), false), NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false), @@ -2586,7 +2584,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![ - NodeInput::network(concrete!(graphene_core::vector::VectorData), 0), + NodeInput::network(concrete!(graphene_core::vector::VectorDataTable), 0), NodeInput::network(concrete!(f64), 1), NodeInput::network(concrete!(u32), 2), ], @@ -2608,7 +2606,7 @@ fn static_nodes() -> Vec { ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), + NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorDataTable::default()), true), NodeInput::value(TaggedValue::F64(10.), false), NodeInput::value(TaggedValue::U32(0), false), ], @@ -2667,29 +2665,6 @@ fn static_nodes() -> Vec { }, }, - description: Cow::Borrowed("TODO"), - properties: None, - }, - // TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data. - DocumentNodeDefinition { - identifier: "Index", - category: "Debug", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::raster::IndexNode"), - inputs: vec![NodeInput::value(TaggedValue::Segments(vec![ImageFrame::empty()]), true), NodeInput::value(TaggedValue::U32(0), false)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![ - "Segmentation".into(), - PropertiesRow::with_override("Index", WidgetOverride::Number(NumberInputSettings { min: Some(0.), ..Default::default() })), - ], - output_names: vec!["Image".to_string()], - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), properties: None, }, @@ -2807,7 +2782,7 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN exports: vec![NodeInput::node(NodeId(1), 0)], nodes: [ DocumentNode { - inputs: vec![NodeInput::network(concrete!(ImageFrame), 0)], + inputs: vec![NodeInput::network(concrete!(ImageFrameTable), 0)], implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), manual_composition: Some(concrete!(())), skip_deduplication: true, @@ -2845,7 +2820,7 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN ..Default::default() }), inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), 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/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index ec738ec2b3..c17f8bf931 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -11,8 +11,9 @@ use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, No use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod}; use graph_craft::Type; use graphene_core::raster::curve::Curve; +use graphene_core::raster::image::ImageFrameTable; use graphene_core::raster::{ - BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute, + BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute, SelectiveColorChoice, }; use graphene_core::text::Font; @@ -22,8 +23,8 @@ use graphene_std::application_io::TextureFrame; use graphene_std::transform::Footprint; use graphene_std::vector::misc::BooleanOperation; use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops}; -use graphene_std::vector::VectorData; -use graphene_std::{GraphicGroup, Raster}; +use graphene_std::vector::VectorDataTable; +use graphene_std::{GraphicGroupTable, RasterFrame}; use glam::{DAffine2, DVec2, IVec2, UVec2}; @@ -152,11 +153,11 @@ pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, numbe } Some(x) if x == TypeId::of::() => curves_widget(document_node, node_id, index, name, true), Some(x) if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true), - Some(x) if x == TypeId::of::() => vector_widget(document_node, node_id, index, name, true).into(), - Some(x) if x == TypeId::of::() || x == TypeId::of::>() || x == TypeId::of::() => { + Some(x) if x == TypeId::of::() => vector_widget(document_node, node_id, index, name, true).into(), + Some(x) if x == TypeId::of::() || x == TypeId::of::>() || x == TypeId::of::() => { raster_widget(document_node, node_id, index, name, true).into() } - Some(x) if x == TypeId::of::() => group_widget(document_node, node_id, index, name, true).into(), + Some(x) if x == TypeId::of::() => group_widget(document_node, node_id, index, name, true).into(), Some(x) if x == TypeId::of::() => { let widgets = footprint_widget(document_node, node_id, index); let (last, rest) = widgets.split_last().expect("Footprint widget should return multiple rows"); diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index 3bfc9e1b4f..24e1ee4129 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -30,7 +30,7 @@ impl FrontendGraphDataType { | TaggedValue::F64Array4(_) | TaggedValue::VecF64(_) | TaggedValue::VecDVec2(_) => Self::Number, - TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, + TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here? TaggedValue::ArtboardGroup(_) => Self::Artboard, _ => Self::General, } diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index 120e3e3297..cde743e624 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -3,8 +3,7 @@ use graph_craft::document::NodeId; use graphene_core::renderer::ClickTarget; use graphene_core::renderer::Quad; use graphene_core::transform::Footprint; -use graphene_std::vector::PointId; -use graphene_std::vector::VectorData; +use graphene_std::vector::{PointId, VectorData}; use glam::{DAffine2, DVec2}; use std::collections::{HashMap, HashSet}; diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 6e850fdc4d..56af91c910 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -1323,7 +1323,7 @@ impl NodeNetworkInterface { } /// Layers excluding ones that are children of other layers in the list. - /// TODO: Cache this + // TODO: Cache this pub fn shallowest_unique_layers(&self, network_path: &[NodeId]) -> impl Iterator { let mut sorted_layers = if let Some(selected_nodes) = self.selected_nodes(network_path) { selected_nodes @@ -5977,9 +5977,11 @@ pub struct NodeNetworkPersistentMetadata { // Stores the transform and navigation state for the network pub navigation_metadata: NavigationMetadata, /// Stack of selection snapshots for previous history states. + // TODO: Use `#[serde(skip)]` here instead? @TrueDoctor claims this isn't valid but hasn't satisfactorily explained how it differs from the situation where `#[serde(default)]` fills in the default value. From brief testing, skip seems to work without issue. #[serde(default)] pub selection_undo_history: VecDeque, /// Stack of selection snapshots for future history states. + // TODO: Use `#[serde(skip)]` here instead? See above. #[serde(default)] pub selection_redo_history: VecDeque, } @@ -6016,7 +6018,7 @@ pub struct NodeNetworkTransientMetadata { // node_group_bounding_box: Vec<(Subpath, Vec)>, /// Cache for all outward wire connections pub outward_wires: TransientMetadata>>, - /// TODO: Cache all wire paths instead of calculating in Graph.svelte + // TODO: Cache all wire paths instead of calculating in Graph.svelte // pub wire_paths: Vec /// All export connector click targets pub import_export_ports: TransientMetadata, @@ -6132,7 +6134,7 @@ pub enum WidgetOverride { #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct PropertiesRow { /// A general datastore than can store key value pairs of any types for any input - /// TODO: This could be simplified to just Value, and key value pairs could be stored as the Object variant + // TODO: This could be simplified to just Value, and key value pairs could be stored as the Object variant pub input_data: HashMap, // An input can override a widget, which would otherwise be automatically generated from the type // The string is the identifier to the widget override function stored in INPUT_OVERRIDES @@ -6220,13 +6222,36 @@ impl PropertiesRow { } } +// TODO: Eventually remove this migration document upgrade code +fn migrate_output_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + use serde::Deserialize; + + const REPLACEMENTS: [(&str, &str); 3] = [ + ("VectorData", "Instances"), + ("GraphicGroup", "Instances"), + ("ImageFrame", "Instances"), + ]; + + let mut names = Vec::::deserialize(deserializer)?; + + for name in names.iter_mut() { + for (old, new) in REPLACEMENTS.iter() { + if name == old { + *name = new.to_string(); + } + } + } + + Ok(names) +} + /// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node. #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct DocumentNodePersistentMetadata { /// The name of the node definition, as originally set by [`DocumentNodeDefinition`], used to display in the UI and to display the appropriate properties if no display name is set. - /// TODO: Used during serialization/deserialization to prevent storing implementation or inputs (and possible other fields) if they are the same as the definition. - /// TODO: The reference is removed once the node is modified, since the node now stores its own implementation and inputs. - /// TODO: Implement node versioning so that references to old nodes can be updated to the new node definition. + // TODO: Used during serialization/deserialization to prevent storing implementation or inputs (and possible other fields) if they are the same as the definition. + // TODO: The reference is removed once the node is modified, since the node now stores its own implementation and inputs. + // TODO: Implement node versioning so that references to old nodes can be updated to the new node definition. pub reference: Option, /// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the reference name is displayed to the user in italics. #[serde(default)] @@ -6234,6 +6259,7 @@ pub struct DocumentNodePersistentMetadata { /// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function. /// Must match the length of node inputs pub input_properties: Vec, + #[serde(deserialize_with = "migrate_output_names")] pub output_names: Vec, /// Indicates to the UI if a primary output should be drawn for this node. /// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output. diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 9500656577..faa9aa81f6 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -428,7 +428,7 @@ impl MessageHandler> for PortfolioMes } }; - const REPLACEMENTS: [(&str, &str); 36] = [ + const REPLACEMENTS: [(&str, &str); 35] = [ ("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"), ("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"), ("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"), @@ -445,8 +445,8 @@ impl MessageHandler> for PortfolioMes ("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"), ("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"), ("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"), - ("graphene_core::raster::IndexNode", "graphene_core::raster::adjustments::IndexNode"), ("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"), + // ("graphene_core::raster::IndexNode", "graphene_core::raster::adjustments::IndexNode"), ("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"), ("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"), ("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"), 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 87764db994..4ef8ac3ede 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -3,7 +3,8 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo use crate::messages::prelude::*; use bezier_rs::Subpath; use graph_craft::document::{value::TaggedValue, NodeId, NodeInput}; -use graphene_core::raster::{BlendMode, ImageFrame}; +use graphene_core::raster::image::ImageFrame; +use graphene_core::raster::BlendMode; use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::vector::style::Gradient; use graphene_core::vector::PointId; @@ -12,7 +13,7 @@ use graphene_core::Color; use glam::DVec2; use std::collections::VecDeque; -/// Create a new vector layer from a vector of [`bezier_rs::Subpath`]. +/// Create a new vector layer. pub fn new_vector_layer(subpaths: Vec>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque) -> LayerNodeIdentifier { let insert_index = 0; responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index }); @@ -21,7 +22,7 @@ pub fn new_vector_layer(subpaths: Vec>, id: NodeId, parent: Lay LayerNodeIdentifier::new_unchecked(id) } -/// Create a new bitmap layer from an [`graphene_core::raster::ImageFrame`] +/// Create a new bitmap layer. pub fn new_image_layer(image_frame: ImageFrame, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque) -> LayerNodeIdentifier { let insert_index = 0; responses.add(GraphOperationMessage::NewBitmapLayer { @@ -33,7 +34,7 @@ pub fn new_image_layer(image_frame: ImageFrame, id: NodeId, parent: Layer LayerNodeIdentifier::new_unchecked(id) } -/// Create a new group layer from an svg +/// Create a new group layer from an SVG string. pub fn new_svg_layer(svg: String, transform: glam::DAffine2, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque) -> LayerNodeIdentifier { let insert_index = 0; responses.add(GraphOperationMessage::NewSvg { diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index dbedeae347..4776ba1cae 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -372,7 +372,7 @@ impl Fsm for ArtboardToolFsmState { responses.add(GraphOperationMessage::NewArtboard { id, artboard: graphene_core::Artboard { - graphic_group: graphene_core::GraphicGroup::EMPTY, + graphic_group: graphene_core::GraphicGroupTable::default(), label: String::from("Artboard"), location: start.round().as_ivec2(), dimensions: IVec2::splat(1), diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index c76f6e4218..029c5b1d53 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -502,7 +502,7 @@ impl PathToolData { }); // Make handles colinear if opposite handle is zero length - if opposite_handle_length.map_or(false, |l| l == 0.) { + if opposite_handle_length == Some(0.) { shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document); return true; } diff --git a/node-graph/gcore/src/application_io.rs b/node-graph/gcore/src/application_io.rs index 53984ac783..43a1af4640 100644 --- a/node-graph/gcore/src/application_io.rs +++ b/node-graph/gcore/src/application_io.rs @@ -1,3 +1,4 @@ +use crate::instances::Instances; use crate::text::FontCache; use crate::transform::{Footprint, Transform, TransformMut}; use crate::vector::style::ViewMode; @@ -64,6 +65,8 @@ impl Size for web_sys::HtmlCanvasElement { } } +pub type TextureFrameTable = Instances; + #[derive(Debug, Clone)] pub struct TextureFrame { #[cfg(feature = "wgpu")] diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 967da9f42a..331e30028a 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -1,14 +1,17 @@ -use crate::application_io::TextureFrame; -use crate::raster::{BlendMode, ImageFrame}; +use crate::application_io::{TextureFrame, TextureFrameTable}; +use crate::instances::Instances; +use crate::raster::image::{ImageFrame, ImageFrameTable}; +use crate::raster::BlendMode; use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut}; use crate::uuid::NodeId; -use crate::vector::VectorData; +use crate::vector::{VectorData, VectorDataTable}; use crate::Color; use dyn_any::DynAny; use core::ops::{Deref, DerefMut}; use glam::{DAffine2, IVec2}; +use std::hash::Hash; pub mod renderer; @@ -38,6 +41,25 @@ impl AlphaBlending { } } +// TODO: Eventually remove this migration document upgrade code +pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { + use serde::Deserialize; + + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(untagged)] + enum EitherFormat { + GraphicGroup(GraphicGroup), + GraphicGroupTable(GraphicGroupTable), + } + + Ok(match EitherFormat::deserialize(deserializer)? { + EitherFormat::GraphicGroup(graphic_group) => GraphicGroupTable::new(graphic_group), + EitherFormat::GraphicGroupTable(graphic_group_table) => graphic_group_table, + }) +} + +pub type GraphicGroupTable = Instances; + /// A list of [`GraphicElement`]s #[derive(Clone, Debug, PartialEq, DynAny, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -56,12 +78,6 @@ impl core::hash::Hash for GraphicGroup { } impl GraphicGroup { - pub const EMPTY: Self = Self { - elements: Vec::new(), - transform: DAffine2::IDENTITY, - alpha_blending: AlphaBlending::new(), - }; - pub fn new(elements: Vec) -> Self { Self { elements: elements.into_iter().map(|element| (element, None)).collect(), @@ -71,127 +87,161 @@ impl GraphicGroup { } } +impl From for GraphicGroupTable { + fn from(graphic_group: GraphicGroup) -> Self { + Self::new(graphic_group) + } +} +impl From for GraphicGroupTable { + fn from(vector_data: VectorData) -> Self { + Self::new(GraphicGroup::new(vec![GraphicElement::VectorData(VectorDataTable::new(vector_data))])) + } +} +impl From for GraphicGroupTable { + fn from(vector_data: VectorDataTable) -> Self { + Self::new(GraphicGroup::new(vec![GraphicElement::VectorData(vector_data)])) + } +} +impl From> for GraphicGroupTable { + fn from(image_frame: ImageFrame) -> Self { + Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame)))])) + } +} +impl From> for GraphicGroupTable { + fn from(image_frame: ImageFrameTable) -> Self { + Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::ImageFrame(image_frame))])) + } +} +impl From for GraphicGroupTable { + fn from(texture_frame: TextureFrame) -> Self { + Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::TextureFrame(TextureFrameTable::new(texture_frame)))])) + } +} +impl From for GraphicGroupTable { + fn from(texture_frame: TextureFrameTable) -> Self { + Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::TextureFrame(texture_frame))])) + } +} + /// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`]. -/// Can be another recursively nested [`GraphicGroup`], a [`VectorData`] shape, an [`ImageFrame`], or an [`Artboard`]. #[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum GraphicElement { /// Equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g - GraphicGroup(GraphicGroup), + GraphicGroup(GraphicGroupTable), /// A vector shape, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path - VectorData(Box), - Raster(Raster), + VectorData(VectorDataTable), + RasterFrame(RasterFrame), } // TODO: Can this be removed? It doesn't necessarily make that much sense to have a default when, instead, the entire GraphicElement just shouldn't exist if there's no specific content to assign it. impl Default for GraphicElement { fn default() -> Self { - Self::VectorData(Box::new(VectorData::empty())) + Self::VectorData(VectorDataTable::default()) } } impl GraphicElement { - pub fn as_group(&self) -> Option<&GraphicGroup> { + pub fn as_group(&self) -> Option<&GraphicGroupTable> { match self { GraphicElement::GraphicGroup(group) => Some(group), _ => None, } } - pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroup> { + pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroupTable> { match self { GraphicElement::GraphicGroup(group) => Some(group), _ => None, } } - pub fn as_vector_data(&self) -> Option<&VectorData> { + pub fn as_vector_data(&self) -> Option<&VectorDataTable> { match self { GraphicElement::VectorData(data) => Some(data), _ => None, } } - pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorData> { + pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorDataTable> { match self { GraphicElement::VectorData(data) => Some(data), _ => None, } } - pub fn as_raster(&self) -> Option<&Raster> { + pub fn as_raster(&self) -> Option<&RasterFrame> { match self { - GraphicElement::Raster(raster) => Some(raster), + GraphicElement::RasterFrame(raster) => Some(raster), _ => None, } } - pub fn as_raster_mut(&mut self) -> Option<&mut Raster> { + pub fn as_raster_mut(&mut self) -> Option<&mut RasterFrame> { match self { - GraphicElement::Raster(raster) => Some(raster), + GraphicElement::RasterFrame(raster) => Some(raster), _ => None, } } } #[derive(Clone, Debug, Hash, PartialEq, DynAny)] -pub enum Raster { - /// A bitmap image with a finite position and extent, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image - ImageFrame(ImageFrame), - Texture(TextureFrame), +pub enum RasterFrame { + /// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image + ImageFrame(ImageFrameTable), + /// A GPU texture with a finite position and extent + TextureFrame(TextureFrameTable), } -impl<'de> serde::Deserialize<'de> for Raster { +impl<'de> serde::Deserialize<'de> for RasterFrame { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - let frame = ImageFrame::deserialize(deserializer)?; - Ok(Raster::ImageFrame(frame)) + Ok(RasterFrame::ImageFrame(ImageFrameTable::new(ImageFrame::deserialize(deserializer)?))) } } -impl serde::Serialize for Raster { +impl serde::Serialize for RasterFrame { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match self { - Raster::ImageFrame(_) => self.serialize(serializer), - Raster::Texture(_) => todo!(), + RasterFrame::ImageFrame(_) => self.serialize(serializer), + RasterFrame::TextureFrame(_) => todo!(), } } } -impl Transform for Raster { +impl Transform for RasterFrame { fn transform(&self) -> DAffine2 { match self { - Raster::ImageFrame(frame) => frame.transform(), - Raster::Texture(frame) => frame.transform(), + RasterFrame::ImageFrame(frame) => frame.transform(), + RasterFrame::TextureFrame(frame) => frame.transform(), } } fn local_pivot(&self, pivot: glam::DVec2) -> glam::DVec2 { match self { - Raster::ImageFrame(frame) => frame.local_pivot(pivot), - Raster::Texture(frame) => frame.local_pivot(pivot), + RasterFrame::ImageFrame(frame) => frame.local_pivot(pivot), + RasterFrame::TextureFrame(frame) => frame.local_pivot(pivot), } } } -impl TransformMut for Raster { +impl TransformMut for RasterFrame { fn transform_mut(&mut self) -> &mut DAffine2 { match self { - Raster::ImageFrame(frame) => frame.transform_mut(), - Raster::Texture(frame) => frame.transform_mut(), + RasterFrame::ImageFrame(frame) => frame.transform_mut(), + RasterFrame::TextureFrame(frame) => frame.transform_mut(), } } } /// Some [`ArtboardData`] with some optional clipping bounds that can be exported. -/// Similar to an Inkscape page: https://media.inkscape.org/media/doc/release_notes/1.2/Inkscape_1.2.html#Page_tool #[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Artboard { - pub graphic_group: GraphicGroup, + pub graphic_group: GraphicGroupTable, pub label: String, pub location: IVec2, pub dimensions: IVec2, @@ -202,7 +252,7 @@ pub struct Artboard { impl Artboard { pub fn new(location: IVec2, dimensions: IVec2) -> Self { Self { - graphic_group: GraphicGroup::EMPTY, + graphic_group: GraphicGroupTable::default(), label: String::from("Artboard"), location: location.min(location + dimensions), dimensions: dimensions.abs(), @@ -220,8 +270,6 @@ pub struct ArtboardGroup { } impl ArtboardGroup { - pub const EMPTY: Self = Self { artboards: Vec::new() }; - pub fn new() -> Self { Default::default() } @@ -239,19 +287,22 @@ async fn layer( )] footprint: F, #[implementations( - () -> GraphicGroup, - Footprint -> GraphicGroup, + () -> GraphicGroupTable, + Footprint -> GraphicGroupTable, )] - stack: impl Node, + stack: impl Node, #[implementations( () -> GraphicElement, Footprint -> GraphicElement, )] element: impl Node, node_path: Vec, -) -> GraphicGroup { +) -> GraphicGroupTable { let mut element = element.eval(footprint).await; - let mut stack = stack.eval(footprint).await; + let stack = stack.eval(footprint).await; + let stack = stack.one_item(); + let mut stack = stack.clone(); + if stack.transform.matrix2.determinant() != 0. { *element.transform_mut() = stack.transform.inverse() * element.transform(); } else { @@ -262,7 +313,8 @@ async fn layer( // 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 + + GraphicGroupTable::new(stack) } #[node_macro::node(category("Debug"))] @@ -276,14 +328,14 @@ async fn to_element + 'n>( )] footprint: F, #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - () -> TextureFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, - Footprint -> TextureFrame, + () -> GraphicGroupTable, + () -> VectorDataTable, + () -> ImageFrameTable, + () -> TextureFrameTable, + Footprint -> GraphicGroupTable, + Footprint -> VectorDataTable, + Footprint -> ImageFrameTable, + Footprint -> TextureFrameTable, )] data: impl Node, ) -> GraphicElement { @@ -291,7 +343,7 @@ async fn to_element + 'n>( } #[node_macro::node(category("General"))] -async fn to_group + 'n>( +async fn to_group + 'n>( #[implementations( (), (), @@ -301,17 +353,17 @@ async fn to_group + 'n>( )] footprint: F, #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - () -> TextureFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, - Footprint -> TextureFrame, + () -> GraphicGroupTable, + () -> VectorDataTable, + () -> ImageFrameTable, + () -> TextureFrameTable, + Footprint -> GraphicGroupTable, + Footprint -> VectorDataTable, + Footprint -> ImageFrameTable, + Footprint -> TextureFrameTable, )] element: impl Node, -) -> GraphicGroup { +) -> GraphicGroupTable { element.eval(footprint).await.into() } @@ -323,20 +375,28 @@ async fn flatten_group( )] footprint: F, #[implementations( - () -> GraphicGroup, - Footprint -> GraphicGroup, + () -> GraphicGroupTable, + Footprint -> GraphicGroupTable, )] - group: impl Node, + group: impl Node, fully_flatten: bool, -) -> GraphicGroup { +) -> GraphicGroupTable { let nested_group = group.eval(footprint).await; - let mut flat_group = GraphicGroup::EMPTY; + let nested_group = nested_group.one_item(); + let nested_group = nested_group.clone(); + + let mut flat_group = GraphicGroup::default(); + fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) { - let mut collection_group = GraphicGroup::EMPTY; + let mut collection_group = GraphicGroup::default(); for (element, reference) in current_group.elements { - if let GraphicElement::GraphicGroup(mut nested_group) = element { - nested_group.transform *= current_group.transform; - let mut sub_group = GraphicGroup::EMPTY; + if let GraphicElement::GraphicGroup(nested_group) = element { + let nested_group = nested_group.one_item(); + let mut nested_group = nested_group.clone(); + + *nested_group.transform_mut() = nested_group.transform() * current_group.transform; + + let mut sub_group = GraphicGroup::default(); if fully_flatten { flatten_group(&mut sub_group, nested_group, fully_flatten); } else { @@ -353,12 +413,14 @@ async fn flatten_group( result_group.append(&mut collection_group.elements); } + flatten_group(&mut flat_group, nested_group, fully_flatten); - flat_group + + GraphicGroupTable::new(flat_group) } #[node_macro::node(category(""))] -async fn to_artboard + 'n>( +async fn to_artboard + 'n>( #[implementations( (), (), @@ -368,13 +430,13 @@ async fn to_artboard + ' )] mut footprint: F, #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, + () -> GraphicGroupTable, + () -> VectorDataTable, + () -> ImageFrameTable, () -> TextureFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, + Footprint -> GraphicGroupTable, + Footprint -> VectorDataTable, + Footprint -> ImageFrameTable, Footprint -> TextureFrame, )] contents: impl Node, @@ -426,23 +488,47 @@ async fn append_artboard( artboards } +// TODO: Remove this one impl From> for GraphicElement { fn from(image_frame: ImageFrame) -> Self { - GraphicElement::Raster(Raster::ImageFrame(image_frame)) + GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame))) } } +impl From> for GraphicElement { + fn from(image_frame: ImageFrameTable) -> Self { + GraphicElement::RasterFrame(RasterFrame::ImageFrame(image_frame)) + } +} +// TODO: Remove this one impl From for GraphicElement { fn from(texture: TextureFrame) -> Self { - GraphicElement::Raster(Raster::Texture(texture)) + GraphicElement::RasterFrame(RasterFrame::TextureFrame(TextureFrameTable::new(texture))) } } +impl From for GraphicElement { + fn from(texture: TextureFrameTable) -> Self { + GraphicElement::RasterFrame(RasterFrame::TextureFrame(texture)) + } +} +// TODO: Remove this one impl From for GraphicElement { fn from(vector_data: VectorData) -> Self { - GraphicElement::VectorData(Box::new(vector_data)) + GraphicElement::VectorData(VectorDataTable::new(vector_data)) + } +} +impl From for GraphicElement { + fn from(vector_data: VectorDataTable) -> Self { + GraphicElement::VectorData(vector_data) } } +// TODO: Remove this one impl From for GraphicElement { fn from(graphic_group: GraphicGroup) -> Self { + GraphicElement::GraphicGroup(GraphicGroupTable::new(graphic_group)) + } +} +impl From for GraphicElement { + fn from(graphic_group: GraphicGroupTable) -> Self { GraphicElement::GraphicGroup(graphic_group) } } @@ -464,8 +550,8 @@ impl DerefMut for GraphicGroup { /// as that would conflict with the implementation for `Self` trait ToGraphicElement: Into {} -impl ToGraphicElement for VectorData {} -impl ToGraphicElement for ImageFrame {} +impl ToGraphicElement for VectorDataTable {} +impl ToGraphicElement for ImageFrameTable {} impl ToGraphicElement for TextureFrame {} impl From for GraphicGroup diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index 54af974626..98b7fd028d 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -3,13 +3,13 @@ mod rect; pub use quad::Quad; pub use rect::Rect; -use crate::raster::{BlendMode, Image, ImageFrame}; +use crate::raster::image::ImageFrameTable; +use crate::raster::{BlendMode, Image}; use crate::transform::{Footprint, Transform}; use crate::uuid::{generate_uuid, NodeId}; use crate::vector::style::{Fill, Stroke, ViewMode}; -use crate::vector::PointId; -use crate::Raster; -use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup}; +use crate::vector::{PointId, VectorDataTable}; +use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame}; use bezier_rs::Subpath; use dyn_any::DynAny; @@ -217,7 +217,7 @@ pub enum ImageRenderMode { #[derive(Clone, Debug, Default)] pub struct RenderContext { #[cfg(feature = "wgpu")] - pub ressource_overrides: std::collections::HashMap>, + pub resource_overrides: std::collections::HashMap>, } /// Static state used whilst rendering @@ -407,59 +407,116 @@ impl GraphicElementRendered for GraphicGroup { } fn to_graphic_element(&self) -> GraphicElement { - GraphicElement::GraphicGroup(self.clone()) + GraphicElement::GraphicGroup(GraphicGroupTable::new(self.clone())) } } -impl GraphicElementRendered for VectorData { +impl GraphicElementRendered for GraphicGroupTable { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { - let multiplied_transform = render.transform * self.transform; - let set_stroke_transform = self.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.); - let applied_stroke_transform = set_stroke_transform.unwrap_or(self.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 = self.bounding_box().unwrap_or_default(); - let transformed_bounds = self.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default(); - - let mut path = String::new(); - for subpath in self.stroke_bezier_paths() { - let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform); + for instance in self.instances() { + instance.render_svg(render, render_params); } + } - render.leaf_tag("path", |attributes| { - attributes.push("d", path); - let matrix = format_transform_matrix(element_transform); - attributes.push("transform", matrix); + fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { + self.instances().flat_map(|instance| instance.bounding_box(transform)).reduce(Quad::combine_bounds) + } - let defs = &mut attributes.0.svg_defs; - let fill_and_stroke = self - .style - .render(render_params.view_mode, defs, element_transform, applied_stroke_transform, layer_bounds, transformed_bounds); - attributes.push_val(fill_and_stroke); + fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { + let instance = self.one_item(); - if self.alpha_blending.opacity < 1. { - attributes.push("opacity", self.alpha_blending.opacity.to_string()); - } + 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()) + } - if self.alpha_blending.blend_mode != BlendMode::default() { - attributes.push("style", self.alpha_blending.blend_mode.render()); + fn new_ids_from_hash(&mut self, _reference: Option) { + for instance in self.instances_mut() { + instance.new_ids_from_hash(None); + } + } + + fn to_graphic_element(&self) -> GraphicElement { + GraphicElement::GraphicGroup(self.clone()) + } +} + +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 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 mut path = String::new(); + for subpath in instance.stroke_bezier_paths() { + let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform); } - }); + + render.leaf_tag("path", |attributes| { + attributes.push("d", path); + let matrix = format_transform_matrix(element_transform); + attributes.push("transform", matrix); + + let defs = &mut attributes.0.svg_defs; + let fill_and_stroke = instance + .style + .render(render_params.view_mode, defs, element_transform, applied_stroke_transform, layer_bounds, transformed_bounds); + attributes.push_val(fill_and_stroke); + + if instance.alpha_blending.opacity < 1. { + attributes.push("opacity", instance.alpha_blending.opacity.to_string()); + } + + if instance.alpha_blending.blend_mode != BlendMode::default() { + attributes.push("style", instance.alpha_blending.blend_mode.render()); + } + }); + } } fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { - let stroke_width = self.style.stroke().map(|s| s.weight()).unwrap_or_default(); - let miter_limit = self.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); - self.bounding_box_with_transform(transform * self.transform).map(|[a, b]| [a - offset, b + offset]) + self.instances() + .flat_map(|instance| { + let stroke_width = 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 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]) + }) + .reduce(Quad::combine_bounds) } fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option) { + let instance = self.one_item(); + if let Some(element_id) = element_id { - let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight); - let filled = self.style.fill() != &Fill::None; + let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight); + let filled = instance.style.fill() != &Fill::None; let fill = |mut subpath: bezier_rs::Subpath<_>| { if filled { subpath.set_closed(true); @@ -467,7 +524,7 @@ impl GraphicElementRendered for VectorData { subpath }; - let click_targets = self + let click_targets = instance .stroke_bezier_paths() .map(fill) .map(|subpath| ClickTarget::new(subpath, stroke_width)) @@ -476,145 +533,155 @@ impl GraphicElementRendered for VectorData { metadata.click_targets.insert(element_id, click_targets); } - if let Some(upstream_graphic_group) = &self.upstream_graphic_group { - footprint.transform *= self.transform; + if let Some(upstream_graphic_group) = &instance.upstream_graphic_group { + footprint.transform *= instance.transform; upstream_graphic_group.collect_metadata(metadata, footprint, None); } } fn add_upstream_click_targets(&self, click_targets: &mut Vec) { - let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight); - let filled = self.style.fill() != &Fill::None; - let fill = |mut subpath: bezier_rs::Subpath<_>| { - if filled { - subpath.set_closed(true); - } - subpath - }; - click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width))); + 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 fill = |mut subpath: bezier_rs::Subpath<_>| { + if filled { + subpath.set_closed(true); + } + subpath + }; + + click_targets.extend(instance.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width))); + } } #[cfg(feature = "vello")] fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _: &mut RenderContext) { use crate::vector::style::GradientType; use vello::peniko; - let mut layer = false; - let multiplied_transform = parent_transform * self.transform; - let set_stroke_transform = self.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 = self.bounding_box().unwrap_or_default(); - - if self.alpha_blending.opacity < 1. || self.alpha_blending.blend_mode != BlendMode::default() { - layer = true; - scene.push_layer( - peniko::BlendMode::new(self.alpha_blending.blend_mode.into(), peniko::Compose::SrcOver), - self.alpha_blending.opacity, - kurbo::Affine::new(multiplied_transform.to_cols_array()), - &kurbo::Rect::new(layer_bounds[0].x, layer_bounds[0].y, layer_bounds[1].x, layer_bounds[1].y), - ); - } - - let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y); - let mut path = kurbo::BezPath::new(); - for subpath in self.stroke_bezier_paths() { - subpath.to_vello_path(applied_stroke_transform, &mut path); - } + 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 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(); + + if instance.alpha_blending.opacity < 1. || instance.alpha_blending.blend_mode != BlendMode::default() { + layer = true; + scene.push_layer( + peniko::BlendMode::new(instance.alpha_blending.blend_mode.into(), peniko::Compose::SrcOver), + instance.alpha_blending.opacity, + kurbo::Affine::new(multiplied_transform.to_cols_array()), + &kurbo::Rect::new(layer_bounds[0].x, layer_bounds[0].y, layer_bounds[1].x, layer_bounds[1].y), + ); + } - match self.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); + 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() { + subpath.to_vello_path(applied_stroke_transform, &mut path); } - Fill::Gradient(gradient) => { - let mut stops = peniko::ColorStops::new(); - for &(offset, color) in &gradient.stops.0 { - stops.push(peniko::ColorStop { - offset: offset as f32, - color: peniko::color::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])), + + match 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); + } + Fill::Gradient(gradient) => { + let mut stops = peniko::ColorStops::new(); + for &(offset, color) in &gradient.stops.0 { + stops.push(peniko::ColorStop { + offset: offset as f32, + color: peniko::color::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])), + }); + } + // Compute bounding box of the shape to determine the gradient start and end points + let bounds = 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(); + let mod_points = inverse_parent_transform * multiplied_transform * bound_transform; + + let start = mod_points.transform_point2(gradient.start); + let end = mod_points.transform_point2(gradient.end); + + let fill = peniko::Brush::Gradient(peniko::Gradient { + kind: match gradient.gradient_type { + GradientType::Linear => peniko::GradientKind::Linear { + start: to_point(start), + end: to_point(end), + }, + GradientType::Radial => { + let radius = start.distance(end); + peniko::GradientKind::Radial { + start_center: to_point(start), + start_radius: 0., + end_center: to_point(start), + end_radius: radius as f32, + } + } + }, + stops, + ..Default::default() }); + // Vello does `element_transform * brush_transform` internally. We don't want element_transform to have any impact so we need to left multiply by the inverse. + // This makes the final internal brush transform equal to `parent_transform`, allowing you to stretch a gradient by transforming the parent folder. + let inverse_element_transform = (element_transform.matrix2.determinant() != 0.).then(|| element_transform.inverse()).unwrap_or_default(); + let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array()); + scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path); } - // Compute bounding box of the shape to determine the gradient start and end points - let bounds = self.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(); - let mod_points = inverse_parent_transform * multiplied_transform * bound_transform; - - let start = mod_points.transform_point2(gradient.start); - let end = mod_points.transform_point2(gradient.end); + Fill::None => (), + }; - let fill = peniko::Brush::Gradient(peniko::Gradient { - kind: match gradient.gradient_type { - GradientType::Linear => peniko::GradientKind::Linear { - start: to_point(start), - end: to_point(end), - }, - GradientType::Radial => { - let radius = start.distance(end); - peniko::GradientKind::Radial { - start_center: to_point(start), - start_radius: 0., - end_center: to_point(start), - end_radius: radius as f32, - } - } - }, - stops, - ..Default::default() - }); - // Vello does `elment_transform * brush_transform` internally. We don't want elment_transform to have any impact so we need to left multiply by the inverse. - // This makes the final internal brush transform equal to `parent_transform`, allowing you to strech a gradient by transforming the parent folder. - let inverse_element_transform = (element_transform.matrix2.determinant() != 0.).then(|| element_transform.inverse()).unwrap_or_default(); - let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array()); - scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path); + if let Some(stroke) = 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, + }; + use crate::vector::style::{LineCap, LineJoin}; + use vello::kurbo::{Cap, Join}; + let cap = match stroke.line_cap { + LineCap::Butt => Cap::Butt, + LineCap::Round => Cap::Round, + LineCap::Square => Cap::Square, + }; + let join = match stroke.line_join { + LineJoin::Miter => Join::Miter, + LineJoin::Bevel => Join::Bevel, + LineJoin::Round => Join::Round, + }; + let stroke = kurbo::Stroke { + width: stroke.weight, + miter_limit: stroke.line_join_miter_limit, + join, + start_cap: cap, + end_cap: cap, + dash_pattern: stroke.dash_lengths.into(), + dash_offset: stroke.dash_offset, + }; + if stroke.width > 0. { + scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), color, None, &path); + } } - Fill::None => (), - }; - - if let Some(stroke) = self.style.stroke() { - let color = match stroke.color { - Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]), - None => peniko::Color::TRANSPARENT, - }; - use crate::vector::style::{LineCap, LineJoin}; - use vello::kurbo::{Cap, Join}; - let cap = match stroke.line_cap { - LineCap::Butt => Cap::Butt, - LineCap::Round => Cap::Round, - LineCap::Square => Cap::Square, - }; - let join = match stroke.line_join { - LineJoin::Miter => Join::Miter, - LineJoin::Bevel => Join::Bevel, - LineJoin::Round => Join::Round, - }; - let stroke = kurbo::Stroke { - width: stroke.weight, - miter_limit: stroke.line_join_miter_limit, - join, - start_cap: cap, - end_cap: cap, - dash_pattern: stroke.dash_lengths.into(), - dash_offset: stroke.dash_offset, - }; - if stroke.width > 0. { - scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), color, None, &path); + if layer { + scene.pop_layer(); } } - if layer { - scene.pop_layer(); - } } fn new_ids_from_hash(&mut self, reference: Option) { - self.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default()); + for instance in self.instances_mut() { + instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default()); + } } fn to_graphic_element(&self) -> GraphicElement { - GraphicElement::VectorData(Box::new(self.clone())) + let instance = self.one_item(); + + GraphicElement::VectorData(VectorDataTable::new(instance.clone())) } } @@ -690,8 +757,8 @@ impl GraphicElementRendered for Artboard { fn add_upstream_click_targets(&self, click_targets: &mut Vec) { let mut subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2()); - if self.graphic_group.transform.matrix2.determinant() != 0. { - subpath.apply_transform(self.graphic_group.transform.inverse()); + if self.graphic_group.transform().matrix2.determinant() != 0. { + subpath.apply_transform(self.graphic_group.transform().inverse()); click_targets.push(ClickTarget::new(subpath, 0.)); } } @@ -726,7 +793,7 @@ impl GraphicElementRendered for Artboard { } } -impl GraphicElementRendered for crate::ArtboardGroup { +impl GraphicElementRendered for ArtboardGroup { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { for (artboard, _) in &self.artboards { artboard.render_svg(render, render_params); @@ -761,56 +828,64 @@ impl GraphicElementRendered for crate::ArtboardGroup { } } -impl GraphicElementRendered for ImageFrame { +impl GraphicElementRendered for ImageFrameTable { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { - let transform = self.transform * render.transform; + for instance in self.instances() { + let transform = instance.transform * render.transform; + + match render_params.image_render_mode { + ImageRenderMode::Base64 => { + let image = &instance.image; + if image.data.is_empty() { + return; + } - match render_params.image_render_mode { - ImageRenderMode::Base64 => { - let image = &self.image; - if image.data.is_empty() { - return; + let base64_string = image.base64_string.clone().unwrap_or_else(|| { + let output = image.to_png(); + let preamble = "data:image/png;base64,"; + let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4); + base64_string.push_str(preamble); + base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string); + base64_string + }); + render.leaf_tag("image", |attributes| { + attributes.push("width", 1.to_string()); + attributes.push("height", 1.to_string()); + attributes.push("preserveAspectRatio", "none"); + attributes.push("href", base64_string); + let matrix = format_transform_matrix(transform); + if !matrix.is_empty() { + attributes.push("transform", matrix); + } + if instance.alpha_blending.opacity < 1. { + attributes.push("opacity", instance.alpha_blending.opacity.to_string()); + } + if instance.alpha_blending.blend_mode != BlendMode::default() { + attributes.push("style", instance.alpha_blending.blend_mode.render()); + } + }); } - - let base64_string = image.base64_string.clone().unwrap_or_else(|| { - let output = image.to_png(); - let preamble = "data:image/png;base64,"; - let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4); - base64_string.push_str(preamble); - base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string); - base64_string - }); - render.leaf_tag("image", |attributes| { - attributes.push("width", 1.to_string()); - attributes.push("height", 1.to_string()); - attributes.push("preserveAspectRatio", "none"); - attributes.push("href", base64_string); - let matrix = format_transform_matrix(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 self.alpha_blending.blend_mode != BlendMode::default() { - attributes.push("style", self.alpha_blending.blend_mode.render()); - } - }); } } } fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { - let transform = transform * self.transform; - (transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box()) + self.instances() + .flat_map(|instance| { + 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 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, self.transform)); + metadata.footprints.insert(element_id, (footprint, instance.transform)); } fn add_upstream_click_targets(&self, click_targets: &mut Vec) { @@ -822,55 +897,61 @@ impl GraphicElementRendered for ImageFrame { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext) { use vello::peniko; - let image = &self.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 * self.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); + for instance in self.instances() { + let image = &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)); - scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array())); + scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array())); + } } } -impl GraphicElementRendered for Raster { + +impl GraphicElementRendered for RasterFrame { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { let transform = self.transform() * render.transform; match render_params.image_render_mode { ImageRenderMode::Base64 => { let image = match self { - Raster::ImageFrame(ref image) => image, - Raster::Texture(_) => return, + RasterFrame::ImageFrame(ref image) => image, + RasterFrame::TextureFrame(_) => return, }; - let (image, blending) = (&image.image, image.alpha_blending); - if image.data.is_empty() { - return; - } - let base64_string = image.base64_string.clone().unwrap_or_else(|| { - let output = image.to_png(); - let preamble = "data:image/png;base64,"; - let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4); - base64_string.push_str(preamble); - base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string); - base64_string - }); - render.leaf_tag("image", |attributes| { - attributes.push("width", 1.to_string()); - attributes.push("height", 1.to_string()); - attributes.push("preserveAspectRatio", "none"); - attributes.push("href", base64_string); - let matrix = format_transform_matrix(transform); - if !matrix.is_empty() { - attributes.push("transform", matrix); + for image in image.instances() { + let (image, blending) = (&image.image, image.alpha_blending); + if image.data.is_empty() { + return; } - if blending.opacity < 1. { - attributes.push("opacity", blending.opacity.to_string()); - } - if blending.blend_mode != BlendMode::default() { - attributes.push("style", blending.blend_mode.render()); - } - }); + + let base64_string = image.base64_string.clone().unwrap_or_else(|| { + let output = image.to_png(); + let preamble = "data:image/png;base64,"; + let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4); + base64_string.push_str(preamble); + base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string); + base64_string + }); + render.leaf_tag("image", |attributes| { + attributes.push("width", 1.to_string()); + attributes.push("height", 1.to_string()); + attributes.push("preserveAspectRatio", "none"); + attributes.push("href", base64_string); + let matrix = format_transform_matrix(transform); + if !matrix.is_empty() { + attributes.push("transform", matrix); + } + if blending.opacity < 1. { + attributes.push("opacity", blending.opacity.to_string()); + } + if blending.blend_mode != BlendMode::default() { + attributes.push("style", blending.blend_mode.render()); + } + }); + } } } } @@ -897,35 +978,46 @@ impl GraphicElementRendered for Raster { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) { use vello::peniko; - let (image, blend_mode) = match self { - Raster::ImageFrame(image_frame) => { - let image = &image_frame.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); - (image, image_frame.alpha_blending) + let mut render_stuff = |image: vello::peniko::Image, blend_mode: crate::AlphaBlending| { + let image_transform = transform * self.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); + let layer = blend_mode != Default::default(); + + let Some(bounds) = self.bounding_box(transform) else { return }; + let blending = vello::peniko::BlendMode::new(blend_mode.blend_mode.into(), vello::peniko::Compose::SrcOver); + + if layer { + let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); + scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect); } - Raster::Texture(texture) => { - let image = vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, texture.texture.width(), texture.texture.height()).with_extend(peniko::Extend::Repeat); - let id = image.data.id(); - context.ressource_overrides.insert(id, texture.texture.clone()); - (image, texture.alpha_blend) + scene.draw_image(&image, vello::kurbo::Affine::new(image_transform.to_cols_array())); + if layer { + scene.pop_layer() } }; - let image_transform = transform * self.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); - let layer = blend_mode != Default::default(); - let Some(bounds) = self.bounding_box(transform) else { return }; - let blending = vello::peniko::BlendMode::new(blend_mode.blend_mode.into(), vello::peniko::Compose::SrcOver); + match self { + RasterFrame::ImageFrame(image_frame) => { + for image_frame in image_frame.instances() { + let image = &image_frame.image; + if image.data.is_empty() { + return; + } - if layer { - let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); - scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect); - } - scene.draw_image(&image, vello::kurbo::Affine::new(image_transform.to_cols_array())); - if layer { - scene.pop_layer() + 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); + } + } + 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); + + let id = image.data.id(); + context.resource_overrides.insert(id, texture.texture.clone()); + + render_stuff(image, texture.alpha_blend); + } + } } } } @@ -934,15 +1026,15 @@ impl GraphicElementRendered for GraphicElement { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { match self { GraphicElement::VectorData(vector_data) => vector_data.render_svg(render, render_params), - GraphicElement::Raster(raster) => raster.render_svg(render, render_params), + GraphicElement::RasterFrame(raster) => raster.render_svg(render, render_params), GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params), } } fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { match self { - GraphicElement::VectorData(vector_data) => GraphicElementRendered::bounding_box(&**vector_data, transform), - GraphicElement::Raster(raster) => raster.bounding_box(transform), + GraphicElement::VectorData(vector_data) => vector_data.bounding_box(transform), + GraphicElement::RasterFrame(raster) => raster.bounding_box(transform), GraphicElement::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform), } } @@ -954,7 +1046,7 @@ impl GraphicElementRendered for GraphicElement { match self { GraphicElement::VectorData(vector_data) => vector_data.collect_metadata(metadata, footprint, element_id), - GraphicElement::Raster(raster) => raster.collect_metadata(metadata, footprint, element_id), + GraphicElement::RasterFrame(raster) => raster.collect_metadata(metadata, footprint, element_id), GraphicElement::GraphicGroup(graphic_group) => graphic_group.collect_metadata(metadata, footprint, element_id), } } @@ -962,7 +1054,7 @@ impl GraphicElementRendered for GraphicElement { fn add_upstream_click_targets(&self, click_targets: &mut Vec) { match self { GraphicElement::VectorData(vector_data) => vector_data.add_upstream_click_targets(click_targets), - GraphicElement::Raster(raster) => raster.add_upstream_click_targets(click_targets), + GraphicElement::RasterFrame(raster) => raster.add_upstream_click_targets(click_targets), GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_upstream_click_targets(click_targets), } } @@ -972,7 +1064,7 @@ impl GraphicElementRendered for GraphicElement { match self { GraphicElement::VectorData(vector_data) => vector_data.render_to_vello(scene, transform, context), GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform, context), - GraphicElement::Raster(raster) => raster.render_to_vello(scene, transform, context), + GraphicElement::RasterFrame(raster) => raster.render_to_vello(scene, transform, context), } } @@ -980,7 +1072,7 @@ impl GraphicElementRendered for GraphicElement { match self { GraphicElement::VectorData(vector_data) => vector_data.contains_artboard(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.contains_artboard(), - GraphicElement::Raster(raster) => raster.contains_artboard(), + GraphicElement::RasterFrame(raster) => raster.contains_artboard(), } } @@ -988,7 +1080,7 @@ impl GraphicElementRendered for GraphicElement { match self { GraphicElement::VectorData(vector_data) => vector_data.new_ids_from_hash(reference), GraphicElement::GraphicGroup(graphic_group) => graphic_group.new_ids_from_hash(reference), - GraphicElement::Raster(_) => (), + GraphicElement::RasterFrame(_) => (), } } } diff --git a/node-graph/gcore/src/instances.rs b/node-graph/gcore/src/instances.rs new file mode 100644 index 0000000000..1eb1325dc2 --- /dev/null +++ b/node-graph/gcore/src/instances.rs @@ -0,0 +1,87 @@ +use crate::vector::InstanceId; +use crate::GraphicElement; + +use dyn_any::StaticType; + +use std::hash::Hash; + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct Instances +where + T: Into + StaticType + 'static, +{ + id: Vec, + instances: Vec, +} + +impl + StaticType + 'static> Instances { + pub fn new(instance: T) -> Self { + Self { + id: vec![InstanceId::generate()], + instances: vec![instance], + } + } + + 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_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 instances(&self) -> impl Iterator { + assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instances.len()); + self.instances.iter() + } + + 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 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 { + fn default() -> Self { + Self::new(T::default()) + } +} + +impl + Hash + StaticType + 'static> core::hash::Hash for Instances { + fn hash(&self, state: &mut H) { + self.id.hash(state); + for instance in &self.instances { + instance.hash(state); + } + } +} + +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) } + } +} + +unsafe impl + StaticType + 'static> dyn_any::StaticType for Instances { + type Static = Instances; +} diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index be6487ed43..5883fbb9c5 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -15,6 +15,7 @@ pub use ctor; pub mod consts; pub mod generic; +pub mod instances; pub mod logic; pub mod ops; pub mod structural; diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 814a3ff5e1..5911109858 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -1,13 +1,14 @@ use crate::transform::Footprint; -use crate::vector::VectorData; +use crate::vector::VectorDataTable; + use glam::{DAffine2, DVec2}; #[node_macro::node(category("Debug"))] async fn log_to_console( #[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F, #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2, + () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2, + Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2, )] value: impl Node, ) -> T { @@ -37,14 +38,14 @@ async fn switch( condition: bool, #[expose] #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2 + () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2, + Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2 )] if_true: impl Node, #[expose] #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2 + () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2, + Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2 )] if_false: impl Node, ) -> T { diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 697182f453..52fe055fd9 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -1,5 +1,5 @@ +use crate::raster::image::ImageFrameTable; use crate::raster::BlendMode; -use crate::raster::ImageFrame; use crate::registry::types::Percentage; use crate::vector::style::GradientStops; use crate::{Color, Node}; @@ -472,7 +472,7 @@ fn unwrap(_: (), #[implementations(Option, Option, Option< /// Meant for debugging purposes, not general use. Clones the input value. #[node_macro::node(category("Debug"))] -fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrame)] value: &'i T) -> T { +fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrameTable)] value: &'i T) -> T { value.clone() } diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 6ea7139649..2abc76cee4 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -1,7 +1,9 @@ pub use self::color::{Color, Luma, SRGBA8}; -use crate::vector::VectorData; -use crate::GraphicGroup; -use crate::{registry::types::Percentage, transform::Footprint}; +use crate::raster::image::ImageFrameTable; +use crate::registry::types::Percentage; +use crate::transform::Footprint; +use crate::vector::VectorDataTable; +use crate::GraphicGroupTable; use bytemuck::{Pod, Zeroable}; use core::fmt::Debug; @@ -283,27 +285,33 @@ impl BitmapMut for &mut T { } #[cfg(feature = "alloc")] -pub use self::image::{Image, ImageFrame}; +pub use self::image::Image; #[cfg(feature = "alloc")] -pub(crate) mod image; +pub mod image; trait SetBlendMode { fn set_blend_mode(&mut self, blend_mode: BlendMode); } -impl SetBlendMode for VectorData { +impl SetBlendMode for VectorDataTable { fn set_blend_mode(&mut self, blend_mode: BlendMode) { - self.alpha_blending.blend_mode = blend_mode; + for instance in self.instances_mut() { + instance.alpha_blending.blend_mode = blend_mode; + } } } -impl SetBlendMode for GraphicGroup { +impl SetBlendMode for GraphicGroupTable { fn set_blend_mode(&mut self, blend_mode: BlendMode) { - self.alpha_blending.blend_mode = blend_mode; + for instance in self.instances_mut() { + instance.alpha_blending.blend_mode = blend_mode; + } } } -impl SetBlendMode for ImageFrame { +impl SetBlendMode for ImageFrameTable { fn set_blend_mode(&mut self, blend_mode: BlendMode) { - self.alpha_blending.blend_mode = blend_mode; + for instance in self.instances_mut() { + instance.alpha_blending.blend_mode = blend_mode; + } } } @@ -317,12 +325,12 @@ async fn blend_mode( )] footprint: F, #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, + () -> GraphicGroupTable, + () -> VectorDataTable, + () -> ImageFrameTable, + Footprint -> GraphicGroupTable, + Footprint -> VectorDataTable, + Footprint -> ImageFrameTable, )] value: impl Node, blend_mode: BlendMode, @@ -342,12 +350,12 @@ async fn opacity( )] footprint: F, #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, + () -> GraphicGroupTable, + () -> VectorDataTable, + () -> ImageFrameTable, + Footprint -> GraphicGroupTable, + Footprint -> VectorDataTable, + Footprint -> ImageFrameTable, )] value: impl Node, #[default(100.)] factor: Percentage, diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 887028a241..6ee2f36c7c 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -1,15 +1,15 @@ #![allow(clippy::too_many_arguments)] #[cfg(feature = "alloc")] -use super::curve::{Curve, CurveManipulatorGroup, ValueMapperNode}; +use crate::raster::curve::{Curve, CurveManipulatorGroup, ValueMapperNode}; #[cfg(feature = "alloc")] -use super::ImageFrame; -use super::{Channel, Color, Pixel}; +use crate::raster::image::{ImageFrame, ImageFrameTable}; +use crate::raster::{Channel, Color, Pixel}; use crate::registry::types::{Angle, Percentage, SignedPercentage}; use crate::transform::Footprint; use crate::vector::style::GradientStops; -use crate::vector::VectorData; -use crate::GraphicGroup; +use crate::vector::VectorDataTable; +use crate::{GraphicElement, GraphicGroupTable}; use dyn_any::DynAny; @@ -294,10 +294,10 @@ async fn luminance>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] input: impl Node, @@ -328,10 +328,10 @@ async fn extract_channel>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] input: impl Node, @@ -361,10 +361,10 @@ async fn make_opaque>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] input: impl Node, @@ -395,10 +395,10 @@ async fn levels>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] image: impl Node, @@ -472,10 +472,10 @@ async fn black_and_white>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] image: impl Node, @@ -554,10 +554,10 @@ async fn hue_saturation>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] input: impl Node, @@ -598,10 +598,10 @@ async fn invert>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] input: impl Node, @@ -630,10 +630,10 @@ async fn threshold>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] image: impl Node, @@ -666,7 +666,6 @@ async fn threshold>( trait Blend { fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self; } - impl Blend for Color { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { blend_fn(*self, *under) @@ -681,24 +680,28 @@ impl Blend for Option { } } } - -impl Blend for ImageFrame { +impl Blend for ImageFrameTable { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { - let data = self.image.data.iter().zip(under.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect(); - - ImageFrame { - image: super::Image { - data, - width: self.image.width, - height: self.image.height, - base64_string: None, - }, - transform: self.transform, - alpha_blending: self.alpha_blending, + 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(); + + *over = ImageFrame { + image: super::Image { + data, + width: over.image.width, + height: over.image.height, + base64_string: None, + }, + transform: over.transform, + alpha_blending: over.alpha_blending, + }; } + + result } } - impl Blend for GradientStops { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { let mut combined_stops = self.0.iter().map(|(position, _)| position).chain(under.0.iter().map(|(position, _)| position)).collect::>(); @@ -730,20 +733,20 @@ async fn blend + Send>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] over: impl Node, #[expose] #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] under: impl Node, @@ -753,7 +756,7 @@ async fn blend + Send>( let over = over.eval(footprint).await; let under = under.eval(footprint).await; - Blend::blend(&over, &under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.)) + over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.)) } #[node_macro::node(category(""))] @@ -800,8 +803,8 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM } } -trait Adjust { - fn adjust(&mut self, map_fn: impl Fn(&C) -> C); +trait Adjust

{ + fn adjust(&mut self, map_fn: impl Fn(&P) -> P); } impl Adjust for Color { fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { @@ -822,10 +825,17 @@ impl Adjust for GradientStops { } } } -impl Adjust for ImageFrame { - fn adjust(&mut self, map_fn: impl Fn(&C) -> C) { - for c in self.image.data.iter_mut() { - *c = map_fn(c); +impl Adjust

for ImageFrameTable

+where + P: dyn_any::StaticType, + P::Static: Pixel, + GraphicElement: From>, +{ + fn adjust(&mut self, map_fn: impl Fn(&P) -> P) { + for instance in self.instances_mut() { + for c in instance.image.data.iter_mut() { + *c = map_fn(c); + } } } } @@ -857,10 +867,10 @@ async fn gradient_map>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] image: impl Node, @@ -896,10 +906,10 @@ async fn vibrance>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] image: impl Node, @@ -1196,10 +1206,10 @@ async fn channel_mixer>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] image: impl Node, @@ -1357,10 +1367,10 @@ async fn selective_color>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] image: impl Node, @@ -1490,19 +1500,30 @@ impl MultiplyAlpha for Color { *self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.)) } } -impl MultiplyAlpha for VectorData { +impl MultiplyAlpha for VectorDataTable { fn multiply_alpha(&mut self, factor: f64) { - self.alpha_blending.opacity *= factor as f32; + for instance in self.instances_mut() { + instance.alpha_blending.opacity *= factor as f32; + } } } -impl MultiplyAlpha for GraphicGroup { +impl MultiplyAlpha for GraphicGroupTable { fn multiply_alpha(&mut self, factor: f64) { - self.alpha_blending.opacity *= factor as f32; + for instance in self.instances_mut() { + instance.alpha_blending.opacity *= factor as f32; + } } } -impl MultiplyAlpha for ImageFrame

{ +impl MultiplyAlpha for ImageFrameTable

+where + P: dyn_any::StaticType, + P::Static: Pixel, + GraphicElement: From>, +{ fn multiply_alpha(&mut self, factor: f64) { - self.alpha_blending.opacity *= factor as f32; + for instance in self.instances_mut() { + instance.alpha_blending.opacity *= factor as f32; + } } } @@ -1523,10 +1544,10 @@ async fn posterize>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] input: impl Node, @@ -1566,10 +1587,10 @@ async fn exposure>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] input: impl Node, @@ -1649,10 +1670,10 @@ async fn color_overlay>( footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] image: impl Node, @@ -1675,33 +1696,34 @@ async fn color_overlay>( input } -#[cfg(feature = "alloc")] -pub use index_node::IndexNode; - -#[cfg(feature = "alloc")] -mod index_node { - use crate::raster::{Color, ImageFrame}; - - #[node_macro::node(category(""))] - pub fn index( - _: (), - #[implementations(Vec>, Vec)] - #[widget(ParsedWidgetOverride::Hidden)] - input: Vec, - index: u32, - ) -> T { - if (index as usize) < input.len() { - input[index as usize].clone() - } else { - warn!("The number of segments is {} but the requested segment is {}!", input.len(), index); - Default::default() - } - } -} +// #[cfg(feature = "alloc")] +// pub use index_node::IndexNode; + +// #[cfg(feature = "alloc")] +// mod index_node { +// use crate::raster::{Color, ImageFrame}; + +// #[node_macro::node(category(""))] +// pub fn index( +// _: (), +// #[implementations(Vec>, Vec)] +// #[widget(ParsedWidgetOverride::Hidden)] +// input: Vec, +// index: u32, +// ) -> T { +// if (index as usize) < input.len() { +// input[index as usize].clone() +// } else { +// warn!("The number of segments is {} but the requested segment is {}!", input.len(), index); +// Default::default() +// } +// } +// } #[cfg(test)] mod test { - use crate::raster::{BlendMode, Image, ImageFrame}; + use crate::raster::image::{ImageFrame, ImageFrameTable}; + use crate::raster::{BlendMode, Image}; use crate::{Color, Node}; use std::pin::Pin; @@ -1730,7 +1752,8 @@ mod test { // 100% of the output should come from the multiplied value let opacity = 100_f64; - let result = super::color_overlay((), &FutureWrapperNode(image), overlay_color, BlendMode::Multiply, opacity).await; + let result = super::color_overlay((), &FutureWrapperNode(ImageFrameTable::new(image.clone())), overlay_color, BlendMode::Multiply, opacity).await; + let result = result.one_item(); // 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 379d59badd..38d0076418 100644 --- a/node-graph/gcore/src/raster/brush_cache.rs +++ b/node-graph/gcore/src/raster/brush_cache.rs @@ -5,8 +5,8 @@ use std::sync::Mutex; use dyn_any::DynAny; +use crate::raster::image::ImageFrame; use crate::raster::Image; -use crate::raster::ImageFrame; use crate::vector::brush_stroke::BrushStroke; use crate::vector::brush_stroke::BrushStyle; use crate::Color; diff --git a/node-graph/gcore/src/raster/image.rs b/node-graph/gcore/src/raster/image.rs index 4b13d47870..f4f89e0606 100644 --- a/node-graph/gcore/src/raster/image.rs +++ b/node-graph/gcore/src/raster/image.rs @@ -1,6 +1,7 @@ use super::discrete_srgb::float_to_srgb_u8; use super::Color; -use crate::AlphaBlending; +use crate::instances::Instances; +use crate::{AlphaBlending, GraphicElement}; use alloc::vec::Vec; use core::hash::{Hash, Hasher}; use dyn_any::StaticType; @@ -216,7 +217,26 @@ impl IntoIterator for Image

{ } } -#[derive(Clone, Debug, PartialEq, Default, specta::Type)] +// TODO: Eventually remove this migration document upgrade code +pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + use serde::Deserialize; + + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(untagged)] + enum EitherFormat { + ImageFrame(ImageFrame), + ImageFrameTable(ImageFrameTable), + } + + Ok(match EitherFormat::deserialize(deserializer)? { + EitherFormat::ImageFrame(image_frame) => ImageFrameTable::::new(image_frame), + EitherFormat::ImageFrameTable(image_frame_table) => image_frame_table, + }) +} + +pub type ImageFrameTable

= Instances>; + +#[derive(Clone, Debug, PartialEq, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ImageFrame { pub image: Image

, @@ -233,6 +253,17 @@ pub struct ImageFrame { 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

{ type Pixel = P; @@ -248,6 +279,22 @@ impl Sample for ImageFrame

{ } } +impl Sample for ImageFrameTable

+where + GraphicElement: From>, + P::Static: Pixel, +{ + type Pixel = P; + + // TODO: Improve sampling logic + #[inline(always)] + fn sample(&self, pos: DVec2, area: DVec2) -> Option { + let image = self.one_item(); + + Sample::sample(image, pos, area) + } +} + impl Bitmap for ImageFrame

{ type Pixel = P; @@ -264,12 +311,50 @@ impl Bitmap for ImageFrame

{ } } +impl Bitmap for ImageFrameTable

+where + P::Static: Pixel, + GraphicElement: From>, +{ + type Pixel = P; + + fn width(&self) -> u32 { + let image = self.one_item(); + + image.width() + } + + fn height(&self) -> u32 { + let image = self.one_item(); + + image.height() + } + + fn get_pixel(&self, x: u32, y: u32) -> Option { + let image = self.one_item(); + + image.get_pixel(x, y) + } +} + impl BitmapMut for ImageFrame

{ fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> { self.image.get_pixel_mut(x, y) } } +impl BitmapMut for ImageFrameTable

+where + GraphicElement: From>, + P::Static: Pixel, +{ + fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> { + let image = self.one_item_mut(); + + BitmapMut::get_pixel_mut(image, x, y) + } +} + unsafe impl StaticType for ImageFrame

where P::Static: Pixel, @@ -278,22 +363,6 @@ where } impl ImageFrame

{ - pub const fn empty() -> Self { - Self { - image: Image::empty(), - transform: DAffine2::ZERO, - alpha_blending: AlphaBlending::new(), - } - } - - pub const fn identity() -> Self { - Self { - image: Image::empty(), - transform: DAffine2::IDENTITY, - alpha_blending: AlphaBlending::new(), - } - } - pub fn get_mut(&mut self, x: usize, y: usize) -> &mut P { &mut self.image.data[y * (self.image.width as usize) + x] } diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 8e2a2d7498..3aaf1c245e 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -1,8 +1,9 @@ -use crate::application_io::TextureFrame; +use crate::application_io::{TextureFrame, TextureFrameTable}; use crate::raster::bbox::AxisAlignedBbox; -use crate::raster::{ImageFrame, Pixel}; -use crate::vector::VectorData; -use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup}; +use crate::raster::image::{ImageFrame, ImageFrameTable}; +use crate::raster::Pixel; +use crate::vector::{VectorData, VectorDataTable}; +use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable}; use glam::{DAffine2, DVec2}; @@ -19,12 +20,6 @@ pub trait Transform { } } -impl Transform for &T { - fn transform(&self) -> DAffine2 { - (*self).transform() - } -} - pub trait TransformMut: Transform { fn transform_mut(&mut self) -> &mut DAffine2; fn translate(&mut self, offset: DVec2) { @@ -32,6 +27,14 @@ pub trait TransformMut: Transform { } } +// Implementation for references to anything that implements Transform +impl Transform for &T { + fn transform(&self) -> DAffine2 { + (*self).transform() + } +} + +// Implementations for ImageFrame

impl Transform for ImageFrame

{ fn transform(&self) -> DAffine2 { self.transform @@ -45,6 +48,54 @@ impl TransformMut for ImageFrame

{ &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 @@ -55,19 +106,35 @@ impl TransformMut for GraphicGroup { &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::Raster(raster) => raster.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::Raster(raster) => raster.local_pivot(pivot), + GraphicElement::RasterFrame(raster) => raster.local_pivot(pivot), } } } @@ -76,11 +143,12 @@ impl TransformMut for GraphicElement { match self { GraphicElement::VectorData(vector_shape) => vector_shape.transform_mut(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(), - GraphicElement::Raster(raster) => raster.transform_mut(), + GraphicElement::RasterFrame(raster) => raster.transform_mut(), } } } +// Implementations for VectorData impl Transform for VectorData { fn transform(&self) -> DAffine2 { self.transform @@ -95,6 +163,25 @@ impl TransformMut for VectorData { } } +// 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 { DAffine2::from_translation(self.location.as_dvec2()) @@ -104,6 +191,7 @@ impl Transform for Artboard { } } +// Implementations for DAffine2 impl Transform for DAffine2 { fn transform(&self) -> DAffine2 { *self @@ -115,6 +203,18 @@ impl TransformMut for DAffine2 { } } +// Implementations for Footprint +impl Transform for Footprint { + fn transform(&self) -> DAffine2 { + self.transform + } +} +impl TransformMut for Footprint { + fn transform_mut(&mut self) -> &mut DAffine2 { + &mut self.transform + } +} + #[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum RenderQuality { @@ -177,7 +277,7 @@ impl From<()> for Footprint { } #[node_macro::node(category("Debug"))] -fn cull(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame, ArtboardGroup)] data: T) -> T { +fn cull(_footprint: Footprint, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable, ArtboardGroup)] data: T) -> T { data } @@ -188,17 +288,6 @@ impl core::hash::Hash for Footprint { } } -impl Transform for Footprint { - fn transform(&self) -> DAffine2 { - self.transform - } -} -impl TransformMut for Footprint { - fn transform_mut(&mut self) -> &mut DAffine2 { - &mut self.transform - } -} - pub trait ApplyTransform { fn apply_transform(&mut self, modification: &DAffine2); } @@ -222,13 +311,13 @@ async fn transform + 'n + ApplyTransform + Clone + Send + Syn )] mut input: I, #[implementations( - () -> VectorData, - () -> GraphicGroup, - () -> ImageFrame, + () -> VectorDataTable, + () -> GraphicGroupTable, + () -> ImageFrameTable, () -> TextureFrame, - Footprint -> VectorData, - Footprint -> GraphicGroup, - Footprint -> ImageFrame, + Footprint -> VectorDataTable, + Footprint -> GraphicGroupTable, + Footprint -> ImageFrameTable, Footprint -> TextureFrame, )] transform_target: impl Node, @@ -255,7 +344,7 @@ async fn transform + 'n + ApplyTransform + Clone + Send + Syn #[node_macro::node(category(""))] fn replace_transform( _: (), - #[implementations(VectorData, ImageFrame, GraphicGroup)] mut data: Data, + #[implementations(VectorDataTable, ImageFrameTable, GraphicGroupTable)] mut data: Data, #[implementations(DAffine2)] transform: TransformInput, ) -> Data { let data_transform = data.transform_mut(); diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index 55971f2a75..1868a4f7a4 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -131,12 +131,15 @@ impl From for ProtoNodeIdentifier { fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { use serde::Deserialize; - // Rename "f32" to "f64" let name = String::deserialize(deserializer)?; let name = match name.as_str() { "f32" => "f64".to_string(), + "graphene_core::graphic_element::GraphicGroup" => "graphene_core::graphic_element::Instances".to_string(), + "graphene_core::vector::vector_data::VectorData" => "graphene_core::graphic_element::Instances".to_string(), + "graphene_core::raster::image::ImageFrame" => "graphene_core::graphic_element::Instances>".to_string(), _ => name, }; + Ok(Cow::Owned(name)) } @@ -150,9 +153,9 @@ pub struct TypeDescriptor { pub name: Cow<'static, str>, #[serde(default)] pub alias: Option>, - #[serde(default)] + #[serde(skip)] pub size: usize, - #[serde(default)] + #[serde(skip)] pub align: usize, } diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index 53292d8a08..89aedc02ea 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -1,21 +1,20 @@ -use super::HandleId; use crate::transform::Footprint; -use crate::vector::{PointId, VectorData}; +use crate::vector::{HandleId, PointId, VectorData, VectorDataTable}; use bezier_rs::Subpath; use glam::DVec2; trait CornerRadius { - fn generate(self, size: DVec2, clamped: bool) -> super::VectorData; + fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable; } impl CornerRadius for f64 { - fn generate(self, size: DVec2, clamped: bool) -> super::VectorData { + fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable { let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self }; - super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])) + VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4]))) } } impl CornerRadius for [f64; 4] { - fn generate(self, size: DVec2, clamped: bool) -> super::VectorData { + fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable { let clamped_radius = if clamped { // Algorithm follows the CSS spec: @@ -31,28 +30,31 @@ impl CornerRadius for [f64; 4] { } else { self }; - super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)) + VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius))) } } #[node_macro::node(category("Vector: Shape"))] -fn circle(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorData { - super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))) +fn circle(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable { + VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))) } #[node_macro::node(category("Vector: Shape"))] -fn ellipse(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorData { +fn ellipse(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable { let radius = DVec2::new(radius_x, radius_y); let corner1 = -radius; let corner2 = radius; - let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2)); + + let mut ellipse = VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2)); + let len = ellipse.segment_domain.ids().len(); for i in 0..len { ellipse .colinear_manipulators .push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]); } - ellipse + + VectorDataTable::new(ellipse) } #[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))] @@ -64,7 +66,7 @@ fn rectangle( _individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability #[implementations(f64, [f64; 4])] corner_radius: T, #[default(true)] clamped: bool, -) -> VectorData { +) -> VectorDataTable { corner_radius.generate(DVec2::new(width, height), clamped) } @@ -76,10 +78,10 @@ fn regular_polygon( #[min(3.)] sides: u32, #[default(50)] radius: f64, -) -> VectorData { +) -> VectorDataTable { let points = sides.into(); let radius: f64 = radius * 2.; - super::VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)) + VectorDataTable::new(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))) } #[node_macro::node(category("Vector: Shape"))] @@ -91,35 +93,39 @@ fn star( sides: u32, #[default(50)] radius: f64, #[default(25)] inner_radius: f64, -) -> VectorData { +) -> VectorDataTable { let points = sides.into(); let diameter: f64 = radius * 2.; let inner_diameter = inner_radius * 2.; - super::VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)) + VectorDataTable::new(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))) } #[node_macro::node(category("Vector: Shape"))] -fn line(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorData { - super::VectorData::from_subpath(Subpath::new_line(start, end)) +fn line(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable { + VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end))) } #[node_macro::node(category("Vector: Shape"))] -fn spline(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec) -> VectorData { - let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(points)); +fn spline(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec) -> VectorDataTable { + let mut spline = VectorData::from_subpath(Subpath::new_cubic_spline(points)); + for pair in spline.segment_domain.ids().windows(2) { spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]); } - spline + + VectorDataTable::new(spline) } // 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(#[implementations((), Footprint)] _footprint: F, path_data: Vec>, colinear_manipulators: Vec) -> super::VectorData { - let mut vector_data = super::VectorData::from_subpaths(path_data, false); +fn path(#[implementations((), Footprint)] _footprint: F, 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(); - vector_data + + 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 ef19ac3853..bd5cf324eb 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -4,7 +4,8 @@ pub use attributes::*; pub use modification::*; use super::style::{PathStyle, Stroke}; -use crate::{AlphaBlending, Color}; +use crate::instances::Instances; +use crate::{AlphaBlending, Color, GraphicGroupTable}; use bezier_rs::ManipulatorGroup; use dyn_any::DynAny; @@ -12,6 +13,26 @@ use dyn_any::DynAny; use core::borrow::Borrow; use glam::{DAffine2, DVec2}; +// TODO: Eventually remove this migration document upgrade code +pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { + use serde::Deserialize; + + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(untagged)] + #[allow(clippy::large_enum_variant)] + enum EitherFormat { + VectorData(VectorData), + VectorDataTable(VectorDataTable), + } + + Ok(match EitherFormat::deserialize(deserializer)? { + EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data), + EitherFormat::VectorDataTable(vector_data_table) => vector_data_table, + }) +} + +pub type VectorDataTable = Instances; + /// [VectorData] is passed between nodes. /// It contains a list of subpaths (that may be open or closed), a transform, and some style information. #[derive(Clone, Debug, PartialEq, DynAny)] @@ -29,7 +50,7 @@ pub struct VectorData { 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, + pub upstream_graphic_group: Option, } impl core::hash::Hash for VectorData { diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index 4b0ebc744e..cb9b7b1db2 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -1,4 +1,5 @@ -use super::HandleId; +use crate::vector::vector_data::{HandleId, VectorData, VectorDataTable}; +use crate::vector::ConcatElement; use dyn_any::DynAny; @@ -46,7 +47,7 @@ macro_rules! create_ids { }; } -create_ids! { PointId, SegmentId, RegionId, StrokeId, FillId } +create_ids! { InstanceId, PointId, SegmentId, RegionId, StrokeId, FillId } /// A no-op hasher that allows writing u64s (the id type). #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -503,7 +504,7 @@ impl RegionDomain { } } -impl super::VectorData { +impl VectorData { /// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles. fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: bezier_rs::BezierHandles) -> bezier_rs::Bezier { let start = self.point_domain.positions()[start]; @@ -698,7 +699,7 @@ impl StrokePathIterPointMetadata { #[derive(Clone)] pub struct StrokePathIter<'a> { - vector_data: &'a super::VectorData, + vector_data: &'a VectorData, points: Vec, skip: usize, done_one: bool, @@ -774,7 +775,7 @@ impl bezier_rs::Identifier for PointId { } } -impl crate::vector::ConcatElement for super::VectorData { +impl ConcatElement for VectorData { fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) { let new_ids = other .point_domain @@ -813,6 +814,14 @@ impl crate::vector::ConcatElement for super::VectorData { } } +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); + } + } +} + /// Represents the conversion of ids used when concatenating vector data with conflicting ids. struct IdMap { point_offset: usize, diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 2e7cecb6e5..a73add8c61 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::Footprint; use crate::uuid::generate_uuid; use bezier_rs::BezierHandles; @@ -421,7 +422,6 @@ impl core::hash::Hash for VectorModification { } } -use crate::transform::Footprint; /// A node that applies a procedural modification to some [`VectorData`]. #[node_macro::node(category(""))] async fn path_modify( @@ -431,15 +431,18 @@ async fn path_modify( )] input: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - vector_data: impl Node, + vector_data: impl Node, modification: Box, -) -> VectorData { +) -> VectorDataTable { let mut vector_data = vector_data.eval(input).await; - modification.apply(&mut vector_data); - vector_data + let vector_data = vector_data.one_item_mut(); + + modification.apply(vector_data); + + VectorDataTable::new(vector_data.clone()) } #[test] diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 53264bf832..59d5f9eb6c 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -1,12 +1,12 @@ use super::misc::CentroidType; use super::style::{Fill, Gradient, GradientStops, Stroke}; -use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData}; +use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable}; 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::{Color, GraphicElement, GraphicGroup}; +use crate::{Color, GraphicElement, GraphicGroup, GraphicGroupTable}; use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue}; use glam::{DAffine2, DVec2}; @@ -18,21 +18,27 @@ trait VectorIterMut { fn vector_iter_mut(&mut self) -> impl Iterator; } -impl VectorIterMut for GraphicGroup { +impl VectorIterMut for GraphicGroupTable { fn vector_iter_mut(&mut self) -> impl Iterator { - let parent_transform = self.transform; - // Grab only the direct children (perhaps unintuitive?) - self.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector| { - let transform = parent_transform * vector.transform; - (vector, transform) + let instance = self.one_item_mut(); + + let parent_transform = instance.transform; + + // 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) }) } } -impl VectorIterMut for VectorData { +impl VectorIterMut for VectorDataTable { fn vector_iter_mut(&mut self) -> impl Iterator { - let transform = self.transform; - std::iter::once((self, transform)) + self.instances_mut().map(|instance| { + let transform = instance.transform; + (instance, transform) + }) } } @@ -45,10 +51,10 @@ async fn assign_colors( )] footprint: F, #[implementations( - () -> GraphicGroup, - () -> VectorData, - Footprint -> GraphicGroup, - Footprint -> VectorData, + () -> GraphicGroupTable, + () -> VectorDataTable, + Footprint -> GraphicGroupTable, + Footprint -> VectorDataTable, )] #[widget(ParsedWidgetOverride::Hidden)] vector_group: impl Node, @@ -60,13 +66,14 @@ async fn assign_colors( #[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue, #[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32, ) -> T { - let mut input = vector_group.eval(footprint).await; - let length = input.vector_iter_mut().count(); + let mut vector_group = vector_group.eval(footprint).await; + + let length = vector_group.vector_iter_mut().count(); let gradient = if reverse { gradient.reversed() } else { gradient }; let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); - for (i, (vector_data, _)) in input.vector_iter_mut().enumerate() { + for (i, (vector_data, _)) in vector_group.vector_iter_mut().enumerate() { let factor = match randomize { true => rng.gen::(), false => match repeat_every { @@ -87,7 +94,8 @@ async fn assign_colors( } } } - input + + vector_group } #[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))] @@ -112,22 +120,22 @@ async fn fill + 'n + Send, TargetTy: VectorIter )] footprint: F, #[implementations( - () -> VectorData, - () -> VectorData, - () -> VectorData, - () -> VectorData, - () -> GraphicGroup, - () -> GraphicGroup, - () -> GraphicGroup, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> VectorData, - Footprint -> VectorData, - Footprint -> VectorData, - Footprint -> GraphicGroup, - Footprint -> GraphicGroup, - Footprint -> GraphicGroup, - Footprint -> GraphicGroup, + () -> VectorDataTable, + () -> VectorDataTable, + () -> VectorDataTable, + () -> VectorDataTable, + () -> GraphicGroupTable, + () -> GraphicGroupTable, + () -> GraphicGroupTable, + () -> GraphicGroupTable, + Footprint -> VectorDataTable, + Footprint -> VectorDataTable, + Footprint -> VectorDataTable, + Footprint -> VectorDataTable, + Footprint -> GraphicGroupTable, + Footprint -> GraphicGroupTable, + Footprint -> GraphicGroupTable, + Footprint -> GraphicGroupTable, )] vector_data: impl Node, #[implementations( @@ -176,14 +184,14 @@ async fn stroke> + 'n + Send, TargetTy )] footprint: F, #[implementations( - () -> VectorData, - () -> VectorData, - () -> GraphicGroup, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> VectorData, - Footprint -> GraphicGroup, - Footprint -> GraphicGroup, + () -> VectorDataTable, + () -> VectorDataTable, + () -> GraphicGroupTable, + () -> GraphicGroupTable, + Footprint -> VectorDataTable, + Footprint -> VectorDataTable, + Footprint -> GraphicGroupTable, + Footprint -> GraphicGroupTable, )] vector_data: impl Node, #[implementations( @@ -234,10 +242,10 @@ async fn repeat VectorData, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> GraphicGroup, + () -> VectorDataTable, + () -> GraphicGroupTable, + Footprint -> VectorDataTable, + Footprint -> GraphicGroupTable, )] instance: impl Node, #[default(100., 100.)] @@ -245,7 +253,7 @@ async fn repeat GraphicGroup { +) -> GraphicGroupTable { let instance = instance.eval(footprint).await; let first_vector_transform = instance.transform(); @@ -253,10 +261,10 @@ async fn repeat VectorData, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> GraphicGroup, + () -> VectorDataTable, + () -> GraphicGroupTable, + Footprint -> VectorDataTable, + Footprint -> GraphicGroupTable, )] instance: impl Node, angle_offset: Angle, #[default(5)] radius: f64, #[default(5)] instances: IntegerCount, -) -> GraphicGroup { +) -> GraphicGroupTable { let instance = instance.eval(footprint).await; let first_vector_transform = instance.transform(); let instances = instances.max(1); - let mut result = GraphicGroup::EMPTY; + let mut result = GraphicGroup::default(); let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else { - return result; + return GraphicGroupTable::new(result); }; let center = (bounding_box[0] + bounding_box[1]) / 2.; @@ -322,7 +330,7 @@ async fn circular_repeat VectorData, - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - points: impl Node, + points: impl Node, #[expose] #[implementations( - () -> VectorData, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> GraphicGroup, + () -> VectorDataTable, + () -> GraphicGroupTable, + Footprint -> VectorDataTable, + Footprint -> GraphicGroupTable, )] instance: impl Node, #[default(1)] random_scale_min: f64, @@ -353,9 +361,12 @@ async fn copy_to_points GraphicGroup { +) -> GraphicGroupTable { let points = points.eval(footprint).await; + let points = points.one_item(); + let instance = instance.eval(footprint).await; + let instance_transform = instance.transform(); let random_scale_difference = random_scale_max - random_scale_min; @@ -406,7 +417,7 @@ async fn copy_to_points( )] footprint: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - vector_data: impl Node, -) -> VectorData { + vector_data: impl Node, +) -> VectorDataTable { let vector_data = vector_data.eval(footprint).await; + let vector_data = vector_data.one_item(); 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); - result + + VectorDataTable::new(result) } #[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))] @@ -439,23 +452,23 @@ async fn offset_path( )] footprint: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - vector_data: impl Node, + vector_data: impl Node, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64, -) -> VectorData { +) -> VectorDataTable { let vector_data = vector_data.eval(footprint).await; + let vector_data = vector_data.one_item(); - let subpaths = vector_data.stroke_bezier_paths(); let mut result = VectorData::empty(); result.style = vector_data.style.clone(); result.style.set_stroke_transform(DAffine2::IDENTITY); // Perform operation on all subpaths in this shape. - for mut subpath in subpaths { + for mut subpath in vector_data.stroke_bezier_paths() { subpath.apply_transform(vector_data.transform); // Taking the existing stroke data and passing it to Bezier-rs to generate new paths. @@ -472,7 +485,7 @@ async fn offset_path( result.append_subpath(subpath_out, false); } - result + VectorDataTable::new(result) } #[node_macro::node(category("Vector"), path(graphene_core::vector))] @@ -483,14 +496,17 @@ async fn solidify_stroke( )] footprint: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - vector_data: impl Node, -) -> VectorData { + vector_data: impl Node, +) -> VectorDataTable { let vector_data = vector_data.eval(footprint).await; + let vector_data = vector_data.one_item(); + + let transform = &vector_data.transform; + let style = &vector_data.style; - let VectorData { transform, style, .. } = &vector_data; let subpaths = vector_data.stroke_bezier_paths(); let mut result = VectorData::empty(); @@ -531,7 +547,7 @@ async fn solidify_stroke( result.style.set_stroke(Stroke::default()); } - result + VectorDataTable::new(result) } #[node_macro::node(category("Vector"), path(graphene_core::vector))] @@ -542,12 +558,14 @@ async fn flatten_vector_elements( )] footprint: F, #[implementations( - () -> GraphicGroup, - Footprint -> GraphicGroup, + () -> GraphicGroupTable, + Footprint -> GraphicGroupTable, )] - graphic_group_input: impl Node, -) -> VectorData { + graphic_group_input: impl Node, +) -> VectorDataTable { let graphic_group = graphic_group_input.eval(footprint).await; + let graphic_group = graphic_group.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. @@ -555,9 +573,12 @@ async fn flatten_vector_elements( for (element, reference) in graphic_group.iter() { match element { GraphicElement::VectorData(vector_data) => { - result.concat(vector_data, current_transform, reference.map(|node_id| node_id.0).unwrap_or_default()); + for instance in vector_data.instances() { + result.concat(instance, current_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); } _ => {} @@ -566,25 +587,29 @@ async fn flatten_vector_elements( } let mut result = VectorData::empty(); - concat_group(&graphic_group, DAffine2::IDENTITY, &mut result); + concat_group(graphic_group, DAffine2::IDENTITY, &mut result); // TODO: This leads to incorrect stroke widths when flattening groups with different transforms. result.style.set_stroke_transform(DAffine2::IDENTITY); - result + VectorDataTable::new(result) } pub trait ConcatElement { fn concat(&mut self, other: &Self, transform: DAffine2, node_id: u64); } -impl ConcatElement for GraphicGroup { +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(); + // 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(); - self.push((element, footprint_mapping)); + own.push((element, footprint_mapping)); } - self.alpha_blending = other.alpha_blending; + + own.alpha_blending = other.alpha_blending; } } @@ -596,10 +621,10 @@ async fn sample_points( )] footprint: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - vector_data: impl Node, + vector_data: impl Node, spacing: f64, start_offset: f64, stop_offset: f64, @@ -609,9 +634,10 @@ async fn sample_points( Footprint -> Vec, )] subpath_segment_lengths: impl Node>, -) -> VectorData { +) -> VectorDataTable { // Evaluate vector data and subpath segment lengths asynchronously. let vector_data = vector_data.eval(footprint).await; + let vector_data = vector_data.one_item(); let subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await; // Create an iterator over the bezier segments with enumeration and peeking capability. @@ -753,7 +779,7 @@ async fn sample_points( result.style.set_stroke_transform(vector_data.transform); // Return the resulting vector data with newly generated points and segments. - result + VectorDataTable::new(result) } #[node_macro::node(category(""), path(graphene_core::vector))] @@ -764,22 +790,23 @@ async fn poisson_disk_points( )] footprint: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - vector_data: impl Node, + vector_data: impl Node, #[default(10.)] #[min(0.01)] separation_disk_diameter: f64, seed: SeedValue, -) -> VectorData { +) -> VectorDataTable { let vector_data = vector_data.eval(footprint).await; + let vector_data = vector_data.one_item(); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let mut result = VectorData::empty(); if separation_disk_diameter <= 0.01 { - return result; + return VectorDataTable::new(result); } for mut subpath in vector_data.stroke_bezier_paths() { @@ -812,7 +839,7 @@ async fn poisson_disk_points( result.style = vector_data.style.clone(); result.style.set_stroke_transform(DAffine2::IDENTITY); - result + VectorDataTable::new(result) } #[node_macro::node(category(""), path(graphene_core::vector))] @@ -823,12 +850,13 @@ async fn subpath_segment_lengths( )] footprint: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - vector_data: impl Node, + vector_data: impl Node, ) -> Vec { let vector_data = vector_data.eval(footprint).await; + let vector_data = vector_data.one_item(); vector_data .segment_bezier_iter() @@ -844,17 +872,18 @@ async fn splines_from_points( )] footprint: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - vector_data: impl Node, -) -> VectorData { + vector_data: impl Node, +) -> VectorDataTable { // Evaluate the vector data within the given footprint. let mut vector_data = vector_data.eval(footprint).await; + let vector_data = vector_data.one_item_mut(); // Exit early if there are no points to generate splines from. if vector_data.point_domain.positions().is_empty() { - return vector_data; + return VectorDataTable::new(vector_data.clone()); } let mut segment_domain = SegmentDomain::default(); @@ -887,7 +916,7 @@ async fn splines_from_points( } vector_data.segment_domain = segment_domain; - vector_data + VectorDataTable::new(vector_data.clone()) } #[node_macro::node(category("Vector"), path(graphene_core::vector))] @@ -898,14 +927,15 @@ async fn jitter_points( )] footprint: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - vector_data: impl Node, + vector_data: impl Node, #[default(5.)] amount: f64, seed: SeedValue, -) -> VectorData { - let mut vector_data = vector_data.eval(footprint).await; +) -> VectorDataTable { + let vector_data = vector_data.eval(footprint).await; + let mut vector_data = vector_data.one_item().clone(); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); @@ -949,7 +979,7 @@ async fn jitter_points( vector_data.transform = DAffine2::IDENTITY; vector_data.style.set_stroke_transform(DAffine2::IDENTITY); - vector_data + VectorDataTable::new(vector_data) } #[node_macro::node(category("Vector"), path(graphene_core::vector))] @@ -960,23 +990,26 @@ async fn morph( )] footprint: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - source: impl Node, + source: impl Node, #[expose] #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - target: impl Node, + target: impl Node, #[range((0., 1.))] #[default(0.5)] time: Fraction, #[min(0.)] start_index: IntegerCount, -) -> VectorData { +) -> VectorDataTable { let source = source.eval(footprint).await; + let source = source.one_item(); let target = target.eval(footprint).await; + let target = target.one_item(); + let mut result = VectorData::empty(); let time = time.clamp(0., 1.); @@ -1055,7 +1088,7 @@ async fn morph( } } - result + VectorDataTable::new(result) } fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData { @@ -1173,18 +1206,24 @@ async fn bevel( )] footprint: F, #[implementations( - () -> VectorData, - Footprint -> VectorData, + () -> VectorDataTable, + Footprint -> VectorDataTable, )] - source: impl Node, + source: impl Node, #[default(10.)] distance: Length, -) -> VectorData { - bevel_algorithm(source.eval(footprint).await, distance) +) -> VectorDataTable { + let source = source.eval(footprint).await; + let source = source.one_item(); + + let result = bevel_algorithm(source.clone(), distance); + + VectorDataTable::new(result) } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn area(_: (), vector_data: impl Node) -> f64 { +async fn area(_: (), vector_data: impl Node) -> f64 { let vector_data = vector_data.eval(Footprint::default()).await; + let vector_data = vector_data.one_item(); let mut area = 0.; let scale = vector_data.transform.decompose_scale(); @@ -1195,8 +1234,9 @@ async fn area(_: (), vector_data: impl Node) -> } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn centroid(_: (), vector_data: impl Node, centroid_type: CentroidType) -> DVec2 { +async fn centroid(_: (), vector_data: impl Node, centroid_type: CentroidType) -> DVec2 { let vector_data = vector_data.eval(Footprint::default()).await; + let vector_data = vector_data.one_item(); if centroid_type == CentroidType::Area { let mut area = 0.; @@ -1260,8 +1300,8 @@ mod test { } } - fn vector_node(data: Subpath) -> FutureWrapperNode { - FutureWrapperNode(VectorData::from_subpath(data)) + fn vector_node(data: Subpath) -> FutureWrapperNode { + FutureWrapperNode(VectorDataTable::new(VectorData::from_subpath(data))) } #[tokio::test] @@ -1270,6 +1310,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(), &FutureWrapperNode(repeated)).await; + let vector_data = vector_data.one_item(); 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); @@ -1281,6 +1322,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(), &FutureWrapperNode(repeated)).await; + let vector_data = vector_data.one_item(); 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); @@ -1290,6 +1332,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(), &FutureWrapperNode(repeated)).await; + let vector_data = vector_data.one_item(); 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.; @@ -1304,18 +1347,20 @@ mod test { vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), }; let bounding_box = bounding_box.eval(Footprint::default()).await; + let bounding_box = bounding_box.one_item(); 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 + // 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 bounding_box = BoundingBoxNode { - vector_data: FutureWrapperNode(square), + vector_data: FutureWrapperNode(VectorDataTable::new(square)), } .eval(Footprint::default()) .await; + let bounding_box = bounding_box.one_item(); 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; @@ -1329,6 +1374,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(), &FutureWrapperNode(copy_to_points)).await; + let flattened_copy_to_points = flattened_copy_to_points.one_item(); 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]; @@ -1342,6 +1388,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, &FutureWrapperNode(vec![100.])).await; + let sample_points = sample_points.one_item(); 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}"); @@ -1351,6 +1398,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, &FutureWrapperNode(vec![100.])).await; + let sample_points = sample_points.one_item(); 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}"); @@ -1365,6 +1413,7 @@ mod test { 0, ) .await; + let sample_points = sample_points.one_item(); assert!( (20..=40).contains(&sample_points.point_domain.positions().len()), "actual len {}", @@ -1383,6 +1432,7 @@ mod test { #[tokio::test] async fn spline() { let spline = splines_from_points(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await; + let spline = spline.one_item(); 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.)]); } @@ -1391,6 +1441,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(); 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.)] @@ -1408,6 +1459,8 @@ mod 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.).await; + let beveled = beveled.one_item(); + assert_eq!(beveled.point_domain.positions().len(), 8); assert_eq!(beveled.segment_domain.ids().len(), 8); @@ -1429,6 +1482,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(Footprint::default(), &vector_node(source), 5.).await; + let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.segment_domain.ids().len(), 3); @@ -1449,7 +1503,8 @@ mod test { let mut vector_data = VectorData::from_subpath(source); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)); vector_data.transform = transform; - let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(vector_data), 5.).await; + let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(VectorDataTable::new(vector_data)), 5.).await; + let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.segment_domain.ids().len(), 3); @@ -1468,6 +1523,8 @@ mod 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.).await; + let beveled = beveled.one_item(); + assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.segment_domain.ids().len(), 5); @@ -1487,6 +1544,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.).await; + let beveled = beveled.one_item(); 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 63874d7e09..81034f915d 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -129,7 +129,8 @@ tagged_value! { DAffine2(DAffine2), Image(graphene_core::raster::Image), ImaginateCache(ImaginateCache), - ImageFrame(graphene_core::raster::ImageFrame), + #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code + ImageFrame(graphene_core::raster::image::ImageFrameTable), Color(graphene_core::raster::color::Color), Subpaths(Vec>), BlendMode(BlendMode), @@ -137,7 +138,8 @@ tagged_value! { ImaginateSamplingMethod(ImaginateSamplingMethod), ImaginateMaskStartingFill(ImaginateMaskStartingFill), ImaginateController(ImaginateController), - VectorData(graphene_core::vector::VectorData), + #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] // TODO: Eventually remove this migration document upgrade code + VectorData(graphene_core::vector::VectorDataTable), Fill(graphene_core::vector::style::Fill), Stroke(graphene_core::vector::style::Stroke), F64Array4([f64; 4]), @@ -169,9 +171,9 @@ tagged_value! { Font(graphene_core::text::Font), BrushStrokes(Vec), BrushCache(BrushCache), - Segments(Vec>), DocumentNode(DocumentNode), - GraphicGroup(graphene_core::GraphicGroup), + #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code + GraphicGroup(graphene_core::GraphicGroupTable), GraphicElement(graphene_core::GraphicElement), ArtboardGroup(graphene_core::ArtboardGroup), Curve(graphene_core::raster::curve::Curve), diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index 2c31183fb8..40876dd1ce 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -3,18 +3,21 @@ use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, Ex 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, BlendColorPairNode, Color, Image, ImageFrame, Pixel, Sample}; +use graphene_core::raster::{Alpha, BlendColorPairNode, Color, Image, Pixel, Sample}; use graphene_core::transform::{Footprint, Transform, TransformMut}; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle}; -use graphene_core::vector::VectorData; +use graphene_core::vector::VectorDataTable; use graphene_core::Node; use glam::{DAffine2, DVec2}; #[node_macro::node(category("Debug"))] -fn vector_points(_: (), vector_data: VectorData) -> Vec { +fn vector_points(_: (), vector_data: VectorDataTable) -> Vec { + let vector_data = vector_data.one_item(); + vector_data.point_domain.positions().to_vec() } @@ -199,7 +202,9 @@ pub fn blend_with_mode(background: ImageFrame, foreground: ImageFrame, bounds: ImageFrame, strokes: Vec, cache: BrushCache) -> ImageFrame { +fn brush(_: Footprint, image: ImageFrameTable, bounds: ImageFrameTable, strokes: Vec, cache: BrushCache) -> ImageFrameTable { + let image = image.one_item().clone(); + 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 bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) }; @@ -211,8 +216,8 @@ fn brush(_footprint: Footprint, image: ImageFrame, bounds: ImageFrame, bounds: ImageFrame, bounds: ImageFrame, bounds: ImageFrame( )] footprint: F, #[implementations( - () -> ImageFrame, - Footprint -> ImageFrame, + () -> ImageFrameTable, + Footprint -> ImageFrameTable, )] - image_frame: impl Node>, + image_frame: impl Node>, strength: Percentage, -) -> ImageFrame { +) -> ImageFrameTable { let image_frame = image_frame.eval(footprint).await; + let image_frame = image_frame.one_item(); // Prepare the image data for processing - let image = image_frame.image; - let image_data = bytemuck::cast_vec(image.data); - let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal ImageFrame into image-rs data type."); + let image = &image_frame.image; + let image_data = bytemuck::cast_vec(image.data.clone()); + let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type."); let dynamic_image: image::DynamicImage = image_buffer.into(); // Run the dehaze algorithm @@ -42,11 +44,13 @@ async fn dehaze( base64_string: None, }; - ImageFrame { + let result = ImageFrame { image: dehazed_image, transform: image_frame.transform, alpha_blending: image_frame.alpha_blending, - } + }; + + ImageFrameTable::new(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 d3d9534e99..c30ddf015d 100644 --- a/node-graph/gstd/src/gpu_nodes.rs +++ b/node-graph/gstd/src/gpu_nodes.rs @@ -4,7 +4,8 @@ use graph_craft::document::value::TaggedValue; use graph_craft::document::*; use graph_craft::proto::*; use graphene_core::application_io::ApplicationIo; -use graphene_core::raster::*; +use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; +use graphene_core::raster::{BlendMode, Image, Pixel}; use graphene_core::*; use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput}; @@ -61,7 +62,10 @@ impl Clone for ComputePass { } #[node_macro::old_node_impl(MapGpuNode)] -async fn map_gpu<'a: 'input>(image: ImageFrame, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi) -> ImageFrame { +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(); + log::debug!("Executing gpu node"); let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap(); @@ -75,9 +79,9 @@ async fn map_gpu<'a: 'input>(image: ImageFrame, node: DocumentNode, edito self.cache.lock().as_ref().unwrap().get("placeholder").unwrap().clone() } else { let name = "placeholder".to_string(); - let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, &image, executor).await else { + 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 ImageFrame::empty(); + return ImageFrameTable::default(); }; self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone()); log::error!("created compute pass"); @@ -105,7 +109,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrame, node: DocumentNode, edito #[cfg(feature = "image-compare")] log::debug!("score: {:?}", score.score); - ImageFrame { + let result = ImageFrame { image: Image { data: colors, width: image.image.width, @@ -114,7 +118,9 @@ async fn map_gpu<'a: 'input>(image: ImageFrame, node: DocumentNode, edito }, transform: image.transform, alpha_blending: image.alpha_blending, - } + }; + + ImageFrameTable::new(result) } impl MapGpuNode { @@ -127,7 +133,13 @@ impl MapGpuNode { } } -async fn create_compute_pass_descriptor(node: DocumentNode, image: &ImageFrame, executor: &&WgpuExecutor) -> Result { +async fn create_compute_pass_descriptor(node: DocumentNode, image: &ImageFrameTable, executor: &&WgpuExecutor) -> Result +where + GraphicElement: From>, + T::Static: Pixel, +{ + let image = image.one_item(); + let compiler = graph_craft::graphene_compiler::Compiler {}; let inner_network = NodeNetwork::value_network(node); @@ -145,40 +157,37 @@ async fn create_compute_pass_descriptor(node implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()), ..Default::default() }, - /*DocumentNode { - name: "Index".into(), - // inputs: vec![NodeInput::Network(concrete!(UVec3))], - inputs: vec![NodeInput::Inline(InlineRust::new("i1.x as usize".into(), concrete![u32]))], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()), - ..Default::default() - },*/ - /* - DocumentNode { - name: "Get Node".into(), - inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::storage::GetNode".into()), - ..Default::default() - },*/ + // DocumentNode { + // name: "Index".into(), + // // inputs: vec![NodeInput::Network(concrete!(UVec3))], + // inputs: vec![NodeInput::Inline(InlineRust::new("i1.x as usize".into(), concrete![u32]))], + // implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()), + // ..Default::default() + // }, + // DocumentNode { + // name: "Get Node".into(), + // inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(0), 0)], + // implementation: DocumentNodeImplementation::ProtoNode("graphene_core::storage::GetNode".into()), + // ..Default::default() + // }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], implementation: DocumentNodeImplementation::Network(inner_network), ..Default::default() }, - /* - DocumentNode { - name: "Save Node".into(), - inputs: vec![ - NodeInput::node(NodeId(5), 0), - NodeInput::Inline(InlineRust::new( - "|x| o0[(_global_index.y * i1 + _global_index.x) as usize] = x".into(), - // "|x|()".into(), - Type::Fn(Box::new(concrete!(PackedPixel)), Box::new(concrete!(()))), - )), - ], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::generic::FnMutNode".into()), - ..Default::default() - }, - */ + // DocumentNode { + // name: "Save Node".into(), + // inputs: vec![ + // NodeInput::node(NodeId(5), 0), + // NodeInput::Inline(InlineRust::new( + // "|x| o0[(_global_index.y * i1 + _global_index.x) as usize] = x".into(), + // // "|x|()".into(), + // Type::Fn(Box::new(concrete!(PackedPixel)), Box::new(concrete!(()))), + // )), + // ], + // implementation: DocumentNodeImplementation::ProtoNode("graphene_core::generic::FnMutNode".into()), + // ..Default::default() + // }, ] .into_iter() .enumerate() @@ -204,7 +213,7 @@ async fn create_compute_pass_descriptor(node ) .await .unwrap(); - // return ImageFrame::empty(); + let len: usize = image.image.data.len(); let storage_buffer = executor @@ -218,21 +227,22 @@ async fn create_compute_pass_descriptor(node }, ) .unwrap(); - /* - let canvas = editor_api.application_io.create_surface(); - let surface = unsafe { executor.create_surface(canvas) }.unwrap(); - let surface_id = surface.surface_id; + // let canvas = editor_api.application_io.create_surface(); - let texture = executor.create_texture_buffer(image.image.clone(), TextureBufferOptions::Texture).unwrap(); + // let surface = unsafe { executor.create_surface(canvas) }.unwrap(); + // let surface_id = surface.surface_id; - // executor.create_render_pass(texture, surface).unwrap(); + // let texture = executor.create_texture_buffer(image.image.clone(), TextureBufferOptions::Texture).unwrap(); + + // // executor.create_render_pass(texture, surface).unwrap(); + + // let frame = SurfaceFrame { + // surface_id, + // transform: image.transform, + // }; + // return frame; - let frame = SurfaceFrame { - surface_id, - transform: image.transform, - }; - return frame;*/ log::debug!("creating buffer"); let width_uniform = executor.create_uniform_buffer(image.image.width).unwrap(); @@ -269,9 +279,13 @@ async fn create_compute_pass_descriptor(node } #[node_macro::node(category("Debug: GPU"))] -async fn blend_gpu_image(_: (), foreground: ImageFrame, background: ImageFrame, blend_mode: BlendMode, opacity: f64) -> ImageFrame { +async fn blend_gpu_image(_: (), foreground: ImageFrameTable, background: ImageFrameTable, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable { + let foreground = foreground.one_item(); + let background = background.one_item(); + 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); @@ -320,7 +334,7 @@ async fn blend_gpu_image(_: (), foreground: ImageFrame, background: Image 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 ImageFrame::empty(); + return ImageFrameTable::default(); }; let proto_networks = proto_networks_result; log::debug!("compiling shader"); @@ -430,7 +444,7 @@ async fn blend_gpu_image(_: (), foreground: ImageFrame, background: Image let result = executor.read_output_buffer(readback_buffer).await.unwrap(); let colors = bytemuck::pod_collect_to_vec::(result.as_slice()); - ImageFrame { + let result = ImageFrame { image: Image { data: colors, width: background.image.width, @@ -439,5 +453,7 @@ async fn blend_gpu_image(_: (), foreground: ImageFrame, background: Image }, transform: background.transform, alpha_blending: background.alpha_blending, - } + }; + + ImageFrameTable::new(result) } diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index 851b6efcd4..03ac5245f6 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -1,4 +1,4 @@ -use graphene_core::raster::ImageFrame; +use graphene_core::raster::image::ImageFrameTable; use graphene_core::transform::Footprint; use graphene_core::Color; @@ -10,10 +10,10 @@ async fn image_color_palette( )] footprint: F, #[implementations( - () -> ImageFrame, - Footprint -> ImageFrame, + () -> ImageFrameTable, + Footprint -> ImageFrameTable, )] - image: impl Node>, + image: impl Node>, #[min(1.)] #[max(28.)] max_size: u32, @@ -26,6 +26,8 @@ async fn image_color_palette( let mut colors: Vec> = vec![vec![]; (bins + 1.) as usize]; let image = image.eval(footprint).await; + let image = image.one_item(); + for pixel in image.image.data.iter() { let r = pixel.r() * GRID; let g = pixel.g() * GRID; @@ -74,7 +76,10 @@ mod test { use super::*; use graph_craft::generic::FnNode; - use graphene_core::{raster::Image, value::CopiedNode, Node}; + use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; + use graphene_core::raster::Image; + use graphene_core::value::CopiedNode; + use graphene_core::Node; #[test] fn test_image_color_palette() { @@ -82,7 +87,7 @@ mod test { max_size: CopiedNode(1u32), image: FnNode::new(|_| { Box::pin(async move { - ImageFrame { + ImageFrameTable::new(ImageFrame { image: Image { width: 100, height: 100, @@ -90,7 +95,7 @@ mod test { base64_string: None, }, ..Default::default() - } + }) }) }), }; diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 90c4b49d01..926d9d7c8b 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -1,12 +1,8 @@ -use crate::wasm_application_io::WasmEditorApi; - use dyn_any::DynAny; -use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; -use graph_craft::proto::DynFuture; use graphene_core::raster::bbox::Bbox; +use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::{ - Alpha, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, ImageFrame, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, - Sample, + Alpha, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Sample, }; use graphene_core::transform::{Footprint, Transform}; use graphene_core::{AlphaBlending, Color, Node}; @@ -15,7 +11,6 @@ use fastnoise_lite; use glam::{DAffine2, DVec2, Vec2}; use rand::prelude::*; use rand_chacha::ChaCha8Rng; -use std::collections::HashMap; use std::fmt::Debug; use std::hash::Hash; use std::marker::PhantomData; @@ -33,10 +28,12 @@ impl From for Error { } #[node_macro::node(category("Debug: Raster"))] -fn sample_image(footprint: Footprint, image_frame: ImageFrame) -> ImageFrame { +fn sample_image(footprint: Footprint, image_frame: ImageFrameTable) -> ImageFrameTable { + let image_frame = image_frame.one_item(); + // Resize the image using the image crate - let image = image_frame.image; - let data = bytemuck::cast_vec(image.data); + let image = &image_frame.image; + let data = bytemuck::cast_vec(image.data.clone()); let viewport_bounds = footprint.viewport_bounds_in_local_space(); let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox(); @@ -47,10 +44,10 @@ fn sample_image(footprint: Footprint, image_frame: ImageFrame) -> ImageFr // If the image would not be visible, return an empty image if size.x <= 0. || size.y <= 0. { - return ImageFrame::empty(); + return ImageFrameTable::default(); } - let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal ImageFrame into image-rs data type."); + let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type."); let dynamic_image: image::DynamicImage = image_buffer.into(); let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO); @@ -83,11 +80,14 @@ fn sample_image(footprint: Footprint, image_frame: ImageFrame) -> ImageFr // 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); - ImageFrame { + + let result = ImageFrame { image, transform: new_transform, alpha_blending: image_frame.alpha_blending, - } + }; + + ImageFrameTable::new(result) } #[derive(Debug, Clone, Copy)] @@ -244,6 +244,7 @@ where MapFn: Fn(_P, _P) -> _P, { let background_size = DVec2::new(background.width() as f64, background.height() as f64); + // Transforms a point from the background image to the foreground image let bg_to_fg = background.transform() * DAffine2::from_scale(1. / background_size); @@ -331,104 +332,104 @@ fn empty_image(_: (), transform: DAffine2, #[implementations(Color)] c } } -#[cfg(feature = "serde")] -macro_rules! generate_imaginate_node { - ($($val:ident: $t:ident: $o:ty,)*) => { - pub struct ImaginateNode { - editor_api: E, - controller: C, - generation_id: G, - $($val: $t,)* - cache: std::sync::Arc>>>, - last_generation: std::sync::atomic::AtomicU64, - } - - impl<'e, P: Pixel, E, C, G, $($t,)*> ImaginateNode - where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* - E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>, - C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, - G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>, - { - #[allow(clippy::too_many_arguments)] - pub fn new(editor_api: E, controller: C, $($val: $t,)* generation_id: G ) -> Self { - Self { editor_api, controller, generation_id, $($val,)* cache: Default::default(), last_generation: std::sync::atomic::AtomicU64::new(u64::MAX) } - } - } - - impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default + Send, E: 'i, C: 'i, G: 'i, $($t: 'i,)*> Node<'i, ImageFrame

> for ImaginateNode - where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* - E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>, - C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, - G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>, - { - type Output = DynFuture<'i, ImageFrame

>; - - fn eval(&'i self, frame: ImageFrame

) -> Self::Output { - let controller = self.controller.eval(()); - $(let $val = self.$val.eval(());)* - - use std::hash::Hasher; - let mut hasher = rustc_hash::FxHasher::default(); - frame.image.hash(&mut hasher); - let hash = hasher.finish(); - let editor_api = self.editor_api.eval(()); - let cache = self.cache.clone(); - let generation_future = self.generation_id.eval(()); - let last_generation = &self.last_generation; - - Box::pin(async move { - let controller: ImaginateController = controller.await; - let generation_id = generation_future.await; - if generation_id != last_generation.swap(generation_id, std::sync::atomic::Ordering::SeqCst) { - let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await; - - cache.lock().unwrap().insert(hash, image.clone()); - - return wrap_image_frame(image, frame.transform); - } - let image = cache.lock().unwrap().get(&hash).cloned().unwrap_or_default(); - - return wrap_image_frame(image, frame.transform); - }) - } - } - } -} - -fn wrap_image_frame(image: Image

, transform: DAffine2) -> ImageFrame

{ - if !transform.decompose_scale().abs_diff_eq(DVec2::ZERO, 0.00001) { - ImageFrame { - image, - transform, - alpha_blending: AlphaBlending::default(), - } - } else { - let resolution = DVec2::new(image.height as f64, image.width as f64); - ImageFrame { - image, - transform: DAffine2::from_scale_angle_translation(resolution, 0., transform.translation), - alpha_blending: AlphaBlending::default(), - } - } -} - -#[cfg(feature = "serde")] -generate_imaginate_node! { - seed: Seed: f64, - res: Res: Option, - samples: Samples: u32, - sampling_method: SamplingMethod: ImaginateSamplingMethod, - prompt_guidance: PromptGuidance: f64, - prompt: Prompt: String, - negative_prompt: NegativePrompt: String, - adapt_input_image: AdaptInputImage: bool, - image_creativity: ImageCreativity: f64, - inpaint: Inpaint: bool, - mask_blur: MaskBlur: f64, - mask_starting_fill: MaskStartingFill: ImaginateMaskStartingFill, - improve_faces: ImproveFaces: bool, - tiling: Tiling: bool, -} +// #[cfg(feature = "serde")] +// macro_rules! generate_imaginate_node { +// ($($val:ident: $t:ident: $o:ty,)*) => { +// pub struct ImaginateNode { +// editor_api: E, +// controller: C, +// generation_id: G, +// $($val: $t,)* +// cache: std::sync::Arc>>>, +// last_generation: std::sync::atomic::AtomicU64, +// } + +// impl<'e, P: Pixel, E, C, G, $($t,)*> ImaginateNode +// where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* +// E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>, +// C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, +// G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>, +// { +// #[allow(clippy::too_many_arguments)] +// pub fn new(editor_api: E, controller: C, $($val: $t,)* generation_id: G ) -> Self { +// Self { editor_api, controller, generation_id, $($val,)* cache: Default::default(), last_generation: std::sync::atomic::AtomicU64::new(u64::MAX) } +// } +// } + +// impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default + Send, E: 'i, C: 'i, G: 'i, $($t: 'i,)*> Node<'i, ImageFrame

> for ImaginateNode +// where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* +// E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>, +// C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, +// G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>, +// { +// type Output = DynFuture<'i, ImageFrame

>; + +// fn eval(&'i self, frame: ImageFrame

) -> Self::Output { +// let controller = self.controller.eval(()); +// $(let $val = self.$val.eval(());)* + +// use std::hash::Hasher; +// let mut hasher = rustc_hash::FxHasher::default(); +// frame.image.hash(&mut hasher); +// let hash = hasher.finish(); +// let editor_api = self.editor_api.eval(()); +// let cache = self.cache.clone(); +// let generation_future = self.generation_id.eval(()); +// let last_generation = &self.last_generation; + +// Box::pin(async move { +// let controller: ImaginateController = controller.await; +// let generation_id = generation_future.await; +// if generation_id != last_generation.swap(generation_id, std::sync::atomic::Ordering::SeqCst) { +// let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await; + +// cache.lock().unwrap().insert(hash, image.clone()); + +// return wrap_image_frame(image, frame.transform); +// } +// let image = cache.lock().unwrap().get(&hash).cloned().unwrap_or_default(); + +// return wrap_image_frame(image, frame.transform); +// }) +// } +// } +// } +// } + +// fn wrap_image_frame(image: Image

, transform: DAffine2) -> ImageFrame

{ +// if !transform.decompose_scale().abs_diff_eq(DVec2::ZERO, 0.00001) { +// ImageFrame { +// image, +// transform, +// alpha_blending: AlphaBlending::default(), +// } +// } else { +// let resolution = DVec2::new(image.height as f64, image.width as f64); +// ImageFrame { +// image, +// transform: DAffine2::from_scale_angle_translation(resolution, 0., transform.translation), +// alpha_blending: AlphaBlending::default(), +// } +// } +// } + +// #[cfg(feature = "serde")] +// generate_imaginate_node! { +// seed: Seed: f64, +// res: Res: Option, +// samples: Samples: u32, +// sampling_method: SamplingMethod: ImaginateSamplingMethod, +// prompt_guidance: PromptGuidance: f64, +// prompt: Prompt: String, +// negative_prompt: NegativePrompt: String, +// adapt_input_image: AdaptInputImage: bool, +// image_creativity: ImageCreativity: f64, +// inpaint: Inpaint: bool, +// mask_blur: MaskBlur: f64, +// mask_starting_fill: MaskStartingFill: ImaginateMaskStartingFill, +// improve_faces: ImproveFaces: bool, +// tiling: Tiling: bool, +// } #[node_macro::node(category("Raster: Generator"))] #[allow(clippy::too_many_arguments)] @@ -450,7 +451,7 @@ fn noise_pattern( cellular_distance_function: CellularDistanceFunction, cellular_return_type: CellularReturnType, cellular_jitter: f64, -) -> ImageFrame { +) -> ImageFrameTable { let viewport_bounds = footprint.viewport_bounds_in_local_space(); let mut size = viewport_bounds.size(); @@ -467,7 +468,7 @@ fn noise_pattern( // If the image would not be visible, return an empty image if size.x <= 0. || size.y <= 0. { - return ImageFrame::empty(); + return ImageFrameTable::default(); } let footprint_scale = footprint.scale(); @@ -511,11 +512,13 @@ fn noise_pattern( } } - return ImageFrame:: { + let result = ImageFrame { image, transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), alpha_blending: AlphaBlending::default(), }; + + return ImageFrameTable::new(result); } }; noise.set_noise_type(Some(noise_type)); @@ -573,16 +576,17 @@ fn noise_pattern( } } - // Return the coherent noise image - ImageFrame:: { + let result = ImageFrame { image, transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), alpha_blending: AlphaBlending::default(), - } + }; + + ImageFrameTable::new(result) } #[node_macro::node(category("Raster: Generator"))] -fn mandelbrot(footprint: Footprint) -> ImageFrame { +fn mandelbrot(footprint: Footprint) -> ImageFrameTable { let viewport_bounds = footprint.viewport_bounds_in_local_space(); let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox(); @@ -593,7 +597,7 @@ fn mandelbrot(footprint: Footprint) -> ImageFrame { // If the image would not be visible, return an empty image if size.x <= 0. || size.y <= 0. { - return ImageFrame::empty(); + return ImageFrameTable::default(); } let scale = footprint.scale(); @@ -614,7 +618,8 @@ fn mandelbrot(footprint: Footprint) -> ImageFrame { data.push(map_color(iter, max_iter)); } } - ImageFrame { + + let result = ImageFrame { image: Image { width, height, @@ -623,7 +628,9 @@ fn mandelbrot(footprint: Footprint) -> ImageFrame { }, transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), ..Default::default() - } + }; + + ImageFrameTable::new(result) } #[inline(always)] diff --git a/node-graph/gstd/src/text.rs b/node-graph/gstd/src/text.rs index dea27dec2e..11a7b49bbb 100644 --- a/node-graph/gstd/src/text.rs +++ b/node-graph/gstd/src/text.rs @@ -1,3 +1,5 @@ +use crate::vector::{VectorData, VectorDataTable}; + use graph_craft::wasm_application_io::WasmEditorApi; use graphene_core::text::TypesettingConfig; pub use graphene_core::text::{bounding_box, load_face, to_path, Font, FontCache}; @@ -13,7 +15,7 @@ fn text<'i: 'n>( #[default(1.)] character_spacing: f64, #[default(None)] max_width: Option, #[default(None)] max_height: Option, -) -> crate::vector::VectorData { +) -> VectorDataTable { let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data)); let typesetting = TypesettingConfig { @@ -23,5 +25,8 @@ fn text<'i: 'n>( max_width, max_height, }; - crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, typesetting), false) + + let result = VectorData::from_subpaths(to_path(&text, buzz_face, typesetting), false); + + VectorDataTable::new(result) } diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index 54c1b2c527..852e498b6b 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -1,13 +1,13 @@ use crate::transform::Footprint; use bezier_rs::{ManipulatorGroup, Subpath}; -use graphene_core::transform::Transform; use graphene_core::vector::misc::BooleanOperation; +use graphene_core::vector::style::Fill; pub use graphene_core::vector::*; -use graphene_core::{Color, GraphicElement, GraphicGroup}; +use graphene_core::{transform::Transform, GraphicGroup}; +use graphene_core::{Color, GraphicElement, GraphicGroupTable}; pub use path_bool as path_bool_lib; -use path_bool::FillRule; -use path_bool::PathBooleanOperation; +use path_bool::{FillRule, PathBooleanOperation}; use glam::{DAffine2, DVec2}; use std::ops::Mul; @@ -20,41 +20,49 @@ async fn boolean_operation( )] footprint: F, #[implementations( - () -> GraphicGroup, - Footprint -> GraphicGroup, + () -> GraphicGroupTable, + Footprint -> GraphicGroupTable, )] - group_of_paths: impl Node, + group_of_paths: impl Node, operation: BooleanOperation, -) -> VectorData { +) -> VectorDataTable { let group_of_paths = group_of_paths.eval(footprint).await; + let group_of_paths = group_of_paths.one_item(); fn vector_from_image(image_frame: T) -> VectorData { let corner1 = DVec2::ZERO; let corner2 = DVec2::new(1., 1.); + let mut subpath = Subpath::new_rect(corner1, corner2); subpath.apply_transform(image_frame.transform()); + let mut vector_data = VectorData::from_subpath(subpath); - vector_data - .style - .set_fill(graphene_core::vector::style::Fill::Solid(Color::from_rgb_str("777777").unwrap().to_gamma_srgb())); + vector_data.style.set_fill(Fill::Solid(Color::from_rgb_str("777777").unwrap().to_gamma_srgb())); + vector_data } fn union_vector_data(graphic_element: &GraphicElement) -> VectorData { match graphic_element { - GraphicElement::VectorData(vector_data) => *vector_data.clone(), + GraphicElement::VectorData(vector_data) => { + let vector_data = vector_data.one_item(); + 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) } - GraphicElement::Raster(image) => vector_from_image(image), + GraphicElement::RasterFrame(image) => vector_from_image(image), } } fn collect_vector_data(graphic_group: &GraphicGroup) -> Vec { // 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)); + // 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; @@ -186,15 +194,15 @@ async fn boolean_operation( } // 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 boolean_operation_result = 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(group_of_paths); + boolean_operation_result.upstream_graphic_group = Some(GraphicGroupTable::new(group_of_paths.clone())); - boolean_operation_result + VectorDataTable::new(boolean_operation_result) } 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 a65178f982..bdbedda9e0 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -6,13 +6,13 @@ use graphene_core::application_io::SurfaceHandle; use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig}; #[cfg(target_arch = "wasm32")] use graphene_core::raster::bbox::Bbox; +use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::Image; -use graphene_core::raster::ImageFrame; use graphene_core::renderer::RenderMetadata; use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender}; use graphene_core::transform::Footprint; -use graphene_core::vector::VectorData; -use graphene_core::GraphicGroup; +use graphene_core::vector::VectorDataTable; +use graphene_core::GraphicGroupTable; use graphene_core::{Color, WasmNotSend}; #[cfg(target_arch = "wasm32")] @@ -22,8 +22,6 @@ use glam::DAffine2; use std::collections::{HashMap, HashSet}; use std::sync::Arc; #[cfg(target_arch = "wasm32")] -use wasm_bindgen::Clamped; -#[cfg(target_arch = "wasm32")] use wasm_bindgen::JsCast; #[cfg(target_arch = "wasm32")] use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; @@ -33,25 +31,33 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc, surface_handle: Arc) -> graphene_core::application_io::SurfaceHandleFrame { - 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 { - let canvas = &surface_handle.surface; - canvas.set_width(image.image.width); - canvas.set_height(image.image.height); - // TODO: replace "2d" with "bitmaprenderer" once we switch to ImageBitmap (lives on gpu) from ImageData (lives on cpu) - let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::().unwrap(); - let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, image.image.width, image.image.height).expect("Failed to construct ImageData"); - context.put_image_data(&image_data, 0., 0.).unwrap(); - } - graphene_core::application_io::SurfaceHandleFrame { - surface_handle, - transform: image.transform, - } -} +// #[cfg(target_arch = "wasm32")] +// use wasm_bindgen::Clamped; +// +// #[node_macro::node(category("Debug: GPU"))] +// #[cfg(target_arch = "wasm32")] +// async fn draw_image_frame( +// _: (), +// image: ImageFrameTable, +// surface_handle: Arc, +// ) -> graphene_core::application_io::SurfaceHandleFrame { +// let image = image.one_item(); +// 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 { +// let canvas = &surface_handle.surface; +// canvas.set_width(image.image.width); +// canvas.set_height(image.image.height); +// // TODO: replace "2d" with "bitmaprenderer" once we switch to ImageBitmap (lives on gpu) from ImageData (lives on cpu) +// let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::().unwrap(); +// let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, image.image.width, image.image.height).expect("Failed to construct ImageData"); +// context.put_image_data(&image_data, 0., 0.).unwrap(); +// } +// graphene_core::application_io::SurfaceHandleFrame { +// surface_handle, +// transform: image.transform, +// } +// } #[node_macro::node(category("Network"))] async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> { @@ -69,8 +75,10 @@ async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] edito } #[node_macro::node(category("Raster"))] -fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrame { - let Some(image) = image::load_from_memory(data.as_ref()).ok() else { return ImageFrame::default() }; +fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrameTable { + let Some(image) = image::load_from_memory(data.as_ref()).ok() else { + return ImageFrameTable::default(); + }; let image = image.to_rgba32f(); let image = ImageFrame { image: Image { @@ -81,7 +89,8 @@ fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrame { }, ..Default::default() }; - image + + ImageFrameTable::new(image) } fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutputType { @@ -144,17 +153,17 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen async fn rasterize( _: (), #[implementations( - Footprint -> VectorData, - Footprint -> ImageFrame, - Footprint -> GraphicGroup, + Footprint -> VectorDataTable, + Footprint -> ImageFrameTable, + Footprint -> GraphicGroupTable, )] data: impl Node, footprint: Footprint, surface_handle: Arc>, -) -> ImageFrame { +) -> ImageFrameTable { if footprint.transform.matrix2.determinant() == 0. { log::trace!("Invalid footprint received for rasterization"); - return ImageFrame::default(); + return ImageFrameTable::default(); } let mut data = data.eval(footprint).await; @@ -192,12 +201,13 @@ async fn rasterize( render_config: RenderConfig, editor_api: &'a WasmEditorApi, #[implementations( - Footprint -> VectorData, - Footprint -> ImageFrame, - Footprint -> GraphicGroup, + Footprint -> VectorDataTable, + Footprint -> ImageFrameTable, + Footprint -> GraphicGroupTable, Footprint -> graphene_core::Artboard, Footprint -> graphene_core::ArtboardGroup, Footprint -> Option, diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 4072f0e42e..dda75861fe 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -1,23 +1,23 @@ use dyn_any::StaticType; use graph_craft::document::value::RenderOutput; -use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graphene_core::fn_type; use graphene_core::ops::IdentityNode; use graphene_core::raster::color::Color; +use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::*; use graphene_core::structural::Then; use graphene_core::transform::Footprint; use graphene_core::value::{ClonedNode, ValueNode}; -use graphene_core::vector::VectorData; -use graphene_core::{concrete, generic, Artboard, GraphicGroup}; +use graphene_core::vector::VectorDataTable; +use graphene_core::{concrete, generic, Artboard, GraphicGroupTable}; use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; use graphene_std::application_io::TextureFrame; use graphene_std::raster::*; use graphene_std::wasm_application_io::*; -use graphene_std::GraphicElement; +use graphene_std::{GraphicElement, GraphicGroup}; #[cfg(feature = "gpu")] use wgpu_executor::{ShaderInputFrame, WgpuExecutor}; use wgpu_executor::{WgpuSurface, WindowHandle}; @@ -112,23 +112,21 @@ fn node_registry() -> HashMap>, input: ImageFrame, params: []), - async_node!(graphene_core::ops::IntoNode>, input: ImageFrame, params: []), - async_node!(graphene_core::ops::IntoNode, input: ImageFrame, params: []), - async_node!(graphene_core::ops::IntoNode, input: VectorData, params: []), - async_node!(graphene_core::ops::IntoNode, input: GraphicGroup, params: []), + // async_node!(graphene_core::ops::IntoNode>, input: ImageFrameTable, params: []), + // async_node!(graphene_core::ops::IntoNode>, input: ImageFrameTable, params: []), + async_node!(graphene_core::ops::IntoNode, input: ImageFrameTable, params: []), + async_node!(graphene_core::ops::IntoNode, input: VectorDataTable, params: []), #[cfg(feature = "gpu")] async_node!(graphene_core::ops::IntoNode<&WgpuExecutor>, input: &WasmEditorApi, params: []), - async_node!(graphene_core::ops::IntoNode, input: VectorData, params: []), - async_node!(graphene_core::ops::IntoNode, input: ImageFrame, params: []), - async_node!(graphene_core::ops::IntoNode, input: GraphicGroup, params: []), - async_node!(graphene_core::ops::IntoNode, input: GraphicGroup, params: []), - async_node!(graphene_core::ops::IntoNode, input: VectorData, params: []), - async_node!(graphene_core::ops::IntoNode, input: ImageFrame, params: []), - register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame, params: [ImageFrame]), - register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame, params: [ImageFrame]), - register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame, params: [ImageFrame, RedGreenBlue]), - register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame, params: [ImageFrame, RedGreenBlue]), + async_node!(graphene_core::ops::IntoNode, input: VectorDataTable, params: []), + async_node!(graphene_core::ops::IntoNode, input: ImageFrameTable, params: []), + async_node!(graphene_core::ops::IntoNode, input: GraphicGroupTable, params: []), + async_node!(graphene_core::ops::IntoNode, input: VectorDataTable, params: []), + async_node!(graphene_core::ops::IntoNode, input: ImageFrameTable, params: []), + register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable, params: [ImageFrameTable]), + register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable, params: [ImageFrameTable, RedGreenBlue]), + // register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable, params: [ImageFrameTable]), + // register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable, params: [ImageFrameTable, RedGreenBlue]), ( ProtoNodeIdentifier::new("graphene_std::raster::CombineChannelsNode"), |args| { @@ -136,10 +134,10 @@ fn node_registry() -> HashMap = DowncastBothNode::new(args[0].clone()).eval(()).await; - let channel_g: ImageFrame = DowncastBothNode::new(args[1].clone()).eval(()).await; - let channel_b: ImageFrame = DowncastBothNode::new(args[2].clone()).eval(()).await; - let channel_a: ImageFrame = DowncastBothNode::new(args[3].clone()).eval(()).await; + let channel_r: ImageFrameTable = DowncastBothNode::new(args[0].clone()).eval(()).await; + let channel_g: ImageFrameTable = DowncastBothNode::new(args[1].clone()).eval(()).await; + let channel_b: ImageFrameTable = DowncastBothNode::new(args[2].clone()).eval(()).await; + let channel_a: ImageFrameTable = DowncastBothNode::new(args[3].clone()).eval(()).await; let insert_r = InsertChannelNode::new(ClonedNode::new(channel_r.clone()), CopiedNode::new(RedGreenBlue::Red)); let insert_g = InsertChannelNode::new(ClonedNode::new(channel_g.clone()), CopiedNode::new(RedGreenBlue::Green)); @@ -147,6 +145,11 @@ fn node_registry() -> HashMap HashMap HashMap), - vec![fn_type!(ImageFrame), fn_type!(ImageFrame), fn_type!(ImageFrame), fn_type!(ImageFrame)], + concrete!(ImageFrameTable), + vec![ + fn_type!(ImageFrameTable), + fn_type!(ImageFrameTable), + fn_type!(ImageFrameTable), + fn_type!(ImageFrameTable), + ], ), ), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => ImageFrame]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [ImageFrame]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => ImageFrameTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [ImageFrameTable]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [TextureFrame]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorData]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => VectorData]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => graphene_core::GraphicGroup]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => graphene_core::GraphicGroup]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => graphene_core::GraphicElement]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => graphene_core::GraphicElement]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => VectorDataTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroupTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => GraphicGroupTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => GraphicElement]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => Artboard]), #[cfg(feature = "gpu")] register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: (), params: [&WasmEditorApi]), @@ -195,13 +204,13 @@ fn node_registry() -> HashMap = DowncastBothNode::new(args[1].clone()); // let document_node = ClonedNode::new(document_node.eval(())); let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api); - let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(node); + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(node); any.into_type_erased() }) }, NodeIOTypes::new( - concrete!(ImageFrame), - concrete!(ImageFrame), + concrete!(ImageFrameTable), + concrete!(ImageFrameTable), vec![fn_type!(graph_craft::document::DocumentNode), fn_type!(WasmEditorApi)], ), ), @@ -239,36 +248,36 @@ fn node_registry() -> HashMap, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); any.into_type_erased() } else { let generate_brightness_contrast_mapper_node = GenerateBrightnessContrastMapperNode::new(brightness, contrast); let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_mapper_node.eval(()))); let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); - let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); any.into_type_erased() } }) }, - NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![fn_type!(f64), fn_type!(f64), fn_type!(bool)]), + NodeIOTypes::new(concrete!(ImageFrameTable), concrete!(ImageFrameTable), vec![fn_type!(f64), fn_type!(f64), fn_type!(bool)]), ), - ( - ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"), - |args| { - use graphene_core::raster::{curve::Curve, GenerateCurvesNode}; - let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone()); - Box::pin(async move { - let curve = ClonedNode::new(curve.eval(()).await); + // ( + // ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"), + // |args| { + // use graphene_core::raster::{curve::Curve, GenerateCurvesNode}; + // let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone()); + // Box::pin(async move { + // let curve = ClonedNode::new(curve.eval(()).await); - let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32)); - let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(()))); - let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); - let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); - any.into_type_erased() - }) - }, - NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![fn_type!(graphene_core::raster::curve::Curve)]), - ), + // let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32)); + // let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(()))); + // let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); + // let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); + // any.into_type_erased() + // }) + // }, + // NodeIOTypes::new(concrete!(ImageFrameTable), concrete!(ImageFrameTable), vec![fn_type!(graphene_core::raster::curve::Curve)]), + // ), // TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color. ( ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"), @@ -281,52 +290,56 @@ fn node_registry() -> HashMap, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); - any.into_type_erased() - }) - }, - NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![fn_type!(graphene_core::raster::curve::Curve)]), - ), - ( - ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"), - |args: Vec| { - Box::pin(async move { - use graphene_std::raster::ImaginateNode; - macro_rules! instantiate_imaginate_node { - ($($i:expr,)*) => { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* ) }; - } - let node: ImaginateNode = instantiate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,); - let any = graphene_std::any::DynAnyNode::new(node); + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); any.into_type_erased() }) }, NodeIOTypes::new( - concrete!(ImageFrame), - concrete!(ImageFrame), - vec![ - fn_type!(&WasmEditorApi), - fn_type!(ImaginateController), - fn_type!(f64), - fn_type!(Option), - fn_type!(u32), - fn_type!(ImaginateSamplingMethod), - fn_type!(f64), - fn_type!(String), - fn_type!(String), - fn_type!(bool), - fn_type!(f64), - fn_type!(bool), - fn_type!(f64), - fn_type!(ImaginateMaskStartingFill), - fn_type!(bool), - fn_type!(bool), - fn_type!(u64), - ], + concrete!(ImageFrameTable), + concrete!(ImageFrameTable), + vec![fn_type!(graphene_core::raster::curve::Curve)], ), ), + // ( + // ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"), + // |args: Vec| { + // Box::pin(async move { + // use graphene_std::raster::ImaginateNode; + // macro_rules! instantiate_imaginate_node { + // ($($i:expr,)*) => { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* ) }; + // } + // let node: ImaginateNode = instantiate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,); + // let any = graphene_std::any::DynAnyNode::new(node); + // any.into_type_erased() + // }) + // }, + // NodeIOTypes::new( + // concrete!(ImageFrameTable), + // concrete!(ImageFrameTable), + // vec![ + // fn_type!(&WasmEditorApi), + // fn_type!(ImaginateController), + // fn_type!(f64), + // fn_type!(Option), + // fn_type!(u32), + // fn_type!(ImaginateSamplingMethod), + // fn_type!(f64), + // fn_type!(String), + // fn_type!(String), + // fn_type!(bool), + // fn_type!(f64), + // fn_type!(bool), + // fn_type!(f64), + // fn_type!(ImaginateMaskStartingFill), + // fn_type!(bool), + // fn_type!(bool), + // fn_type!(u64), + // ], + // ), + // ), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Image]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [VectorData]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ImageFrame]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [VectorDataTable]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ImageFrameTable]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Vec]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Arc]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [WindowHandle]), @@ -339,8 +352,8 @@ fn node_registry() -> HashMap, input: (), params: [graphene_std::SurfaceFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [RenderOutput]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Image]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => VectorData]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ImageFrame]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ImageFrameTable]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Vec]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Arc]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => WindowHandle]), @@ -355,7 +368,7 @@ fn node_registry() -> HashMap, input: Footprint, fn_params: [Footprint => RenderOutput]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroup]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorData]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]), #[cfg(feature = "gpu")] async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => WgpuSurface]), diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 2ac6908a30..96b010c27e 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -825,7 +825,7 @@ mod tests { fn test_node_with_implementations() { let attr = quote!(category("Raster: Adjustment")); let input = quote!( - fn levels(image: ImageFrame

, #[implementations(f32, f64)] shadows: f64) -> ImageFrame

{ + fn levels(image: ImageFrameTable

, #[implementations(f32, f64)] shadows: f64) -> ImageFrameTable

{ // Implementation details... } ); @@ -846,10 +846,10 @@ mod tests { where_clause: None, input: Input { pat_ident: pat_ident("image"), - ty: parse_quote!(ImageFrame

), + ty: parse_quote!(ImageFrameTable

), implementations: Punctuated::new(), }, - output_type: parse_quote!(ImageFrame

), + output_type: parse_quote!(ImageFrameTable

), is_async: false, fields: vec![ParsedField::Regular { pat_ident: pat_ident("shadows"), @@ -939,7 +939,7 @@ mod tests { fn test_async_node() { let attr = quote!(category("IO")); let input = quote!( - async fn load_image(api: &WasmEditorApi, #[expose] path: String) -> ImageFrame { + async fn load_image(api: &WasmEditorApi, #[expose] path: String) -> ImageFrameTable { // Implementation details... } ); @@ -963,7 +963,7 @@ mod tests { ty: parse_quote!(&WasmEditorApi), implementations: Punctuated::new(), }, - output_type: parse_quote!(ImageFrame), + output_type: parse_quote!(ImageFrameTable), is_async: true, fields: vec![ParsedField::Regular { pat_ident: pat_ident("path"), @@ -1077,7 +1077,7 @@ mod tests { fn test_invalid_implementation_syntax() { let attr = quote!(category("Test")); let input = quote!( - fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, ImageFrame))] input: impl Node) -> T { + fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, ImageFrameTable))] input: impl Node) -> T { // Implementation details... } ); @@ -1103,10 +1103,10 @@ mod tests { #[implementations((), #tuples, Footprint)] footprint: F, #[implementations( () -> Color, - () -> ImageFrame, + () -> ImageFrameTable, () -> GradientStops, Footprint -> Color, - Footprint -> ImageFrame, + Footprint -> ImageFrameTable, Footprint -> GradientStops, )] image: impl Node, diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index f283e62d0d..a11d35cff9 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -6,11 +6,9 @@ pub use executor::GpuExecutor; use dyn_any::{DynAny, StaticType}; use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer}; -use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle, TextureFrame}; -use graphene_core::raster::{Image, ImageFrame, SRGBA8}; +use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle}; use graphene_core::transform::{Footprint, Transform}; -use graphene_core::Type; -use graphene_core::{Color, Cow, Node, SurfaceFrame}; +use graphene_core::{Cow, Node, SurfaceFrame, Type}; use anyhow::{bail, Result}; use futures::Future; @@ -154,7 +152,7 @@ impl WgpuExecutor { let surface_texture = surface.get_current_texture()?; let render_params = RenderParams { - // We are using an explicit opaque color here to eliminate the alpha premulitplication step + // We are using an explicit opaque color here to eliminate the alpha premultiplication step // which would be required to support a transparent webgpu canvas base_color: vello::peniko::Color::from_rgba8(0x22, 0x22, 0x22, 0xff), width, @@ -164,7 +162,7 @@ impl WgpuExecutor { { let mut renderer = self.vello_renderer.lock().await; - for (id, texture) in context.ressource_overrides.iter() { + for (id, texture) in context.resource_overrides.iter() { let texture_view = wgpu::ImageCopyTextureBase { texture: texture.clone(), mip_level: 0, @@ -905,32 +903,34 @@ async fn render_texture<'a: 'n>(_: (), footprint: Footprint, image: impl Node( - #[implementations((), Footprint)] footprint: F, - #[implementations(() -> ImageFrame, Footprint -> ImageFrame)] input: impl Node>, - executor: &'a WgpuExecutor, -) -> TextureFrame { - // let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); - let input = input.eval(footprint).await; - let new_data = input.image.data.into_iter().map(SRGBA8::from).collect(); - let new_image = Image { - width: input.image.width, - height: input.image.height, - data: new_data, - base64_string: None, - }; - - let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap(); - let texture = match shader_input { - ShaderInput::TextureBuffer(buffer, _) => buffer, - ShaderInput::StorageTextureBuffer(buffer, _) => buffer, - _ => unreachable!("Unsupported ShaderInput type"), - }; - - TextureFrame { - texture: texture.into(), - transform: input.transform, - alpha_blend: Default::default(), - } -} +// #[node_macro::node(category(""))] +// async fn upload_texture<'a: 'n, F: Copy + Send + Sync + 'n>( +// #[implementations((), Footprint)] footprint: F, +// #[implementations(() -> ImageFrameTable, Footprint -> ImageFrameTable)] input: impl Node>, +// executor: &'a WgpuExecutor, +// ) -> TextureFrame { +// // let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); +// let input = input.eval(footprint).await; +// let input = input.one_item(); + +// let new_data: Vec = input.image.data.iter().map(|x| (*x).into()).collect(); +// let new_image = Image { +// width: input.image.width, +// height: input.image.height, +// data: new_data, +// base64_string: None, +// }; + +// let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap(); +// let texture = match shader_input { +// ShaderInput::TextureBuffer(buffer, _) => buffer, +// ShaderInput::StorageTextureBuffer(buffer, _) => buffer, +// _ => unreachable!("Unsupported ShaderInput type"), +// }; + +// TextureFrame { +// texture: texture.into(), +// transform: input.transform, +// alpha_blend: Default::default(), +// } +// }