From d82f133514789255c932dd8d7068f75092a9fac2 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 6 Sep 2023 12:39:21 +0200 Subject: [PATCH] Lay groundwork for adaptive resolution system (#1395) * Make transform node accept footprint as input and pass it along to its input use f32 instead of f64 and add default to document node definition * Add cull node * Fix types for Transform and Cull Nodes * Add render config struct * Add Render Node skeleton * Add Render Node to node_registry * Make types macro use macro hygiene * Place Render Node as output * Start making DownresNode footprint aware * Correctly calculate footprint in Transform Node * Add cropping and resizing to downres node * Fix Output node declaration * Fix image transform * Fix Vector Data rendering * Add concept of ImageRenderMode * Take base image size into account when calculating the final image size * Supply viewport transform to the node graph * Start adapting document graph to resolution agnosticism * Make document node short circuting not shift the input index * Apply clippy lints --- Cargo.lock | 1 + Cargo.toml | 3 + document-legacy/src/document.rs | 60 +++- document-legacy/src/document_metadata.rs | 2 +- document-legacy/src/layers/layer_layer.rs | 2 + document-legacy/src/operation.rs | 4 + .../export_dialog_message_handler.rs | 13 - .../document/document_message_handler.rs | 30 +- .../graph_operation_message_handler.rs | 29 +- .../transform_utils.rs | 2 +- .../document_node_types.rs | 270 +++++++++++------- .../node_properties.rs | 6 +- editor/src/node_graph_executor.rs | 39 ++- node-graph/compilation-client/src/main.rs | 2 - node-graph/gcore/Cargo.toml | 22 +- node-graph/gcore/src/application_io.rs | 25 +- node-graph/gcore/src/graphic_element.rs | 50 +++- .../gcore/src/graphic_element/renderer.rs | 67 ++++- node-graph/gcore/src/lib.rs | 1 + node-graph/gcore/src/raster.rs | 2 +- node-graph/gcore/src/raster/bbox.rs | 8 + node-graph/gcore/src/raster/image.rs | 2 +- .../gcore/src/raster/image/base64_serde.rs | 0 node-graph/gcore/src/transform.rs | 111 ++++++- node-graph/gcore/src/types.rs | 14 +- node-graph/graph-craft/src/document.rs | 111 +++---- node-graph/graph-craft/src/document/value.rs | 20 +- node-graph/graph-craft/src/proto.rs | 7 +- node-graph/graphene-cli/src/main.rs | 11 +- node-graph/gstd/src/any.rs | 3 + node-graph/gstd/src/raster.rs | 59 ++-- node-graph/gstd/src/wasm_application_io.rs | 36 ++- .../interpreted-executor/src/node_registry.rs | 129 +++++++-- 33 files changed, 836 insertions(+), 305 deletions(-) create mode 100644 node-graph/gcore/src/raster/image/base64_serde.rs diff --git a/Cargo.lock b/Cargo.lock index bbf13ffd..e7f06573 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2075,6 +2075,7 @@ dependencies = [ "bytemuck", "dyn-any", "glam", + "image", "js-sys", "kurbo", "log", diff --git a/Cargo.toml b/Cargo.toml index 77511ef8..7ebb010b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,9 @@ opt-level = 3 [profile.dev.package.image] opt-level = 3 +[profile.dev.package.rustc-hash] +opt-level = 3 + [profile.dev] opt-level = 1 diff --git a/document-legacy/src/document.rs b/document-legacy/src/document.rs index a83ff031..fc53f322 100644 --- a/document-legacy/src/document.rs +++ b/document-legacy/src/document.rs @@ -8,6 +8,8 @@ use crate::layers::style::RenderData; use crate::{DocumentError, DocumentResponse, Operation}; use glam::{DAffine2, DVec2}; +use graphene_core::transform::Footprint; +use graphene_std::wasm_application_io::WasmEditorApi; use serde::{Deserialize, Serialize}; use std::cmp::max; use std::collections::hash_map::DefaultHasher; @@ -15,6 +17,10 @@ use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::vec; +use graph_craft::document::{DocumentNode, NodeOutput}; +use graph_craft::document::{DocumentNodeImplementation, NodeId}; +use graphene_core::{concrete, generic, NodeIdentifier}; + /// A number that identifies a layer. /// This does not technically need to be unique globally, only within a folder. pub type LayerId = u64; @@ -51,12 +57,54 @@ impl Default for Document { let mut network = NodeNetwork::default(); let node = graph_craft::document::DocumentNode { name: "Output".into(), - inputs: vec![NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true)], - implementation: graph_craft::document::DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()), + inputs: vec![NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true), NodeInput::Network(concrete!(WasmEditorApi))], + implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork { + inputs: vec![3, 0], + outputs: vec![NodeOutput::new(4, 0)], + nodes: [ + DocumentNode { + name: "EditorApi".to_string(), + inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), + ..Default::default() + }, + DocumentNode { + name: "Create Canvas".to_string(), + inputs: vec![NodeInput::node(0, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), + skip_deduplication: true, + ..Default::default() + }, + DocumentNode { + name: "Cache".to_string(), + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(1, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), + ..Default::default() + }, + DocumentNode { + name: "Conversion".to_string(), + inputs: vec![NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IntoNode<_, GraphicGroup>")), + ..Default::default() + }, + DocumentNode { + name: "RenderNode".to_string(), + inputs: vec![NodeInput::node(0, 0), NodeInput::node(3, 0), NodeInput::node(2, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _>")), + ..Default::default() + }, + ] + .into_iter() + .enumerate() + .map(|(id, node)| (id as NodeId, node)) + .collect(), + ..Default::default() + }), metadata: graph_craft::document::DocumentNodeMetadata::position((8, 4)), ..Default::default() }; - network.push_node(node, false); + network.push_node(node); network }, metadata: Default::default(), @@ -886,6 +934,12 @@ impl Document { } Some(Vec::new()) } + Operation::SetSvg { path, svg } => { + if let LayerDataType::Layer(layer) = &mut self.layer_mut(&path)?.data { + layer.cached_output_data = CachedOutputData::Svg(svg); + } + Some(Vec::new()) + } Operation::TransformLayerInScope { path, transform, scope } => { let transform = DAffine2::from_cols_array(&transform); let scope = DAffine2::from_cols_array(&scope); diff --git a/document-legacy/src/document_metadata.rs b/document-legacy/src/document_metadata.rs index 31aec7f2..db807fda 100644 --- a/document-legacy/src/document_metadata.rs +++ b/document-legacy/src/document_metadata.rs @@ -92,7 +92,7 @@ impl DocumentMetadata { } /// Find all of the layers that were clicked on from a viewport space location - pub fn click_xray<'a>(&'a self, viewport_location: DVec2) -> impl Iterator + 'a { + pub fn click_xray(&self, viewport_location: DVec2) -> impl Iterator + '_ { let point = self.document_to_viewport.inverse().transform_point2(viewport_location); self.root() .decendants(self) diff --git a/document-legacy/src/layers/layer_layer.rs b/document-legacy/src/layers/layer_layer.rs index f8ea93a6..bd268a96 100644 --- a/document-legacy/src/layers/layer_layer.rs +++ b/document-legacy/src/layers/layer_layer.rs @@ -17,6 +17,7 @@ pub enum CachedOutputData { BlobURL(String), VectorPath(Box), SurfaceId(SurfaceId), + Svg(String), } #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] @@ -91,6 +92,7 @@ impl LayerData for LayerLayer { id ); } + CachedOutputData::Svg(new_svg) => svg.push_str(new_svg), _ => { // Render a dotted blue outline if there is no image or vector data let _ = write!( diff --git a/document-legacy/src/operation.rs b/document-legacy/src/operation.rs index ef1c708f..79c718d0 100644 --- a/document-legacy/src/operation.rs +++ b/document-legacy/src/operation.rs @@ -80,6 +80,10 @@ pub enum Operation { path: Vec, surface_id: graphene_core::SurfaceId, }, + SetSvg { + path: Vec, + svg: String, + }, TransformLayerInScope { path: Vec, transform: [f64; 6], diff --git a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs index 879fb2eb..cd883922 100644 --- a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs +++ b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs @@ -86,19 +86,6 @@ impl LayoutHolder for ExportDialogMessageHandler { .widget_holder(), ]; - let resolution = vec![ - TextLabel::new("Scale Factor").table_align(true).min_width(100).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - NumberInput::new(Some(self.scale_factor)) - .unit("") - .min(0.) - .max((1u64 << std::f64::MANTISSA_DIGITS) as f64) - .disabled(self.file_type == FileType::Svg) - .on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into()) - .min_width(200) - .widget_holder(), - ]; - let artboards = self.artboards.iter().map(|(&layer, name)| (ExportBounds::Artboard(layer), name.to_string(), false)); let mut export_area_options = vec![ (ExportBounds::AllArtwork, "All Artwork".to_string(), false), diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index b8a96576..484e6cea 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -6,6 +6,7 @@ use crate::messages::frontend::utility_types::ExportBounds; use crate::messages::frontend::utility_types::FileType; use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::layout::utility_types::widget_prelude::*; +use crate::messages::portfolio::document::node_graph::new_raster_network; use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData; use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData; use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; @@ -588,21 +589,7 @@ impl MessageHandler ModifyInputsContext<'a> { // Locate the node output of the first sibling layer to the new layer let new_id = if let NodeInput::Node { node_id, output_index, .. } = &self.network.nodes.get(&output_node_id)?.inputs[input_index] { let sibling_node = &self.network.nodes.get(node_id)?; + let node_id = *node_id; + let output_index = *output_index; let sibling_layer = if sibling_node.name == "Layer" { // There is already a layer node - NodeOutput::new(*node_id, 0) + NodeOutput::new(node_id, 0) } else { // The user has connected another node to the output. Insert a layer node between the output and the node. - let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node(); - let node_id = self.insert_between(generate_uuid(), NodeOutput::new(*node_id, *output_index), output, node, 0, 0, IVec2::new(-8, 0))?; + let mut node = resolve_document_node_type("Layer").expect("Layer node").default_document_node(); + self.add_empty_stack(&mut node); + let node_id = self.insert_between(generate_uuid(), NodeOutput::new(node_id, output_index), output, node, 0, 0, IVec2::new(-8, 0))?; NodeOutput::new(node_id, 0) }; let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node(); self.insert_between(new_id, sibling_layer, output, node, 7, 0, IVec2::new(0, 3)) } else { - let layer_node = resolve_document_node_type("Layer").expect("Node").default_document_node(); + let mut layer_node = resolve_document_node_type("Layer").expect("Node").default_document_node(); + self.add_empty_stack(&mut layer_node); self.insert_node_before(new_id, output_node_id, input_index, layer_node, IVec2::new(-5, 3)) }; @@ -125,7 +129,15 @@ impl<'a> ModifyInputsContext<'a> { new_id } + fn add_empty_stack(&mut self, node: &mut DocumentNode) { + let empty_stack = resolve_document_node_type("Empty Stack").expect("EmptyStack node").default_document_node(); + let empty_id = generate_uuid(); + self.network.nodes.insert(empty_id, empty_stack); + *node.inputs.last_mut().unwrap() = NodeInput::node(empty_id, 0); + } + fn insert_artboard(&mut self, artboard: Artboard, layer: NodeId) -> Option { + let cull_node = resolve_document_node_type("Cull").expect("Node").default_document_node(); let artboard_node = resolve_document_node_type("Artboard").expect("Node").to_document_node_default_inputs( [ None, @@ -137,7 +149,9 @@ impl<'a> ModifyInputsContext<'a> { Default::default(), ); self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true }); - self.insert_node_before(generate_uuid(), layer, 0, artboard_node, IVec2::new(-8, 0)) + let cull_id = generate_uuid(); + self.insert_node_before(cull_id, layer, 0, cull_node, IVec2::new(-8, 0)); + self.insert_node_before(generate_uuid(), cull_id, 0, artboard_node, IVec2::new(-8, 0)) } fn insert_vector_data(&mut self, subpaths: Vec>, layer: NodeId) { @@ -145,6 +159,7 @@ impl<'a> ModifyInputsContext<'a> { let node_type = resolve_document_node_type("Shape").expect("Shape node does not exist"); node_type.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], Default::default()) }; + let cull = resolve_document_node_type("Cull").expect("Cull node does not exist").default_document_node(); let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_document_node(); let fill = resolve_document_node_type("Fill").expect("Fill node does not exist").default_document_node(); let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_document_node(); @@ -155,8 +170,10 @@ impl<'a> ModifyInputsContext<'a> { self.insert_node_before(fill_id, stroke_id, 0, fill, IVec2::new(-8, 0)); let transform_id = generate_uuid(); self.insert_node_before(transform_id, fill_id, 0, transform, IVec2::new(-8, 0)); + let cull_id = generate_uuid(); + self.insert_node_before(cull_id, transform_id, 0, cull, IVec2::new(-8, 0)); let shape_id = generate_uuid(); - self.insert_node_before(shape_id, transform_id, 0, shape, IVec2::new(-8, 0)); + self.insert_node_before(shape_id, cull_id, 0, shape, IVec2::new(-8, 0)); self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true }); } diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs index 495195e6..fcd78742 100644 --- a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs @@ -126,7 +126,7 @@ pub fn get_current_normalized_pivot(inputs: &[NodeInput]) -> DVec2 { if let NodeInput::Value { tagged_value: TaggedValue::DVec2(pivot), .. - } = inputs[5] + } = inputs[4] { pivot } else { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index e51dc41d..cc07f5cd 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -13,6 +13,7 @@ use graphene_core::application_io::SurfaceHandle; use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice}; use graphene_core::text::Font; +use graphene_core::transform::Footprint; use graphene_core::vector::VectorData; use graphene_core::*; @@ -101,6 +102,7 @@ pub struct DocumentNodeType { pub outputs: Vec, pub primary_output: bool, pub properties: fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec, + pub manual_composition: Option, } impl Default for DocumentNodeType { @@ -113,6 +115,7 @@ impl Default for DocumentNodeType { outputs: Default::default(), primary_output: Default::default(), properties: node_properties::no_properties, + manual_composition: Default::default(), } } } @@ -187,7 +190,8 @@ fn static_nodes() -> Vec { ( 0, DocumentNode { - inputs: vec![NodeInput::Network(concrete!(graphene_core::vector::VectorData))], + name: "To Graphic Element".to_string(), + inputs: vec![NodeInput::Network(generic!(T))], implementation: DocumentNodeImplementation::proto("graphene_core::ToGraphicElementData"), ..Default::default() }, @@ -196,6 +200,7 @@ fn static_nodes() -> Vec { ( 1, DocumentNode { + name: "Monitor".to_string(), inputs: vec![NodeInput::node(0, 0)], implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"), skip_deduplication: true, @@ -205,6 +210,8 @@ fn static_nodes() -> Vec { ( 2, DocumentNode { + name: "ConstructLayer".to_string(), + manual_composition: Some(concrete!(Footprint)), inputs: vec![ NodeInput::node(1, 0), NodeInput::Network(concrete!(String)), @@ -213,9 +220,9 @@ fn static_nodes() -> Vec { NodeInput::Network(concrete!(bool)), NodeInput::Network(concrete!(bool)), NodeInput::Network(concrete!(bool)), - NodeInput::Network(concrete!(graphene_core::GraphicGroup)), + NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(concrete!(graphene_core::GraphicGroup)))), ], - implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>"), + implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _, _>"), ..Default::default() }, ), @@ -253,55 +260,14 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNodeType { - name: "Downres", - category: "Raster", - identifier: NodeImplementation::DocumentNode(NodeNetwork { - inputs: vec![0], - outputs: vec![NodeOutput::new(1, 0)], - nodes: [ - DocumentNode { - name: "Downres".to_string(), - inputs: vec![NodeInput::Network(concrete!(ImageFrame))], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::raster::DownresNode<_>")), - ..Default::default() - }, - DocumentNode { - name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), - ..Default::default() - }, - // We currently just clone by default - /*DocumentNode { - name: "Clone".to_string(), - inputs: vec![NodeInput::node(1, 0)], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::CloneNode<_>")), - ..Default::default() - },*/ - ] - .into_iter() - .enumerate() - .map(|(id, node)| (id as NodeId, node)) - .collect(), - ..Default::default() - }), - inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), false)], - outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: |_document_node, _node_id, _context| node_properties::string_properties("Downres the image to a lower resolution"), + name: "Empty Stack", + category: "Hidden", + identifier: NodeImplementation::proto("graphene_core::transform::CullNode<_>"), + manual_composition: Some(concrete!(Footprint)), + inputs: vec![DocumentInputType::value("Graphic Group", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), false)], + outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Artboard)], ..Default::default() }, - // DocumentNodeType { - // name: "Input Frame", - // category: "Ignore", - // identifier: NodeImplementation::proto("graphene_core::ops::IdNode"), - // inputs: vec![DocumentInputType { - // name: "In", - // data_type: FrontendGraphDataType::Raster, - // default: NodeInput::Network, - // }], - // outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Raster)], - // properties: node_properties::input_properties, - // }, DocumentNodeType { name: "Input Frame", category: "Ignore", @@ -374,11 +340,13 @@ fn static_nodes() -> Vec { name: "Create Canvas".to_string(), inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), + skip_deduplication: true, ..Default::default() }, DocumentNode { name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(0, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, @@ -417,11 +385,13 @@ fn static_nodes() -> Vec { name: "Create Canvas".to_string(), inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), + skip_deduplication: true, ..Default::default() }, DocumentNode { name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(1, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, @@ -465,7 +435,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "SetNode".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(WasmEditorApi))], + manual_composition: Some(concrete!(WasmEditorApi)), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")), ..Default::default() }, @@ -477,7 +447,8 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "RefNode".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::lambda(1, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::lambda(1, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::RefNode<_, _>")), ..Default::default() }, @@ -533,12 +504,61 @@ fn static_nodes() -> Vec { DocumentNodeType { name: "Output", category: "Ignore", - identifier: NodeImplementation::proto("graphene_core::ops::IdNode"), - inputs: vec![DocumentInputType { - name: "Output", - data_type: FrontendGraphDataType::Raster, - default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), - }], + identifier: NodeImplementation::DocumentNode(NodeNetwork { + inputs: vec![3, 0], + outputs: vec![NodeOutput::new(4, 0)], + nodes: [ + DocumentNode { + name: "EditorApi".to_string(), + inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), + ..Default::default() + }, + DocumentNode { + name: "Create Canvas".to_string(), + inputs: vec![NodeInput::node(0, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), + skip_deduplication: true, + ..Default::default() + }, + DocumentNode { + name: "Cache".to_string(), + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(1, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), + ..Default::default() + }, + DocumentNode { + name: "Conversion".to_string(), + inputs: vec![NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IntoNode<_, GraphicGroup>")), + ..Default::default() + }, + DocumentNode { + name: "RenderNode".to_string(), + inputs: vec![NodeInput::node(0, 0), NodeInput::node(3, 0), NodeInput::node(2, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _>")), + ..Default::default() + }, + ] + .into_iter() + .enumerate() + .map(|(id, node)| (id as NodeId, node)) + .collect(), + ..Default::default() + }), + inputs: vec![ + DocumentInputType { + name: "Output", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::default()), true), + }, + DocumentInputType { + name: "In", + data_type: FrontendGraphDataType::General, + default: NodeInput::Network(concrete!(WasmEditorApi)), + }, + ], outputs: vec![], properties: node_properties::output_properties, ..Default::default() @@ -866,11 +886,9 @@ fn static_nodes() -> Vec { name: "Memoize", category: "Structural", identifier: NodeImplementation::proto("graphene_core::memo::MemoNode<_, _>"), - inputs: vec![ - DocumentInputType::value("ShortCircut", TaggedValue::None, false), - DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), - ], + inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + manual_composition: Some(concrete!(())), ..Default::default() }, DocumentNodeType { @@ -882,14 +900,6 @@ fn static_nodes() -> Vec { properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"), ..Default::default() }, - DocumentNodeType { - name: "Ref", - category: "Structural", - identifier: NodeImplementation::proto("graphene_core::memo::MemoNode<_, _>"), - inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)], - outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - ..Default::default() - }, #[cfg(feature = "gpu")] DocumentNodeType { name: "Uniform", @@ -912,7 +922,8 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(1, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, @@ -963,7 +974,8 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(1, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, @@ -1014,7 +1026,8 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(1, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, @@ -1076,7 +1089,8 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(1, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, @@ -1172,7 +1186,8 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(1, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, @@ -1223,7 +1238,8 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(1, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, @@ -1268,7 +1284,8 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(0, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, @@ -1366,7 +1383,8 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "Cache".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::node(1, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, @@ -2065,6 +2083,24 @@ fn static_nodes() -> Vec { outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], ..Default::default() }, + DocumentNodeType { + name: "Downres", + category: "Structural", + identifier: NodeImplementation::proto("graphene_std::raster::DownresNode<_>"), + manual_composition: Some(concrete!(Footprint)), + inputs: vec![DocumentInputType::value("Raseter Data", TaggedValue::ImageFrame(ImageFrame::empty()), true)], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Raster)], + ..Default::default() + }, + DocumentNodeType { + name: "Cull", + category: "Vector", + identifier: NodeImplementation::proto("graphene_core::transform::CullNode<_>"), + manual_composition: Some(concrete!(Footprint)), + inputs: vec![DocumentInputType::value("Vector Data", TaggedValue::VectorData(VectorData::empty()), true)], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + ..Default::default() + }, DocumentNodeType { name: "Text", category: "Vector", @@ -2082,9 +2118,10 @@ fn static_nodes() -> Vec { DocumentNodeType { name: "Transform", category: "Transform", - identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _, _>"), + identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _, _, _>"), + manual_composition: Some(concrete!(Footprint)), inputs: vec![ - DocumentInputType::value("Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), + DocumentInputType::value("Vector Data", TaggedValue::VectorData(VectorData::empty()), true), DocumentInputType::value("Translation", TaggedValue::DVec2(DVec2::ZERO), false), DocumentInputType::value("Rotation", TaggedValue::F32(0.), false), DocumentInputType::value("Scale", TaggedValue::DVec2(DVec2::ONE), false), @@ -2387,6 +2424,7 @@ impl DocumentNodeType { inputs, implementation: self.generate_implementation(), metadata, + manual_composition: self.manual_composition.clone(), ..Default::default() } } @@ -2421,7 +2459,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork { if input_type.is_none() { input_type = Some(input.clone()); } - assert_eq!(input, input_type.as_ref().unwrap(), "Networks wrapped in scope must have the same input type"); + assert_eq!(input, input_type.as_ref().unwrap(), "Networks wrapped in scope must have the same input type {network:#?}"); network_inputs.push(*id); } } @@ -2431,6 +2469,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork { // if the network has no inputs, it doesn't need to be wrapped in a scope if len == 0 { + log::warn!("Network has no inputs, not wrapping in scope"); return network; } @@ -2469,19 +2508,18 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw resolve_document_node_type("Input Frame") .expect("Input Frame node does not exist") .to_document_node_default_inputs([], DocumentNodeMetadata::position((8, 4))), - false, ); network.push_node( resolve_document_node_type("Output") .expect("Output node does not exist") .to_document_node([NodeInput::node(output_node_id, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))), - false, ); network } pub fn new_vector_network(subpaths: Vec>) -> NodeNetwork { let path_generator = resolve_document_node_type("Shape").expect("Shape node does not exist"); + let cull_node = resolve_document_node_type("Cull").expect("Cull node does not exist"); let transform = resolve_document_node_type("Transform").expect("Transform node does not exist"); let fill = resolve_document_node_type("Fill").expect("Fill node does not exist"); let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist"); @@ -2489,14 +2527,31 @@ pub fn new_vector_network(subpaths: Vec) -> NodeNetwork { + let sample_node = resolve_document_node_type("Downres").expect("Downres node does not exist"); + let transform = resolve_document_node_type("Transform").expect("Transform node does not exist"); + let output = resolve_document_node_type("Output").expect("Output node does not exist"); + + let mut network = NodeNetwork::default(); + + let image_node_type = resolve_document_node_type("Image").expect("Image node should be in registry"); + + network.push_node(image_node_type.to_document_node( + [graph_craft::document::NodeInput::value(graph_craft::document::value::TaggedValue::ImageFrame(image_frame), false)], + Default::default(), + )); + network.push_node(sample_node.to_document_node_default_inputs([None], Default::default())); + network.push_node(transform.to_document_node_default_inputs([None], Default::default())); + network.push_node(output.to_document_node_default_inputs([None], Default::default())); network } @@ -2511,21 +2566,18 @@ pub fn new_text_network(text: String, font: Font, size: f64) -> NodeNetwork { inputs: vec![0], ..Default::default() }; - network.push_node( - text_generator.to_document_node( - [ - NodeInput::Network(concrete!(WasmEditorApi)), - NodeInput::value(TaggedValue::String(text), false), - NodeInput::value(TaggedValue::Font(font), false), - NodeInput::value(TaggedValue::F64(size), false), - ], - DocumentNodeMetadata::position((0, 4)), - ), - false, - ); - network.push_node(transform.to_document_node_default_inputs([None], Default::default()), true); - network.push_node(fill.to_document_node_default_inputs([None], Default::default()), true); - network.push_node(stroke.to_document_node_default_inputs([None], Default::default()), true); - network.push_node(output.to_document_node_default_inputs([None], Default::default()), true); + network.push_node(text_generator.to_document_node( + [ + NodeInput::Network(concrete!(WasmEditorApi)), + NodeInput::value(TaggedValue::String(text), false), + NodeInput::value(TaggedValue::Font(font), false), + NodeInput::value(TaggedValue::F64(size), false), + ], + DocumentNodeMetadata::position((0, 4)), + )); + network.push_node(transform.to_document_node_default_inputs([None], Default::default())); + network.push_node(fill.to_document_node_default_inputs([None], Default::default())); + network.push_node(stroke.to_document_node_default_inputs([None], Default::default())); + network.push_node(output.to_document_node_default_inputs([None], Default::default())); network } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index 74cb2ff4..a7a41f76 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -13,7 +13,6 @@ use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingM use graphene_core::raster::{BlendMode, Color, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice}; use graphene_core::text::Font; use graphene_core::vector::style::{FillType, GradientType, LineCap, LineJoin}; -use graphene_core::{Cow, Type, TypeDescriptor}; use glam::{DVec2, IVec2}; @@ -1222,7 +1221,10 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont }; let scale = vec2_widget(document_node, node_id, 3, "Scale", "W", "H", "x", add_blank_assist); - vec![translation, rotation, scale] + + let vector_data = start_widgets(document_node, node_id, 0, "Data", FrontendGraphDataType::Vector, false); + let vector_data = LayoutGroup::Row { widgets: vector_data }; + vec![vector_data, translation, rotation, scale] } pub fn node_section_font(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 503e08dc..3f3c626b 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -13,12 +13,12 @@ use graph_craft::document::value::TaggedValue; use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork}; use graph_craft::graphene_compiler::Compiler; use graph_craft::imaginate_input::ImaginatePreferences; -use graph_craft::{concrete, Type, TypeDescriptor}; -use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender}; +use graph_craft::{concrete, Type}; +use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_core::raster::{Image, ImageFrame}; use graphene_core::renderer::{ClickTarget, SvgSegment, SvgSegmentList}; use graphene_core::text::FontCache; -use graphene_core::transform::Transform; +use graphene_core::transform::{Footprint, Transform}; use graphene_core::vector::style::ViewMode; use graphene_core::{Color, SurfaceFrame, SurfaceId}; @@ -26,7 +26,6 @@ use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use interpreted_executor::dynamic_executor::DynamicExecutor; use glam::{DAffine2, DVec2}; -use std::borrow::Cow; use std::cell::RefCell; use std::rc::Rc; use std::sync::mpsc::{Receiver, Sender}; @@ -72,6 +71,7 @@ pub(crate) struct GenerationRequest { graph: NodeNetwork, path: Vec, image_frame: Option>, + transform: DAffine2, } pub(crate) struct GenerationResponse { @@ -140,6 +140,7 @@ impl NodeRuntime { generation_id, graph, image_frame, + transform, path, .. }) => { @@ -151,7 +152,7 @@ impl NodeRuntime { .map(|node| node.path.clone().unwrap_or_default()) .collect(); - let result = self.execute_network(&path, network, image_frame).await; + let result = self.execute_network(&path, network, image_frame, transform).await; let mut responses = VecDeque::new(); self.update_thumbnails(&path, monitor_nodes, &mut responses); let response = GenerationResponse { @@ -168,7 +169,7 @@ impl NodeRuntime { } } - async fn execute_network<'a>(&'a mut self, path: &[LayerId], scoped_network: NodeNetwork, image_frame: Option>) -> Result { + async fn execute_network<'a>(&'a mut self, path: &[LayerId], scoped_network: NodeNetwork, image_frame: Option>, transform: DAffine2) -> Result { if self.wasm_io.is_none() { self.wasm_io = Some(WasmApplicationIo::new().await); } @@ -179,6 +180,10 @@ impl NodeRuntime { application_io: self.wasm_io.as_ref().unwrap(), node_graph_message_sender: &self.sender, imaginate_preferences: &self.imaginate_preferences, + render_config: RenderConfig { + viewport: Footprint { transform, ..Default::default() }, + ..Default::default() + }, }; // We assume only one output @@ -197,7 +202,8 @@ impl NodeRuntime { let result = match self.executor.input_type() { Some(t) if t == concrete!(WasmEditorApi) => (&self.executor).execute(editor_api).await.map_err(|e| e.to_string()), Some(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()), - _ => Err("Invalid input type".to_string()), + Some(t) => Err(format!("Invalid input type {:?}", t)), + _ => Err("No input type".to_string()), }?; if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform: _ }) = result { @@ -231,7 +237,7 @@ impl NodeRuntime { }; use graphene_core::renderer::*; let bounds = graphic_element_data.bounding_box(DAffine2::IDENTITY); - let render_params = RenderParams::new(ViewMode::Normal, bounds, true); + let render_params = RenderParams::new(ViewMode::Normal, ImageRenderMode::BlobUrl, bounds, true); let mut render = SvgRender::new(); graphic_element_data.render_svg(&mut render, &render_params); let [min, max] = bounds.unwrap_or_default(); @@ -339,13 +345,14 @@ impl Default for NodeGraphExecutor { impl NodeGraphExecutor { /// Execute the network by flattening it and creating a borrow stack. - fn queue_execution(&self, network: NodeNetwork, image_frame: Option>, layer_path: Vec) -> u64 { + fn queue_execution(&self, network: NodeNetwork, image_frame: Option>, layer_path: Vec, transform: DAffine2) -> u64 { let generation_id = generate_uuid(); let request = GenerationRequest { path: layer_path, graph: network, image_frame, generation_id, + transform, }; self.sender.send(NodeRuntimeMessage::GenerationRequest(request)).expect("Failed to send generation request"); @@ -452,9 +459,10 @@ impl NodeGraphExecutor { // Construct the input image frame let transform = DAffine2::IDENTITY; let image_frame = ImageFrame { image, transform }; + let document_transform = document.document_legacy.metadata.document_to_viewport; // Execute the node graph - let generation_id = self.queue_execution(network, Some(image_frame), layer_path.clone()); + let generation_id = self.queue_execution(network, Some(image_frame), layer_path.clone(), document_transform); self.futures.insert(generation_id, ExecutionContext { layer_path, document_id }); @@ -533,12 +541,21 @@ impl NodeGraphExecutor { warn!("Rendered graph produced artboard (which is not currently rendered): {artboard:#?}"); return Err("Artboard (see console)".to_string()); } + TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => { + // Send to frontend + log::debug!("svg: {svg}"); + responses.add(FrontendMessage::UpdateDocumentNodeRender { svg }); + responses.add(DocumentMessage::RenderScrollbars); + //responses.add(FrontendMessage::UpdateDocumentNodeRender { svg }); + + //return Err("Graphic group (see console)".to_string()); + } TaggedValue::GraphicGroup(graphic_group) => { use graphene_core::renderer::{GraphicElementRendered, RenderParams, SvgRender}; // Setup rendering let mut render = SvgRender::new(); - let render_params = RenderParams::new(ViewMode::Normal, None, false); + let render_params = RenderParams::new(ViewMode::Normal, graphene_core::renderer::ImageRenderMode::BlobUrl, None, false); // Render svg graphic_group.render_svg(&mut render, &render_params); diff --git a/node-graph/compilation-client/src/main.rs b/node-graph/compilation-client/src/main.rs index f8479c18..56642a9c 100644 --- a/node-graph/compilation-client/src/main.rs +++ b/node-graph/compilation-client/src/main.rs @@ -3,11 +3,9 @@ use gpu_executor::{ShaderIO, ShaderInput}; use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::*; -use graph_craft::*; use graphene_core::raster::adjustments::BlendMode; use graphene_core::Color; -use std::borrow::Cow; use std::time::Duration; fn main() { diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index f08c8eae..a44bc56d 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -9,10 +9,25 @@ license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -std = ["dyn-any", "dyn-any/std", "alloc", "glam/std", "specta", "num-traits/std", "rustybuzz"] +std = [ + "dyn-any", + "dyn-any/std", + "alloc", + "glam/std", + "specta", + "num-traits/std", + "rustybuzz", + "image", +] default = ["async", "serde", "kurbo", "log", "std", "rand_chacha", "wasm"] log = ["dep:log"] -serde = ["dep:serde", "glam/serde", "bezier-rs/serde", "bezier-rs/serde", "base64"] +serde = [ + "dep:serde", + "glam/serde", + "bezier-rs/serde", + "bezier-rs/serde", + "base64", +] gpu = ["spirv-std", "glam/bytemuck", "dyn-any", "glam/libm"] async = ["async-trait", "alloc"] nightly = [] @@ -46,6 +61,9 @@ glam = { version = "0.24", default-features = false, features = [ ] } node-macro = { path = "../node-macro" } base64 = { version = "0.21", optional = true } +image = { version = "0.24", optional = true, default-features = false, features = [ + "png", +] } specta.workspace = true specta.optional = true diff --git a/node-graph/gcore/src/application_io.rs b/node-graph/gcore/src/application_io.rs index 5616bf46..a4c04c44 100644 --- a/node-graph/gcore/src/application_io.rs +++ b/node-graph/gcore/src/application_io.rs @@ -1,6 +1,6 @@ use crate::raster::ImageFrame; use crate::text::FontCache; -use crate::transform::{Transform, TransformMut}; +use crate::transform::{Footprint, Transform, TransformMut}; use crate::{Color, Node}; use dyn_any::{StaticType, StaticTypeSized}; @@ -43,7 +43,7 @@ impl From> for SurfaceFrame { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct SurfaceHandle { pub surface_id: SurfaceId, pub surface: Surface, @@ -53,7 +53,7 @@ unsafe impl StaticType for SurfaceHandle { type Static = SurfaceHandle; } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct SurfaceHandleFrame { pub surface_handle: Arc>, pub transform: DAffine2, @@ -136,12 +136,30 @@ pub trait GetImaginatePreferences { fn get_host_name(&self) -> &str; } +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ExportFormat { + #[default] + Svg, + Png { + transparent: bool, + }, + Jpeg, + Canvas, +} + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct RenderConfig { + pub viewport: Footprint, + pub export_format: ExportFormat, +} + pub struct EditorApi<'a, Io> { pub image_frame: Option>, pub font_cache: &'a FontCache, pub application_io: &'a Io, pub node_graph_message_sender: &'a dyn NodeGraphUpdateSender, pub imaginate_preferences: &'a dyn GetImaginatePreferences, + pub render_config: RenderConfig, } impl<'a, Io> Clone for EditorApi<'a, Io> { @@ -152,6 +170,7 @@ impl<'a, Io> Clone for EditorApi<'a, Io> { application_io: self.application_io, node_graph_message_sender: self.node_graph_message_sender, imaginate_preferences: self.imaginate_preferences, + render_config: self.render_config, } } } diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index fa271c6b..4200397e 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -5,6 +5,7 @@ use crate::{Color, Node}; use dyn_any::{DynAny, StaticType}; use node_macro::node_fn; +use core::future::Future; use core::ops::{Deref, DerefMut}; use glam::IVec2; @@ -40,6 +41,20 @@ pub struct GraphicElement { pub graphic_element_data: GraphicElementData, } +impl Default for GraphicElement { + fn default() -> Self { + Self { + name: "".to_owned(), + blend_mode: BlendMode::Normal, + opacity: 1., + visible: true, + locked: false, + collapsed: false, + graphic_element_data: GraphicElementData::VectorShape(Box::new(VectorData::empty())), + } + } +} + /// 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)] @@ -64,7 +79,8 @@ impl Artboard { } } -pub struct ConstructLayerNode { +pub struct ConstructLayerNode { + graphic_element_data: GraphicElementData, name: Name, blend_mode: BlendMode, opacity: Opacity, @@ -75,16 +91,19 @@ pub struct ConstructLayerNode>( - graphic_element_data: Data, +async fn construct_layer, Fut1: Future, Fut2: Future>( + footprint: crate::transform::Footprint, + graphic_element_data: impl Node, name: String, blend_mode: BlendMode, opacity: f32, visible: bool, locked: bool, collapsed: bool, - mut stack: GraphicGroup, + mut stack: impl Node, ) -> GraphicGroup { + let graphic_element_data = self.graphic_element_data.eval(footprint).await; + let mut stack = self.stack.eval(footprint).await; stack.push(GraphicElement { name, blend_mode, @@ -137,7 +156,6 @@ impl From for GraphicElementData { GraphicElementData::GraphicGroup(graphic_group) } } - impl From for GraphicElementData { fn from(artboard: Artboard) -> Self { GraphicElementData::Artboard(artboard) @@ -156,6 +174,28 @@ impl DerefMut for GraphicGroup { } } +/// This is a helper trait used for the Into Implementation. +/// We can't just implement this for all for which from is implemented +/// as that would conflict with the implementation for `Self` +trait ToGraphicElement: Into {} + +impl ToGraphicElement for VectorData {} +impl ToGraphicElement for ImageFrame {} +impl ToGraphicElement for Artboard {} + +impl From for GraphicGroup +where + T: ToGraphicElement, +{ + fn from(value: T) -> Self { + let element = GraphicElement { + graphic_element_data: value.into(), + ..Default::default() + }; + Self(vec![element]) + } +} + impl GraphicGroup { pub const EMPTY: Self = Self(Vec::new()); } diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index 18e19a03..fe69c268 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -1,7 +1,9 @@ use crate::raster::{Image, ImageFrame}; use crate::uuid::{generate_uuid, ManipulatorGroupId}; use crate::{vector::VectorData, Artboard, Color, GraphicElementData, GraphicGroup}; +use base64::Engine; use bezier_rs::Subpath; +use image::ImageEncoder; pub use quad::Quad; use glam::{DAffine2, DVec2}; @@ -124,16 +126,28 @@ impl Default for SvgRender { } } +pub enum ImageRenderMode { + BlobUrl, + Canvas, + Base64, +} + /// Static state used whilst rendering pub struct RenderParams { pub view_mode: crate::vector::style::ViewMode, + pub image_render_mode: ImageRenderMode, pub culling_bounds: Option<[DVec2; 2]>, pub thumbnail: bool, } impl RenderParams { - pub fn new(view_mode: crate::vector::style::ViewMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool) -> Self { - Self { view_mode, culling_bounds, thumbnail } + pub fn new(view_mode: crate::vector::style::ViewMode, image_render_mode: ImageRenderMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool) -> Self { + Self { + view_mode, + image_render_mode, + culling_bounds, + thumbnail, + } } } @@ -263,17 +277,48 @@ impl GraphicElementRendered for Artboard { } impl GraphicElementRendered for ImageFrame { - fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) { + fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { let transform: String = format_transform_matrix(self.transform * render.transform); let uuid = generate_uuid(); - render.leaf_tag("image", |attributes| { - attributes.push("width", 1.to_string()); - attributes.push("height", 1.to_string()); - attributes.push("preserveAspectRatio", "none"); - attributes.push("transform", transform); - attributes.push("href", SvgSegment::BlobUrl(uuid)) - }); - render.image_data.push((uuid, self.image.clone())) + + match render_params.image_render_mode { + ImageRenderMode::BlobUrl => { + render.leaf_tag("image", move |attributes| { + attributes.push("width", 1.to_string()); + attributes.push("height", 1.to_string()); + attributes.push("preserveAspectRatio", "none"); + attributes.push("transform", transform); + attributes.push("href", SvgSegment::BlobUrl(uuid)) + }); + render.image_data.push((uuid, self.image.clone())) + } + ImageRenderMode::Base64 => { + let image = &self.image; + let (flat_data, _, _) = image.clone().into_flat_u8(); + let mut output = Vec::new(); + let encoder = image::codecs::png::PngEncoder::new(&mut output); + encoder + .write_image(&flat_data, image.width, image.height, image::ColorType::Rgba8) + .expect("failed to encode image as png"); + let preamble = "data:image/png;base64,"; + let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4); + base64_string.push_str(preamble); + log::debug!("len: {}", image.data.len()); + base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string); + + render.leaf_tag("image", |attributes| { + attributes.push("width", image.width.to_string()); + + attributes.push("height", image.height.to_string()); + attributes.push("preserveAspectRatio", "none"); + attributes.push("transform", transform); + attributes.push("href", base64_string) + }); + } + ImageRenderMode::Canvas => { + todo!("Canvas rendering is not yet implemented") + } + } } fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { let transform = self.transform * transform; diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index cc57f4e0..cd4f42a0 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -43,6 +43,7 @@ pub mod quantization; use core::any::TypeId; pub use raster::Color; +pub use types::Cow; // pub trait Node: for<'n> NodeIO<'n> { pub trait Node<'i, Input: 'i>: 'i { diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 128e026f..955f6d66 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -616,7 +616,7 @@ fn dimensions_node<_P>(input: ImageSlice<'input, _P>) -> (u32, u32) { } #[cfg(feature = "alloc")] -pub use image::{CollectNode, Image, ImageFrame, ImageRefNode, MapImageSliceNode}; +pub use self::image::{CollectNode, Image, ImageFrame, ImageRefNode, MapImageSliceNode}; #[cfg(feature = "alloc")] pub(crate) mod image; diff --git a/node-graph/gcore/src/raster/bbox.rs b/node-graph/gcore/src/raster/bbox.rs index c67c119a..04a9d8de 100644 --- a/node-graph/gcore/src/raster/bbox.rs +++ b/node-graph/gcore/src/raster/bbox.rs @@ -10,6 +10,7 @@ pub struct AxisAlignedBbox { impl AxisAlignedBbox { pub const ZERO: Self = Self { start: DVec2::ZERO, end: DVec2::ZERO }; + pub const ONE: Self = Self { start: DVec2::ZERO, end: DVec2::ONE }; pub fn size(&self) -> DVec2 { self.end - self.start @@ -44,6 +45,13 @@ impl AxisAlignedBbox { }), } } + + pub fn intersect(&self, other: &AxisAlignedBbox) -> AxisAlignedBbox { + AxisAlignedBbox { + start: DVec2::new(self.start.x.max(other.start.x), self.start.y.max(other.start.y)), + end: DVec2::new(self.end.x.min(other.end.x), self.end.y.min(other.end.y)), + } + } } #[cfg_attr(not(target_arch = "spirv"), derive(Debug))] diff --git a/node-graph/gcore/src/raster/image.rs b/node-graph/gcore/src/raster/image.rs index 7cd9a9db..ad6bf458 100644 --- a/node-graph/gcore/src/raster/image.rs +++ b/node-graph/gcore/src/raster/image.rs @@ -145,7 +145,7 @@ where /// Flattens each channel cast to a u8 pub fn into_flat_u8(self) -> (Vec, u32, u32) { let Image { width, height, data } = self; - assert!(data.len() == width as usize * height as usize); + assert_eq!(data.len(), width as usize * height as usize); // Cache the last sRGB value we computed, speeds up fills. let mut last_r = 0.; diff --git a/node-graph/gcore/src/raster/image/base64_serde.rs b/node-graph/gcore/src/raster/image/base64_serde.rs new file mode 100644 index 00000000..e69de29b diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 9e7ced23..1cc8ad59 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -1,7 +1,11 @@ +use core::future::Future; + +use dyn_any::StaticType; use glam::DAffine2; use glam::DVec2; +use crate::raster::bbox::AxisAlignedBbox; use crate::raster::ImageFrame; use crate::raster::Pixel; use crate::vector::VectorData; @@ -116,7 +120,8 @@ impl TransformMut for DAffine2 { } #[derive(Debug, Clone, Copy)] -pub struct TransformNode { +pub struct TransformNode { + pub(crate) transform_target: TransformTarget, pub(crate) translate: Translation, pub(crate) rotate: Rotation, pub(crate) scale: Scale, @@ -124,11 +129,107 @@ pub struct TransformNode { pub(crate) pivot: Pivot, } -#[node_macro::node_fn(TransformNode)] -pub(crate) fn transform_vector_data(mut data: Data, translate: DVec2, rotate: f32, scale: DVec2, shear: DVec2, pivot: DVec2) -> Data { - let pivot = DAffine2::from_translation(data.local_pivot(pivot)); +#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum RenderQuality { + /// Low quality, fast rendering + Preview, + /// Ensure that the render is available with at least the specified quality + /// A value of 0.5 means that the render is available with at least 50% of the final image resolution + Scale(f32), + /// Flip a coin to decide if the render should be available with the current quality or done at full quality + /// This should be used to gradually update the render quality of a cached node + Probabilty(f32), + /// Render at full quality + Full, +} +#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Footprint { + /// Inverse of the transform which will be applied to the node output during the rendering process + pub transform: DAffine2, + /// Resolution of the target output area in pixels + pub resolution: glam::UVec2, + /// Quality of the render, this may be used by caching nodes to decide if the cached render is sufficient + pub quality: RenderQuality, + /// When the transform is set downstream, all upsream modifications have to be ignored + pub ignore_modifications: bool, +} - let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate as f64, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse(); +impl Default for Footprint { + fn default() -> Self { + Self { + transform: DAffine2::IDENTITY, + resolution: glam::UVec2::new(1920, 1080), + quality: RenderQuality::Full, + ignore_modifications: false, + } + } +} + +impl Footprint { + pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox { + let inverse = self.transform.inverse(); + let start = inverse.transform_point2((0., 0.).into()); + let end = inverse.transform_point2(self.resolution.as_dvec2()); + AxisAlignedBbox { start, end } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct CullNode { + pub(crate) vector_data: VectorData, +} + +#[node_macro::node_fn(CullNode)] +fn cull_vector_data(footprint: Footprint, vector_data: T) -> T { + // TODO: Implement culling + vector_data +} + +impl core::hash::Hash for Footprint { + fn hash(&self, state: &mut H) { + self.transform.to_cols_array().iter().for_each(|x| x.to_le_bytes().hash(state)); + self.resolution.hash(state) + } +} + +impl Transform for Footprint { + fn transform(&self) -> DAffine2 { + self.transform + } +} +impl TransformMut for Footprint { + fn transform_mut(&mut self) -> &mut DAffine2 { + &mut self.transform + } +} + +#[node_macro::node_fn(TransformNode)] +pub(crate) async fn transform_vector_data( + mut footprint: Footprint, + transform_target: impl Node, + translate: DVec2, + rotate: f32, + scale: DVec2, + shear: DVec2, + pivot: DVec2, +) -> Fut::Output +where + Fut::Output: TransformMut, +{ + // TOOD: This is hack and might break for Vector data because the pivot may be incorrect + let transform = DAffine2::from_scale_angle_translation(scale, rotate as f64, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]); + if !footprint.ignore_modifications { + let pivot_transform = DAffine2::from_translation(pivot); + let modification = pivot_transform * transform * pivot_transform.inverse(); + *footprint.transform_mut() = footprint.transform() * modification; + } + + let mut data = self.transform_target.eval(footprint).await; + let pivot_transform = DAffine2::from_translation(data.local_pivot(pivot)); + + let modification = pivot_transform * transform * pivot_transform.inverse(); let data_transform = data.transform_mut(); *data_transform = modification * (*data_transform); diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index 2bcbc852..b069101f 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -26,9 +26,9 @@ impl NodeIOTypes { #[macro_export] macro_rules! concrete { ($type:ty) => { - Type::Concrete(TypeDescriptor { + $crate::Type::Concrete($crate::TypeDescriptor { id: Some(core::any::TypeId::of::<$type>()), - name: Cow::Borrowed(core::any::type_name::<$type>()), + name: $crate::Cow::Borrowed(core::any::type_name::<$type>()), size: core::mem::size_of::<$type>(), align: core::mem::align_of::<$type>(), }) @@ -38,9 +38,9 @@ macro_rules! concrete { #[macro_export] macro_rules! concrete_with_name { ($type:ty, $name:expr) => { - Type::Concrete(TypeDescriptor { + $crate::Type::Concrete($crate::TypeDescriptor { id: Some(core::any::TypeId::of::<$type>()), - name: Cow::Borrowed($name), + name: $crate::Cow::Borrowed($name), size: core::mem::size_of::<$type>(), align: core::mem::align_of::<$type>(), }) @@ -50,17 +50,17 @@ macro_rules! concrete_with_name { #[macro_export] macro_rules! generic { ($type:ty) => {{ - Type::Generic(Cow::Borrowed(stringify!($type))) + $crate::Type::Generic($crate::Cow::Borrowed(stringify!($type))) }}; } #[macro_export] macro_rules! fn_type { ($type:ty) => { - Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type))) + $crate::Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type))) }; ($in_type:ty, $type:ty) => { - Type::Fn(Box::new(concrete!(($in_type))), Box::new(concrete!($type))) + $crate::Type::Fn(Box::new(concrete!(($in_type))), Box::new(concrete!($type))) }; } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 7a3ccfaa..5fd1ece6 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -5,8 +5,6 @@ use graphene_core::{NodeIdentifier, Type}; use dyn_any::{DynAny, StaticType}; use glam::IVec2; pub use graphene_core::uuid::generate_uuid; -use graphene_core::TypeDescriptor; -use std::borrow::Cow; use std::collections::{HashMap, HashSet}; pub mod value; @@ -38,6 +36,7 @@ impl DocumentNodeMetadata { pub struct DocumentNode { pub name: String, pub inputs: Vec, + pub manual_composition: Option, pub implementation: DocumentNodeImplementation, pub metadata: DocumentNodeMetadata, #[serde(default)] @@ -51,7 +50,7 @@ impl DocumentNode { .inputs .iter() .enumerate() - .filter(|(_, input)| matches!(input, NodeInput::Network(_) | NodeInput::ShortCircut(_))) + .filter(|(_, input)| matches!(input, NodeInput::Network(_))) .nth(offset) .unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}")); @@ -59,10 +58,16 @@ impl DocumentNode { } fn resolve_proto_node(mut self) -> ProtoNode { - assert_ne!(self.inputs.len(), 0, "Resolving document node {:#?} with no inputs", self); - let first = self.inputs.remove(0); - if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation { - let (input, mut args) = match first { + assert!(!self.inputs.is_empty() || self.manual_composition.is_some(), "Resolving document node {:#?} with no inputs", self); + let DocumentNodeImplementation::Unresolved(fqn) = self.implementation + else { + unreachable!("tried to resolve not flattened node on resolved node {:?}", self); + }; + let (input, mut args) = if let Some(ty) = self.manual_composition { + (ProtoNodeInput::ShortCircut(ty), ConstructionArgs::Nodes(vec![])) + } else { + let first = self.inputs.remove(0); + match first { NodeInput::Value { tagged_value, .. } => { assert_eq!(self.inputs.len(), 0, "{}, {:?}", &self.name, &self.inputs); (ProtoNodeInput::None, ConstructionArgs::Value(tagged_value)) @@ -72,37 +77,33 @@ impl DocumentNode { (ProtoNodeInput::Node(node_id, lambda), ConstructionArgs::Nodes(vec![])) } NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])), - NodeInput::ShortCircut(ty) => (ProtoNodeInput::ShortCircut(ty), ConstructionArgs::Nodes(vec![])), NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)), - }; - assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter"); - assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::ShortCircut(_))), "recieved non resolved parameter"); - assert!( - !self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })), - "recieved value as parameter. inupts: {:#?}, construction_args: {:#?}", - &self.inputs, - &args - ); + } + }; + assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter"); + assert!( + !self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })), + "recieved value as parameter. inupts: {:#?}, construction_args: {:#?}", + &self.inputs, + &args + ); - // If we have one parameter of the type inline, set it as the construction args - if let &[NodeInput::Inline(ref inline)] = &self.inputs[..] { - args = ConstructionArgs::Inline(inline.clone()); - } - if let ConstructionArgs::Nodes(nodes) = &mut args { - nodes.extend(self.inputs.iter().map(|input| match input { - NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda), - _ => unreachable!(), - })); - } - ProtoNode { - identifier: fqn, - input, - construction_args: args, - document_node_path: self.path.unwrap_or(Vec::new()), - skip_deduplication: self.skip_deduplication, - } - } else { - unreachable!("tried to resolve not flattened node on resolved node {:?}", self); + // If we have one parameter of the type inline, set it as the construction args + if let &[NodeInput::Inline(ref inline)] = &self.inputs[..] { + args = ConstructionArgs::Inline(inline.clone()); + } + if let ConstructionArgs::Nodes(nodes) = &mut args { + nodes.extend(self.inputs.iter().map(|input| match input { + NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda), + _ => unreachable!(), + })); + } + ProtoNode { + identifier: fqn, + input, + construction_args: args, + document_node_path: self.path.unwrap_or(Vec::new()), + skip_deduplication: self.skip_deduplication, } } @@ -185,7 +186,7 @@ pub enum NodeInput { /// A short circuting input represents an input that is not resolved through function composition /// but actually consuming the provided input instead of passing it to its predecessor. /// See [NodeInput] docs for more explanation. - ShortCircut(Type), + // TODO: Update Inline(InlineRust), } @@ -226,7 +227,6 @@ impl NodeInput { NodeInput::Node { .. } => true, NodeInput::Value { exposed, .. } => *exposed, NodeInput::Network(_) => false, - NodeInput::ShortCircut(_) => false, NodeInput::Inline(_) => false, } } @@ -235,7 +235,6 @@ impl NodeInput { NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"), NodeInput::Value { tagged_value, .. } => tagged_value.ty(), NodeInput::Network(ty) => ty.clone(), - NodeInput::ShortCircut(ty) => ty.clone(), NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"), } } @@ -350,7 +349,7 @@ impl NodeNetwork { 0, DocumentNode { name: "Input Frame".into(), - inputs: vec![NodeInput::ShortCircut(concrete!(u32))], + manual_composition: Some(concrete!(u32)), implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()), metadata: DocumentNodeMetadata { position: (8, 4).into() }, ..Default::default() @@ -363,7 +362,7 @@ impl NodeNetwork { } /// Appends a new node to the network after the output node and sets it as the new output - pub fn push_node(&mut self, mut node: DocumentNode, connect_to_previous: bool) -> NodeId { + pub fn push_node(&mut self, mut node: DocumentNode) -> NodeId { let id = self.nodes.len().try_into().expect("Too many nodes in network"); // Set the correct position for the new node if node.metadata.position == IVec2::default() { @@ -371,7 +370,7 @@ impl NodeNetwork { node.metadata.position = pos + IVec2::new(8, 0); } } - if connect_to_previous && !self.outputs.is_empty() { + if !self.outputs.is_empty() { let input = NodeInput::node(self.outputs[0].node_id, self.outputs[0].node_output_index); if node.inputs.is_empty() { node.inputs.push(input); @@ -392,7 +391,7 @@ impl NodeNetwork { implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()), ..Default::default() }; - self.push_node(node, true) + self.push_node(node) } /// Adds a Cache and a Clone node to the network @@ -408,7 +407,8 @@ impl NodeNetwork { 0, DocumentNode { name: "MemoNode".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::Network(ty)], + manual_composition: Some(concrete!(())), + inputs: vec![NodeInput::Network(ty)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode")), ..Default::default() }, @@ -430,7 +430,7 @@ impl NodeNetwork { metadata: DocumentNodeMetadata { position: (0, 0).into() }, ..Default::default() }; - self.push_node(node, true) + self.push_node(node) } /// Get the nested network given by the path of node ids @@ -543,6 +543,14 @@ impl NodeNetwork { } true } + + pub fn output_as_input(&self, arg: usize) -> NodeInput { + NodeInput::Node { + node_id: self.outputs[arg].node_id, + output_index: self.outputs[arg].node_output_index, + lambda: false, + } + } } struct FlowIter<'a> { @@ -693,9 +701,8 @@ impl NodeNetwork { break; } - let mut dummy_input = NodeInput::ShortCircut(concrete!(())); - std::mem::swap(&mut dummy_input, input); - if let NodeInput::Value { tagged_value, exposed } = dummy_input { + let previous_input = std::mem::replace(input, NodeInput::Network(concrete!(()))); + if let NodeInput::Value { tagged_value, exposed } = previous_input { let value_node_id = gen_id(); let merged_node_id = map_ids(id, value_node_id); let path = if let Some(mut new_path) = node.path.clone() { @@ -721,7 +728,7 @@ impl NodeNetwork { lambda: false, }; } else { - *input = dummy_input; + *input = previous_input; } } @@ -744,6 +751,11 @@ impl NodeNetwork { node.inputs, inner_network.inputs ); + assert_eq!( + node.inputs.len(), + inner_network.inputs.len(), + "Document Nodes with a Network implementation should have the same number of inner network inputs as inputs declared on the Document Node" + ); // Match the document node input and the inputs of the inner network for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) { // Keep track of how many network inputs we have already connected for each node @@ -760,7 +772,6 @@ impl NodeNetwork { self.inputs[index] = *network_input; } } - NodeInput::ShortCircut(_) => (), NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"), NodeInput::Inline(_) => (), } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 0bf632b4..5de06834 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -65,6 +65,8 @@ pub enum TaggedValue { Curve(graphene_core::raster::curve::Curve), IVec2(glam::IVec2), SurfaceFrame(graphene_core::SurfaceFrame), + Footprint(graphene_core::transform::Footprint), + RenderOutput(RenderOutput), } #[allow(clippy::derived_hash_with_manual_eq)] @@ -134,6 +136,8 @@ impl Hash for TaggedValue { Self::Curve(curve) => curve.hash(state), Self::IVec2(v) => v.hash(state), Self::SurfaceFrame(surface_id) => surface_id.hash(state), + Self::Footprint(footprint) => footprint.hash(state), + Self::RenderOutput(render_output) => render_output.hash(state), } } } @@ -190,6 +194,8 @@ impl<'a> TaggedValue { TaggedValue::Curve(x) => Box::new(x), TaggedValue::IVec2(x) => Box::new(x), TaggedValue::SurfaceFrame(x) => Box::new(x), + TaggedValue::Footprint(x) => Box::new(x), + TaggedValue::RenderOutput(x) => Box::new(x), } } @@ -208,8 +214,6 @@ impl<'a> TaggedValue { } pub fn ty(&self) -> Type { - use graphene_core::TypeDescriptor; - use std::borrow::Cow; match self { TaggedValue::None => concrete!(()), TaggedValue::String(_) => concrete!(String), @@ -259,6 +263,8 @@ impl<'a> TaggedValue { TaggedValue::Curve(_) => concrete!(graphene_core::raster::curve::Curve), TaggedValue::IVec2(_) => concrete!(glam::IVec2), TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame), + TaggedValue::Footprint(_) => concrete!(graphene_core::transform::Footprint), + TaggedValue::RenderOutput(_) => concrete!(RenderOutput), } } @@ -314,10 +320,12 @@ impl<'a> TaggedValue { x if x == TypeId::of::() => Ok(TaggedValue::Artboard(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::IVec2(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())), + x if x == TypeId::of::() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())), x if x == TypeId::of::() => { let frame = *downcast::(input).unwrap(); Ok(TaggedValue::SurfaceFrame(frame.into())) } + x if x == TypeId::of::() => Ok(TaggedValue::Footprint(*downcast(input).unwrap())), _ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))), } } @@ -338,3 +346,11 @@ impl UpcastNode { Self { value } } } + +#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum RenderOutput { + CanvasFrame(graphene_core::SurfaceFrame), + Svg(String), + Raster(Vec), +} diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index a2f41c85..4ced4029 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -428,7 +428,7 @@ impl ProtoNetwork { let mut visited = HashSet::new(); let inwards_edges = self.collect_inwards_edges(); - for (id, _node) in &self.nodes { + for (id, _) in &self.nodes { for &dependency in inwards_edges.get(id).unwrap_or(&Vec::new()) { if !visited.contains(&dependency) { dbg!(id, dependency); @@ -580,7 +580,10 @@ impl TypingContext { input.output.clone() } }; - let impls = self.lookup.get(&node.identifier).ok_or(format!("No implementations found for {:?}", node.identifier))?; + let impls = self + .lookup + .get(&node.identifier) + .ok_or(format!("No implementations found for {:?}. Other implementations found {:?}", node.identifier, self.lookup))?; if matches!(input, Type::Generic(_)) { return Err(format!("Generic types are not supported as inputs yet {:?} occurred in {:?}", &input, node.identifier)); diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index d9f8adef..fd1ff4fa 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -8,12 +8,11 @@ use graph_craft::{ document::*, graphene_compiler::{Compiler, Executor}, imaginate_input::ImaginatePreferences, - NodeIdentifier, Type, TypeDescriptor, + NodeIdentifier, }; use graphene_core::{ application_io::{ApplicationIo, NodeGraphUpdateSender}, text::FontCache, - Cow, }; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use interpreted_executor::dynamic_executor::DynamicExecutor; @@ -55,6 +54,7 @@ async fn main() -> Result<(), Box> { application_io: &application_io, node_graph_message_sender: &UpdateLogger {}, imaginate_preferences: &ImaginatePreferences::default(), + render_config: graphene_core::application_io::RenderConfig::default(), }; loop { @@ -163,7 +163,7 @@ fn begin_scope() -> DocumentNode { nodes: [ DocumentNode { name: "SetNode".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(WasmEditorApi))], + manual_composition: Some(concrete!(WasmEditorApi)), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")), ..Default::default() }, @@ -175,7 +175,8 @@ fn begin_scope() -> DocumentNode { }, DocumentNode { name: "RefNode".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::lambda(1, 0)], + manual_composition: Some(concrete!(WasmEditorApi)), + inputs: vec![NodeInput::lambda(1, 0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::RefNode<_, _>")), ..Default::default() }, @@ -207,6 +208,7 @@ mod test { application_io: &block_on(WasmApplicationIo::new()), node_graph_message_sender: &UpdateLogger {}, imaginate_preferences: &ImaginatePreferences::default(), + render_config: graphene_core::application_io::RenderConfig::default(), }; let result = (&executor).execute(editor_api.clone()).await.unwrap(); println!("result: {:?}", result); @@ -223,6 +225,7 @@ mod test { application_io: &block_on(WasmApplicationIo::new()), node_graph_message_sender: &UpdateLogger {}, imaginate_preferences: &ImaginatePreferences::default(), + render_config: graphene_core::application_io::RenderConfig::default(), }; let result = (&executor).execute(editor_api.clone()).await.unwrap(); println!("result: {:?}", result); diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 0e4302a7..febcadd0 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -241,6 +241,9 @@ impl ComposeTypeErased { } pub fn input_node(n: SharedNodeContainer) -> DowncastBothNode<(), O> { + downcast_node(n) +} +pub fn downcast_node(n: SharedNodeContainer) -> DowncastBothNode { DowncastBothNode::new(n) } diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 176536f9..d84c43f2 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -3,7 +3,7 @@ use glam::{DAffine2, DVec2}; use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; use graph_craft::proto::DynFuture; use graphene_core::raster::{Alpha, BlendMode, BlendNode, Image, ImageFrame, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, Raster, RasterMut, RedGreenBlue, Sample}; -use graphene_core::transform::Transform; +use graphene_core::transform::{Footprint, Transform}; use crate::wasm_application_io::WasmEditorApi; use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; @@ -58,29 +58,50 @@ fn buffer_node(reader: R) -> Result, Error> { Ok(std::io::Read::bytes(reader).collect::, _>>()?) } -pub struct DownresNode

{ - _p: PhantomData

, +pub struct DownresNode { + image_frame: ImageFrame, } -#[node_macro::node_fn(DownresNode<_P>)] -fn downres<_P: Pixel>(image_frame: ImageFrame<_P>) -> ImageFrame<_P> { - let target_width = (image_frame.transform.transform_vector2((1., 0.).into()).length() as usize).min(image_frame.image.width as usize); - let target_height = (image_frame.transform.transform_vector2((0., 1.).into()).length() as usize).min(image_frame.image.height as usize); +#[node_macro::node_fn(DownresNode)] +fn downres(footprint: Footprint, image_frame: ImageFrame) -> ImageFrame { + // resize the image using the image crate + let image = image_frame.image; + let data = bytemuck::cast_vec(image.data); - let mut image = Image { - width: target_width as u32, - height: target_height as u32, - data: Vec::with_capacity(target_width * target_height), + let viewport_bounds = footprint.viewport_bounds_in_local_space(); + log::debug!("viewport_bounds: {viewport_bounds:?}"); + let bbox = Bbox::from_transform(image_frame.transform * DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64))); + log::debug!("local_bounds: {bbox:?}"); + let bounds = viewport_bounds.intersect(&bbox.to_axis_aligned_bbox()); + log::debug!("intersection: {bounds:?}"); + let union = viewport_bounds.union(&bbox.to_axis_aligned_bbox()); + log::debug!("union: {union:?}"); + let size = bounds.size(); + + let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal ImageFrame into image-rs data type."); + + let dynamic_image: image::DynamicImage = image_buffer.into(); + let offset = (bounds.start - viewport_bounds.start).as_uvec2(); + let cropped = dynamic_image.crop_imm(offset.x, offset.y, size.x as u32, size.y as u32); + + log::debug!("transform: {:?}", footprint.transform); + log::debug!("size: {size:?}"); + let viewport_resolution_x = footprint.transform.transform_vector2(DVec2::X * size.x).length(); + let viewport_resolution_y = footprint.transform.transform_vector2(DVec2::Y * size.y).length(); + let nwidth = viewport_resolution_x as u32; + let nheight = viewport_resolution_y as u32; + log::debug!("x: {viewport_resolution_x}, y: {viewport_resolution_y}"); + + let resized = cropped.resize_exact(nwidth, nheight, image::imageops::Lanczos3); + let buffer = resized.to_rgba32f(); + let buffer = buffer.into_raw(); + let vec = bytemuck::cast_vec(buffer); + let image = Image { + width: nwidth, + height: nheight, + data: vec, }; - let scale_factor = DVec2::new(image_frame.image.width as f64, image_frame.image.height as f64) / DVec2::new(target_width as f64, target_height as f64); - for y in 0..target_height { - for x in 0..target_width { - let pixel = image_frame.sample(DVec2::new(x as f64, y as f64) * scale_factor); - image.data.push(pixel); - } - } - ImageFrame { image, transform: image_frame.transform, diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 6d8b714b..7437ef18 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -2,13 +2,16 @@ use std::cell::RefCell; use core::future::Future; use dyn_any::StaticType; -use graphene_core::application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId}; +use graphene_core::application_io::{ApplicationError, ApplicationIo, ExportFormat, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId}; use graphene_core::raster::Image; -use graphene_core::Color; +use graphene_core::renderer::{GraphicElementRendered, RenderParams, SvgRender}; +use graphene_core::transform::Footprint; +use graphene_core::vector::style::ViewMode; use graphene_core::{ raster::{color::SRGBA8, ImageFrame}, Node, }; +use graphene_core::{Color, GraphicGroup}; #[cfg(target_arch = "wasm32")] use js_sys::{Object, Reflect}; use std::collections::HashMap; @@ -280,3 +283,32 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame { }; image } +pub use graph_craft::document::value::RenderOutput; + +pub struct RenderNode { + data: Data, + surface_handle: Surface, +} + +#[node_macro::node_fn(RenderNode)] +async fn render_node<'a: 'input, F: Future>( + editor: WasmEditorApi<'a>, + data: impl Node<'input, Footprint, Output = F>, + surface_handle: Arc>, +) -> RenderOutput { + let footprint = editor.render_config.viewport; + let data = self.data.eval(footprint).await; + let mut render = SvgRender::new(); + let render_params = RenderParams::new(ViewMode::Normal, graphene_core::renderer::ImageRenderMode::Base64, None, false); + let output_format = editor.render_config.export_format; + + match output_format { + ExportFormat::Svg => { + data.render_svg(&mut render, &render_params); + // TODO: reenable once we switch to full node graph + //render.format_svg((0., 0.).into(), (1., 1.).into()); + RenderOutput::Svg(render.svg.to_string()) + } + _ => todo!("Non svg render output"), + } +} diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index a7adc61e..66446ebb 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -6,13 +6,14 @@ use graphene_core::quantization::{PackedPixel, QuantizationChannels}; use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::color::Color; use graphene_core::structural::Then; +use graphene_core::transform::Footprint; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::VectorData; use graphene_core::{application_io::SurfaceHandle, SurfaceFrame, WasmSurfaceHandleFrame}; -use graphene_core::{concrete, generic}; +use graphene_core::{concrete, generic, GraphicGroup}; use graphene_core::{fn_type, raster::*}; -use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor}; +use graphene_core::{Cow, NodeIdentifier, Type}; use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; use graphene_std::wasm_application_io::*; @@ -31,12 +32,12 @@ use std::collections::HashMap; use std::sync::Arc; macro_rules! construct_node { - ($args: ident, $path:ty, [$($type:ty),*]) => { async move { + ($args: ident, $path:ty, [$($arg:ty => $type:ty),*]) => { async move { let mut args = $args.clone(); args.reverse(); let node = <$path>::new($( { - let node = graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node")); + let node = graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node")); let value = node.eval(()).await; graphene_core::value::ClonedNode::new(value) } @@ -49,12 +50,15 @@ macro_rules! construct_node { macro_rules! register_node { ($path:ty, input: $input:ty, params: [ $($type:ty),*]) => { + register_node!($path, input: $input, fn_params: [ $(() => $type),*]) + }; + ($path:ty, input: $input:ty, fn_params: [ $($arg:ty => $type:ty),*]) => { vec![ ( NodeIdentifier::new(stringify!($path)), |args| { Box::pin(async move { - let node = construct_node!(args, $path, [$($type),*]).await; + let node = construct_node!(args, $path, [$($arg => $type),*]).await; let node = graphene_std::any::FutureWrapperNode::new(node); let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node); Box::new(any) as TypeErasedBox @@ -77,24 +81,27 @@ macro_rules! async_node { // TODO: we currently need to annotate the type here because the compiler would otherwise (correctly) // assign a Pin>> type to the node, which is not what we want for now. ($path:ty, input: $input:ty, output: $output:ty, params: [ $($type:ty),*]) => { + async_node!($path, input: $input, output: $output, fn_params: [ $(() => $type),*]) + }; + ($path:ty, input: $input:ty, output: $output:ty, fn_params: [ $($arg:ty => $type:ty),*]) => { vec![ ( NodeIdentifier::new(stringify!($path)), |mut args| { Box::pin(async move { args.reverse(); - let node = <$path>::new($(graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node"))),*); + let node = <$path>::new($(graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"))),*); let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node); Box::new(any) as TypeErasedBox }) }, { let node = <$path>::new($( - graphene_std::any::PanicNode::<(), core::pin::Pin>>>::new() + graphene_std::any::PanicNode::<$arg, core::pin::Pin>>>::new() ),*); // TODO: Propagate the future type through the node graph //let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(Type::Future(Box::new(concrete!($type)))))),*]; - let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type)))),*]; + let params = vec![$(fn_type!($arg, $type)),*]; let mut node_io = NodeIO::<'_, $input>::to_node_io(&node, params); node_io.input = concrete!(<$input as StaticType>::Static); node_io.input = concrete!(<$input as StaticType>::Static); @@ -121,7 +128,7 @@ macro_rules! raster_node { NodeIdentifier::new(stringify!($path)), |args| { Box::pin(async move { - let node = construct_node!(args, $path, [$($type),*]).await; + let node = construct_node!(args, $path, [$(() => $type),*]).await; let node = graphene_std::any::FutureWrapperNode::new(node); let any: DynAnyNode = graphene_std::any::DynAnyNode::new(node); any.into_type_erased() @@ -136,7 +143,7 @@ macro_rules! raster_node { NodeIdentifier::new(stringify!($path)), |args| { Box::pin(async move { - let node = construct_node!(args, $path, [$($type),*]).await; + let node = construct_node!(args, $path, [$(() => $type),*]).await; let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); let map_node = graphene_std::any::FutureWrapperNode::new(map_node); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_node); @@ -152,7 +159,7 @@ macro_rules! raster_node { NodeIdentifier::new(stringify!($path)), |args| { Box::pin(async move { - let node = construct_node!(args, $path, [$($type),*]).await; + let node = construct_node!(args, $path, [$(() => $type),*]).await; let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); let map_node = graphene_std::any::FutureWrapperNode::new(map_node); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_node); @@ -266,9 +273,12 @@ fn node_registry() -> HashMap>, input: ImageFrame, output: ImageFrame, params: []), async_node!(graphene_core::ops::IntoNode<_, ImageFrame>, input: ImageFrame, output: ImageFrame, params: []), + async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: ImageFrame, output: GraphicGroup, params: []), + async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: VectorData, output: GraphicGroup, params: []), + async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: GraphicGroup, output: GraphicGroup, params: []), + async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: graphene_core::Artboard, output: GraphicGroup, params: []), #[cfg(feature = "gpu")] async_node!(graphene_core::ops::IntoNode<_, &WgpuExecutor>, input: WasmEditorApi, output: &WgpuExecutor, params: []), - register_node!(graphene_std::raster::DownresNode<_>, 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]), @@ -535,6 +545,7 @@ fn node_registry() -> HashMap, input: Option, params: []), async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: ImageFrame, params: [ImageFrame]), async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: VectorData, params: [VectorData]), + async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: RenderOutput, params: [RenderOutput]), async_node!( graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, @@ -616,6 +627,7 @@ fn node_registry() -> HashMap, input: (), output: wgpu_executor::WgpuSurface, params: [wgpu_executor::WgpuSurface]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: SurfaceFrame, params: [SurfaceFrame]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: RenderOutput, params: [RenderOutput]), register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image, params: [DAffine2]), register_node!(graphene_std::raster::PixelNoiseNode<_, _, _>, input: u32, params: [u32, u32, NoiseType]), @@ -624,9 +636,90 @@ fn node_registry() -> HashMap, input: Color, params: [QuantizationChannels]), register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]), register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []), - register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f32, DVec2, DVec2, DVec2]), - register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame, params: [DVec2, f32, DVec2, DVec2, DVec2]), - register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: WasmSurfaceHandleFrame, params: [DVec2, f32, DVec2, DVec2, DVec2]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc]), + //register_node!(graphene_core::transform::TranformNode<_, _, _, _, _, _>, input: , output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc]), + vec![ + ( + NodeIdentifier::new("graphene_core::transform::TransformNode<_, _, _, _, _, _>"), + |mut args| { + Box::pin(async move { + args.reverse(); + let node = >::new( + DowncastBothNode::::new(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + ); + let any: DynAnyNode = graphene_std::any::DynAnyNode::new(node); + Box::new(any) as TypeErasedBox + }) + }, + { + let params = vec![fn_type!(Footprint, VectorData), fn_type!(DVec2), fn_type!(f32), fn_type!(DVec2), fn_type!(DVec2), fn_type!(DVec2)]; + NodeIOTypes::new(concrete!(Footprint), concrete!(VectorData), params) + }, + ), + ( + NodeIdentifier::new("graphene_core::transform::TransformNode<_, _, _, _, _, _>"), + |mut args| { + Box::pin(async move { + args.reverse(); + let node = >::new( + DowncastBothNode::::new(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + ); + let any: DynAnyNode = graphene_std::any::DynAnyNode::new(node); + Box::new(any) as TypeErasedBox + }) + }, + { + let params = vec![ + fn_type!(Footprint, WasmSurfaceHandleFrame), + fn_type!(DVec2), + fn_type!(f32), + fn_type!(DVec2), + fn_type!(DVec2), + fn_type!(DVec2), + ]; + NodeIOTypes::new(concrete!(Footprint), concrete!(WasmSurfaceHandleFrame), params) + }, + ), + ( + NodeIdentifier::new("graphene_core::transform::TransformNode<_, _, _, _, _, _>"), + |mut args| { + Box::pin(async move { + args.reverse(); + let node = >::new( + DowncastBothNode::>::new(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node")), + ); + let any: DynAnyNode = graphene_std::any::DynAnyNode::new(node); + Box::new(any) as TypeErasedBox + }) + }, + { + let params = vec![ + fn_type!(Footprint, ImageFrame), + fn_type!(DVec2), + fn_type!(f32), + fn_type!(DVec2), + fn_type!(DVec2), + fn_type!(DVec2), + ]; + NodeIOTypes::new(concrete!(Footprint), concrete!(ImageFrame), params) + }, + ), + ], register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [VectorData]), register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame, params: [ImageFrame]), register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [DAffine2]), @@ -636,6 +729,10 @@ fn node_registry() -> HashMap, input: VectorData, params: [DVec2, u32]), register_node!(graphene_core::vector::BoundingBoxNode, input: VectorData, params: []), register_node!(graphene_core::vector::CircularRepeatNode<_, _, _>, input: VectorData, params: [f32, f32, u32]), + register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [VectorData]), + register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [graphene_core::Artboard]), + register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [graphene_core::GraphicGroup]), + register_node!(graphene_std::raster::DownresNode<_>, input: Footprint, params: [ImageFrame]), register_node!(graphene_core::vector::ResamplePoints<_>, input: VectorData, params: [f64]), register_node!(graphene_core::vector::SplineFromPointsNode, input: VectorData, params: []), register_node!(graphene_core::vector::generator_nodes::CircleGenerator<_>, input: (), params: [f32]), @@ -653,7 +750,7 @@ fn node_registry() -> HashMap, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]), register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []), register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []), - register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicElementData, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), + async_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => graphene_core::GraphicElementData, () => String, () => BlendMode, () => f32, () => bool, () => bool, () => bool, Footprint => graphene_core::GraphicGroup]), register_node!(graphene_core::ToGraphicElementData, input: graphene_core::vector::VectorData, params: []), register_node!(graphene_core::ToGraphicElementData, input: ImageFrame, params: []), register_node!(graphene_core::ToGraphicElementData, input: graphene_core::GraphicGroup, params: []),