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
This commit is contained in:
Dennis Kobert 2023-09-06 12:39:21 +02:00 committed by Keavon Chambers
parent 239ca698e5
commit d82f133514
33 changed files with 836 additions and 305 deletions

1
Cargo.lock generated
View File

@ -2075,6 +2075,7 @@ dependencies = [
"bytemuck",
"dyn-any",
"glam",
"image",
"js-sys",
"kurbo",
"log",

View File

@ -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

View File

@ -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);

View File

@ -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<Item = LayerNodeIdentifier> + 'a {
pub fn click_xray(&self, viewport_location: DVec2) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
let point = self.document_to_viewport.inverse().transform_point2(viewport_location);
self.root()
.decendants(self)

View File

@ -17,6 +17,7 @@ pub enum CachedOutputData {
BlobURL(String),
VectorPath(Box<VectorData>),
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!(

View File

@ -80,6 +80,10 @@ pub enum Operation {
path: Vec<LayerId>,
surface_id: graphene_core::SurfaceId,
},
SetSvg {
path: Vec<LayerId>,
svg: String,
},
TransformLayerInScope {
path: Vec<LayerId>,
transform: [f64; 6],

View File

@ -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),

View File

@ -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<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
let image_size = DVec2::new(image.width as f64, image.height as f64);
let Some(image_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Image") else {
warn!("Image node should be in registry");
return;
};
let Some(transform_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Transform") else {
warn!("Transform node should be in registry");
return;
};
let Some(downres_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Downres") else {
warn!("Downres node should be in registry");
return;
};
let path = vec![generate_uuid()];
let mut network = NodeNetwork::default();
// Transform of parent folder
let to_parent_folder = self.document_legacy.generate_transform_across_scope(&path[..path.len() - 1], None).unwrap_or_default();
@ -622,19 +609,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.add(DocumentMessage::StartTransaction);
network.push_node(
image_node_type.to_document_node(
[graph_craft::document::NodeInput::value(
graph_craft::document::value::TaggedValue::ImageFrame(ImageFrame { image, transform: DAffine2::IDENTITY }),
false,
)],
graph_craft::document::DocumentNodeMetadata::position((8, 4)),
),
false,
);
network.push_node(transform_node_type.to_document_node_default_inputs([], Default::default()), true);
network.push_node(downres_node_type.to_document_node_default_inputs([], Default::default()), true);
network.push_output_node();
let image_frame = ImageFrame { image, transform: DAffine2::IDENTITY };
let network = new_raster_network(image_frame);
responses.add(DocumentOperation::AddFrame {
path: path.clone(),

View File

@ -97,20 +97,24 @@ impl<'a> 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<NodeId> {
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<Subpath<ManipulatorGroupId>>, 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 });
}

View File

@ -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 {

View File

@ -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<DocumentOutputType>,
pub primary_output: bool,
pub properties: fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup>,
pub manual_composition: Option<graphene_core::Type>,
}
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<DocumentNodeType> {
(
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<DocumentNodeType> {
(
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<DocumentNodeType> {
(
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<DocumentNodeType> {
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<DocumentNodeType> {
..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<Color>))],
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<DocumentNodeType> {
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<DocumentNodeType> {
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<DocumentNodeType> {
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<DocumentNodeType> {
},
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> {
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<DocumentNodeType> {
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<DocumentNodeType> {
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<DocumentNodeType> {
},
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<DocumentNodeType> {
},
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<DocumentNodeType> {
},
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<DocumentNodeType> {
},
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<DocumentNodeType> {
},
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<DocumentNodeType> {
},
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<DocumentNodeType> {
},
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<DocumentNodeType> {
},
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<DocumentNodeType> {
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> {
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<bezier_rs::Subpath<uuid::ManipulatorGroupId>>) -> 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<bezier_rs::Subpath<uuid::ManipulatorGrou
let mut network = NodeNetwork::default();
network.push_node(
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), 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(path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], DocumentNodeMetadata::position((0, 4))));
network.push_node(cull_node.to_document_node_default_inputs([None], Default::default()));
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
}
pub fn new_raster_network(image_frame: ImageFrame<Color>) -> 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
}

View File

@ -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<LayoutGroup> {

View File

@ -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<LayerId>,
image_frame: Option<ImageFrame<Color>>,
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<ImageFrame<Color>>) -> Result<TaggedValue, String> {
async fn execute_network<'a>(&'a mut self, path: &[LayerId], scoped_network: NodeNetwork, image_frame: Option<ImageFrame<Color>>, transform: DAffine2) -> Result<TaggedValue, String> {
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<ImageFrame<Color>>, layer_path: Vec<LayerId>) -> u64 {
fn queue_execution(&self, network: NodeNetwork, image_frame: Option<ImageFrame<Color>>, layer_path: Vec<LayerId>, 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);

View File

@ -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() {

View File

@ -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

View File

@ -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<S> From<SurfaceHandleFrame<S>> for SurfaceFrame {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SurfaceHandle<Surface> {
pub surface_id: SurfaceId,
pub surface: Surface,
@ -53,7 +53,7 @@ unsafe impl<T: 'static> StaticType for SurfaceHandle<T> {
type Static = SurfaceHandle<T>;
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct SurfaceHandleFrame<Surface> {
pub surface_handle: Arc<SurfaceHandle<Surface>>,
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<ImageFrame<Color>>,
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,
}
}
}

View File

@ -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<Name, BlendMode, Opacity, Visible, Locked, Collapsed, Stack> {
pub struct ConstructLayerNode<GraphicElementData, Name, BlendMode, Opacity, Visible, Locked, Collapsed, Stack> {
graphic_element_data: GraphicElementData,
name: Name,
blend_mode: BlendMode,
opacity: Opacity,
@ -75,16 +91,19 @@ pub struct ConstructLayerNode<Name, BlendMode, Opacity, Visible, Locked, Collaps
}
#[node_fn(ConstructLayerNode)]
fn construct_layer<Data: Into<GraphicElementData>>(
graphic_element_data: Data,
async fn construct_layer<Data: Into<GraphicElementData>, Fut1: Future<Output = Data>, Fut2: Future<Output = GraphicGroup>>(
footprint: crate::transform::Footprint,
graphic_element_data: impl Node<crate::transform::Footprint, Output = Fut1>,
name: String,
blend_mode: BlendMode,
opacity: f32,
visible: bool,
locked: bool,
collapsed: bool,
mut stack: GraphicGroup,
mut stack: impl Node<crate::transform::Footprint, Output = Fut2>,
) -> 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<GraphicGroup> for GraphicElementData {
GraphicElementData::GraphicGroup(graphic_group)
}
}
impl From<Artboard> 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<GraphicElementData> {}
impl ToGraphicElement for VectorData {}
impl ToGraphicElement for ImageFrame<Color> {}
impl ToGraphicElement for Artboard {}
impl<T> From<T> 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());
}

View File

@ -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<Color> {
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;

View File

@ -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 {

View File

@ -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;

View File

@ -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))]

View File

@ -145,7 +145,7 @@ where
/// Flattens each channel cast to a u8
pub fn into_flat_u8(self) -> (Vec<u8>, 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.;

View File

@ -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<Translation, Rotation, Scale, Shear, Pivot> {
pub struct TransformNode<TransformTarget, Translation, Rotation, Scale, Shear, Pivot> {
pub(crate) transform_target: TransformTarget,
pub(crate) translate: Translation,
pub(crate) rotate: Rotation,
pub(crate) scale: Scale,
@ -124,11 +129,107 @@ pub struct TransformNode<Translation, Rotation, Scale, Shear, Pivot> {
pub(crate) pivot: Pivot,
}
#[node_macro::node_fn(TransformNode)]
pub(crate) fn transform_vector_data<Data: TransformMut>(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<VectorData> {
pub(crate) vector_data: VectorData,
}
#[node_macro::node_fn(CullNode)]
fn cull_vector_data<T>(footprint: Footprint, vector_data: T) -> T {
// TODO: Implement culling
vector_data
}
impl core::hash::Hash for Footprint {
fn hash<H: core::hash::Hasher>(&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<Fut: Future>(
mut footprint: Footprint,
transform_target: impl Node<Footprint, Output = Fut>,
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);

View File

@ -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)))
};
}

View File

@ -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<NodeInput>,
pub manual_composition: Option<Type>,
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(_) => (),
}

View File

@ -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::<graphene_core::Artboard>() => Ok(TaggedValue::Artboard(*downcast(input).unwrap())),
x if x == TypeId::of::<glam::IVec2>() => Ok(TaggedValue::IVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
let frame = *downcast::<graphene_core::WasmSurfaceHandleFrame>(input).unwrap();
Ok(TaggedValue::SurfaceFrame(frame.into()))
}
x if x == TypeId::of::<graphene_core::transform::Footprint>() => 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<u8>),
}

View File

@ -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));

View File

@ -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<dyn Error>> {
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);

View File

@ -241,6 +241,9 @@ impl ComposeTypeErased {
}
pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(), O> {
downcast_node(n)
}
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
DowncastBothNode::new(n)
}

View File

@ -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<R: std::io::Read>(reader: R) -> Result<Vec<u8>, Error> {
Ok(std::io::Read::bytes(reader).collect::<Result<Vec<_>, _>>()?)
}
pub struct DownresNode<P> {
_p: PhantomData<P>,
pub struct DownresNode<ImageFrame> {
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<Color>) -> ImageFrame<Color> {
// 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,

View File

@ -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<Color> {
};
image
}
pub use graph_craft::document::value::RenderOutput;
pub struct RenderNode<Data, Surface> {
data: Data,
surface_handle: Surface,
}
#[node_macro::node_fn(RenderNode)]
async fn render_node<'a: 'input, F: Future<Output = GraphicGroup>>(
editor: WasmEditorApi<'a>,
data: impl Node<'input, Footprint, Output = F>,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
) -> 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"),
}
}

View File

@ -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<Box<dyn Fututure<Output=T>>> 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<Box<dyn core::future::Future<Output = $type>>>>::new()
graphene_std::any::PanicNode::<$arg, core::pin::Pin<Box<dyn core::future::Future<Output = $type>>>>::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<Color, _, _> = 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<Image<Color>, _, _> = 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<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_node);
@ -266,9 +273,12 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::logic::LogicNotNode, input: bool, params: []),
async_node!(graphene_core::ops::IntoNode<_, ImageFrame<SRGBA8>>, input: ImageFrame<Color>, output: ImageFrame<SRGBA8>, params: []),
async_node!(graphene_core::ops::IntoNode<_, ImageFrame<Color>>, input: ImageFrame<SRGBA8>, output: ImageFrame<Color>, params: []),
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: ImageFrame<Color>, 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<Color>, params: []),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>, RedGreenBlue]),
@ -535,6 +545,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::memo::LetNode<_>, input: Option<WasmEditorApi>, params: []),
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: ImageFrame<Color>, params: [ImageFrame<Color>]),
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<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, 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<Color>, params: [&str]),
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, params: [DAffine2]),
register_node!(graphene_std::raster::PixelNoiseNode<_, _, _>, input: u32, params: [u32, u32, NoiseType]),
@ -624,9 +636,90 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::quantization::QuantizeNode<_>, 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<Color>, 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<WasmSurfaceHandle>]),
//register_node!(graphene_core::transform::TranformNode<_, _, _, _, _, _>, input: , output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
vec![
(
NodeIdentifier::new("graphene_core::transform::TransformNode<_, _, _, _, _, _>"),
|mut args| {
Box::pin(async move {
args.reverse();
let node = <graphene_core::transform::TransformNode<_, _, _, _, _, _>>::new(
DowncastBothNode::<Footprint, VectorData>::new(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<f32>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
);
let any: DynAnyNode<Footprint, _, _> = 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 = <graphene_core::transform::TransformNode<_, _, _, _, _, _>>::new(
DowncastBothNode::<Footprint, WasmSurfaceHandleFrame>::new(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<f32>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
);
let any: DynAnyNode<Footprint, _, _> = 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 = <graphene_core::transform::TransformNode<_, _, _, _, _, _>>::new(
DowncastBothNode::<Footprint, ImageFrame<Color>>::new(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<f32>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
);
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let params = vec![
fn_type!(Footprint, ImageFrame<Color>),
fn_type!(DVec2),
fn_type!(f32),
fn_type!(DVec2),
fn_type!(DVec2),
fn_type!(DVec2),
];
NodeIOTypes::new(concrete!(Footprint), concrete!(ImageFrame<Color>), params)
},
),
],
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [VectorData]),
register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [DAffine2]),
@ -636,6 +729,10 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::vector::RepeatNode<_, _>, 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<Color>]),
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<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::text::TextGenerator<_, _, _>, 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<Color>, params: []),
register_node!(graphene_core::ToGraphicElementData, input: graphene_core::GraphicGroup, params: []),