Use canvas as target for raster rendering (#1256)
* Implement ApplicationIo * Simplify output duplication logic * Fix let node initialization for ExtractImageFrame * Async macros * Use manual node registry impl * Fix canvas insertion into the dom
This commit is contained in:
parent
57415b948b
commit
259dcdc628
|
|
@ -1684,6 +1684,7 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
|
"js-sys",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
|
|
@ -1696,6 +1697,8 @@ dependencies = [
|
||||||
"specta",
|
"specta",
|
||||||
"spin 0.9.8",
|
"spin 0.9.8",
|
||||||
"spirv-std",
|
"spirv-std",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -871,6 +871,12 @@ impl Document {
|
||||||
}
|
}
|
||||||
Some(Vec::new())
|
Some(Vec::new())
|
||||||
}
|
}
|
||||||
|
Operation::SetSurface { path, surface_id } => {
|
||||||
|
if let LayerDataType::Layer(layer) = &mut self.layer_mut(&path)?.data {
|
||||||
|
layer.cached_output_data = CachedOutputData::SurfaceId(surface_id);
|
||||||
|
}
|
||||||
|
Some(Vec::new())
|
||||||
|
}
|
||||||
Operation::TransformLayerInScope { path, transform, scope } => {
|
Operation::TransformLayerInScope { path, transform, scope } => {
|
||||||
let transform = DAffine2::from_cols_array(&transform);
|
let transform = DAffine2::from_cols_array(&transform);
|
||||||
let scope = DAffine2::from_cols_array(&scope);
|
let scope = DAffine2::from_cols_array(&scope);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use crate::LayerId;
|
||||||
|
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use graphene_core::vector::VectorData;
|
use graphene_core::vector::VectorData;
|
||||||
|
use graphene_core::SurfaceId;
|
||||||
use kurbo::{Affine, BezPath, Shape as KurboShape};
|
use kurbo::{Affine, BezPath, Shape as KurboShape};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
@ -15,6 +16,7 @@ pub enum CachedOutputData {
|
||||||
None,
|
None,
|
||||||
BlobURL(String),
|
BlobURL(String),
|
||||||
VectorPath(Box<VectorData>),
|
VectorPath(Box<VectorData>),
|
||||||
|
SurfaceId(SurfaceId),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
|
||||||
|
|
@ -76,6 +78,19 @@ impl LayerData for LayerLayer {
|
||||||
matrix
|
matrix
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
CachedOutputData::SurfaceId(SurfaceId(id)) => {
|
||||||
|
// Render the image if it exists
|
||||||
|
let _ = write!(
|
||||||
|
svg,
|
||||||
|
r#"
|
||||||
|
<foreignObject width="{}" height="{}" transform="matrix({})"><div data-canvas-placeholder="canvas{}"></div></foreignObject>
|
||||||
|
"#,
|
||||||
|
width.abs(),
|
||||||
|
height.abs(),
|
||||||
|
matrix,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Render a dotted blue outline if there is no image or vector data
|
// Render a dotted blue outline if there is no image or vector data
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,10 @@ pub enum Operation {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
vector_data: graphene_core::vector::VectorData,
|
vector_data: graphene_core::vector::VectorData,
|
||||||
},
|
},
|
||||||
|
SetSurface {
|
||||||
|
path: Vec<LayerId>,
|
||||||
|
surface_id: graphene_core::SurfaceId,
|
||||||
|
},
|
||||||
TransformLayerInScope {
|
TransformLayerInScope {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
},
|
},
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Downres",
|
name: "Downres",
|
||||||
category: "Ignore",
|
category: "Raster",
|
||||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||||
inputs: vec![0],
|
inputs: vec![0],
|
||||||
outputs: vec![NodeOutput::new(1, 0)],
|
outputs: vec![NodeOutput::new(1, 0)],
|
||||||
|
|
@ -234,15 +234,87 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Input Frame",
|
name: "Input Frame",
|
||||||
category: "Ignore",
|
category: "Ignore",
|
||||||
|
identifier: NodeImplementation::proto("graphene_core::ExtractImageFrame"),
|
||||||
|
inputs: vec![DocumentInputType {
|
||||||
|
name: "In",
|
||||||
|
data_type: FrontendGraphDataType::General,
|
||||||
|
default: NodeInput::Network(concrete!(EditorApi)),
|
||||||
|
}],
|
||||||
|
outputs: vec![DocumentOutputType {
|
||||||
|
name: "Image Frame",
|
||||||
|
data_type: FrontendGraphDataType::Raster,
|
||||||
|
}],
|
||||||
|
properties: node_properties::input_properties,
|
||||||
|
},
|
||||||
|
DocumentNodeType {
|
||||||
|
name: "Create Canvas",
|
||||||
|
category: "Structural",
|
||||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||||
inputs: vec![0, 1],
|
inputs: vec![0],
|
||||||
outputs: vec![NodeOutput::new(0, 0), NodeOutput::new(1, 0)],
|
outputs: vec![NodeOutput::new(1, 0)],
|
||||||
nodes: [DocumentNode {
|
nodes: [
|
||||||
name: "Identity".to_string(),
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::Network(concrete!(EditorApi))],
|
name: "Create Canvas".to_string(),
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ExtractImageFrame")),
|
inputs: vec![NodeInput::Network(concrete!(EditorApi))],
|
||||||
..Default::default()
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::wasm_application_io::CreateSurfaceNode")),
|
||||||
}]
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
name: "Cache".to_string(),
|
||||||
|
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (id as NodeId, node))
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
inputs: vec![DocumentInputType {
|
||||||
|
name: "In",
|
||||||
|
data_type: FrontendGraphDataType::General,
|
||||||
|
default: NodeInput::Network(concrete!(EditorApi)),
|
||||||
|
}],
|
||||||
|
outputs: vec![DocumentOutputType {
|
||||||
|
name: "Canvas",
|
||||||
|
data_type: FrontendGraphDataType::General,
|
||||||
|
}],
|
||||||
|
properties: node_properties::input_properties,
|
||||||
|
},
|
||||||
|
DocumentNodeType {
|
||||||
|
name: "Draw Canvas",
|
||||||
|
category: "Structural",
|
||||||
|
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||||
|
inputs: vec![0, 2],
|
||||||
|
outputs: vec![NodeOutput::new(3, 0)],
|
||||||
|
nodes: [
|
||||||
|
DocumentNode {
|
||||||
|
name: "Convert Image Frame".to_string(),
|
||||||
|
inputs: vec![NodeInput::Network(concrete!(ImageFrame<Color>))],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrame<SRGBA8>>")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
name: "Create Canvas".to_string(),
|
||||||
|
inputs: vec![NodeInput::Network(concrete!(EditorApi))],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::wasm_application_io::CreateSurfaceNode")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
name: "Cache".to_string(),
|
||||||
|
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
name: "Draw Canvas".to_string(),
|
||||||
|
inputs: vec![NodeInput::node(0, 0), NodeInput::node(2, 0)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::wasm_application_io::DrawImageFrameNode<_>")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(id, node)| (id as NodeId, node))
|
.map(|(id, node)| (id as NodeId, node))
|
||||||
|
|
@ -252,14 +324,18 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
DocumentInputType {
|
DocumentInputType {
|
||||||
name: "In",
|
name: "In",
|
||||||
data_type: FrontendGraphDataType::General,
|
data_type: FrontendGraphDataType::Raster,
|
||||||
default: NodeInput::Network(concrete!(ImageFrame<Color>)),
|
default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
|
},
|
||||||
|
DocumentInputType {
|
||||||
|
name: "In",
|
||||||
|
data_type: FrontendGraphDataType::General,
|
||||||
|
default: NodeInput::Network(concrete!(EditorApi)),
|
||||||
},
|
},
|
||||||
DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false),
|
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType {
|
outputs: vec![DocumentOutputType {
|
||||||
name: "Image Frame",
|
name: "Canvas",
|
||||||
data_type: FrontendGraphDataType::Raster,
|
data_type: FrontendGraphDataType::General,
|
||||||
}],
|
}],
|
||||||
properties: node_properties::input_properties,
|
properties: node_properties::input_properties,
|
||||||
},
|
},
|
||||||
|
|
@ -267,12 +343,12 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
name: "Begin Scope",
|
name: "Begin Scope",
|
||||||
category: "Ignore",
|
category: "Ignore",
|
||||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||||
inputs: vec![0, 2],
|
inputs: vec![0],
|
||||||
outputs: vec![NodeOutput::new(1, 0), NodeOutput::new(2, 0)],
|
outputs: vec![NodeOutput::new(1, 0), NodeOutput::new(2, 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "SetNode".to_string(),
|
name: "SetNode".to_string(),
|
||||||
inputs: vec![NodeInput::Network(concrete!(EditorApi))],
|
inputs: vec![NodeInput::ShortCircut(concrete!(EditorApi))],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
@ -284,7 +360,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "RefNode".to_string(),
|
name: "RefNode".to_string(),
|
||||||
inputs: vec![NodeInput::Network(concrete!(())), NodeInput::lambda(1, 0)],
|
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::lambda(1, 0)],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::RefNode<_, _>")),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::RefNode<_, _>")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
@ -299,7 +375,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
inputs: vec![DocumentInputType {
|
inputs: vec![DocumentInputType {
|
||||||
name: "In",
|
name: "In",
|
||||||
data_type: FrontendGraphDataType::Raster,
|
data_type: FrontendGraphDataType::Raster,
|
||||||
default: NodeInput::value(TaggedValue::EditorApi(EditorApi::empty()), true),
|
default: NodeInput::Network(concrete!(EditorApi)),
|
||||||
}],
|
}],
|
||||||
outputs: vec![
|
outputs: vec![
|
||||||
DocumentOutputType {
|
DocumentOutputType {
|
||||||
|
|
@ -1243,24 +1319,39 @@ impl DocumentNodeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
|
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
|
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||||
if network.inputs.is_empty() {
|
|
||||||
return network;
|
network.generate_node_paths(&[]);
|
||||||
|
for id in node_ids {
|
||||||
|
network.flatten(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(network.inputs.len(), 1, "Networks wrapped in scope must have exactly one input");
|
let mut network_inputs = Vec::new();
|
||||||
let input = network.nodes[&network.inputs[0]].inputs.iter().find(|&i| matches!(i, NodeInput::Network(_))).cloned();
|
let mut input_type = None;
|
||||||
|
for (id, node) in network.nodes.iter() {
|
||||||
|
for (index, input) in node.inputs.iter().enumerate() {
|
||||||
|
if let NodeInput::Network(_) = input {
|
||||||
|
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");
|
||||||
|
network_inputs.push(*id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let len = network_inputs.len();
|
||||||
|
network.inputs = network_inputs;
|
||||||
|
|
||||||
// if the network has no network inputs, it doesn't need to be wrapped in a scope either
|
// if the network has no inputs, it doesn't need to be wrapped in a scope
|
||||||
let Some(input_type) = input else {
|
if len == 0 {
|
||||||
return network;
|
return network;
|
||||||
};
|
}
|
||||||
|
|
||||||
let inner_network = DocumentNode {
|
let inner_network = DocumentNode {
|
||||||
name: "Scope".to_string(),
|
name: "Scope".to_string(),
|
||||||
implementation: DocumentNodeImplementation::Network(network),
|
implementation: DocumentNodeImplementation::Network(network),
|
||||||
inputs: vec![NodeInput::node(0, 1)],
|
inputs: core::iter::repeat(NodeInput::node(0, 1)).take(len).collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1268,7 +1359,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
|
||||||
let nodes = vec![
|
let nodes = vec![
|
||||||
resolve_document_node_type("Begin Scope")
|
resolve_document_node_type("Begin Scope")
|
||||||
.expect("Begin Scope node type not found")
|
.expect("Begin Scope node type not found")
|
||||||
.to_document_node(vec![input_type], DocumentNodeMetadata::default()),
|
.to_document_node(vec![input_type.unwrap()], DocumentNodeMetadata::default()),
|
||||||
inner_network,
|
inner_network,
|
||||||
resolve_document_node_type("End Scope")
|
resolve_document_node_type("End Scope")
|
||||||
.expect("End Scope node type not found")
|
.expect("End Scope node type not found")
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,9 @@ impl Default for BrushOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
diameter: 40.,
|
diameter: 40.,
|
||||||
hardness: 50.,
|
hardness: 0.,
|
||||||
flow: 100.,
|
flow: 100.,
|
||||||
spacing: 50.,
|
spacing: 20.,
|
||||||
color: ToolColorOptions::default(),
|
color: ToolColorOptions::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +252,7 @@ impl BrushToolData {
|
||||||
|
|
||||||
self.transform = DAffine2::IDENTITY;
|
self.transform = DAffine2::IDENTITY;
|
||||||
|
|
||||||
matches!(layer.cached_output_data, CachedOutputData::BlobURL(_)).then_some(&self.layer_path)
|
matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)).then_some(&self.layer_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_strokes(&self, brush_options: &BrushOptions, responses: &mut VecDeque<Message>) {
|
fn update_strokes(&self, brush_options: &BrushOptions, responses: &mut VecDeque<Message>) {
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,19 @@ use crate::messages::prelude::*;
|
||||||
use document_legacy::layers::layer_info::LayerDataType;
|
use document_legacy::layers::layer_info::LayerDataType;
|
||||||
use document_legacy::{LayerId, Operation};
|
use document_legacy::{LayerId, Operation};
|
||||||
|
|
||||||
|
use dyn_any::DynAny;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
|
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
|
||||||
use graph_craft::executor::Compiler;
|
use graph_craft::executor::Compiler;
|
||||||
use graph_craft::{concrete, Type, TypeDescriptor};
|
use graph_craft::{concrete, Type, TypeDescriptor};
|
||||||
|
use graphene_core::application_io::ApplicationIo;
|
||||||
use graphene_core::raster::{Image, ImageFrame};
|
use graphene_core::raster::{Image, ImageFrame};
|
||||||
use graphene_core::renderer::{SvgSegment, SvgSegmentList};
|
use graphene_core::renderer::{SvgSegment, SvgSegmentList};
|
||||||
use graphene_core::text::FontCache;
|
use graphene_core::text::FontCache;
|
||||||
use graphene_core::vector::style::ViewMode;
|
use graphene_core::vector::style::ViewMode;
|
||||||
|
|
||||||
use graphene_core::{Color, EditorApi};
|
use graphene_core::wasm_application_io::{WasmApplicationIo, WasmSurfaceHandleFrame};
|
||||||
|
use graphene_core::{Color, EditorApi, SurfaceFrame, SurfaceId};
|
||||||
use interpreted_executor::executor::DynamicExecutor;
|
use interpreted_executor::executor::DynamicExecutor;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
@ -31,7 +34,9 @@ pub struct NodeRuntime {
|
||||||
font_cache: FontCache,
|
font_cache: FontCache,
|
||||||
receiver: Receiver<NodeRuntimeMessage>,
|
receiver: Receiver<NodeRuntimeMessage>,
|
||||||
sender: Sender<GenerationResponse>,
|
sender: Sender<GenerationResponse>,
|
||||||
|
wasm_io: WasmApplicationIo,
|
||||||
pub(crate) thumbnails: HashMap<LayerId, HashMap<NodeId, SvgSegmentList>>,
|
pub(crate) thumbnails: HashMap<LayerId, HashMap<NodeId, SvgSegmentList>>,
|
||||||
|
canvas_cache: HashMap<Vec<LayerId>, SurfaceId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_imaginate_index(name: &str) -> usize {
|
fn get_imaginate_index(name: &str) -> usize {
|
||||||
|
|
@ -70,6 +75,8 @@ impl NodeRuntime {
|
||||||
sender,
|
sender,
|
||||||
font_cache: FontCache::default(),
|
font_cache: FontCache::default(),
|
||||||
thumbnails: Default::default(),
|
thumbnails: Default::default(),
|
||||||
|
wasm_io: WasmApplicationIo::default(),
|
||||||
|
canvas_cache: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn run(&mut self) {
|
pub async fn run(&mut self) {
|
||||||
|
|
@ -94,7 +101,7 @@ impl NodeRuntime {
|
||||||
}) => {
|
}) => {
|
||||||
let (network, monitor_nodes) = Self::wrap_network(graph);
|
let (network, monitor_nodes) = Self::wrap_network(graph);
|
||||||
|
|
||||||
let result = self.execute_network(network, image_frame).await;
|
let result = self.execute_network(&path, network, image_frame).await;
|
||||||
let mut responses = VecDeque::new();
|
let mut responses = VecDeque::new();
|
||||||
self.update_thumbnails(&path, monitor_nodes, &mut responses);
|
self.update_thumbnails(&path, monitor_nodes, &mut responses);
|
||||||
let response = GenerationResponse {
|
let response = GenerationResponse {
|
||||||
|
|
@ -113,22 +120,22 @@ impl NodeRuntime {
|
||||||
fn wrap_network(network: NodeNetwork) -> (NodeNetwork, Vec<Vec<NodeId>>) {
|
fn wrap_network(network: NodeNetwork) -> (NodeNetwork, Vec<Vec<NodeId>>) {
|
||||||
let mut scoped_network = wrap_network_in_scope(network);
|
let mut scoped_network = wrap_network_in_scope(network);
|
||||||
|
|
||||||
scoped_network.generate_node_paths(&[]);
|
//scoped_network.generate_node_paths(&[]);
|
||||||
let monitor_nodes = scoped_network
|
let monitor_nodes = scoped_network
|
||||||
.recursive_nodes()
|
.recursive_nodes()
|
||||||
.filter(|(node, _, _)| node.implementation == DocumentNodeImplementation::proto("graphene_std::memo::MonitorNode<_>"))
|
.filter(|(node, _, _)| node.implementation == DocumentNodeImplementation::proto("graphene_std::memo::MonitorNode<_>"))
|
||||||
.map(|(_, _, path)| path)
|
.map(|(_, _, path)| path)
|
||||||
.collect();
|
.collect();
|
||||||
scoped_network.duplicate_outputs(&mut generate_uuid);
|
//scoped_network.remove_dead_nodes();
|
||||||
scoped_network.remove_dead_nodes();
|
|
||||||
|
|
||||||
(scoped_network, monitor_nodes)
|
(scoped_network, monitor_nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_network<'a>(&'a mut self, 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>>) -> Result<TaggedValue, String> {
|
||||||
let editor_api = EditorApi {
|
let editor_api = EditorApi {
|
||||||
font_cache: Some(&self.font_cache),
|
font_cache: &self.font_cache,
|
||||||
image_frame,
|
image_frame,
|
||||||
|
application_io: &self.wasm_io,
|
||||||
};
|
};
|
||||||
|
|
||||||
// We assume only one output
|
// We assume only one output
|
||||||
|
|
@ -150,11 +157,30 @@ impl NodeRuntime {
|
||||||
Some(t) if t == concrete!(()) => self.executor.execute(().into_dyn()).await.map_err(|e| e.to_string()),
|
Some(t) if t == concrete!(()) => self.executor.execute(().into_dyn()).await.map_err(|e| e.to_string()),
|
||||||
_ => Err("Invalid input type".to_string()),
|
_ => Err("Invalid input type".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(result) => match TaggedValue::try_from_any(result) {
|
Ok(result) => {
|
||||||
Some(x) => Ok(x),
|
if DynAny::type_id(result.as_ref()) == core::any::TypeId::of::<WasmSurfaceHandleFrame>() {
|
||||||
None => Err("Invalid output type".to_string()),
|
let Ok(value) = dyn_any::downcast::<WasmSurfaceHandleFrame>(result) else { unreachable!()};
|
||||||
},
|
let new_id = value.surface_handle.surface_id;
|
||||||
|
let old_id = self.canvas_cache.insert(path.to_vec(), new_id);
|
||||||
|
if let Some(old_id) = old_id {
|
||||||
|
if old_id != new_id {
|
||||||
|
self.wasm_io.destroy_surface(old_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(TaggedValue::SurfaceFrame(SurfaceFrame {
|
||||||
|
surface_id: new_id,
|
||||||
|
transform: value.transform,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let type_name = DynAny::type_name(result.as_ref());
|
||||||
|
match TaggedValue::try_from_any(result) {
|
||||||
|
Some(x) => Ok(x),
|
||||||
|
None => Err(format!("Invalid output type: {}", type_name)),
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -439,6 +465,11 @@ impl NodeGraphExecutor {
|
||||||
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
|
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
|
||||||
responses.add(Operation::SetVectorData { path: layer_path, vector_data });
|
responses.add(Operation::SetVectorData { path: layer_path, vector_data });
|
||||||
}
|
}
|
||||||
|
TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform }) => {
|
||||||
|
let transform = transform.to_cols_array();
|
||||||
|
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
|
||||||
|
responses.add(Operation::SetSurface { path: layer_path, surface_id });
|
||||||
|
}
|
||||||
TaggedValue::ImageFrame(ImageFrame { image, transform }) => {
|
TaggedValue::ImageFrame(ImageFrame { image, transform }) => {
|
||||||
// Don't update the frame's transform if the new transform is DAffine2::ZERO.
|
// Don't update the frame's transform if the new transform is DAffine2::ZERO.
|
||||||
let transform = (!transform.abs_diff_eq(DAffine2::ZERO, f64::EPSILON)).then_some(transform.to_cols_array());
|
let transform = (!transform.abs_diff_eq(DAffine2::ZERO, f64::EPSILON)).then_some(transform.to_cols_array());
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,17 @@
|
||||||
export async function updateDocumentArtwork(svg: string) {
|
export async function updateDocumentArtwork(svg: string) {
|
||||||
artworkSvg = svg;
|
artworkSvg = svg;
|
||||||
rasterizedCanvas = undefined;
|
rasterizedCanvas = undefined;
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
const placeholders = window.document.querySelectorAll("[data-canvas] [data-canvas-placeholder]");
|
||||||
|
// Replace the placeholders with the actual canvas elements
|
||||||
|
placeholders.forEach((placeholder) => {
|
||||||
|
const canvasName = placeholder.getAttribute("data-canvas-placeholder");
|
||||||
|
// Get the canvas element from the global storage
|
||||||
|
const context = (window as any).imageCanvases[canvasName];
|
||||||
|
placeholder.replaceWith(context.canvas);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateDocumentOverlays(svg: string) {
|
export function updateDocumentOverlays(svg: string) {
|
||||||
|
|
@ -578,6 +589,11 @@
|
||||||
// Allows dev tools to select the artwork without being blocked by the SVG containers
|
// Allows dev tools to select the artwork without being blocked by the SVG containers
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent inheritance from reaching the child elements
|
// Prevent inheritance from reaching the child elements
|
||||||
> * {
|
> * {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ export async function initWasm(): Promise<void> {
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
await init();
|
await init();
|
||||||
wasmImport = await wasmMemory();
|
wasmImport = await wasmMemory();
|
||||||
|
window["imageCanvases"] = {};
|
||||||
|
|
||||||
|
|
||||||
// Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers
|
// Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,12 @@ bezier-rs = { path = "../../libraries/bezier-rs" }
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
features = ["Window"]
|
features = [
|
||||||
|
"Window",
|
||||||
|
"CanvasRenderingContext2d",
|
||||||
|
"Document",
|
||||||
|
"HtmlCanvasElement",
|
||||||
|
]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = "0.3.22"
|
wasm-bindgen-test = "0.3.22"
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ std = [
|
||||||
"num-traits/std",
|
"num-traits/std",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
]
|
]
|
||||||
default = ["async", "serde", "kurbo", "log", "std", "rand_chacha"]
|
default = ["async", "serde", "kurbo", "log", "std", "rand_chacha", "wasm"]
|
||||||
log = ["dep:log"]
|
log = ["dep:log"]
|
||||||
serde = [
|
serde = [
|
||||||
"dep:serde",
|
"dep:serde",
|
||||||
|
|
@ -32,6 +32,7 @@ async = ["async-trait", "alloc"]
|
||||||
nightly = []
|
nightly = []
|
||||||
alloc = ["dyn-any", "bezier-rs", "once_cell"]
|
alloc = ["dyn-any", "bezier-rs", "once_cell"]
|
||||||
type_id_logging = []
|
type_id_logging = []
|
||||||
|
wasm = ["wasm-bindgen", "web-sys", "js-sys", "std"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dyn-any = { path = "../../libraries/dyn-any", features = [
|
dyn-any = { path = "../../libraries/dyn-any", features = [
|
||||||
|
|
@ -68,3 +69,18 @@ num-derive = { version = "0.3.3" }
|
||||||
num-traits = { version = "0.2.15", default-features = false, features = [
|
num-traits = { version = "0.2.15", default-features = false, features = [
|
||||||
"i128",
|
"i128",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|
||||||
|
wasm-bindgen = { version = "0.2.84", optional = true }
|
||||||
|
js-sys = { version = "0.3.55", optional = true }
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
version = "0.3.4"
|
||||||
|
optional = true
|
||||||
|
features = [
|
||||||
|
"Window",
|
||||||
|
"CanvasRenderingContext2d",
|
||||||
|
"ImageData",
|
||||||
|
"Document",
|
||||||
|
"HtmlCanvasElement",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
use crate::raster::ImageFrame;
|
||||||
|
use crate::transform::Transform;
|
||||||
|
use crate::transform::TransformMut;
|
||||||
|
use crate::Color;
|
||||||
|
use crate::Node;
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
use dyn_any::StaticType;
|
||||||
|
use dyn_any::StaticTypeSized;
|
||||||
|
use glam::DAffine2;
|
||||||
|
|
||||||
|
use core::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
use crate::text::FontCache;
|
||||||
|
|
||||||
|
use core::fmt::Debug;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct SurfaceId(pub u64);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct SurfaceFrame {
|
||||||
|
pub surface_id: SurfaceId,
|
||||||
|
pub transform: DAffine2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for SurfaceFrame {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.surface_id.hash(state);
|
||||||
|
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl StaticType for SurfaceFrame {
|
||||||
|
type Static = SurfaceFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SurfaceHandle<'a, Surface> {
|
||||||
|
pub surface_id: SurfaceId,
|
||||||
|
pub surface: Surface,
|
||||||
|
application_io: &'a dyn ApplicationIo<Surface = Surface>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: 'static> StaticType for SurfaceHandle<'_, T> {
|
||||||
|
type Static = SurfaceHandle<'static, T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SurfaceHandleFrame<'a, Surface> {
|
||||||
|
pub surface_handle: Arc<SurfaceHandle<'a, Surface>>,
|
||||||
|
pub transform: DAffine2,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: 'static> StaticType for SurfaceHandleFrame<'_, T> {
|
||||||
|
type Static = SurfaceHandleFrame<'static, T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Transform for SurfaceHandleFrame<'_, T> {
|
||||||
|
fn transform(&self) -> DAffine2 {
|
||||||
|
self.transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> TransformMut for SurfaceHandleFrame<'_, T> {
|
||||||
|
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||||
|
&mut self.transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: think about how to automatically clean up memory
|
||||||
|
/*
|
||||||
|
impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.application_io.destroy_surface(self.surface_id)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
pub trait ApplicationIo {
|
||||||
|
type Surface;
|
||||||
|
fn create_surface(&self) -> SurfaceHandle<Self::Surface>;
|
||||||
|
fn destroy_surface(&self, surface_id: SurfaceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ApplicationIo> ApplicationIo for &T {
|
||||||
|
type Surface = T::Surface;
|
||||||
|
fn create_surface(&self) -> SurfaceHandle<T::Surface> {
|
||||||
|
(**self).create_surface()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy_surface(&self, surface_id: SurfaceId) {
|
||||||
|
(**self).destroy_surface(surface_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EditorApi<'a, Io> {
|
||||||
|
pub image_frame: Option<ImageFrame<Color>>,
|
||||||
|
pub font_cache: &'a FontCache,
|
||||||
|
pub application_io: &'a Io,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Io> Clone for EditorApi<'a, Io> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
image_frame: self.image_frame.clone(),
|
||||||
|
font_cache: self.font_cache,
|
||||||
|
application_io: self.application_io,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> PartialEq for EditorApi<'a, T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.image_frame == other.image_frame && self.font_cache == other.font_cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Hash for EditorApi<'a, T> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.image_frame.hash(state);
|
||||||
|
self.font_cache.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Debug for EditorApi<'a, T> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
f.debug_struct("EditorApi").field("image_frame", &self.image_frame).field("font_cache", &self.font_cache).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: StaticTypeSized> StaticType for EditorApi<'_, T> {
|
||||||
|
type Static = EditorApi<'static, T::Static>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> AsRef<EditorApi<'a, T>> for EditorApi<'a, T> {
|
||||||
|
fn as_ref(&self) -> &EditorApi<'a, T> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct ExtractImageFrame;
|
||||||
|
|
||||||
|
impl<'a: 'input, 'input, T> Node<'input, &'a EditorApi<'a, T>> for ExtractImageFrame {
|
||||||
|
type Output = ImageFrame<Color>;
|
||||||
|
fn eval(&'input self, editor_api: &'a EditorApi<'a, T>) -> Self::Output {
|
||||||
|
editor_api.image_frame.clone().unwrap_or(ImageFrame::identity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtractImageFrame {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm")]
|
||||||
|
pub mod wasm_application_io;
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
use std::{cell::RefCell, collections::HashMap, sync::Mutex};
|
||||||
|
|
||||||
|
use super::{ApplicationIo, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
|
||||||
|
use crate::{
|
||||||
|
raster::{color::SRGBA8, ImageFrame, Pixel},
|
||||||
|
Node,
|
||||||
|
};
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
use dyn_any::StaticType;
|
||||||
|
use js_sys::{Object, Reflect};
|
||||||
|
use wasm_bindgen::{Clamped, JsCast, JsValue};
|
||||||
|
use web_sys::{window, CanvasRenderingContext2d, HtmlCanvasElement};
|
||||||
|
|
||||||
|
pub struct Canvas(CanvasRenderingContext2d);
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct WasmApplicationIo {
|
||||||
|
ids: RefCell<u64>,
|
||||||
|
canvases: RefCell<HashMap<SurfaceId, CanvasRenderingContext2d>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmApplicationIo {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl StaticType for WasmApplicationIo {
|
||||||
|
type Static = WasmApplicationIo;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type WasmEditorApi<'a> = super::EditorApi<'a, WasmApplicationIo>;
|
||||||
|
|
||||||
|
impl ApplicationIo for WasmApplicationIo {
|
||||||
|
type Surface = CanvasRenderingContext2d;
|
||||||
|
|
||||||
|
fn create_surface(&self) -> SurfaceHandle<Self::Surface> {
|
||||||
|
let mut wrapper = || {
|
||||||
|
let document = window().expect("should have a window in this context").document().expect("window should have a document");
|
||||||
|
|
||||||
|
let canvas: HtmlCanvasElement = document.create_element("canvas")?.dyn_into::<HtmlCanvasElement>()?;
|
||||||
|
// TODO: replace "2d" with "bitmaprenderer" once we switch to ImageBitmap (lives on gpu) from ImageData (lives on cpu)
|
||||||
|
let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap();
|
||||||
|
let mut guard = self.ids.borrow_mut();
|
||||||
|
let id = SurfaceId(*guard);
|
||||||
|
*guard += 1;
|
||||||
|
self.canvases.borrow_mut().insert(id, context.clone());
|
||||||
|
// store the canvas in the global scope so it doesn't get garbage collected
|
||||||
|
|
||||||
|
let window = window().expect("should have a window in this context");
|
||||||
|
let window = Object::from(window);
|
||||||
|
|
||||||
|
let image_canvases_key = JsValue::from_str("imageCanvases");
|
||||||
|
|
||||||
|
let mut canvases = Reflect::get(&window, &image_canvases_key);
|
||||||
|
if let Err(e) = canvases {
|
||||||
|
Reflect::set(&JsValue::from(web_sys::window().unwrap()), &image_canvases_key, &Object::new()).unwrap();
|
||||||
|
canvases = Reflect::get(&window, &image_canvases_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert key and value to JsValue
|
||||||
|
let js_key = JsValue::from_str(format!("canvas{}", id.0).as_str());
|
||||||
|
let js_value = JsValue::from(context.clone());
|
||||||
|
|
||||||
|
let canvases = Object::from(canvases.unwrap());
|
||||||
|
|
||||||
|
// Use Reflect API to set property
|
||||||
|
Reflect::set(&canvases, &js_key, &js_value)?;
|
||||||
|
Ok::<_, JsValue>(SurfaceHandle {
|
||||||
|
surface_id: id,
|
||||||
|
surface: context,
|
||||||
|
application_io: self,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
wrapper().expect("should be able to set canvas in global scope")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy_surface(&self, surface_id: SurfaceId) {
|
||||||
|
self.canvases.borrow_mut().remove(&surface_id);
|
||||||
|
|
||||||
|
let window = window().expect("should have a window in this context");
|
||||||
|
let window = Object::from(window);
|
||||||
|
|
||||||
|
let image_canvases_key = JsValue::from_str("imageCanvases");
|
||||||
|
|
||||||
|
let wrapper = || {
|
||||||
|
if let Ok(canvases) = Reflect::get(&window, &image_canvases_key) {
|
||||||
|
// Convert key and value to JsValue
|
||||||
|
let js_key = JsValue::from_str(format!("canvas{}", surface_id.0).as_str());
|
||||||
|
|
||||||
|
// Use Reflect API to set property
|
||||||
|
Reflect::delete_property(&canvases.into(), &js_key)?;
|
||||||
|
}
|
||||||
|
Ok::<_, JsValue>(())
|
||||||
|
};
|
||||||
|
|
||||||
|
wrapper().expect("should be able to set canvas in global scope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type WasmSurfaceHandle<'a> = SurfaceHandle<'a, CanvasRenderingContext2d>;
|
||||||
|
pub type WasmSurfaceHandleFrame<'a> = SurfaceHandleFrame<'a, CanvasRenderingContext2d>;
|
||||||
|
|
||||||
|
pub struct CreateSurfaceNode {}
|
||||||
|
|
||||||
|
#[node_macro::node_fn(CreateSurfaceNode)]
|
||||||
|
fn create_surface_node<'a: 'input>(editor: &'a WasmEditorApi<'a>) -> Arc<SurfaceHandle<'a, CanvasRenderingContext2d>> {
|
||||||
|
editor.application_io.create_surface().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DrawImageFrameNode<Surface> {
|
||||||
|
surface_handle: Surface,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node_fn(DrawImageFrameNode)]
|
||||||
|
async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_handle: Arc<SurfaceHandle<'a, CanvasRenderingContext2d>>) -> SurfaceHandleFrame<'a, CanvasRenderingContext2d> {
|
||||||
|
let image_data = image.image.data;
|
||||||
|
let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
|
||||||
|
if image.image.width > 0 && image.image.height > 0 {
|
||||||
|
let canvas = surface_handle.surface.canvas().expect("Failed to get canvas");
|
||||||
|
canvas.set_width(image.image.width);
|
||||||
|
canvas.set_height(image.image.height);
|
||||||
|
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, image.image.width as u32, image.image.height as u32).expect("Failed to construct ImageData");
|
||||||
|
surface_handle.surface.put_image_data(&image_data, 0.0, 0.0).unwrap();
|
||||||
|
}
|
||||||
|
SurfaceHandleFrame {
|
||||||
|
surface_handle: surface_handle.into(),
|
||||||
|
transform: image.transform,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,8 @@ pub use graphic_element::*;
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub mod vector;
|
pub mod vector;
|
||||||
|
|
||||||
|
pub mod application_io;
|
||||||
|
|
||||||
pub mod quantization;
|
pub mod quantization;
|
||||||
|
|
||||||
use core::any::TypeId;
|
use core::any::TypeId;
|
||||||
|
|
@ -142,5 +144,6 @@ impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> +
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
pub use crate::application_io::{ExtractImageFrame, SurfaceFrame, SurfaceId};
|
||||||
pub use crate::raster::image::{EditorApi, ExtractImageFrame};
|
#[cfg(feature = "wasm")]
|
||||||
|
pub use application_io::{wasm_application_io, wasm_application_io::WasmEditorApi as EditorApi};
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ pub struct AddParameterNode<Second> {
|
||||||
second: Second,
|
second: Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node_new(AddParameterNode)]
|
#[node_macro::node_fn(AddParameterNode)]
|
||||||
fn add_parameter<U, T>(first: U, second: T) -> <U as Add<T>>::Output
|
fn add_parameter<U, T>(first: U, second: T) -> <U as Add<T>>::Output
|
||||||
where
|
where
|
||||||
U: Add<T>,
|
U: Add<T>,
|
||||||
|
|
@ -30,24 +30,6 @@ where
|
||||||
first + second
|
first + second
|
||||||
}
|
}
|
||||||
|
|
||||||
#[automatically_derived]
|
|
||||||
impl<'input, U: 'input, T: 'input, S0: 'input> Node<'input, U> for AddParameterNode<S0>
|
|
||||||
where
|
|
||||||
U: Add<T>,
|
|
||||||
S0: Node<'input, (), Output = T>,
|
|
||||||
{
|
|
||||||
type Output = <U as Add<T>>::Output;
|
|
||||||
#[inline]
|
|
||||||
fn eval(&'input self, first: U) -> Self::Output {
|
|
||||||
let second = self.second.eval(());
|
|
||||||
{
|
|
||||||
{
|
|
||||||
first + second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MulParameterNode<Second> {
|
pub struct MulParameterNode<Second> {
|
||||||
second: Second,
|
second: Second,
|
||||||
}
|
}
|
||||||
|
|
@ -224,6 +206,18 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct IntoNode<I, O> {
|
||||||
|
_i: PhantomData<I>,
|
||||||
|
_o: PhantomData<O>,
|
||||||
|
}
|
||||||
|
#[node_macro::node_fn(IntoNode<_I, _O>)]
|
||||||
|
fn into<_I, _O>(input: _I) -> _O
|
||||||
|
where
|
||||||
|
_I: Into<_O>,
|
||||||
|
{
|
||||||
|
input.into()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::Node;
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
pub use self::color::{Color, Luma};
|
pub use self::color::{Color, Luma, SRGBA8};
|
||||||
|
|
||||||
pub mod adjustments;
|
pub mod adjustments;
|
||||||
pub mod bbox;
|
pub mod bbox;
|
||||||
|
|
|
||||||
|
|
@ -451,8 +451,8 @@ pub struct BlendNode<BlendMode, Opacity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(BlendNode)]
|
#[node_macro::node_fn(BlendNode)]
|
||||||
fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f32) -> Color {
|
fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f64) -> Color {
|
||||||
let opacity = opacity / 100.;
|
let opacity = opacity as f32 / 100.;
|
||||||
|
|
||||||
let (foreground, background) = input;
|
let (foreground, background) = input;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,92 @@ use spirv_std::num_traits::Euclid;
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
use super::{Alpha, AssociatedAlpha, Luminance, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
|
use super::{
|
||||||
|
discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float},
|
||||||
|
Alpha, AssociatedAlpha, Luminance, Pixel, RGBMut, Rec709Primaries, RGB, SRGB,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, Pod, Zeroable)]
|
||||||
|
pub struct SRGBA8 {
|
||||||
|
red: u8,
|
||||||
|
green: u8,
|
||||||
|
blue: u8,
|
||||||
|
alpha: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for SRGBA8 {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(c: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
red: float_to_srgb_u8(c.r()),
|
||||||
|
green: float_to_srgb_u8(c.g()),
|
||||||
|
blue: float_to_srgb_u8(c.b()),
|
||||||
|
alpha: (c.a() * 255.0) as u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SRGBA8> for Color {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(color: SRGBA8) -> Self {
|
||||||
|
Self {
|
||||||
|
red: srgb_u8_to_float(color.red),
|
||||||
|
green: srgb_u8_to_float(color.green),
|
||||||
|
blue: srgb_u8_to_float(color.blue),
|
||||||
|
alpha: color.alpha as f32 / 255.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Luminance for SRGBA8 {
|
||||||
|
type LuminanceChannel = f32;
|
||||||
|
#[inline(always)]
|
||||||
|
fn luminance(&self) -> f32 {
|
||||||
|
// TODO: verify this is correct for sRGB
|
||||||
|
0.2126 * self.red() + 0.7152 * self.green() + 0.0722 * self.blue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RGB for SRGBA8 {
|
||||||
|
type ColorChannel = f32;
|
||||||
|
#[inline(always)]
|
||||||
|
fn red(&self) -> f32 {
|
||||||
|
self.red as f32 / 255.0
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
fn green(&self) -> f32 {
|
||||||
|
self.green as f32 / 255.0
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
fn blue(&self) -> f32 {
|
||||||
|
self.blue as f32 / 255.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rec709Primaries for SRGBA8 {}
|
||||||
|
impl SRGB for SRGBA8 {}
|
||||||
|
|
||||||
|
impl Alpha for SRGBA8 {
|
||||||
|
type AlphaChannel = f32;
|
||||||
|
#[inline(always)]
|
||||||
|
fn alpha(&self) -> f32 {
|
||||||
|
self.alpha as f32 / 255.0
|
||||||
|
}
|
||||||
|
|
||||||
|
const TRANSPARENT: Self = SRGBA8 { red: 0, green: 0, blue: 0, alpha: 0 };
|
||||||
|
|
||||||
|
fn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self {
|
||||||
|
let alpha = alpha * 255.0;
|
||||||
|
let mut result = *self;
|
||||||
|
result.alpha = (alpha * self.alpha()) as u8;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pixel for SRGBA8 {}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
|
|
||||||
|
|
@ -325,44 +325,43 @@ impl<P: Hash + Pixel> Hash for ImageFrame<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::text::FontCache;
|
/* This does not work because of missing specialization
|
||||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
* so we have to manually implement this for now
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
impl<S: Into<P> + Pixel, P: Pixel> From<Image<S>> for Image<P> {
|
||||||
pub struct EditorApi<'a> {
|
fn from(image: Image<S>) -> Self {
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
let data = image.data.into_iter().map(|x| x.into()).collect();
|
||||||
pub image_frame: Option<ImageFrame<Color>>,
|
Self {
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
data,
|
||||||
pub font_cache: Option<&'a FontCache>,
|
width: image.width,
|
||||||
}
|
height: image.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
unsafe impl StaticType for EditorApi<'_> {
|
impl From<ImageFrame<Color>> for ImageFrame<SRGBA8> {
|
||||||
type Static = EditorApi<'static>;
|
fn from(image: ImageFrame<Color>) -> Self {
|
||||||
}
|
let data = image.image.data.into_iter().map(|x| x.into()).collect();
|
||||||
|
Self {
|
||||||
impl EditorApi<'_> {
|
image: Image {
|
||||||
pub fn empty() -> Self {
|
data,
|
||||||
Self { image_frame: None, font_cache: None }
|
width: image.image.width,
|
||||||
|
height: image.image.height,
|
||||||
|
},
|
||||||
|
transform: image.transform,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AsRef<EditorApi<'a>> for EditorApi<'a> {
|
impl From<ImageFrame<SRGBA8>> for ImageFrame<Color> {
|
||||||
fn as_ref(&self) -> &EditorApi<'a> {
|
fn from(image: ImageFrame<SRGBA8>) -> Self {
|
||||||
self
|
let data = image.image.data.into_iter().map(|x| x.into()).collect();
|
||||||
}
|
Self {
|
||||||
}
|
image: Image {
|
||||||
|
data,
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
width: image.image.width,
|
||||||
pub struct ExtractImageFrame;
|
height: image.image.height,
|
||||||
|
},
|
||||||
impl<'a: 'input, 'input> Node<'input, &'a EditorApi<'a>> for ExtractImageFrame {
|
transform: image.transform,
|
||||||
type Output = ImageFrame<Color>;
|
}
|
||||||
fn eval(&'input self, editor_api: &'a EditorApi<'a>) -> Self::Output {
|
|
||||||
editor_api.image_frame.clone().unwrap_or(ImageFrame::identity())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtractImageFrame {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,6 @@ pub struct TextGenerator<Text, FontName, Size> {
|
||||||
|
|
||||||
#[node_fn(TextGenerator)]
|
#[node_fn(TextGenerator)]
|
||||||
fn generate_text<'a: 'input>(editor: &'a EditorApi<'a>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
|
fn generate_text<'a: 'input>(editor: &'a EditorApi<'a>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
|
||||||
let buzz_face = editor.font_cache.and_then(|cache| cache.get(&font_name)).map(|data| load_face(data));
|
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
|
||||||
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None))
|
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ pub struct DocumentNode {
|
||||||
|
|
||||||
impl DocumentNode {
|
impl DocumentNode {
|
||||||
pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize, lambda: bool) {
|
pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize, lambda: bool) {
|
||||||
let input = self
|
let (index, _) = self
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
|
@ -53,7 +53,6 @@ impl DocumentNode {
|
||||||
.nth(offset)
|
.nth(offset)
|
||||||
.unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}"));
|
.unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}"));
|
||||||
|
|
||||||
let index = input.0;
|
|
||||||
self.inputs[index] = NodeInput::Node { node_id, output_index, lambda };
|
self.inputs[index] = NodeInput::Node { node_id, output_index, lambda };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,7 +66,7 @@ impl DocumentNode {
|
||||||
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
|
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
|
||||||
}
|
}
|
||||||
NodeInput::Node { node_id, output_index, lambda } => {
|
NodeInput::Node { node_id, output_index, lambda } => {
|
||||||
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode.");
|
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode. {:#?}", self.name);
|
||||||
(ProtoNodeInput::Node(node_id, lambda), ConstructionArgs::Nodes(vec![]))
|
(ProtoNodeInput::Node(node_id, lambda), ConstructionArgs::Nodes(vec![]))
|
||||||
}
|
}
|
||||||
NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])),
|
NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])),
|
||||||
|
|
@ -564,9 +563,8 @@ impl NodeNetwork {
|
||||||
self.previous_outputs
|
self.previous_outputs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|nodes| nodes.iter_mut().for_each(|output| output.node_id = f(output.node_id)));
|
.for_each(|nodes| nodes.iter_mut().for_each(|output| output.node_id = f(output.node_id)));
|
||||||
let mut empty = HashMap::new();
|
let nodes = std::mem::take(&mut self.nodes);
|
||||||
std::mem::swap(&mut self.nodes, &mut empty);
|
self.nodes = nodes
|
||||||
self.nodes = empty
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(id, mut node)| {
|
.map(|(id, mut node)| {
|
||||||
node.inputs.iter_mut().for_each(|input| input.map_ids(f));
|
node.inputs.iter_mut().for_each(|input| input.map_ids(f));
|
||||||
|
|
@ -596,70 +594,28 @@ impl NodeNetwork {
|
||||||
network.generate_node_paths(new_path.as_slice());
|
network.generate_node_paths(new_path.as_slice());
|
||||||
}
|
}
|
||||||
if node.path.is_some() {
|
if node.path.is_some() {
|
||||||
log::warn!("Overwriting node path");
|
log::warn!("Attempting to overwrite node path");
|
||||||
|
} else {
|
||||||
|
node.path = Some(new_path);
|
||||||
}
|
}
|
||||||
node.path = Some(new_path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When a node has multiple outputs, we actually just duplicate the node and evaluate each output separately
|
fn replace_node_inputs(&mut self, old_input: NodeInput, new_input: NodeInput) {
|
||||||
pub fn duplicate_outputs(&mut self, mut gen_id: &mut impl FnMut() -> NodeId) {
|
|
||||||
let mut duplicating_nodes = HashMap::new();
|
|
||||||
// Find the nodes where the inputs require duplicating
|
|
||||||
for node in &mut self.nodes.values_mut() {
|
|
||||||
// Recursively duplicate children
|
|
||||||
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
|
|
||||||
network.duplicate_outputs(gen_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for input in &mut node.inputs {
|
|
||||||
let &mut NodeInput::Node { node_id, output_index, .. } = input else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
// Use the initial node when getting the first output
|
|
||||||
if output_index == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Get the existing duplicated node id (or create a new one)
|
|
||||||
let duplicated_node_id = *duplicating_nodes.entry((node_id, output_index)).or_insert_with(&mut gen_id);
|
|
||||||
// Use the first output from the duplicated node
|
|
||||||
*input = NodeInput::node(duplicated_node_id, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Find the network outputs that require duplicating
|
|
||||||
for network_output in &mut self.outputs {
|
|
||||||
// Use the initial node when getting the first output
|
|
||||||
if network_output.node_output_index == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Get the existing duplicated node id (or create a new one)
|
|
||||||
let duplicated_node_id = *duplicating_nodes.entry((network_output.node_id, network_output.node_output_index)).or_insert_with(&mut gen_id);
|
|
||||||
// Use the first output from the duplicated node
|
|
||||||
*network_output = NodeOutput::new(duplicated_node_id, 0);
|
|
||||||
}
|
|
||||||
// Duplicate the nodes
|
|
||||||
for ((original_node_id, output_index), new_node_id) in duplicating_nodes {
|
|
||||||
let Some(original_node) = self.nodes.get(&original_node_id) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let mut new_node = original_node.clone();
|
|
||||||
// Update the required outputs from a nested network to be just the relevant output
|
|
||||||
if let DocumentNodeImplementation::Network(network) = &mut new_node.implementation {
|
|
||||||
if network.outputs.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
network.outputs = vec![network.outputs[output_index]];
|
|
||||||
}
|
|
||||||
self.nodes.insert(new_node_id, new_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure all nodes only have one output
|
|
||||||
for node in self.nodes.values_mut() {
|
for node in self.nodes.values_mut() {
|
||||||
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
|
let node_string = format!("{:?}", node);
|
||||||
if network.outputs.is_empty() {
|
node.inputs.iter_mut().for_each(|input| {
|
||||||
continue;
|
if *input == old_input {
|
||||||
|
*input = new_input.clone();
|
||||||
}
|
}
|
||||||
network.outputs = vec![network.outputs[0]];
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_network_outputs(&mut self, old_output: NodeOutput, new_output: NodeOutput) {
|
||||||
|
for output in self.outputs.iter_mut() {
|
||||||
|
if *output == old_output {
|
||||||
|
*output = new_output.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -715,8 +671,14 @@ impl NodeNetwork {
|
||||||
self.nodes.insert(id, node);
|
self.nodes.insert(id, node);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace value inputs with value nodes
|
// replace value inputs with value nodes
|
||||||
for input in &mut node.inputs {
|
for input in &mut node.inputs {
|
||||||
|
// Skip inputs that are already value nodes
|
||||||
|
if node.implementation == DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
let mut dummy_input = NodeInput::ShortCircut(concrete!(()));
|
let mut dummy_input = NodeInput::ShortCircut(concrete!(()));
|
||||||
std::mem::swap(&mut dummy_input, input);
|
std::mem::swap(&mut dummy_input, input);
|
||||||
if let NodeInput::Value { tagged_value, exposed } = dummy_input {
|
if let NodeInput::Value { tagged_value, exposed } = dummy_input {
|
||||||
|
|
@ -749,54 +711,61 @@ impl NodeNetwork {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match node.implementation {
|
if let DocumentNodeImplementation::Network(mut inner_network) = node.implementation {
|
||||||
DocumentNodeImplementation::Network(mut inner_network) => {
|
// Connect all network inputs to either the parent network nodes, or newly created value nodes.
|
||||||
// Connect all network inputs to either the parent network nodes, or newly created value nodes.
|
inner_network.map_ids(|inner_id| map_ids(id, inner_id));
|
||||||
inner_network.map_ids(|inner_id| map_ids(id, inner_id));
|
let new_nodes = inner_network.nodes.keys().cloned().collect::<Vec<_>>();
|
||||||
let new_nodes = inner_network.nodes.keys().cloned().collect::<Vec<_>>();
|
// Copy nodes from the inner network into the parent network
|
||||||
// Copy nodes from the inner network into the parent network
|
self.nodes.extend(inner_network.nodes);
|
||||||
self.nodes.extend(inner_network.nodes);
|
self.disabled.extend(inner_network.disabled);
|
||||||
self.disabled.extend(inner_network.disabled);
|
|
||||||
|
|
||||||
let mut network_offsets = HashMap::new();
|
let mut network_offsets = HashMap::new();
|
||||||
for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) {
|
assert_eq!(
|
||||||
let offset = network_offsets.entry(network_input).or_insert(0);
|
node.inputs.len(),
|
||||||
match document_input {
|
inner_network.inputs.len(),
|
||||||
NodeInput::Node { node_id, output_index, lambda } => {
|
"The number of inputs to the node and the inner network must be the same {}",
|
||||||
let network_input = self.nodes.get_mut(network_input).unwrap();
|
node.name
|
||||||
network_input.populate_first_network_input(node_id, output_index, *offset, lambda);
|
);
|
||||||
}
|
// Match the document node input and the inputs of the inner network
|
||||||
NodeInput::Network(_) => {
|
for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) {
|
||||||
*network_offsets.get_mut(network_input).unwrap() += 1;
|
// Keep track of how many network inputs we have already connected for each node
|
||||||
if let Some(index) = self.inputs.iter().position(|i| *i == id) {
|
let offset = network_offsets.entry(network_input).or_insert(0);
|
||||||
self.inputs[index] = *network_input;
|
match document_input {
|
||||||
}
|
// If the input to self is a node, connect the corresponding output of the inner network to it
|
||||||
}
|
NodeInput::Node { node_id, output_index, lambda } => {
|
||||||
NodeInput::ShortCircut(_) => (),
|
let network_input = self.nodes.get_mut(network_input).unwrap();
|
||||||
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
|
network_input.populate_first_network_input(node_id, output_index, *offset, lambda);
|
||||||
NodeInput::Inline(_) => (),
|
|
||||||
}
|
}
|
||||||
}
|
NodeInput::Network(_) => {
|
||||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
|
*network_offsets.get_mut(network_input).unwrap() += 1;
|
||||||
node.inputs = inner_network
|
if let Some(index) = self.inputs.iter().position(|i| *i == id) {
|
||||||
.outputs
|
self.inputs[index] = *network_input;
|
||||||
.iter()
|
}
|
||||||
.map(|&NodeOutput { node_id, node_output_index }| NodeInput::Node {
|
}
|
||||||
node_id,
|
NodeInput::ShortCircut(_) => (),
|
||||||
output_index: node_output_index,
|
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
|
||||||
lambda: false,
|
NodeInput::Inline(_) => (),
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for node_id in new_nodes {
|
|
||||||
self.flatten_with_fns(node_id, map_ids, gen_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentNodeImplementation::Unresolved(_) => (),
|
|
||||||
DocumentNodeImplementation::Extract => (),
|
// Connect all nodes that were previously connected to this node to the nodes of the inner network
|
||||||
|
for (i, output) in inner_network.outputs.into_iter().enumerate() {
|
||||||
|
let node_input = |node_id, output_index, lambda| NodeInput::Node { node_id, output_index, lambda };
|
||||||
|
|
||||||
|
self.replace_node_inputs(node_input(id, i, false), node_input(output.node_id, output.node_output_index, false));
|
||||||
|
self.replace_node_inputs(node_input(id, i, true), node_input(output.node_id, output.node_output_index, true));
|
||||||
|
|
||||||
|
self.replace_network_outputs(NodeOutput::new(id, i), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
for node_id in new_nodes {
|
||||||
|
self.flatten_with_fns(node_id, map_ids, gen_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the node is not a network, it is a primitive node and can be inserted into the network as is.
|
||||||
|
assert!(!self.nodes.contains_key(&id), "Trying to insert a node into the network caused an id conflict");
|
||||||
|
self.nodes.insert(id, node);
|
||||||
}
|
}
|
||||||
assert!(!self.nodes.contains_key(&id), "Trying to insert a node into the network caused an id conflict");
|
|
||||||
self.nodes.insert(id, node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> {
|
fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> {
|
||||||
|
|
@ -1090,17 +1059,8 @@ mod test {
|
||||||
fn resolve_flatten_add_as_proto_network() {
|
fn resolve_flatten_add_as_proto_network() {
|
||||||
let construction_network = ProtoNetwork {
|
let construction_network = ProtoNetwork {
|
||||||
inputs: vec![10],
|
inputs: vec![10],
|
||||||
output: 1,
|
output: 11,
|
||||||
nodes: [
|
nodes: [
|
||||||
(
|
|
||||||
1,
|
|
||||||
ProtoNode {
|
|
||||||
identifier: "graphene_core::ops::IdNode".into(),
|
|
||||||
input: ProtoNodeInput::Node(11, false),
|
|
||||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
|
||||||
document_node_path: vec![1],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
10,
|
10,
|
||||||
ProtoNode {
|
ProtoNode {
|
||||||
|
|
@ -1135,18 +1095,8 @@ mod test {
|
||||||
fn flat_network() -> NodeNetwork {
|
fn flat_network() -> NodeNetwork {
|
||||||
NodeNetwork {
|
NodeNetwork {
|
||||||
inputs: vec![10],
|
inputs: vec![10],
|
||||||
outputs: vec![NodeOutput::new(1, 0)],
|
outputs: vec![NodeOutput::new(11, 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
(
|
|
||||||
1,
|
|
||||||
DocumentNode {
|
|
||||||
name: "Inc".into(),
|
|
||||||
inputs: vec![NodeInput::node(11, 0)],
|
|
||||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
|
||||||
path: Some(vec![1]),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
10,
|
10,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
|
|
@ -1223,7 +1173,7 @@ mod test {
|
||||||
outputs: network_outputs,
|
outputs: network_outputs,
|
||||||
nodes: [
|
nodes: [
|
||||||
(
|
(
|
||||||
10,
|
1,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Nested network".into(),
|
name: "Nested network".into(),
|
||||||
inputs: vec![NodeInput::value(TaggedValue::F32(1.), false), NodeInput::value(TaggedValue::F32(2.), false)],
|
inputs: vec![NodeInput::value(TaggedValue::F32(1.), false), NodeInput::value(TaggedValue::F32(2.), false)],
|
||||||
|
|
@ -1232,7 +1182,7 @@ mod test {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
11,
|
2,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Result".into(),
|
name: "Result".into(),
|
||||||
inputs: vec![result_node_input],
|
inputs: vec![result_node_input],
|
||||||
|
|
@ -1246,26 +1196,25 @@ mod test {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut new_ids = 101..;
|
let mut new_ids = 101..;
|
||||||
network.duplicate_outputs(&mut || new_ids.next().unwrap());
|
network.flatten_with_fns(1, |self_id, inner_id| self_id * 10 + inner_id, || 10000);
|
||||||
|
network.flatten_with_fns(2, |self_id, inner_id| self_id * 10 + inner_id, || 10001);
|
||||||
network.remove_dead_nodes();
|
network.remove_dead_nodes();
|
||||||
network
|
network
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_duplicate() {
|
fn simple_duplicate() {
|
||||||
let result = output_duplicate(vec![NodeOutput::new(10, 1)], NodeInput::node(10, 0));
|
let result = output_duplicate(vec![NodeOutput::new(1, 0)], NodeInput::node(1, 0));
|
||||||
|
println!("{:#?}", result);
|
||||||
assert_eq!(result.outputs.len(), 1, "The number of outputs should remain as 1");
|
assert_eq!(result.outputs.len(), 1, "The number of outputs should remain as 1");
|
||||||
assert_eq!(result.outputs[0], NodeOutput::new(101, 0), "The outer network output should be from a duplicated inner network");
|
assert_eq!(result.outputs[0], NodeOutput::new(11, 0), "The outer network output should be from a duplicated inner network");
|
||||||
assert_eq!(result.nodes.keys().copied().collect::<Vec<_>>(), vec![101], "Should just call nested network");
|
let mut ids = result.nodes.keys().copied().collect::<Vec<_>>();
|
||||||
let nested_network_node = result.nodes.get(&101).unwrap();
|
ids.sort();
|
||||||
assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change");
|
assert_eq!(ids, vec![11, 10010], "Should only contain identity and values");
|
||||||
assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(2.), false)], "Input should be 2");
|
|
||||||
let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network");
|
|
||||||
assert_eq!(inner_network.inputs, vec![2], "The input should be sent to the second node");
|
|
||||||
assert_eq!(inner_network.outputs, vec![NodeOutput::new(2, 0)], "The output should be node id 2");
|
|
||||||
assert_eq!(inner_network.nodes.get(&2).unwrap().name, "Identity 2", "The node should be identity 2");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Write more tests
|
||||||
|
/*
|
||||||
#[test]
|
#[test]
|
||||||
fn out_of_order_duplicate() {
|
fn out_of_order_duplicate() {
|
||||||
let result = output_duplicate(vec![NodeOutput::new(10, 1), NodeOutput::new(10, 0)], NodeInput::node(10, 0));
|
let result = output_duplicate(vec![NodeOutput::new(10, 1), NodeOutput::new(10, 0)], NodeInput::node(10, 0));
|
||||||
|
|
@ -1303,4 +1252,5 @@ mod test {
|
||||||
assert_eq!(inner_network.outputs, vec![NodeOutput::new(2, 0)], "The output should be node id 2");
|
assert_eq!(inner_network.outputs, vec![NodeOutput::new(2, 0)], "The output should be node id 2");
|
||||||
assert_eq!(inner_network.nodes.get(&2).unwrap().name, "Identity 2", "The node should be identity 2");
|
assert_eq!(inner_network.nodes.get(&2).unwrap().name, "Identity 2", "The node should be identity 2");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,11 @@ pub enum TaggedValue {
|
||||||
Font(graphene_core::text::Font),
|
Font(graphene_core::text::Font),
|
||||||
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
|
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
|
||||||
Segments(Vec<graphene_core::raster::ImageFrame<Color>>),
|
Segments(Vec<graphene_core::raster::ImageFrame<Color>>),
|
||||||
EditorApi(graphene_core::EditorApi<'static>),
|
|
||||||
DocumentNode(DocumentNode),
|
DocumentNode(DocumentNode),
|
||||||
GraphicGroup(graphene_core::GraphicGroup),
|
GraphicGroup(graphene_core::GraphicGroup),
|
||||||
Artboard(graphene_core::Artboard),
|
Artboard(graphene_core::Artboard),
|
||||||
IVec2(glam::IVec2),
|
IVec2(glam::IVec2),
|
||||||
|
SurfaceFrame(graphene_core::SurfaceFrame),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||||
|
|
@ -121,11 +121,11 @@ impl Hash for TaggedValue {
|
||||||
segment.hash(state)
|
segment.hash(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::EditorApi(editor_api) => editor_api.hash(state),
|
|
||||||
Self::DocumentNode(document_node) => document_node.hash(state),
|
Self::DocumentNode(document_node) => document_node.hash(state),
|
||||||
Self::GraphicGroup(graphic_group) => graphic_group.hash(state),
|
Self::GraphicGroup(graphic_group) => graphic_group.hash(state),
|
||||||
Self::Artboard(artboard) => artboard.hash(state),
|
Self::Artboard(artboard) => artboard.hash(state),
|
||||||
Self::IVec2(v) => v.hash(state),
|
Self::IVec2(v) => v.hash(state),
|
||||||
|
Self::SurfaceFrame(surface_id) => surface_id.hash(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,11 +173,11 @@ impl<'a> TaggedValue {
|
||||||
TaggedValue::Font(x) => Box::new(x),
|
TaggedValue::Font(x) => Box::new(x),
|
||||||
TaggedValue::BrushStrokes(x) => Box::new(x),
|
TaggedValue::BrushStrokes(x) => Box::new(x),
|
||||||
TaggedValue::Segments(x) => Box::new(x),
|
TaggedValue::Segments(x) => Box::new(x),
|
||||||
TaggedValue::EditorApi(x) => Box::new(x),
|
|
||||||
TaggedValue::DocumentNode(x) => Box::new(x),
|
TaggedValue::DocumentNode(x) => Box::new(x),
|
||||||
TaggedValue::GraphicGroup(x) => Box::new(x),
|
TaggedValue::GraphicGroup(x) => Box::new(x),
|
||||||
TaggedValue::Artboard(x) => Box::new(x),
|
TaggedValue::Artboard(x) => Box::new(x),
|
||||||
TaggedValue::IVec2(x) => Box::new(x),
|
TaggedValue::IVec2(x) => Box::new(x),
|
||||||
|
TaggedValue::SurfaceFrame(x) => Box::new(x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,11 +237,11 @@ impl<'a> TaggedValue {
|
||||||
TaggedValue::Font(_) => concrete!(graphene_core::text::Font),
|
TaggedValue::Font(_) => concrete!(graphene_core::text::Font),
|
||||||
TaggedValue::BrushStrokes(_) => concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
|
TaggedValue::BrushStrokes(_) => concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
|
||||||
TaggedValue::Segments(_) => concrete!(graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>),
|
TaggedValue::Segments(_) => concrete!(graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>),
|
||||||
TaggedValue::EditorApi(_) => concrete!(graphene_core::EditorApi),
|
|
||||||
TaggedValue::DocumentNode(_) => concrete!(crate::document::DocumentNode),
|
TaggedValue::DocumentNode(_) => concrete!(crate::document::DocumentNode),
|
||||||
TaggedValue::GraphicGroup(_) => concrete!(graphene_core::GraphicGroup),
|
TaggedValue::GraphicGroup(_) => concrete!(graphene_core::GraphicGroup),
|
||||||
TaggedValue::Artboard(_) => concrete!(graphene_core::Artboard),
|
TaggedValue::Artboard(_) => concrete!(graphene_core::Artboard),
|
||||||
TaggedValue::IVec2(_) => concrete!(glam::IVec2),
|
TaggedValue::IVec2(_) => concrete!(glam::IVec2),
|
||||||
|
TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,11 +289,11 @@ impl<'a> TaggedValue {
|
||||||
x if x == TypeId::of::<graphene_core::text::Font>() => Some(TaggedValue::Font(*downcast(input).unwrap())),
|
x if x == TypeId::of::<graphene_core::text::Font>() => Some(TaggedValue::Font(*downcast(input).unwrap())),
|
||||||
x if x == TypeId::of::<Vec<graphene_core::vector::brush_stroke::BrushStroke>>() => Some(TaggedValue::BrushStrokes(*downcast(input).unwrap())),
|
x if x == TypeId::of::<Vec<graphene_core::vector::brush_stroke::BrushStroke>>() => Some(TaggedValue::BrushStrokes(*downcast(input).unwrap())),
|
||||||
x if x == TypeId::of::<graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>>() => Some(TaggedValue::Segments(*downcast(input).unwrap())),
|
x if x == TypeId::of::<graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>>() => Some(TaggedValue::Segments(*downcast(input).unwrap())),
|
||||||
x if x == TypeId::of::<graphene_core::EditorApi>() => Some(TaggedValue::EditorApi(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<crate::document::DocumentNode>() => Some(TaggedValue::DocumentNode(*downcast(input).unwrap())),
|
x if x == TypeId::of::<crate::document::DocumentNode>() => Some(TaggedValue::DocumentNode(*downcast(input).unwrap())),
|
||||||
x if x == TypeId::of::<graphene_core::GraphicGroup>() => Some(TaggedValue::GraphicGroup(*downcast(input).unwrap())),
|
x if x == TypeId::of::<graphene_core::GraphicGroup>() => Some(TaggedValue::GraphicGroup(*downcast(input).unwrap())),
|
||||||
x if x == TypeId::of::<graphene_core::Artboard>() => Some(TaggedValue::Artboard(*downcast(input).unwrap())),
|
x if x == TypeId::of::<graphene_core::Artboard>() => Some(TaggedValue::Artboard(*downcast(input).unwrap())),
|
||||||
x if x == TypeId::of::<glam::IVec2>() => Some(TaggedValue::IVec2(*downcast(input).unwrap())),
|
x if x == TypeId::of::<glam::IVec2>() => Some(TaggedValue::IVec2(*downcast(input).unwrap())),
|
||||||
|
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Some(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,10 @@ impl Compiler {
|
||||||
proto_networks.map(move |mut proto_network| {
|
proto_networks.map(move |mut proto_network| {
|
||||||
if resolve_inputs {
|
if resolve_inputs {
|
||||||
println!("resolving inputs");
|
println!("resolving inputs");
|
||||||
log::debug!("resolving inputs");
|
|
||||||
proto_network.resolve_inputs();
|
proto_network.resolve_inputs();
|
||||||
}
|
}
|
||||||
proto_network.reorder_ids();
|
proto_network.reorder_ids();
|
||||||
proto_network.generate_stable_node_ids();
|
proto_network.generate_stable_node_ids();
|
||||||
log::debug!("proto network: {:?}", proto_network);
|
|
||||||
proto_network
|
proto_network
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,7 @@ impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'i
|
||||||
#[inline]
|
#[inline]
|
||||||
fn eval(&'input self, input: I) -> Self::Output {
|
fn eval(&'input self, input: I) -> Self::Output {
|
||||||
{
|
{
|
||||||
|
let node_name = self.node.node_name();
|
||||||
let input = Box::new(input);
|
let input = Box::new(input);
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let out: Box<&_> = dyn_any::downcast::<&O>(self.node.eval(input).await).unwrap_or_else(|e| panic!("DowncastBothRefNode Input {e}"));
|
let out: Box<&_> = dyn_any::downcast::<&O>(self.node.eval(input).await).unwrap_or_else(|e| panic!("DowncastBothRefNode Input {e}"));
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,8 @@ where
|
||||||
{
|
{
|
||||||
type Output = <Input>::Output;
|
type Output = <Input>::Output;
|
||||||
fn eval(&'i self, _: &'i T) -> Self::Output {
|
fn eval(&'i self, _: &'i T) -> Self::Output {
|
||||||
self.input.eval(())
|
let result = self.input.eval(());
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ impl DynamicExecutor {
|
||||||
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(), String> {
|
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(), String> {
|
||||||
self.output = proto_network.output;
|
self.output = proto_network.output;
|
||||||
self.typing_context.update(&proto_network)?;
|
self.typing_context.update(&proto_network)?;
|
||||||
trace!("setting output to {}", self.output);
|
|
||||||
let mut orphans = self.tree.update(proto_network, &self.typing_context).await?;
|
let mut orphans = self.tree.update(proto_network, &self.typing_context).await?;
|
||||||
core::mem::swap(&mut self.orphaned_nodes, &mut orphans);
|
core::mem::swap(&mut self.orphaned_nodes, &mut orphans);
|
||||||
for node_id in orphans {
|
for node_id in orphans {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ use graphene_core::structural::Then;
|
||||||
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
|
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
|
||||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||||
use graphene_core::vector::VectorData;
|
use graphene_core::vector::VectorData;
|
||||||
|
use graphene_core::wasm_application_io::WasmSurfaceHandle;
|
||||||
|
use graphene_core::wasm_application_io::*;
|
||||||
use graphene_core::{concrete, generic, value_fn};
|
use graphene_core::{concrete, generic, value_fn};
|
||||||
use graphene_core::{fn_type, raster::*};
|
use graphene_core::{fn_type, raster::*};
|
||||||
use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor};
|
use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor};
|
||||||
|
|
@ -22,6 +24,7 @@ use dyn_any::StaticType;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
macro_rules! construct_node {
|
macro_rules! construct_node {
|
||||||
($args: ident, $path:ty, [$($type:tt),*]) => { async move {
|
($args: ident, $path:ty, [$($type:tt),*]) => { async move {
|
||||||
|
|
@ -41,7 +44,7 @@ macro_rules! construct_node {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! register_node {
|
macro_rules! register_node {
|
||||||
($path:ty, input: $input:ty, params: [$($type:ty),*]) => {
|
($path:ty, input: $input:ty, params: [ $($type:ty),*]) => {
|
||||||
vec![
|
vec![
|
||||||
(
|
(
|
||||||
NodeIdentifier::new(stringify!($path)),
|
NodeIdentifier::new(stringify!($path)),
|
||||||
|
|
@ -158,6 +161,8 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [&f64]),
|
register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [&f64]),
|
||||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [&f64]),
|
register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [&f64]),
|
||||||
register_node!(graphene_core::ops::SomeNode, input: graphene_core::EditorApi, params: []),
|
register_node!(graphene_core::ops::SomeNode, input: graphene_core::EditorApi, params: []),
|
||||||
|
register_node!(graphene_core::ops::IntoNode<_, ImageFrame<SRGBA8>>, input: ImageFrame<Color>, params: []),
|
||||||
|
register_node!(graphene_core::ops::IntoNode<_, ImageFrame<Color>>, input: ImageFrame<SRGBA8>, params: []),
|
||||||
register_node!(graphene_std::raster::DownresNode<_>, input: ImageFrame<Color>, 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<Color>]),
|
||||||
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
|
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
|
||||||
|
|
@ -209,6 +214,19 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||||
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
|
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
|
||||||
register_node!(graphene_std::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
|
register_node!(graphene_std::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
|
||||||
register_node!(graphene_std::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, params: []),
|
register_node!(graphene_std::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, params: []),
|
||||||
|
register_node!(graphene_core::wasm_application_io::CreateSurfaceNode, input: &graphene_core::EditorApi, params: []),
|
||||||
|
vec![(
|
||||||
|
NodeIdentifier::new("graphene_core::wasm_application_io::DrawImageFrameNode<_>"),
|
||||||
|
|args| {
|
||||||
|
Box::pin(async move {
|
||||||
|
let surface: DowncastBothNode<(), Arc<WasmSurfaceHandle>> = DowncastBothNode::new(args[0]);
|
||||||
|
let node = graphene_core::wasm_application_io::DrawImageFrameNode::new(surface);
|
||||||
|
let any: DynAnyNode<ImageFrame<SRGBA8>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
|
||||||
|
Box::pin(any) as TypeErasedPinned
|
||||||
|
})
|
||||||
|
},
|
||||||
|
NodeIOTypes::new(concrete!(ImageFrame<SRGBA8>), concrete!(WasmSurfaceHandleFrame), vec![value_fn!(Arc<WasmSurfaceHandle>)]),
|
||||||
|
)],
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
vec![(
|
vec![(
|
||||||
NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode<_>"),
|
NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode<_>"),
|
||||||
|
|
@ -346,7 +364,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
|
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
|
||||||
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]);
|
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]);
|
||||||
let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2]);
|
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]);
|
||||||
let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(blend_mode.eval(()).await), CopiedNode::new(opacity.eval(()).await));
|
let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(blend_mode.eval(()).await), CopiedNode::new(opacity.eval(()).await));
|
||||||
let node = graphene_std::raster::BlendImageNode::new(image, FutureWrapperNode::new(ValueNode::new(blend_node)));
|
let node = graphene_std::raster::BlendImageNode::new(image, FutureWrapperNode::new(ValueNode::new(blend_node)));
|
||||||
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
|
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
|
||||||
|
|
@ -356,7 +374,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||||
NodeIOTypes::new(
|
NodeIOTypes::new(
|
||||||
concrete!(ImageFrame<Color>),
|
concrete!(ImageFrame<Color>),
|
||||||
concrete!(ImageFrame<Color>),
|
concrete!(ImageFrame<Color>),
|
||||||
vec![value_fn!(ImageFrame<Color>), value_fn!(BlendMode), value_fn!(f32)],
|
vec![value_fn!(ImageFrame<Color>), value_fn!(BlendMode), value_fn!(f64)],
|
||||||
),
|
),
|
||||||
)],
|
)],
|
||||||
raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]),
|
raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]),
|
||||||
|
|
@ -475,6 +493,18 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(generic!(T), concrete!(graphene_core::Artboard), vec![value_fn!(graphene_core::Artboard)]),
|
NodeIOTypes::new(generic!(T), concrete!(graphene_core::Artboard), vec![value_fn!(graphene_core::Artboard)]),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"),
|
||||||
|
|args| {
|
||||||
|
Box::pin(async move {
|
||||||
|
let input: DowncastBothNode<(), WasmSurfaceHandleFrame> = DowncastBothNode::new(args[0]);
|
||||||
|
let node = graphene_std::memo::EndLetNode::new(input);
|
||||||
|
let any: DynAnyInRefNode<graphene_core::EditorApi, _, _> = graphene_std::any::DynAnyInRefNode::new(node);
|
||||||
|
Box::pin(any) as TypeErasedPinned
|
||||||
|
})
|
||||||
|
},
|
||||||
|
NodeIOTypes::new(generic!(T), concrete!(WasmSurfaceHandleFrame), vec![value_fn!(WasmSurfaceHandleFrame)]),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"),
|
NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"),
|
||||||
|args| {
|
|args| {
|
||||||
|
|
@ -633,6 +663,18 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(concrete!(()), concrete!(Vec<DVec2>), vec![value_fn!(Vec<DVec2>)]),
|
NodeIOTypes::new(concrete!(()), concrete!(Vec<DVec2>), vec![value_fn!(Vec<DVec2>)]),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
NodeIdentifier::new("graphene_std::memo::CacheNode"),
|
||||||
|
|args| {
|
||||||
|
Box::pin(async move {
|
||||||
|
let input: DowncastBothNode<(), Arc<WasmSurfaceHandle>> = DowncastBothNode::new(args[0]);
|
||||||
|
let node: CacheNode<Arc<WasmSurfaceHandle>, _> = graphene_std::memo::CacheNode::new(input);
|
||||||
|
let any = DynAnyNode::new(ValueNode::new(node));
|
||||||
|
Box::pin(any) as TypeErasedPinned
|
||||||
|
})
|
||||||
|
},
|
||||||
|
NodeIOTypes::new(concrete!(()), concrete!(Arc<WasmSurfaceHandle>), vec![value_fn!(Arc<WasmSurfaceHandle>)]),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
|
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::ImageFrameNode<_, _>, input: Image<Color>, params: [DAffine2]),
|
||||||
|
|
@ -643,6 +685,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||||
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
|
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
|
||||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
||||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame<Color>, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame<Color>, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
||||||
|
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: WasmSurfaceHandleFrame, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
||||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [VectorData]),
|
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: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [DAffine2]),
|
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [DAffine2]),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue