Update Imaginate to output bitmap data to the graph via Image Frame node (#1001)

* Multiple node outputs

* Add new nodes

* gcore use std by default to allow for testing

* Allow multiple node outputs

* Multiple outputs to frontend

* Add ImageFrameNode to node registry

* Minor cleanup

* Basic transform implementation

* Add some logging to image encoding

* Fix ImageFrameNode

* Add transform input to Imaginate node (#1014)

* Add transform input to imaginate node

* Force the resolution to be edited with no transform

* Add transform to imaginate generation

* Fix compilation

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>

* Add labels to node outputs

* Fix seed; disable mask when transform is disconnected; add Imaginate tooltips

* Rename 'Input Multiple' node to 'Input'

* Code review

* Replicate to Svelte

* Show only the primary input chain in the Properties panel

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-02-11 08:56:31 +00:00 committed by Keavon Chambers
parent a709a772d5
commit 1b0e1b9bdf
35 changed files with 1172 additions and 553 deletions

View file

@ -11,5 +11,6 @@ pub mod application;
pub mod consts; pub mod consts;
pub mod dispatcher; pub mod dispatcher;
pub mod messages; pub mod messages;
pub mod node_graph_executor;
pub mod test_utils; pub mod test_utils;
pub mod utility_traits; pub mod utility_traits;

View file

@ -107,6 +107,7 @@ pub enum DocumentMessage {
}, },
NodeGraphFrameImaginateRandom { NodeGraphFrameImaginateRandom {
imaginate_node: Vec<NodeId>, imaginate_node: Vec<NodeId>,
then_generate: bool,
}, },
NodeGraphFrameImaginateTerminate { NodeGraphFrameImaginateTerminate {
layer_path: Vec<LayerId>, layer_path: Vec<LayerId>,

View file

@ -21,6 +21,7 @@ use crate::messages::portfolio::document::utility_types::vectorize_layer_metadat
use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::utility_types::ToolType; use crate::messages::tool::utility_types::ToolType;
use crate::node_graph_executor::NodeGraphExecutor;
use document_legacy::boolean_ops::BooleanOperationError; use document_legacy::boolean_ops::BooleanOperationError;
use document_legacy::document::Document as DocumentLegacy; use document_legacy::document::Document as DocumentLegacy;
@ -104,13 +105,13 @@ impl Default for DocumentMessageHandler {
} }
} }
impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &PersistentData, &PreferencesMessageHandler)> for DocumentMessageHandler { impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &PersistentData, &PreferencesMessageHandler, &mut NodeGraphExecutor)> for DocumentMessageHandler {
#[remain::check] #[remain::check]
fn process_message( fn process_message(
&mut self, &mut self,
message: DocumentMessage, message: DocumentMessage,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
(document_id, ipp, persistent_data, preferences): (u64, &InputPreprocessorMessageHandler, &PersistentData, &PreferencesMessageHandler), (document_id, ipp, persistent_data, preferences, executor): (u64, &InputPreprocessorMessageHandler, &PersistentData, &PreferencesMessageHandler, &mut NodeGraphExecutor),
) { ) {
use DocumentMessage::*; use DocumentMessage::*;
@ -203,6 +204,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
artboard_document: &self.artboard_message_handler.artboards_document, artboard_document: &self.artboard_message_handler.artboards_document,
selected_layers: &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice())), selected_layers: &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice())),
node_graph_message_handler: &self.node_graph_handler, node_graph_message_handler: &self.node_graph_handler,
executor,
}; };
self.properties_panel_message_handler self.properties_panel_message_handler
.process_message(message, responses, (persistent_data, properties_panel_message_handler_data)); .process_message(message, responses, (persistent_data, properties_panel_message_handler_data));
@ -527,18 +529,22 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.push_back(message); responses.push_back(message);
} }
} }
NodeGraphFrameImaginateRandom { imaginate_node } => { NodeGraphFrameImaginateRandom { imaginate_node, then_generate } => {
// Set a random seed input // Set a random seed input
responses.push_back( responses.push_back(
NodeGraphMessage::SetInputValue { NodeGraphMessage::SetInputValue {
node_id: *imaginate_node.last().unwrap(), node_id: *imaginate_node.last().unwrap(),
input_index: 1, // Needs to match the index of the seed parameter in `pub const IMAGINATE_NODE: DocumentNodeType` in `document_node_type.rs`
input_index: 2,
value: graph_craft::document::value::TaggedValue::F64((generate_uuid() >> 1) as f64), value: graph_craft::document::value::TaggedValue::F64((generate_uuid() >> 1) as f64),
} }
.into(), .into(),
); );
// Generate the image // Generate the image
responses.push_back(DocumentMessage::NodeGraphFrameImaginate { imaginate_node }.into()); if then_generate {
responses.push_back(DocumentMessage::NodeGraphFrameImaginate { imaginate_node }.into());
}
} }
NodeGraphFrameImaginateTerminate { layer_path, node_path } => { NodeGraphFrameImaginateTerminate { layer_path, node_path } => {
responses.push_back( responses.push_back(
@ -599,8 +605,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::StartTransaction.into());
let path = vec![generate_uuid()]; let path = vec![generate_uuid()];
let image_node_id = 2; let image_node_id = 100;
let mut network = graph_craft::document::NodeNetwork::new_network(32, image_node_id); let mut network = crate::messages::portfolio::document::node_graph::new_image_network(32, image_node_id);
let Some(image_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Image") else { let Some(image_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Image") else {
warn!("Image node should be in registry"); warn!("Image node should be in registry");
@ -609,12 +615,10 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
network.nodes.insert( network.nodes.insert(
image_node_id, image_node_id,
graph_craft::document::DocumentNode { image_node_type.to_document_node(
name: image_node_type.name.to_string(), [graph_craft::document::NodeInput::value(graph_craft::document::value::TaggedValue::Image(image), false)],
inputs: vec![graph_craft::document::NodeInput::value(graph_craft::document::value::TaggedValue::Image(image), false)], graph_craft::document::DocumentNodeMetadata::position((20, 4)),
implementation: image_node_type.generate_implementation(), ),
metadata: graph_craft::document::DocumentNodeMetadata { position: (20, 4).into() },
},
); );
responses.push_back( responses.push_back(

View file

@ -12,6 +12,7 @@ pub enum NodeGraphMessage {
CloseNodeGraph, CloseNodeGraph,
ConnectNodesByLink { ConnectNodesByLink {
output_node: u64, output_node: u64,
output_node_connector_index: usize,
input_node: u64, input_node: u64,
input_node_connector_index: usize, input_node_connector_index: usize,
}, },

View file

@ -9,7 +9,7 @@ use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
use document_legacy::layers::nodegraph_layer::NodeGraphFrameLayer; use document_legacy::layers::nodegraph_layer::NodeGraphFrameLayer;
use document_legacy::LayerId; use document_legacy::LayerId;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork}; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, NodeOutput};
mod document_node_types; mod document_node_types;
mod node_properties; mod node_properties;
@ -39,7 +39,7 @@ impl FrontendGraphDataType {
pub const fn with_tagged_value(value: &TaggedValue) -> Self { pub const fn with_tagged_value(value: &TaggedValue) -> Self {
match value { match value {
TaggedValue::String(_) => Self::Text, TaggedValue::String(_) => Self::Text,
TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::U32(_) => Self::Number, TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::U32(_) | TaggedValue::DAffine2(_) => Self::Number,
TaggedValue::Bool(_) => Self::Boolean, TaggedValue::Bool(_) => Self::Boolean,
TaggedValue::DVec2(_) => Self::Vector, TaggedValue::DVec2(_) => Self::Vector,
TaggedValue::Image(_) => Self::Raster, TaggedValue::Image(_) => Self::Raster,
@ -57,6 +57,13 @@ pub struct NodeGraphInput {
name: String, name: String,
} }
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct NodeGraphOutput {
#[serde(rename = "dataType")]
data_type: FrontendGraphDataType,
name: String,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNode { pub struct FrontendNode {
pub id: graph_craft::document::NodeId, pub id: graph_craft::document::NodeId,
@ -66,10 +73,10 @@ pub struct FrontendNode {
pub primary_input: Option<FrontendGraphDataType>, pub primary_input: Option<FrontendGraphDataType>,
#[serde(rename = "exposedInputs")] #[serde(rename = "exposedInputs")]
pub exposed_inputs: Vec<NodeGraphInput>, pub exposed_inputs: Vec<NodeGraphInput>,
pub outputs: Vec<FrontendGraphDataType>, pub outputs: Vec<NodeGraphOutput>, // TODO: Break this apart into `primary_output` and `exposed_outputs`
pub position: (i32, i32), pub position: (i32, i32),
pub disabled: bool, pub disabled: bool,
pub output: bool, pub previewed: bool,
} }
// (link_start, link_end, link_end_input_index) // (link_start, link_end, link_end_input_index)
@ -77,6 +84,8 @@ pub struct FrontendNode {
pub struct FrontendNodeLink { pub struct FrontendNodeLink {
#[serde(rename = "linkStart")] #[serde(rename = "linkStart")]
pub link_start: u64, pub link_start: u64,
#[serde(rename = "linkStartOutputIndex")]
pub link_start_output_index: usize,
#[serde(rename = "linkEnd")] #[serde(rename = "linkEnd")]
pub link_end: u64, pub link_end: u64,
#[serde(rename = "linkEndInputIndex")] #[serde(rename = "linkEndInputIndex")]
@ -176,7 +185,7 @@ impl NodeGraphMessageHandler {
let mut widgets = Vec::new(); let mut widgets = Vec::new();
// Don't allow disabling input or output nodes // Don't allow disabling input or output nodes
let mut selected_nodes = self.selected_nodes.iter().filter(|&&id| !network.inputs.contains(&id) && network.original_output() != id); let mut selected_nodes = self.selected_nodes.iter().filter(|&&id| !network.inputs.contains(&id) && !network.original_outputs_contain(id));
// If there is at least one other selected node then show the hide or show button // If there is at least one other selected node then show the hide or show button
if selected_nodes.next().is_some() { if selected_nodes.next().is_some() {
@ -187,13 +196,11 @@ impl NodeGraphMessageHandler {
let multiple_nodes = selected_nodes.next().is_some(); let multiple_nodes = selected_nodes.next().is_some();
// Generate the enable or disable button accordingly // Generate the enable or disable button accordingly
let hide_button = WidgetHolder::new(Widget::TextButton(TextButton { let hide_button = TextButton::new(if is_hidden { "Show" } else { "Hide" })
label: if is_hidden { "Show" } else { "Hide" }.to_string(), .tooltip(if is_hidden { "Show node" } else { "Hide node" }.to_string() + if multiple_nodes { "s" } else { "" })
tooltip: if is_hidden { "Show node" } else { "Hide node" }.to_string() + if multiple_nodes { "s" } else { "" }, .tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleHidden))
tooltip_shortcut: action_keys!(NodeGraphMessageDiscriminant::ToggleHidden), .on_update(move |_| NodeGraphMessage::ToggleHidden.into())
on_update: WidgetCallback::new(move |_| NodeGraphMessage::ToggleHidden.into()), .widget_holder();
..Default::default()
}));
widgets.push(hide_button); widgets.push(hide_button);
} }
@ -201,13 +208,13 @@ impl NodeGraphMessageHandler {
if self.selected_nodes.len() == 1 { if self.selected_nodes.len() == 1 {
let node_id = self.selected_nodes[0]; let node_id = self.selected_nodes[0];
// Is this node the current output // Is this node the current output
let is_output = network.output == node_id; let is_output = network.outputs_contain(node_id);
// Don't show stop previewing button on the original output node // Don't show stop previewing button on the original output node
if !(is_output && network.previous_output.filter(|&id| id != self.selected_nodes[0]).is_none()) { if !(is_output && network.previous_outputs_contain(node_id).unwrap_or(true)) {
let output_button = WidgetHolder::new(Widget::TextButton(TextButton { let output_button = WidgetHolder::new(Widget::TextButton(TextButton {
label: if is_output { "End Preview" } else { "Preview" }.to_string(), label: if is_output { "End Preview" } else { "Preview" }.to_string(),
tooltip: if is_output { "Restore preview to Output node" } else { "Preview node" }.to_string() + " (shortcut: Alt+click node)", tooltip: if is_output { "Restore preview to Output node" } else { "Preview node" }.to_string() + " (Shortcut: Alt-click node)",
on_update: WidgetCallback::new(move |_| NodeGraphMessage::TogglePreview { node_id }.into()), on_update: WidgetCallback::new(move |_| NodeGraphMessage::TogglePreview { node_id }.into()),
..Default::default() ..Default::default()
})); }));
@ -220,6 +227,7 @@ impl NodeGraphMessageHandler {
self.send_node_bar_layout(responses); self.send_node_bar_layout(responses);
} }
/// Collate the properties panel sections for a node graph
pub fn collate_properties(&self, node_graph_frame: &NodeGraphFrameLayer, context: &mut NodePropertiesContext, sections: &mut Vec<LayoutGroup>) { pub fn collate_properties(&self, node_graph_frame: &NodeGraphFrameLayer, context: &mut NodePropertiesContext, sections: &mut Vec<LayoutGroup>) {
let mut network = &node_graph_frame.network; let mut network = &node_graph_frame.network;
for segment in &self.nested_path { for segment in &self.nested_path {
@ -228,20 +236,27 @@ impl NodeGraphMessageHandler {
// If empty, show all nodes in the network starting with the output // If empty, show all nodes in the network starting with the output
if self.selected_nodes.is_empty() { if self.selected_nodes.is_empty() {
let mut stack = vec![network.output]; let mut stack = network.outputs.iter().map(|output| output.node_id).collect::<Vec<_>>();
let mut nodes = Vec::new(); let mut nodes = Vec::new();
while let Some(node_id) = stack.pop() { while let Some(node_id) = stack.pop() {
let Some(document_node) = network.nodes.get(&node_id) else { let Some(document_node) = network.nodes.get(&node_id) else {
continue; continue;
}; };
stack.extend(document_node.inputs.iter().filter_map(|input| if let NodeInput::Node(ref_id) = input { Some(*ref_id) } else { None })); stack.extend(
document_node
.inputs
.iter()
.take(1) // Only show the primary input
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
);
nodes.push((document_node, node_id)); nodes.push((document_node, node_id));
} }
for &(document_node, node_id) in nodes.iter().rev() { for &(document_node, node_id) in nodes.iter().rev() {
sections.push(node_properties::generate_node_properties(document_node, node_id, context)); sections.push(node_properties::generate_node_properties(document_node, node_id, context));
} }
} }
// Show properties for all selected nodes
for node_id in &self.selected_nodes { for node_id in &self.selected_nodes {
let Some(document_node) = network.nodes.get(node_id) else { let Some(document_node) = network.nodes.get(node_id) else {
continue; continue;
@ -260,9 +275,14 @@ impl NodeGraphMessageHandler {
.iter() .iter()
.flat_map(|(link_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, link_end, index))) .flat_map(|(link_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, link_end, index)))
.filter_map(|(input, &link_end, link_end_input_index)| { .filter_map(|(input, &link_end, link_end_input_index)| {
if let NodeInput::Node(link_start) = *input { if let NodeInput::Node {
node_id: link_start,
output_index: link_start_index,
} = *input
{
Some(FrontendNodeLink { Some(FrontendNodeLink {
link_start, link_start,
link_start_output_index: link_start_index,
link_end, link_end,
link_end_input_index: link_end_input_index as u64, link_end_input_index: link_end_input_index as u64,
}) })
@ -298,9 +318,16 @@ impl NodeGraphMessageHandler {
name: input_type.name.to_string(), name: input_type.name.to_string(),
}) })
.collect(), .collect(),
outputs: node_type.outputs.to_vec(), outputs: node_type
.outputs
.iter()
.map(|output_type| NodeGraphOutput {
data_type: output_type.data_type,
name: output_type.name.to_string(),
})
.collect(),
position: node.metadata.position.into(), position: node.metadata.position.into(),
output: network.output == *id, previewed: network.outputs_contain(*id),
disabled: network.disabled.contains(id), disabled: network.disabled.contains(id),
}) })
} }
@ -318,24 +345,24 @@ impl NodeGraphMessageHandler {
); );
} }
fn remove_references_from_network(network: &mut NodeNetwork, node_id: NodeId) -> bool { fn remove_references_from_network(network: &mut NodeNetwork, deleting_node_id: NodeId) -> bool {
if network.inputs.iter().any(|&id| id == node_id) { if network.inputs.contains(&deleting_node_id) {
warn!("Deleting input node"); warn!("Deleting input node");
return false; return false;
} }
if network.output == node_id { if network.outputs_contain(deleting_node_id) {
warn!("Deleting the output node!"); warn!("Deleting the output node!");
return false; return false;
} }
for (id, node) in network.nodes.iter_mut() { for (node_id, node) in network.nodes.iter_mut() {
if *id == node_id { if *node_id == deleting_node_id {
continue; continue;
} }
for (input_index, input) in node.inputs.iter_mut().enumerate() { for (input_index, input) in node.inputs.iter_mut().enumerate() {
let NodeInput::Node(id) = input else { let NodeInput::Node{ node_id, .. } = input else {
continue; continue;
}; };
if *id != node_id { if *node_id != deleting_node_id {
continue; continue;
} }
@ -344,19 +371,17 @@ impl NodeGraphMessageHandler {
return false; return false;
}; };
if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default { if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default {
*input = NodeInput::Value { *input = NodeInput::value(tagged_value.clone(), true);
tagged_value: tagged_value.clone(),
exposed: true,
};
} }
} }
if let DocumentNodeImplementation::Network(network) = &mut node.implementation { if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
Self::remove_references_from_network(network, node_id); Self::remove_references_from_network(network, deleting_node_id);
} }
} }
true true
} }
/// Tries to remove a node from the network, returning true on success.
fn remove_node(&mut self, network: &mut NodeNetwork, node_id: NodeId) -> bool { fn remove_node(&mut self, network: &mut NodeNetwork, node_id: NodeId) -> bool {
if Self::remove_references_from_network(network, node_id) { if Self::remove_references_from_network(network, node_id) {
network.nodes.remove(&node_id); network.nodes.remove(&node_id);
@ -378,7 +403,7 @@ impl NodeGraphMessageHandler {
fn copy_nodes<'a>(network: &'a NodeNetwork, new_ids: &'a HashMap<NodeId, NodeId>) -> impl Iterator<Item = (NodeId, DocumentNode)> + 'a { fn copy_nodes<'a>(network: &'a NodeNetwork, new_ids: &'a HashMap<NodeId, NodeId>) -> impl Iterator<Item = (NodeId, DocumentNode)> + 'a {
new_ids new_ids
.iter() .iter()
.filter(|&(&id, _)| id != network.output && !network.inputs.contains(&id)) .filter(|&(&id, _)| !network.outputs_contain(id) && !network.inputs.contains(&id))
.filter_map(|(&id, &new)| network.nodes.get(&id).map(|node| (new, node.clone()))) .filter_map(|(&id, &new)| network.nodes.get(&id).map(|node| (new, node.clone())))
.map(move |(new, node)| (new, node.map_ids(Self::default_node_input, new_ids))) .map(move |(new, node)| (new, node.map_ids(Self::default_node_input, new_ids)))
} }
@ -402,6 +427,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
} }
NodeGraphMessage::ConnectNodesByLink { NodeGraphMessage::ConnectNodesByLink {
output_node, output_node,
output_node_connector_index,
input_node, input_node,
input_node_connector_index, input_node_connector_index,
} => { } => {
@ -422,7 +448,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::StartTransaction.into());
let input = NodeInput::Node(output_node); let input = NodeInput::node(output_node, output_node_connector_index);
responses.push_back(NodeGraphMessage::SetNodeInput { node_id, input_index, input }.into()); responses.push_back(NodeGraphMessage::SetNodeInput { node_id, input_index, input }.into());
let should_rerender = network.connected_to_output(node_id); let should_rerender = network.connected_to_output(node_id);
@ -454,12 +480,10 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::StartTransaction.into());
let document_node = DocumentNode { let document_node = document_node_type.to_document_node(
name: node_type.clone(), document_node_type.inputs.iter().map(|input| input.default.clone()),
inputs: document_node_type.inputs.iter().map(|input| input.default.clone()).collect(), graph_craft::document::DocumentNodeMetadata::position((x, y)),
implementation: document_node_type.generate_implementation(), );
metadata: graph_craft::document::DocumentNodeMetadata { position: (x, y).into() },
};
responses.push_back(NodeGraphMessage::InsertNode { node_id, document_node }.into()); responses.push_back(NodeGraphMessage::InsertNode { node_id, document_node }.into());
responses.push_back(NodeGraphMessage::SendGraph { should_rerender: false }.into()); responses.push_back(NodeGraphMessage::SendGraph { should_rerender: false }.into());
@ -782,7 +806,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
.get(&node_id) .get(&node_id)
.map_or(&Vec::new(), |node| &node.inputs) .map_or(&Vec::new(), |node| &node.inputs)
.iter() .iter()
.filter_map(|input| if let NodeInput::Node(previous_id) = input { Some(*previous_id) } else { None }) .filter_map(|input| if let NodeInput::Node { node_id: previous_id, .. } = input { Some(*previous_id) } else { None })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for input_node in inputs { for input_node in inputs {
@ -812,9 +836,11 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
// Remove all selected nodes from the disabled list // Remove all selected nodes from the disabled list
network.disabled.retain(|id| !self.selected_nodes.contains(id)); network.disabled.retain(|id| !self.selected_nodes.contains(id));
} else { } else {
let original_output = network.original_output(); let original_outputs = network.original_outputs().iter().map(|output| output.node_id).collect::<Vec<_>>();
// Add all selected nodes to the disabled list (excluding input or output nodes) // Add all selected nodes to the disabled list (excluding input or output nodes)
network.disabled.extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && original_output != *id)); network
.disabled
.extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id)));
} }
Self::send_graph(network, responses); Self::send_graph(network, responses);
@ -831,12 +857,12 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
} }
NodeGraphMessage::TogglePreviewImpl { node_id } => { NodeGraphMessage::TogglePreviewImpl { node_id } => {
if let Some(network) = self.get_active_network_mut(document) { if let Some(network) = self.get_active_network_mut(document) {
// Check if the node is not already // Check if the node is not already being previewed
if network.output != node_id { if !network.outputs_contain(node_id) {
network.previous_output = Some(network.previous_output.unwrap_or(network.output)); network.previous_outputs = Some(network.previous_outputs.to_owned().unwrap_or_else(|| network.outputs.clone()));
network.output = node_id; network.outputs[0] = NodeOutput::new(node_id, 0);
} else if let Some(output) = network.previous_output.take() { } else if let Some(outputs) = network.previous_outputs.take() {
network.output = output network.outputs = outputs
} else { } else {
return; return;
} }

View file

@ -1,5 +1,6 @@
use super::{node_properties, FrontendGraphDataType, FrontendNodeType}; use super::{node_properties, FrontendGraphDataType, FrontendNodeType};
use crate::messages::layout::utility_types::layout_widget::LayoutGroup; use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
use crate::node_graph_executor::NodeGraphExecutor;
use graph_craft::document::value::*; use graph_craft::document::value::*;
use graph_craft::document::*; use graph_craft::document::*;
@ -32,12 +33,25 @@ impl DocumentInputType {
} }
} }
pub struct DocumentOutputType {
pub name: &'static str,
pub data_type: FrontendGraphDataType,
}
impl DocumentOutputType {
pub const fn new(name: &'static str, data_type: FrontendGraphDataType) -> Self {
Self { name, data_type }
}
}
pub struct NodePropertiesContext<'a> { pub struct NodePropertiesContext<'a> {
pub persistent_data: &'a crate::messages::portfolio::utility_types::PersistentData, pub persistent_data: &'a crate::messages::portfolio::utility_types::PersistentData,
pub document: &'a document_legacy::document::Document, pub document: &'a document_legacy::document::Document,
pub responses: &'a mut VecDeque<crate::messages::prelude::Message>, pub responses: &'a mut VecDeque<crate::messages::prelude::Message>,
pub layer_path: &'a [document_legacy::LayerId], pub layer_path: &'a [document_legacy::LayerId],
pub nested_path: &'a [NodeId], pub nested_path: &'a [NodeId],
pub executor: &'a mut NodeGraphExecutor,
pub network: &'a NodeNetwork,
} }
#[derive(Clone)] #[derive(Clone)]
@ -58,25 +72,24 @@ pub struct DocumentNodeType {
pub category: &'static str, pub category: &'static str,
pub identifier: NodeImplementation, pub identifier: NodeImplementation,
pub inputs: &'static [DocumentInputType], pub inputs: &'static [DocumentInputType],
pub outputs: &'static [FrontendGraphDataType], pub outputs: &'static [DocumentOutputType],
pub properties: fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup>, pub properties: fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup>,
} }
fn document_node_types() -> Vec<DocumentNodeType> { fn document_node_types() -> Vec<DocumentNodeType> {
let mut vec: Vec<_> = STATIC_NODES.to_vec(); let mut vec: Vec<_> = STATIC_NODES.to_vec();
const INPUTS: &[DocumentInputType] = &[ const GAUSSIAN_BLUR_NODE_INPUTS: &[DocumentInputType] = &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Radius", TaggedValue::U32(3), false), DocumentInputType::new("Radius", TaggedValue::U32(3), false),
DocumentInputType::new("Sigma", TaggedValue::F64(1.), false), DocumentInputType::new("Sigma", TaggedValue::F64(1.), false),
]; ];
let blur = DocumentNodeType { let blur = DocumentNodeType {
name: "Gaussian Blur", name: "Gaussian Blur",
category: "Image Filters", category: "Image Filters",
identifier: NodeImplementation::DocumentNode(NodeNetwork { identifier: NodeImplementation::DocumentNode(NodeNetwork {
inputs: vec![0, 1, 1], inputs: vec![0, 1, 1],
output: 1, outputs: vec![NodeOutput::new(1, 0)],
nodes: vec![ nodes: vec![
( (
0, 0,
@ -91,7 +104,7 @@ fn document_node_types() -> Vec<DocumentNodeType> {
1, 1,
DocumentNode { DocumentNode {
name: "BlurNode".to_string(), name: "BlurNode".to_string(),
inputs: vec![NodeInput::Node(0), NodeInput::Network, NodeInput::Network, NodeInput::Node(0)], inputs: vec![NodeInput::node(0, 0), NodeInput::Network, NodeInput::Network, NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::BlurNode", &[concrete!("Image")])), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::BlurNode", &[concrete!("Image")])),
metadata: Default::default(), metadata: Default::default(),
}, },
@ -101,11 +114,64 @@ fn document_node_types() -> Vec<DocumentNodeType> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: INPUTS, inputs: GAUSSIAN_BLUR_NODE_INPUTS,
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType {
name: "Image",
data_type: FrontendGraphDataType::Raster,
}],
properties: node_properties::blur_image_properties, properties: node_properties::blur_image_properties,
}; };
vec.push(blur); vec.push(blur);
const INPUT_NODE_INPUTS: &[DocumentInputType] = &[
DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::General,
default: NodeInput::Network,
},
DocumentInputType::new("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false),
];
let input = DocumentNodeType {
name: "Input",
category: "Ignore",
identifier: NodeImplementation::DocumentNode(NodeNetwork {
inputs: vec![0, 1],
outputs: vec![NodeOutput::new(0, 0), NodeOutput::new(1, 0)],
nodes: [
DocumentNode {
name: "Identity".to_string(),
inputs: vec![NodeInput::Network],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
metadata: Default::default(),
},
DocumentNode {
name: "Identity".to_string(),
inputs: vec![NodeInput::Network],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
metadata: Default::default(),
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (id as NodeId, node))
.collect(),
..Default::default()
}),
inputs: INPUT_NODE_INPUTS,
outputs: &[
DocumentOutputType {
name: "Image",
data_type: FrontendGraphDataType::Raster,
},
DocumentOutputType {
name: "Transform",
data_type: FrontendGraphDataType::Number,
},
],
properties: node_properties::input_properties,
};
vec.push(input);
vec vec
} }
@ -122,9 +188,9 @@ static STATIC_NODES: &[DocumentNodeType] = &[
inputs: &[DocumentInputType { inputs: &[DocumentInputType {
name: "In", name: "In",
data_type: FrontendGraphDataType::General, data_type: FrontendGraphDataType::General,
default: NodeInput::Node(0), default: NodeInput::node(0, 0),
}], }],
outputs: &[FrontendGraphDataType::General], outputs: &[DocumentOutputType::new("Out", FrontendGraphDataType::General)],
properties: |_document_node, _node_id, _context| node_properties::string_properties("The identity node simply returns the input"), properties: |_document_node, _node_id, _context| node_properties::string_properties("The identity node simply returns the input"),
}, },
DocumentNodeType { DocumentNodeType {
@ -132,21 +198,21 @@ static STATIC_NODES: &[DocumentNodeType] = &[
category: "Ignore", category: "Ignore",
identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]), identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]),
inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), false)], inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), false)],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"), properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"),
}, },
DocumentNodeType { // DocumentNodeType {
name: "Input", // name: "Input",
category: "Ignore", // category: "Ignore",
identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]), // identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]),
inputs: &[DocumentInputType { // inputs: &[DocumentInputType {
name: "In", // name: "In",
data_type: FrontendGraphDataType::Raster, // data_type: FrontendGraphDataType::Raster,
default: NodeInput::Network, // default: NodeInput::Network,
}], // }],
outputs: &[FrontendGraphDataType::Raster], // outputs: &[DocumentOutputType::new("Out", FrontendGraphDataType::Raster)],
properties: node_properties::input_properties, // properties: node_properties::input_properties,
}, // },
DocumentNodeType { DocumentNodeType {
name: "Output", name: "Output",
category: "Ignore", category: "Ignore",
@ -157,7 +223,18 @@ static STATIC_NODES: &[DocumentNodeType] = &[
default: NodeInput::value(TaggedValue::Image(Image::empty()), true), default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
}], }],
outputs: &[], outputs: &[],
properties: |_document_node, _node_id, _context| node_properties::string_properties("The graph's output is rendered into the frame".to_string()), properties: |_document_node, _node_id, _context| node_properties::string_properties("The graph's output is rendered into the frame"),
},
DocumentNodeType {
name: "Image Frame",
category: "General",
identifier: NodeImplementation::proto("graphene_std::raster::ImageFrameNode<_>", &[concrete!("Image"), concrete!("DAffine2")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), true),
],
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: |_document_node, _node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform"),
}, },
DocumentNodeType { DocumentNodeType {
name: "Grayscale", name: "Grayscale",
@ -217,7 +294,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
default: NodeInput::value(TaggedValue::F64(50.), false), default: NodeInput::value(TaggedValue::F64(50.), false),
}, },
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::grayscale_properties, properties: node_properties::grayscale_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -228,7 +305,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false), DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::luminance_properties, properties: node_properties::luminance_properties,
}, },
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
@ -244,7 +321,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
default: NodeInput::value(TaggedValue::String(String::new()), false), default: NodeInput::value(TaggedValue::String(String::new()), false),
}, },
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::gpu_map_properties, properties: node_properties::gpu_map_properties,
}, },
#[cfg(feature = "quantization")] #[cfg(feature = "quantization")]
@ -269,7 +346,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
default: NodeInput::value(TaggedValue::U32(0), false), default: NodeInput::value(TaggedValue::U32(0), false),
}, },
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::quantize_properties, properties: node_properties::quantize_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -277,7 +354,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
category: "Structural", category: "Structural",
identifier: NodeImplementation::proto("graphene_std::memo::CacheNode", &[concrete!("Image")]), identifier: NodeImplementation::proto("graphene_std::memo::CacheNode", &[concrete!("Image")]),
inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)], inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::no_properties, properties: node_properties::no_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -285,7 +362,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
category: "Image Adjustments", category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::InvertRGBNode", &[concrete!("Image")]), identifier: NodeImplementation::proto("graphene_core::raster::InvertRGBNode", &[concrete!("Image")]),
inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)], inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::no_properties, properties: node_properties::no_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -301,7 +378,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Saturation Shift", TaggedValue::F64(0.), false), DocumentInputType::new("Saturation Shift", TaggedValue::F64(0.), false),
DocumentInputType::new("Lightness Shift", TaggedValue::F64(0.), false), DocumentInputType::new("Lightness Shift", TaggedValue::F64(0.), false),
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::adjust_hsl_properties, properties: node_properties::adjust_hsl_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -313,7 +390,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Brightness", TaggedValue::F64(0.), false), DocumentInputType::new("Brightness", TaggedValue::F64(0.), false),
DocumentInputType::new("Contrast", TaggedValue::F64(0.), false), DocumentInputType::new("Contrast", TaggedValue::F64(0.), false),
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::brighten_image_properties, properties: node_properties::brighten_image_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -325,7 +402,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false), DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
DocumentInputType::new("Threshold", TaggedValue::F64(50.), false), DocumentInputType::new("Threshold", TaggedValue::F64(50.), false),
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::adjust_threshold_properties, properties: node_properties::adjust_threshold_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -336,7 +413,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Vibrance", TaggedValue::F64(0.), false), DocumentInputType::new("Vibrance", TaggedValue::F64(0.), false),
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::adjust_vibrance_properties, properties: node_properties::adjust_vibrance_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -347,7 +424,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Factor", TaggedValue::F64(100.), false), DocumentInputType::new("Factor", TaggedValue::F64(100.), false),
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::multiply_opacity, properties: node_properties::multiply_opacity,
}, },
DocumentNodeType { DocumentNodeType {
@ -358,7 +435,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Value", TaggedValue::F64(4.), false), DocumentInputType::new("Value", TaggedValue::F64(4.), false),
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::posterize_properties, properties: node_properties::posterize_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -374,7 +451,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Offset", TaggedValue::F64(0.), false), DocumentInputType::new("Offset", TaggedValue::F64(0.), false),
DocumentInputType::new("Gamma Correction", TaggedValue::F64(1.), false), DocumentInputType::new("Gamma Correction", TaggedValue::F64(1.), false),
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::exposure_properties, properties: node_properties::exposure_properties,
}, },
IMAGINATE_NODE, IMAGINATE_NODE,
@ -386,7 +463,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Input", TaggedValue::F64(0.), true), DocumentInputType::new("Input", TaggedValue::F64(0.), true),
DocumentInputType::new("Addend", TaggedValue::F64(0.), true), DocumentInputType::new("Addend", TaggedValue::F64(0.), true),
], ],
outputs: &[FrontendGraphDataType::Number], outputs: &[DocumentOutputType::new("Output", FrontendGraphDataType::Number)],
properties: node_properties::add_properties, properties: node_properties::add_properties,
}, },
/*DocumentNodeType { /*DocumentNodeType {
@ -394,7 +471,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
category: "Vector", category: "Vector",
identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]), identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]),
inputs: &[DocumentInputType::none()], inputs: &[DocumentInputType::none()],
outputs: &[FrontendGraphDataType::Subpath], outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
properties: node_properties::no_properties, properties: node_properties::no_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -402,7 +479,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
category: "Vector", category: "Vector",
identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]), identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]),
inputs: &[DocumentInputType::none()], inputs: &[DocumentInputType::none()],
outputs: &[FrontendGraphDataType::Subpath], outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
properties: node_properties::no_properties, properties: node_properties::no_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -414,7 +491,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
data_type: FrontendGraphDataType::Subpath, data_type: FrontendGraphDataType::Subpath,
default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), false), default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), false),
}], }],
outputs: &[FrontendGraphDataType::Subpath], outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
properties: node_properties::no_properties, properties: node_properties::no_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -428,7 +505,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Scale", TaggedValue::DVec2(DVec2::ONE), false), DocumentInputType::new("Scale", TaggedValue::DVec2(DVec2::ONE), false),
DocumentInputType::new("Skew", TaggedValue::DVec2(DVec2::ZERO), false), DocumentInputType::new("Skew", TaggedValue::DVec2(DVec2::ZERO), false),
], ],
outputs: &[FrontendGraphDataType::Subpath], outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
properties: node_properties::transform_properties, properties: node_properties::transform_properties,
}, },
DocumentNodeType { DocumentNodeType {
@ -439,7 +516,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Subpath", TaggedValue::Subpath(Subpath::empty()), true), DocumentInputType::new("Subpath", TaggedValue::Subpath(Subpath::empty()), true),
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Raster)],
properties: node_properties::no_properties, properties: node_properties::no_properties,
},*/ },*/
]; ];
@ -450,11 +527,12 @@ pub const IMAGINATE_NODE: DocumentNodeType = DocumentNodeType {
identifier: NodeImplementation::proto("graphene_std::raster::ImaginateNode<_>", &[concrete!("Image"), concrete!("Option<std::sync::Arc<Image>>")]), identifier: NodeImplementation::proto("graphene_std::raster::ImaginateNode<_>", &[concrete!("Image"), concrete!("Option<std::sync::Arc<Image>>")]),
inputs: &[ inputs: &[
DocumentInputType::new("Input Image", TaggedValue::Image(Image::empty()), true), DocumentInputType::new("Input Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Seed", TaggedValue::F64(0.), false), DocumentInputType::new("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), true),
DocumentInputType::new("Seed", TaggedValue::F64(0.), false), // Remember to keep index used in `NodeGraphFrameImaginateRandom` updated with this entry's index
DocumentInputType::new("Resolution", TaggedValue::OptionalDVec2(None), false), DocumentInputType::new("Resolution", TaggedValue::OptionalDVec2(None), false),
DocumentInputType::new("Samples", TaggedValue::F64(30.), false), DocumentInputType::new("Samples", TaggedValue::F64(30.), false),
DocumentInputType::new("Sampling Method", TaggedValue::ImaginateSamplingMethod(ImaginateSamplingMethod::EulerA), false), DocumentInputType::new("Sampling Method", TaggedValue::ImaginateSamplingMethod(ImaginateSamplingMethod::EulerA), false),
DocumentInputType::new("Prompt Guidance", TaggedValue::F64(10.), false), DocumentInputType::new("Prompt Guidance", TaggedValue::F64(7.5), false),
DocumentInputType::new("Prompt", TaggedValue::String(String::new()), false), DocumentInputType::new("Prompt", TaggedValue::String(String::new()), false),
DocumentInputType::new("Negative Prompt", TaggedValue::String(String::new()), false), DocumentInputType::new("Negative Prompt", TaggedValue::String(String::new()), false),
DocumentInputType::new("Adapt Input Image", TaggedValue::Bool(false), false), DocumentInputType::new("Adapt Input Image", TaggedValue::Bool(false), false),
@ -470,7 +548,7 @@ pub const IMAGINATE_NODE: DocumentNodeType = DocumentNodeType {
DocumentInputType::new("Percent Complete", TaggedValue::F64(0.), false), DocumentInputType::new("Percent Complete", TaggedValue::F64(0.), false),
DocumentInputType::new("Status", TaggedValue::ImaginateStatus(ImaginateStatus::Idle), false), DocumentInputType::new("Status", TaggedValue::ImaginateStatus(ImaginateStatus::Idle), false),
], ],
outputs: &[FrontendGraphDataType::Raster], outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::imaginate_properties, properties: node_properties::imaginate_properties,
}; };
@ -495,7 +573,7 @@ impl DocumentNodeType {
NodeImplementation::ProtoNode(ident) => { NodeImplementation::ProtoNode(ident) => {
NodeNetwork { NodeNetwork {
inputs: (0..num_inputs).map(|_| 0).collect(), inputs: (0..num_inputs).map(|_| 0).collect(),
output: 0, outputs: vec![NodeOutput::new(0, 0)],
nodes: [( nodes: [(
0, 0,
DocumentNode { DocumentNode {
@ -515,4 +593,37 @@ impl DocumentNodeType {
}; };
DocumentNodeImplementation::Network(inner_network) DocumentNodeImplementation::Network(inner_network)
} }
pub fn to_document_node(&self, inputs: impl IntoIterator<Item = NodeInput>, metadata: graph_craft::document::DocumentNodeMetadata) -> DocumentNode {
DocumentNode {
name: self.name.to_string(),
inputs: inputs.into_iter().collect(),
implementation: self.generate_implementation(),
metadata,
}
}
}
pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetwork {
NodeNetwork {
inputs: vec![0],
outputs: vec![NodeOutput::new(1, 0)],
nodes: [
resolve_document_node_type("Input").expect("Input node does not exist").to_document_node(
[NodeInput::Network, NodeInput::value(TaggedValue::DAffine2(DAffine2::IDENTITY), false)],
DocumentNodeMetadata::position((8, 4)),
),
resolve_document_node_type("Output")
.expect("Output node does not exist")
.to_document_node([NodeInput::node(2, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))),
resolve_document_node_type("Image Frame")
.expect("Image frame node does not exist")
.to_document_node([NodeInput::node(output_node_id, 0), NodeInput::node(0, 1)], DocumentNodeMetadata::position((output_offset, 4))),
]
.into_iter()
.enumerate()
.map(|(id, node)| (id as NodeId, node))
.collect(),
..Default::default()
}
} }

View file

@ -6,7 +6,7 @@ use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
use document_legacy::Operation; use document_legacy::Operation;
use glam::DVec2; use glam::DVec2;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput}; use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::imaginate_input::*; use graph_craft::imaginate_input::*;
use graphene_core::raster::{Color, LuminanceCalculation}; use graphene_core::raster::{Color, LuminanceCalculation};
@ -407,10 +407,10 @@ pub fn _transform_properties(document_node: &DocumentNode, node_id: NodeId, _con
pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let imaginate_node = [context.nested_path, &[node_id]].concat(); let imaginate_node = [context.nested_path, &[node_id]].concat();
let imaginate_node_1 = imaginate_node.clone();
let layer_path = context.layer_path.to_vec(); let layer_path = context.layer_path.to_vec();
let resolve_input = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found")); let resolve_input = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
let transform_index = resolve_input("Transform");
let seed_index = resolve_input("Seed"); let seed_index = resolve_input("Seed");
let resolution_index = resolve_input("Resolution"); let resolution_index = resolve_input("Resolution");
let samples_index = resolve_input("Samples"); let samples_index = resolve_input("Samples");
@ -478,6 +478,9 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
} else { } else {
true true
}; };
let transform_not_connected = matches!(document_node.inputs[transform_index], NodeInput::Value { .. });
let progress = { let progress = {
// Since we don't serialize the status, we need to derive from other state whether the Idle state is actually supposed to be the Terminated state // Since we don't serialize the status, we need to derive from other state whether the Idle state is actually supposed to be the Terminated state
let mut interpreted_status = imaginate_status; let mut interpreted_status = imaginate_status;
@ -527,12 +530,15 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
widgets.push( widgets.push(
TextButton::new("Terminate") TextButton::new("Terminate")
.tooltip("Cancel the in-progress image generation and keep the latest progress") .tooltip("Cancel the in-progress image generation and keep the latest progress")
.on_update(move |_| { .on_update({
DocumentMessage::NodeGraphFrameImaginateTerminate { let imaginate_node = imaginate_node.clone();
layer_path: layer_path.clone(), move |_| {
node_path: imaginate_node.clone(), DocumentMessage::NodeGraphFrameImaginateTerminate {
layer_path: layer_path.clone(),
node_path: imaginate_node.clone(),
}
.into()
} }
.into()
}) })
.widget_holder(), .widget_holder(),
); );
@ -549,21 +555,28 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
ImaginateStatus::Idle | ImaginateStatus::Terminated => widgets.extend_from_slice(&[ ImaginateStatus::Idle | ImaginateStatus::Terminated => widgets.extend_from_slice(&[
IconButton::new("Random", 24) IconButton::new("Random", 24)
.tooltip("Generate with a new random seed") .tooltip("Generate with a new random seed")
.on_update(move |_| { .on_update({
DocumentMessage::NodeGraphFrameImaginateRandom { let imaginate_node = imaginate_node.clone();
imaginate_node: imaginate_node.clone(), move |_| {
DocumentMessage::NodeGraphFrameImaginateRandom {
imaginate_node: imaginate_node.clone(),
then_generate: true,
}
.into()
} }
.into()
}) })
.widget_holder(), .widget_holder(),
WidgetHolder::unrelated_separator(), WidgetHolder::unrelated_separator(),
TextButton::new("Generate") TextButton::new("Generate")
.tooltip("Fill layer frame by generating a new image") .tooltip("Fill layer frame by generating a new image")
.on_update(move |_| { .on_update({
DocumentMessage::NodeGraphFrameImaginate { let imaginate_node = imaginate_node.clone();
imaginate_node: imaginate_node_1.clone(), move |_| {
DocumentMessage::NodeGraphFrameImaginate {
imaginate_node: imaginate_node.clone(),
}
.into()
} }
.into()
}) })
.widget_holder(), .widget_holder(),
WidgetHolder::related_separator(), WidgetHolder::related_separator(),
@ -590,7 +603,16 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
WidgetHolder::unrelated_separator(), WidgetHolder::unrelated_separator(),
IconButton::new("Regenerate", 24) IconButton::new("Regenerate", 24)
.tooltip("Set a new random seed") .tooltip("Set a new random seed")
.on_update(update_value(move |_| TaggedValue::F64((generate_uuid() >> 1) as f64), node_id, seed_index)) .on_update({
let imaginate_node = imaginate_node.clone();
move |_| {
DocumentMessage::NodeGraphFrameImaginateRandom {
imaginate_node: imaginate_node.clone(),
then_generate: false,
}
.into()
}
})
.widget_holder(), .widget_holder(),
WidgetHolder::unrelated_separator(), WidgetHolder::unrelated_separator(),
NumberInput::new(Some(seed)) NumberInput::new(Some(seed))
@ -604,6 +626,16 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
LayoutGroup::Row { widgets }.with_tooltip("Seed determines the random outcome, enabling limitless unique variations") LayoutGroup::Row { widgets }.with_tooltip("Seed determines the random outcome, enabling limitless unique variations")
}; };
// Get the existing layer transform
let transform = context.document.root.transform.inverse() * context.document.multiply_transforms(context.layer_path).unwrap();
// Create the input to the graph using an empty image
let image_frame = std::borrow::Cow::Owned(graphene_core::raster::ImageFrame {
image: graphene_core::raster::Image::empty(),
transform,
});
// Compute the transform input to the node graph frame
let transform: glam::DAffine2 = context.executor.compute_input(context.network, &imaginate_node, 1, image_frame).unwrap_or_default();
let resolution = { let resolution = {
use document_legacy::document::pick_safe_imaginate_resolution; use document_legacy::document::pick_safe_imaginate_resolution;
@ -621,7 +653,6 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
{ {
let dimensions_is_auto = vec2.is_none(); let dimensions_is_auto = vec2.is_none();
let vec2 = vec2.unwrap_or_else(|| { let vec2 = vec2.unwrap_or_else(|| {
let transform = context.document.root.transform.inverse() * context.document.multiply_transforms(context.layer_path).unwrap();
let w = transform.transform_vector2(DVec2::new(1., 0.)).length(); let w = transform.transform_vector2(DVec2::new(1., 0.)).length();
let h = transform.transform_vector2(DVec2::new(0., 1.)).length(); let h = transform.transform_vector2(DVec2::new(0., 1.)).length();
@ -644,9 +675,21 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
}) })
.widget_holder(), .widget_holder(),
WidgetHolder::unrelated_separator(), WidgetHolder::unrelated_separator(),
CheckboxInput::new(!dimensions_is_auto) CheckboxInput::new(!dimensions_is_auto || transform_not_connected)
.icon("Edit") .icon("Edit")
.tooltip("Set a custom resolution instead of using the frame's rounded dimensions") .tooltip({
let message = "Set a custom resolution instead of using the frame's rounded dimensions";
let manual_message = "Set a custom resolution instead of using the frame's rounded dimensions.\n\
\n\
(Resolution must be set manually while the 'Transform' input is disconnected.)";
if transform_not_connected {
manual_message
} else {
message
}
})
.disabled(transform_not_connected)
.on_update(update_value( .on_update(update_value(
move |checkbox_input: &CheckboxInput| { move |checkbox_input: &CheckboxInput| {
if checkbox_input.checked { if checkbox_input.checked {
@ -663,7 +706,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
NumberInput::new(Some(vec2.x)) NumberInput::new(Some(vec2.x))
.label("W") .label("W")
.unit(" px") .unit(" px")
.disabled(dimensions_is_auto) .disabled(dimensions_is_auto && !transform_not_connected)
.on_update(update_value( .on_update(update_value(
move |number_input: &NumberInput| TaggedValue::OptionalDVec2(round(DVec2::new(number_input.value.unwrap(), vec2.y))), move |number_input: &NumberInput| TaggedValue::OptionalDVec2(round(DVec2::new(number_input.value.unwrap(), vec2.y))),
node_id, node_id,
@ -674,7 +717,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
NumberInput::new(Some(vec2.y)) NumberInput::new(Some(vec2.y))
.label("H") .label("H")
.unit(" px") .unit(" px")
.disabled(dimensions_is_auto) .disabled(dimensions_is_auto && !transform_not_connected)
.on_update(update_value( .on_update(update_value(
move |number_input: &NumberInput| TaggedValue::OptionalDVec2(round(DVec2::new(vec2.x, number_input.value.unwrap()))), move |number_input: &NumberInput| TaggedValue::OptionalDVec2(round(DVec2::new(vec2.x, number_input.value.unwrap()))),
node_id, node_id,
@ -778,18 +821,22 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
let layer_reference_input_layer_name = layer_reference_input_layer.as_ref().map(|(layer_name, _)| layer_name); let layer_reference_input_layer_name = layer_reference_input_layer.as_ref().map(|(layer_name, _)| layer_name);
let layer_reference_input_layer_type = layer_reference_input_layer.as_ref().map(|(_, layer_type)| layer_type); let layer_reference_input_layer_type = layer_reference_input_layer.as_ref().map(|(_, layer_type)| layer_type);
widgets.extend_from_slice(&[ widgets.push(WidgetHolder::unrelated_separator());
WidgetHolder::unrelated_separator(), if !transform_not_connected {
LayerReferenceInput::new(layer_path.clone(), layer_reference_input_layer_name.cloned(), layer_reference_input_layer_type.cloned()) widgets.push(
.disabled(!use_base_image) LayerReferenceInput::new(layer_path.clone(), layer_reference_input_layer_name.cloned(), layer_reference_input_layer_type.cloned())
.on_update(update_value(|input: &LayerReferenceInput| TaggedValue::LayerPath(input.value.clone()), node_id, mask_index)) .disabled(!use_base_image)
.widget_holder(), .on_update(update_value(|input: &LayerReferenceInput| TaggedValue::LayerPath(input.value.clone()), node_id, mask_index))
]); .widget_holder(),
);
} else {
widgets.push(TextLabel::new("Requires Transform Input").italic(true).widget_holder());
}
} }
LayoutGroup::Row { widgets }.with_tooltip( LayoutGroup::Row { widgets }.with_tooltip(
"Reference to a layer or folder which masks parts of the input image. Image generation is constrained to masked areas.\n\ "Reference to a layer or folder which masks parts of the input image. Image generation is constrained to masked areas.\n\
\n\ \n\
Black shapes represent the masked regions. Lighter shades of gray act as a partial mask, and colors become grayscale.", Black shapes represent the masked regions. Lighter shades of gray act as a partial mask, and colors become grayscale. (This is the reverse of traditional masks because it is easier to draw black shapes; this will be changed later when the mask input is a bitmap.)",
) )
}; };

View file

@ -28,6 +28,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
artboard_document, artboard_document,
selected_layers, selected_layers,
node_graph_message_handler, node_graph_message_handler,
executor,
} = data; } = data;
let get_document = |document_selector: TargetDocument| match document_selector { let get_document = |document_selector: TargetDocument| match document_selector {
TargetDocument::Artboard => artboard_document, TargetDocument::Artboard => artboard_document,
@ -166,7 +167,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
let layer = document.layer(&path).unwrap(); let layer = document.layer(&path).unwrap();
match target_document { match target_document {
TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, persistent_data), TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, persistent_data),
TargetDocument::Artwork => register_artwork_layer_properties(document, path, layer, responses, persistent_data, node_graph_message_handler), TargetDocument::Artwork => register_artwork_layer_properties(document, path, layer, responses, persistent_data, node_graph_message_handler, executor),
} }
} }
} }

View file

@ -8,6 +8,7 @@ use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInp
use crate::messages::layout::utility_types::widgets::label_widgets::{IconLabel, TextLabel}; use crate::messages::layout::utility_types::widgets::label_widgets::{IconLabel, TextLabel};
use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::node_graph_executor::NodeGraphExecutor;
use document_legacy::document::Document; use document_legacy::document::Document;
use document_legacy::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant}; use document_legacy::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant};
@ -246,6 +247,7 @@ pub fn register_artwork_layer_properties(
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
persistent_data: &PersistentData, persistent_data: &PersistentData,
node_graph_message_handler: &NodeGraphMessageHandler, node_graph_message_handler: &NodeGraphMessageHandler,
executor: &mut NodeGraphExecutor,
) { ) {
let options_bar = vec![LayoutGroup::Row { let options_bar = vec![LayoutGroup::Row {
widgets: vec![ widgets: vec![
@ -323,6 +325,8 @@ pub fn register_artwork_layer_properties(
responses, responses,
nested_path: &node_graph_message_handler.nested_path, nested_path: &node_graph_message_handler.nested_path,
layer_path: &layer_path, layer_path: &layer_path,
executor,
network: &node_graph_frame.network,
}; };
node_graph_message_handler.collate_properties(node_graph_frame, &mut context, &mut properties_sections); node_graph_message_handler.collate_properties(node_graph_frame, &mut context, &mut properties_sections);

View file

@ -3,13 +3,14 @@ use document_legacy::LayerId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::messages::prelude::NodeGraphMessageHandler; use crate::{messages::prelude::NodeGraphMessageHandler, node_graph_executor::NodeGraphExecutor};
pub struct PropertiesPanelMessageHandlerData<'a> { pub struct PropertiesPanelMessageHandlerData<'a> {
pub artwork_document: &'a DocumentLegacy, pub artwork_document: &'a DocumentLegacy,
pub artboard_document: &'a DocumentLegacy, pub artboard_document: &'a DocumentLegacy,
pub selected_layers: &'a mut dyn Iterator<Item = &'a [LayerId]>, pub selected_layers: &'a mut dyn Iterator<Item = &'a [LayerId]>,
pub node_graph_message_handler: &'a NodeGraphMessageHandler, pub node_graph_message_handler: &'a NodeGraphMessageHandler,
pub executor: &'a mut NodeGraphExecutor,
} }
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, specta::Type)] #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, specta::Type)]

View file

@ -2,37 +2,29 @@ use super::utility_types::PersistentData;
use crate::application::generate_uuid; use crate::application::generate_uuid;
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION}; use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
use crate::messages::dialog::simple_dialogs; use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::{FrontendDocumentDetails, FrontendImageData}; use crate::messages::frontend::utility_types::FrontendDocumentDetails;
use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::layout::utility_types::misc::LayoutTarget; use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE; use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::misc::DocumentRenderMode;
use crate::messages::portfolio::utility_types::ImaginateServerStatus; use crate::messages::portfolio::utility_types::ImaginateServerStatus;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup}; use crate::messages::tool::utility_types::{HintData, HintGroup};
use document_legacy::document::pick_safe_imaginate_resolution; use crate::node_graph_executor::NodeGraphExecutor;
use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
use document_legacy::layers::style::RenderData; use document_legacy::layers::style::RenderData;
use document_legacy::layers::text_layer::Font; use document_legacy::layers::text_layer::Font;
use document_legacy::{LayerId, Operation as DocumentOperation}; use document_legacy::Operation as DocumentOperation;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::NodeId;
use graph_craft::document::{NodeInput, NodeNetwork};
use graph_craft::executor::Compiler;
use graphene_core::raster::Image; use graphene_core::raster::Image;
use glam::DVec2;
use interpreted_executor::executor::DynamicExecutor;
use std::borrow::Cow;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct PortfolioMessageHandler { pub struct PortfolioMessageHandler {
menu_bar_message_handler: MenuBarMessageHandler, menu_bar_message_handler: MenuBarMessageHandler,
documents: HashMap<u64, DocumentMessageHandler>, documents: HashMap<u64, DocumentMessageHandler>,
document_ids: Vec<u64>, document_ids: Vec<u64>,
executor: interpreted_executor::executor::DynamicExecutor, executor: NodeGraphExecutor,
active_document_id: Option<u64>, active_document_id: Option<u64>,
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize], copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
pub persistent_data: PersistentData, pub persistent_data: PersistentData,
@ -50,7 +42,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
PortfolioMessage::Document(message) => { PortfolioMessage::Document(message) => {
if let Some(document_id) = self.active_document_id { if let Some(document_id) = self.active_document_id {
if let Some(document) = self.documents.get_mut(&document_id) { if let Some(document) = self.documents.get_mut(&document_id) {
document.process_message(message, responses, (document_id, ipp, &self.persistent_data, preferences)) document.process_message(message, responses, (document_id, ipp, &self.persistent_data, preferences, &mut self.executor))
} }
} }
} }
@ -59,7 +51,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
#[remain::unsorted] #[remain::unsorted]
PortfolioMessage::DocumentPassMessage { document_id, message } => { PortfolioMessage::DocumentPassMessage { document_id, message } => {
if let Some(document) = self.documents.get_mut(&document_id) { if let Some(document) = self.documents.get_mut(&document_id) {
document.process_message(message, responses, (document_id, ipp, &self.persistent_data, preferences)) document.process_message(message, responses, (document_id, ipp, &self.persistent_data, preferences, &mut self.executor))
} }
} }
PortfolioMessage::AutoSaveActiveDocument => { PortfolioMessage::AutoSaveActiveDocument => {
@ -489,7 +481,14 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
size, size,
imaginate_node, imaginate_node,
} => { } => {
if let Err(description) = self.evaluate_node_graph(document_id, layer_path, (image_data, size), imaginate_node, preferences, responses) { if let Err(description) = self.executor.evaluate_node_graph(
(document_id, &mut self.documents),
layer_path,
(image_data, size),
imaginate_node,
(preferences, &self.persistent_data),
responses,
) {
responses.push_back( responses.push_back(
DialogMessage::DisplayDialogError { DialogMessage::DisplayDialogError {
title: "Unable to update node graph".to_string(), title: "Unable to update node graph".to_string(),
@ -681,227 +680,4 @@ impl PortfolioMessageHandler {
fn document_index(&self, document_id: u64) -> usize { fn document_index(&self, document_id: u64) -> usize {
self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids") self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
} }
/// Execute the network by flattening it and creating a borrow stack. Casts the output to the generic `T`.
fn execute_network<T: dyn_any::StaticType>(executor: &mut DynamicExecutor, network: NodeNetwork, image: Image) -> Result<T, String> {
let c = Compiler {};
let proto_network = c.compile(network, true);
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
executor.update(proto_network);
use dyn_any::IntoDynAny;
use graph_craft::executor::Executor;
let boxed = executor.execute(image.into_dyn()).map_err(|e| e.to_string())?;
dyn_any::downcast::<T>(boxed).map(|v| *v)
}
/// Computes an input for a node in the graph
fn compute_input<T: dyn_any::StaticType>(executor: &mut DynamicExecutor, old_network: &NodeNetwork, node_path: &[NodeId], mut input_index: usize, image: Cow<Image>) -> Result<T, String> {
let mut network = old_network.clone();
// Adjust the output of the graph so we find the relevant output
'outer: for end in (0..node_path.len()).rev() {
let mut inner_network = &mut network;
for &node_id in &node_path[..end] {
inner_network.output = node_id;
let Some(new_inner) = inner_network.nodes.get_mut(&node_id).and_then(|node| node.implementation.get_network_mut()) else {
return Err("Failed to find network".to_string());
};
inner_network = new_inner;
}
match &inner_network.nodes.get(&node_path[end]).unwrap().inputs[input_index] {
// If the input is from a parent network then adjust the input index and continue iteration
NodeInput::Network => {
input_index = inner_network
.inputs
.iter()
.enumerate()
.filter(|&(_index, &id)| id == node_path[end])
.nth(input_index)
.ok_or_else(|| "Invalid network input".to_string())?
.0;
}
// If the input is just a value, return that value
NodeInput::Value { tagged_value, .. } => return dyn_any::downcast::<T>(tagged_value.clone().to_any()).map(|v| *v),
// If the input is from a node, set the node to be the output (so that is what is evaluated)
NodeInput::Node(n) => {
inner_network.output = *n;
break 'outer;
}
}
}
Self::execute_network(executor, network, image.into_owned())
}
/// Encodes an image into a format using the image crate
fn encode_img(image: Image, resize: Option<DVec2>, format: image::ImageOutputFormat) -> Result<(Vec<u8>, (u32, u32)), String> {
use image::{ImageBuffer, Rgba};
use std::io::Cursor;
let mut image_data: Vec<u8> = Vec::new();
let [image_width, image_height] = [image.width, image.height];
let size_estimate = (image_width * image_height * 4) as usize;
let mut result_bytes = Vec::with_capacity(size_estimate);
result_bytes.extend(image.data.into_iter().flat_map(|color| color.to_rgba8()));
let mut output: ImageBuffer<Rgba<u8>, _> = image::ImageBuffer::from_raw(image_width, image_height, result_bytes).ok_or_else(|| "Invalid image size".to_string())?;
if let Some(size) = resize {
let size = size.as_uvec2();
if size.x > 0 && size.y > 0 {
output = image::imageops::resize(&output, size.x, size.y, image::imageops::Triangle);
}
}
let size = output.dimensions();
output.write_to(&mut Cursor::new(&mut image_data), format).map_err(|e| e.to_string())?;
Ok::<_, String>((image_data, size))
}
/// Evaluates a node graph, computing either the imaginate node or the entire graph
fn evaluate_node_graph(
&mut self,
document_id: u64,
layer_path: Vec<LayerId>,
(image_data, (width, height)): (Vec<u8>, (u32, u32)),
imaginate_node: Option<Vec<NodeId>>,
preferences: &PreferencesMessageHandler,
responses: &mut VecDeque<Message>,
) -> Result<(), String> {
// Reformat the input image data into an f32 image
let image = graphene_core::raster::Image::from_image_data(&image_data, width, height);
// Get the node graph layer
let document = self.documents.get_mut(&document_id).ok_or_else(|| "Invalid document".to_string())?;
let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?;
let node_graph_frame = match &layer.data {
LayerDataType::NodeGraphFrame(frame) => Ok(frame),
_ => Err("Invalid layer type".to_string()),
}?;
let network = node_graph_frame.network.clone();
// Execute the node graph
if let Some(imaginate_node) = imaginate_node {
use graph_craft::imaginate_input::*;
let get = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
let resolution: Option<glam::DVec2> = Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Resolution"), Cow::Borrowed(&image))?;
let resolution = resolution.unwrap_or_else(|| {
let transform = document.document_legacy.root.transform.inverse() * document.document_legacy.multiply_transforms(&layer_path).unwrap();
let (x, y) = pick_safe_imaginate_resolution((transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()));
DVec2::new(x as f64, y as f64)
});
let transform = document.document_legacy.root.transform.inverse() * document.document_legacy.multiply_transforms(&layer_path).unwrap();
let parameters = ImaginateGenerationParameters {
seed: Self::compute_input::<f64>(&mut self.executor, &network, &imaginate_node, get("Seed"), Cow::Borrowed(&image))? as u64,
resolution: resolution.as_uvec2().into(),
samples: Self::compute_input::<f64>(&mut self.executor, &network, &imaginate_node, get("Samples"), Cow::Borrowed(&image))? as u32,
sampling_method: Self::compute_input::<ImaginateSamplingMethod>(&mut self.executor, &network, &imaginate_node, get("Sampling Method"), Cow::Borrowed(&image))?
.api_value()
.to_string(),
text_guidance: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Prompt Guidance"), Cow::Borrowed(&image))?,
text_prompt: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Prompt"), Cow::Borrowed(&image))?,
negative_prompt: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Negative Prompt"), Cow::Borrowed(&image))?,
image_creativity: Some(Self::compute_input::<f64>(&mut self.executor, &network, &imaginate_node, get("Image Creativity"), Cow::Borrowed(&image))? / 100.),
restore_faces: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Improve Faces"), Cow::Borrowed(&image))?,
tiling: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Tiling"), Cow::Borrowed(&image))?,
};
let use_base_image = Self::compute_input::<bool>(&mut self.executor, &network, &imaginate_node, get("Adapt Input Image"), Cow::Borrowed(&image))?;
let base_image = if use_base_image {
let image: Image = Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Input Image"), Cow::Borrowed(&image))?;
// Only use if has size
if image.width > 0 && image.height > 0 {
let (image_data, size) = Self::encode_img(image, Some(resolution), image::ImageOutputFormat::Png)?;
let size = DVec2::new(size.0 as f64, size.1 as f64);
let mime = "image/png".to_string();
Some(ImaginateBaseImage { image_data, size, mime })
} else {
None
}
} else {
None
};
let mask_image =
if base_image.is_some() {
let mask_path: Option<Vec<LayerId>> = Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Masking Layer"), Cow::Borrowed(&image))?;
// Calculate the size of the node graph frame
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
// Render the masking layer within the node graph frame
let old_transforms = document.remove_document_transform();
let mask_is_some = mask_path.is_some();
let mask_image = mask_path.filter(|mask_layer_path| document.document_legacy.layer(mask_layer_path).is_ok()).map(|mask_layer_path| {
let render_mode = DocumentRenderMode::LayerCutout(&mask_layer_path, graphene_core::raster::color::Color::WHITE);
let svg = document.render_document(size, transform.inverse(), &self.persistent_data, render_mode);
ImaginateMaskImage { svg, size }
});
if mask_is_some && mask_image.is_none() {
return Err("Imagination masking layer is missing.\nIt may have been deleted or moved. Please drag a new layer reference\ninto the 'Masking Layer' parameter input, then generate again.".to_string());
}
document.restore_document_transform(old_transforms);
mask_image
} else {
None
};
responses.push_back(
FrontendMessage::TriggerImaginateGenerate {
parameters: Box::new(parameters),
base_image: base_image.map(Box::new),
mask_image: mask_image.map(Box::new),
mask_paint_mode: if Self::compute_input::<bool>(&mut self.executor, &network, &imaginate_node, get("Inpaint"), Cow::Borrowed(&image))? {
ImaginateMaskPaintMode::Inpaint
} else {
ImaginateMaskPaintMode::Outpaint
},
mask_blur_px: Self::compute_input::<f64>(&mut self.executor, &network, &imaginate_node, get("Mask Blur"), Cow::Borrowed(&image))? as u32,
imaginate_mask_starting_fill: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Mask Starting Fill"), Cow::Borrowed(&image))?,
hostname: preferences.imaginate_server_hostname.clone(),
refresh_frequency: preferences.imaginate_refresh_frequency,
document_id,
layer_path,
node_path: imaginate_node,
}
.into(),
);
} else {
let mut image: Image = Self::execute_network(&mut self.executor, network, image)?;
// If no image was generated, use the input image
if image.width == 0 || image.height == 0 {
image = graphene_core::raster::Image::from_image_data(&image_data, width, height);
}
let (image_data, _size) = Self::encode_img(image, None, image::ImageOutputFormat::Bmp)?;
responses.push_back(
DocumentOperation::SetNodeGraphFrameImageData {
layer_path: layer_path.clone(),
image_data: image_data.clone(),
}
.into(),
);
let mime = "image/bmp".to_string();
let image_data = std::sync::Arc::new(image_data);
responses.push_back(
FrontendMessage::UpdateImageData {
document_id,
image_data: vec![FrontendImageData { path: layer_path, image_data, mime }],
}
.into(),
);
}
Ok(())
}
} }

View file

@ -1,7 +1,7 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE; use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
@ -123,18 +123,14 @@ impl Fsm for ImaginateToolFsmState {
let imaginate_node_type = IMAGINATE_NODE; let imaginate_node_type = IMAGINATE_NODE;
let mut imaginate_inputs: Vec<NodeInput> = imaginate_node_type.inputs.iter().map(|input| input.default.clone()).collect(); let mut imaginate_inputs: Vec<NodeInput> = imaginate_node_type.inputs.iter().map(|input| input.default.clone()).collect();
imaginate_inputs[0] = NodeInput::Node(0); imaginate_inputs[0] = NodeInput::node(0, 0);
imaginate_inputs[1] = NodeInput::node(0, 1);
let imaginate_node_id = 2; let imaginate_node_id = 100;
let mut network = NodeNetwork::new_network(32, imaginate_node_id); let mut network = node_graph::new_image_network(32, imaginate_node_id);
network.nodes.insert( network.nodes.insert(
imaginate_node_id, imaginate_node_id,
DocumentNode { imaginate_node_type.to_document_node(imaginate_inputs, graph_craft::document::DocumentNodeMetadata::position((20, 3))),
name: imaginate_node_type.name.to_string(),
inputs: imaginate_inputs,
implementation: imaginate_node_type.generate_implementation(),
metadata: graph_craft::document::DocumentNodeMetadata { position: (20, 4).into() },
},
); );
responses.push_back( responses.push_back(

View file

@ -1,6 +1,7 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::portfolio::document::node_graph;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
@ -117,7 +118,7 @@ impl Fsm for NodeGraphToolFsmState {
shape_data.path = Some(document.get_path_for_new_layer()); shape_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
let network = graph_craft::document::NodeNetwork::new_network(20, 0); let network = node_graph::new_image_network(20, 0);
responses.push_back( responses.push_back(
Operation::AddNodeGraphFrame { Operation::AddNodeGraphFrame {

View file

@ -0,0 +1,277 @@
use crate::messages::frontend::utility_types::FrontendImageData;
use crate::messages::portfolio::document::utility_types::misc::DocumentRenderMode;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
use document_legacy::{document::pick_safe_imaginate_resolution, layers::layer_info::LayerDataType};
use document_legacy::{LayerId, Operation};
use graph_craft::document::{generate_uuid, value::TaggedValue, NodeId, NodeInput, NodeNetwork, NodeOutput};
use graph_craft::executor::Compiler;
use graphene_core::raster::{Image, ImageFrame};
use interpreted_executor::executor::DynamicExecutor;
use glam::{DAffine2, DVec2};
use std::borrow::Cow;
#[derive(Debug, Clone, Default)]
pub struct NodeGraphExecutor {
executor: DynamicExecutor,
}
impl NodeGraphExecutor {
/// Sets the transform property on the input node
fn set_input_transform(network: &mut NodeNetwork, transform: DAffine2) {
let Some(input_node) = network.nodes.get_mut(&network.inputs[0]) else {
return;
};
input_node.inputs[1] = NodeInput::value(TaggedValue::DAffine2(transform), false);
}
/// Execute the network by flattening it and creating a borrow stack. Casts the output to the generic `T`.
fn execute_network<T: dyn_any::StaticType>(&mut self, mut network: NodeNetwork, image_frame: ImageFrame) -> Result<T, String> {
Self::set_input_transform(&mut network, image_frame.transform);
network.duplicate_outputs(&mut generate_uuid);
network.remove_dead_nodes();
// We assume only one output
assert_eq!(network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
let c = Compiler {};
let proto_network = c.compile_single(network, true)?;
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
self.executor.update(proto_network);
use dyn_any::IntoDynAny;
use graph_craft::executor::Executor;
let boxed = self.executor.execute(image_frame.image.into_dyn()).map_err(|e| e.to_string())?;
dyn_any::downcast::<T>(boxed).map(|v| *v)
}
/// Computes an input for a node in the graph
pub fn compute_input<T: dyn_any::StaticType>(&mut self, old_network: &NodeNetwork, node_path: &[NodeId], mut input_index: usize, image_frame: Cow<ImageFrame>) -> Result<T, String> {
let mut network = old_network.clone();
// Adjust the output of the graph so we find the relevant output
'outer: for end in (0..node_path.len()).rev() {
let mut inner_network = &mut network;
for &node_id in &node_path[..end] {
inner_network.outputs[0] = NodeOutput::new(node_id, 0);
let Some(new_inner) = inner_network.nodes.get_mut(&node_id).and_then(|node| node.implementation.get_network_mut()) else {
return Err("Failed to find network".to_string());
};
inner_network = new_inner;
}
match &inner_network.nodes.get(&node_path[end]).unwrap().inputs[input_index] {
// If the input is from a parent network then adjust the input index and continue iteration
NodeInput::Network => {
input_index = inner_network
.inputs
.iter()
.enumerate()
.filter(|&(_index, &id)| id == node_path[end])
.nth(input_index)
.ok_or_else(|| "Invalid network input".to_string())?
.0;
}
// If the input is just a value, return that value
NodeInput::Value { tagged_value, .. } => return dyn_any::downcast::<T>(tagged_value.clone().to_any()).map(|v| *v),
// If the input is from a node, set the node to be the output (so that is what is evaluated)
NodeInput::Node { node_id, output_index } => {
inner_network.outputs[0] = NodeOutput::new(*node_id, *output_index);
break 'outer;
}
}
}
self.execute_network(network, image_frame.into_owned())
}
/// Encodes an image into a format using the image crate
fn encode_img(image: Image, resize: Option<DVec2>, format: image::ImageOutputFormat) -> Result<(Vec<u8>, (u32, u32)), String> {
use image::{ImageBuffer, Rgba};
use std::io::Cursor;
let (result_bytes, width, height) = image.as_flat_u8();
let mut output: ImageBuffer<Rgba<u8>, _> = image::ImageBuffer::from_raw(width, height, result_bytes).ok_or_else(|| "Invalid image size".to_string())?;
if let Some(size) = resize {
let size = size.as_uvec2();
if size.x > 0 && size.y > 0 {
output = image::imageops::resize(&output, size.x, size.y, image::imageops::Triangle);
}
}
let size = output.dimensions();
let mut image_data: Vec<u8> = Vec::new();
output.write_to(&mut Cursor::new(&mut image_data), format).map_err(|e| e.to_string())?;
Ok::<_, String>((image_data, size))
}
fn generate_imaginate(
&mut self,
network: NodeNetwork,
imaginate_node: Vec<NodeId>,
(document, document_id): (&mut DocumentMessageHandler, u64),
layer_path: Vec<LayerId>,
image_frame: ImageFrame,
(preferences, persistent_data): (&PreferencesMessageHandler, &PersistentData),
) -> Result<Message, String> {
use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE;
use graph_craft::imaginate_input::*;
let get = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
let transform: DAffine2 = self.compute_input(&network, &imaginate_node, get("Transform"), Cow::Borrowed(&image_frame))?;
let resolution: Option<glam::DVec2> = self.compute_input(&network, &imaginate_node, get("Resolution"), Cow::Borrowed(&image_frame))?;
let resolution = resolution.unwrap_or_else(|| {
let (x, y) = pick_safe_imaginate_resolution((transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()));
DVec2::new(x as f64, y as f64)
});
let parameters = ImaginateGenerationParameters {
seed: self.compute_input::<f64>(&network, &imaginate_node, get("Seed"), Cow::Borrowed(&image_frame))? as u64,
resolution: resolution.as_uvec2().into(),
samples: self.compute_input::<f64>(&network, &imaginate_node, get("Samples"), Cow::Borrowed(&image_frame))? as u32,
sampling_method: self
.compute_input::<ImaginateSamplingMethod>(&network, &imaginate_node, get("Sampling Method"), Cow::Borrowed(&image_frame))?
.api_value()
.to_string(),
text_guidance: self.compute_input(&network, &imaginate_node, get("Prompt Guidance"), Cow::Borrowed(&image_frame))?,
text_prompt: self.compute_input(&network, &imaginate_node, get("Prompt"), Cow::Borrowed(&image_frame))?,
negative_prompt: self.compute_input(&network, &imaginate_node, get("Negative Prompt"), Cow::Borrowed(&image_frame))?,
image_creativity: Some(self.compute_input::<f64>(&network, &imaginate_node, get("Image Creativity"), Cow::Borrowed(&image_frame))? / 100.),
restore_faces: self.compute_input(&network, &imaginate_node, get("Improve Faces"), Cow::Borrowed(&image_frame))?,
tiling: self.compute_input(&network, &imaginate_node, get("Tiling"), Cow::Borrowed(&image_frame))?,
};
let use_base_image = self.compute_input::<bool>(&network, &imaginate_node, get("Adapt Input Image"), Cow::Borrowed(&image_frame))?;
let base_image = if use_base_image {
let image: Image = self.compute_input(&network, &imaginate_node, get("Input Image"), Cow::Borrowed(&image_frame))?;
// Only use if has size
if image.width > 0 && image.height > 0 {
let (image_data, size) = Self::encode_img(image, Some(resolution), image::ImageOutputFormat::Png)?;
let size = DVec2::new(size.0 as f64, size.1 as f64);
let mime = "image/png".to_string();
Some(ImaginateBaseImage { image_data, size, mime })
} else {
None
}
} else {
None
};
let mask_image = if base_image.is_some() {
let mask_path: Option<Vec<LayerId>> = self.compute_input(&network, &imaginate_node, get("Masking Layer"), Cow::Borrowed(&image_frame))?;
// Calculate the size of the node graph frame
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
// Render the masking layer within the node graph frame
let old_transforms = document.remove_document_transform();
let mask_is_some = mask_path.is_some();
let mask_image = mask_path.filter(|mask_layer_path| document.document_legacy.layer(mask_layer_path).is_ok()).map(|mask_layer_path| {
let render_mode = DocumentRenderMode::LayerCutout(&mask_layer_path, graphene_core::raster::color::Color::WHITE);
let svg = document.render_document(size, transform.inverse(), persistent_data, render_mode);
ImaginateMaskImage { svg, size }
});
if mask_is_some && mask_image.is_none() {
return Err(
"Imagination masking layer is missing.\nIt may have been deleted or moved. Please drag a new layer reference\ninto the 'Masking Layer' parameter input, then generate again."
.to_string(),
);
}
document.restore_document_transform(old_transforms);
mask_image
} else {
None
};
Ok(FrontendMessage::TriggerImaginateGenerate {
parameters: Box::new(parameters),
base_image: base_image.map(Box::new),
mask_image: mask_image.map(Box::new),
mask_paint_mode: if self.compute_input::<bool>(&network, &imaginate_node, get("Inpaint"), Cow::Borrowed(&image_frame))? {
ImaginateMaskPaintMode::Inpaint
} else {
ImaginateMaskPaintMode::Outpaint
},
mask_blur_px: self.compute_input::<f64>(&network, &imaginate_node, get("Mask Blur"), Cow::Borrowed(&image_frame))? as u32,
imaginate_mask_starting_fill: self.compute_input(&network, &imaginate_node, get("Mask Starting Fill"), Cow::Borrowed(&image_frame))?,
hostname: preferences.imaginate_server_hostname.clone(),
refresh_frequency: preferences.imaginate_refresh_frequency,
document_id,
layer_path,
node_path: imaginate_node,
}
.into())
}
/// Evaluates a node graph, computing either the imaginate node or the entire graph
pub fn evaluate_node_graph(
&mut self,
(document_id, documents): (u64, &mut HashMap<u64, DocumentMessageHandler>),
layer_path: Vec<LayerId>,
(image_data, (width, height)): (Vec<u8>, (u32, u32)),
imaginate_node: Option<Vec<NodeId>>,
persistent_data: (&PreferencesMessageHandler, &PersistentData),
responses: &mut VecDeque<Message>,
) -> Result<(), String> {
// Reformat the input image data into an f32 image
let image = graphene_core::raster::Image::from_image_data(&image_data, width, height);
// Get the node graph layer
let document = documents.get_mut(&document_id).ok_or_else(|| "Invalid document".to_string())?;
let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?;
// Construct the input image frame
let transform = layer.transform;
let image_frame = ImageFrame { image, transform };
let node_graph_frame = match &layer.data {
LayerDataType::NodeGraphFrame(frame) => Ok(frame),
_ => Err("Invalid layer type".to_string()),
}?;
let network = node_graph_frame.network.clone();
// Execute the node graph
if let Some(imaginate_node) = imaginate_node {
responses.push_back(self.generate_imaginate(network, imaginate_node, (document, document_id), layer_path, image_frame, persistent_data)?);
} else {
let ImageFrame { mut image, transform } = self.execute_network(network, image_frame)?;
// If no image was generated, use the input image
if image.width == 0 || image.height == 0 {
image = graphene_core::raster::Image::from_image_data(&image_data, width, height);
}
let (image_data, _size) = Self::encode_img(image, None, image::ImageOutputFormat::Bmp)?;
responses.push_back(
Operation::SetNodeGraphFrameImageData {
layer_path: layer_path.clone(),
image_data: image_data.clone(),
}
.into(),
);
let mime = "image/bmp".to_string();
let image_data = std::sync::Arc::new(image_data);
let image_data = vec![FrontendImageData {
path: layer_path.clone(),
image_data,
mime,
}];
responses.push_back(FrontendMessage::UpdateImageData { document_id, image_data }.into());
// Update the transform based on the graph output
let transform = transform.to_cols_array();
responses.push_back(Operation::SetLayerTransform { path: layer_path, transform }.into());
}
Ok(())
}
}

View file

@ -92,12 +92,15 @@
} }
function resolveLink(link: FrontendNodeLink, containerBounds: HTMLDivElement): { nodePrimaryOutput: HTMLDivElement | undefined; nodePrimaryInput: HTMLDivElement | undefined } { function resolveLink(link: FrontendNodeLink, containerBounds: HTMLDivElement): { nodePrimaryOutput: HTMLDivElement | undefined; nodePrimaryInput: HTMLDivElement | undefined } {
const connectorIndex = Number(link.linkEndInputIndex); const outputIndex = Number(link.linkStartOutputIndex);
const inputIndex = Number(link.linkEndInputIndex);
const nodePrimaryOutput = (containerBounds.querySelector(`[data-node="${String(link.linkStart)}"] [data-port="output"]`) || undefined) as HTMLDivElement | undefined; const nodeOutputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkStart)}"] [data-port="output"]`) || undefined;
const nodeInputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkEnd)}"] [data-port="input"]`) || undefined; const nodeInputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkEnd)}"] [data-port="input"]`) || undefined;
const nodePrimaryInput = nodeInputConnectors?.[connectorIndex] as HTMLDivElement | undefined;
const nodePrimaryOutput = nodeOutputConnectors?.[outputIndex] as HTMLDivElement | undefined;
const nodePrimaryInput = nodeInputConnectors?.[inputIndex] as HTMLDivElement | undefined;
return { nodePrimaryOutput, nodePrimaryInput }; return { nodePrimaryOutput, nodePrimaryInput };
} }
@ -263,8 +266,8 @@
const inputIndexInt = BigInt(inputIndex); const inputIndexInt = BigInt(inputIndex);
const links = $nodeGraph.links; const links = $nodeGraph.links;
const linkIndex = links.findIndex((value) => value.linkEnd === nodeIdInt && value.linkEndInputIndex === inputIndexInt); const linkIndex = links.findIndex((value) => value.linkEnd === nodeIdInt && value.linkEndInputIndex === inputIndexInt);
const queryString = `[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`; const nodeOutputConnectors = nodesContainer.querySelectorAll(`[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined;
linkInProgressFromConnector = (nodesContainer.querySelector(queryString) || undefined) as HTMLDivElement | undefined; linkInProgressFromConnector = nodeOutputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
const nodeInputConnectors = nodesContainer.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined; const nodeInputConnectors = nodesContainer.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined;
linkInProgressToConnector = nodeInputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined; linkInProgressToConnector = nodeInputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
disconnecting = { nodeId: nodeIdInt, inputIndex, linkIndex }; disconnecting = { nodeId: nodeIdInt, inputIndex, linkIndex };
@ -359,13 +362,16 @@
if (outputNode && inputNode && outputConnectedNodeID && inputConnectedNodeID) { if (outputNode && inputNode && outputConnectedNodeID && inputConnectedNodeID) {
const inputNodeInPorts = Array.from(inputNode.querySelectorAll(`[data-port="input"]`)); const inputNodeInPorts = Array.from(inputNode.querySelectorAll(`[data-port="input"]`));
const outputNodeInPorts = Array.from(outputNode.querySelectorAll(`[data-port="output"]`));
const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(linkInProgressToConnector); const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(linkInProgressToConnector);
const outputNodeConnectionIndexSearch = outputNodeInPorts.indexOf(linkInProgressFromConnector);
const inputNodeConnectionIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined; const inputNodeConnectionIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined;
const outputNodeConnectionIndex = outputNodeConnectionIndexSearch > -1 ? outputNodeConnectionIndexSearch : undefined;
if (inputNodeConnectionIndex !== undefined) { if (inputNodeConnectionIndex !== undefined && outputNodeConnectionIndex !== undefined) {
// const oneBasedIndex = inputNodeConnectionIndex + 1; editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), outputNodeConnectionIndex, BigInt(inputConnectedNodeID), inputNodeConnectionIndex);
editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), BigInt(inputConnectedNodeID), inputNodeConnectionIndex);
} }
} }
} else if (draggingNodes) { } else if (draggingNodes) {
@ -411,8 +417,8 @@
}); });
// If the node has been dragged on top of the link then connect it into the middle. // If the node has been dragged on top of the link then connect it into the middle.
if (link) { if (link) {
editor.instance.connectNodesByLink(link.linkStart, selectedNodeId, 0); editor.instance.connectNodesByLink(link.linkStart, 0, selectedNodeId, 0);
editor.instance.connectNodesByLink(selectedNodeId, link.linkEnd, Number(link.linkEndInputIndex)); editor.instance.connectNodesByLink(selectedNodeId, 0, link.linkEnd, Number(link.linkEndInputIndex));
editor.instance.shiftNode(selectedNodeId); editor.instance.shiftNode(selectedNodeId);
} }
} }
@ -482,10 +488,11 @@
{/if} {/if}
<div class="nodes" style:transform={`scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`} style:transform-origin={`0 0`} bind:this={nodesContainer}> <div class="nodes" style:transform={`scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`} style:transform-origin={`0 0`} bind:this={nodesContainer}>
{#each $nodeGraph.nodes as node (String(node.id))} {#each $nodeGraph.nodes as node (String(node.id))}
{@const exposedInputsOutputs = [...node.exposedInputs, ...node.outputs.slice(1)]}
<div <div
class="node" class="node"
class:selected={selected.includes(node.id)} class:selected={selected.includes(node.id)}
class:output={node.output} class:previewed={node.previewed}
class:disabled={node.disabled} class:disabled={node.disabled}
style:--offset-left={(node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)} style:--offset-left={(node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)}
style:--offset-top={(node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)} style:--offset-top={(node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
@ -508,9 +515,9 @@
<div <div
class="output port" class="output port"
data-port="output" data-port="output"
data-datatype={node.outputs[0]} data-datatype={node.outputs[0].dataType}
style:--data-color={`var(--color-data-${node.outputs[0]})`} style:--data-color={`var(--color-data-${node.outputs[0].dataType})`}
style:--data-color-dim={`var(--color-data-${node.outputs[0]}-dim)`} style:--data-color-dim={`var(--color-data-${node.outputs[0].dataType}-dim)`}
> >
<div /> <div />
</div> </div>
@ -519,22 +526,34 @@
<IconLabel icon={nodeIcon(node.displayName)} /> <IconLabel icon={nodeIcon(node.displayName)} />
<TextLabel>{node.displayName}</TextLabel> <TextLabel>{node.displayName}</TextLabel>
</div> </div>
{#if node.exposedInputs.length > 0} {#if exposedInputsOutputs.length > 0}
<div class="arguments"> <div class="parameters">
{#each node.exposedInputs as argument, index (index)} {#each exposedInputsOutputs as parameter, index (index)}
<div class="argument"> <div class="parameter">
<div class="ports"> <div class="ports">
<div {#if index < node.exposedInputs.length}
class="input port" <div
data-port="input" class="input port"
data-datatype={argument.dataType} data-port="input"
style:--data-color={`var(--color-data-${argument.dataType})`} data-datatype={parameter.dataType}
style:--data-color-dim={`var(--color-data-${argument.dataType}-dim)`} style:--data-color={`var(--color-data-${parameter.dataType})`}
> style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
<div /> >
</div> <div />
</div>
{:else}
<div
class="output port"
data-port="output"
data-datatype={parameter.dataType}
style:--data-color={`var(--color-data-${parameter.dataType})`}
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
>
<div />
</div>
{/if}
</div> </div>
<TextLabel>{argument.name}</TextLabel> <TextLabel class={index < node.exposedInputs.length ? "name" : "output"}>{parameter.name}</TextLabel>
</div> </div>
{/each} {/each}
</div> </div>
@ -656,7 +675,7 @@
} }
} }
&.output { &.previewed {
outline: 3px solid var(--color-data-vector); outline: 3px solid var(--color-data-vector);
} }
@ -679,20 +698,28 @@
} }
} }
.arguments { .parameters {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
position: relative; position: relative;
.argument { .parameter {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
height: 24px; height: 24px;
width: 100%; width: calc(100% - 24px * 2);
margin-left: 24px; margin-left: 24px;
margin-right: 24px; margin-right: 24px;
.text-label {
width: 100%;
&.output {
text-align: right;
}
}
} }
// Squares to cover up the rounded corners of the primary area and make them have a straight edge // Squares to cover up the rounded corners of the primary area and make them have a straight edge

View file

@ -83,6 +83,12 @@ export class NodeGraphInput {
readonly name!: string; readonly name!: string;
} }
export class NodeGraphOutput {
readonly dataType!: FrontendGraphDataType;
readonly name!: string;
}
export class FrontendNode { export class FrontendNode {
readonly id!: bigint; readonly id!: bigint;
@ -92,12 +98,12 @@ export class FrontendNode {
readonly exposedInputs!: NodeGraphInput[]; readonly exposedInputs!: NodeGraphInput[];
readonly outputs!: FrontendGraphDataType[]; readonly outputs!: NodeGraphOutput[];
@TupleToVec2 @TupleToVec2
readonly position!: XY | undefined; readonly position!: XY | undefined;
readonly output!: boolean; readonly previewed!: boolean;
readonly disabled!: boolean; readonly disabled!: boolean;
} }
@ -105,6 +111,8 @@ export class FrontendNode {
export class FrontendNodeLink { export class FrontendNodeLink {
readonly linkStart!: bigint; readonly linkStart!: bigint;
readonly linkStartOutputIndex!: bigint;
readonly linkEnd!: bigint; readonly linkEnd!: bigint;
readonly linkEndInputIndex!: bigint; readonly linkEndInputIndex!: bigint;

View file

@ -578,9 +578,10 @@ impl JsEditorHandle {
/// Notifies the backend that the user connected a node's primary output to one of another node's inputs /// Notifies the backend that the user connected a node's primary output to one of another node's inputs
#[wasm_bindgen(js_name = connectNodesByLink)] #[wasm_bindgen(js_name = connectNodesByLink)]
pub fn connect_nodes_by_link(&self, output_node: u64, input_node: u64, input_node_connector_index: usize) { pub fn connect_nodes_by_link(&self, output_node: u64, output_node_connector_index: usize, input_node: u64, input_node_connector_index: usize) {
let message = NodeGraphMessage::ConnectNodesByLink { let message = NodeGraphMessage::ConnectNodesByLink {
output_node, output_node,
output_node_connector_index,
input_node, input_node,
input_node_connector_index, input_node_connector_index,
}; };

View file

@ -36,7 +36,7 @@
v-for="node in nodes" v-for="node in nodes"
:key="String(node.id)" :key="String(node.id)"
class="node" class="node"
:class="{ selected: selected.includes(node.id), output: node.output, disabled: node.disabled }" :class="{ selected: selected.includes(node.id), previewed: node.previewed, disabled: node.disabled }"
:style="{ :style="{
'--offset-left': (node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0), '--offset-left': (node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0),
'--offset-top': (node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0), '--offset-top': (node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0),
@ -58,8 +58,8 @@
v-if="node.outputs.length > 0" v-if="node.outputs.length > 0"
class="output port" class="output port"
data-port="output" data-port="output"
:data-datatype="node.outputs[0]" :data-datatype="node.outputs[0].dataType"
:style="{ '--data-color': `var(--color-data-${node.outputs[0]})`, '--data-color-dim': `var(--color-data-${node.outputs[0]}-dim)` }" :style="{ '--data-color': `var(--color-data-${node.outputs[0].dataType})`, '--data-color-dim': `var(--color-data-${node.outputs[0].dataType}-dim)` }"
> >
<div></div> <div></div>
</div> </div>
@ -67,19 +67,32 @@
<IconLabel :icon="nodeIcon(node.displayName)" /> <IconLabel :icon="nodeIcon(node.displayName)" />
<TextLabel>{{ node.displayName }}</TextLabel> <TextLabel>{{ node.displayName }}</TextLabel>
</div> </div>
<div v-if="node.exposedInputs.length > 0" class="arguments"> <div v-if="[...node.exposedInputs, ...node.outputs.slice(1)].length > 0" class="parameters">
<div v-for="(argument, index) in node.exposedInputs" :key="index" class="argument"> <div v-for="(parameter, index) in [...node.exposedInputs, ...node.outputs.slice(1)]" :key="index" class="parameter">
<div class="ports"> <div class="ports">
<div <div
v-if="index < node.exposedInputs.length"
class="input port" class="input port"
data-port="input" data-port="input"
:data-datatype="argument.dataType" :data-datatype="parameter.dataType"
:style="{ '--data-color': `var(--color-data-${argument.dataType})`, '--data-color-dim': `var(--color-data-${argument.dataType}-dim)` }" :style="{
'--data-color': `var(--color-data-${parameter.dataType})`,
'--data-color-dim': `var(--color-data-${parameter.dataType}-dim)`,
}"
>
<div></div>
</div>
<div
v-else
class="output port"
data-port="output"
:data-datatype="parameter.dataType"
:style="{ '--data-color': `var(--color-data-${parameter.dataType})`, '--data-color-dim': `var(--color-data-${parameter.dataType}-dim)` }"
> >
<div></div> <div></div>
</div> </div>
</div> </div>
<TextLabel>{{ argument.name }}</TextLabel> <TextLabel :class="index < node.exposedInputs.length ? 'name' : 'output'">{{ parameter.name }}</TextLabel>
</div> </div>
</div> </div>
</div> </div>
@ -208,7 +221,7 @@
} }
} }
&.output { &.previewed {
outline: 3px solid var(--color-data-vector); outline: 3px solid var(--color-data-vector);
} }
@ -231,20 +244,28 @@
} }
} }
.arguments { .parameters {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
position: relative; position: relative;
.argument { .parameter {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
height: 24px; height: 24px;
width: 100%; width: calc(100% - 24px * 2);
margin-left: 24px; margin-left: 24px;
margin-right: 24px; margin-right: 24px;
.text-label {
width: 100%;
&.output {
text-align: right;
}
}
} }
// Squares to cover up the rounded corners of the primary area and make them have a straight edge // Squares to cover up the rounded corners of the primary area and make them have a straight edge
@ -315,8 +336,6 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, nextTick } from "vue"; import { defineComponent, nextTick } from "vue";
// import type { FrontendNode } from "@/wasm-communication/messages";
import type { IconName } from "@/utility-functions/icons"; import type { IconName } from "@/utility-functions/icons";
import { UpdateNodeGraphSelection, type FrontendNodeLink } from "@/wasm-communication/messages"; import { UpdateNodeGraphSelection, type FrontendNodeLink } from "@/wasm-communication/messages";
@ -411,12 +430,15 @@ export default defineComponent({
}, },
methods: { methods: {
resolveLink(link: FrontendNodeLink, containerBounds: HTMLDivElement): { nodePrimaryOutput: HTMLDivElement | undefined; nodePrimaryInput: HTMLDivElement | undefined } { resolveLink(link: FrontendNodeLink, containerBounds: HTMLDivElement): { nodePrimaryOutput: HTMLDivElement | undefined; nodePrimaryInput: HTMLDivElement | undefined } {
const connectorIndex = Number(link.linkEndInputIndex); const outputIndex = Number(link.linkStartOutputIndex);
const inputIndex = Number(link.linkEndInputIndex);
const nodePrimaryOutput = (containerBounds.querySelector(`[data-node="${String(link.linkStart)}"] [data-port="output"]`) || undefined) as HTMLDivElement | undefined;
const nodeOutputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkStart)}"] [data-port="output"]`) || undefined;
const nodeInputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkEnd)}"] [data-port="input"]`) || undefined; const nodeInputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkEnd)}"] [data-port="input"]`) || undefined;
const nodePrimaryInput = nodeInputConnectors?.[connectorIndex] as HTMLDivElement | undefined;
const nodePrimaryOutput = nodeOutputConnectors?.[outputIndex] as HTMLDivElement | undefined;
const nodePrimaryInput = nodeInputConnectors?.[inputIndex] as HTMLDivElement | undefined;
return { nodePrimaryOutput, nodePrimaryInput }; return { nodePrimaryOutput, nodePrimaryInput };
}, },
async refreshLinks(): Promise<void> { async refreshLinks(): Promise<void> {
@ -579,8 +601,8 @@ export default defineComponent({
const inputIndexInt = BigInt(inputIndex); const inputIndexInt = BigInt(inputIndex);
const links = this.nodeGraph.state.links; const links = this.nodeGraph.state.links;
const linkIndex = links.findIndex((value) => value.linkEnd === nodeIdInt && value.linkEndInputIndex === inputIndexInt); const linkIndex = links.findIndex((value) => value.linkEnd === nodeIdInt && value.linkEndInputIndex === inputIndexInt);
const queryString = `[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`; const nodeOutputConnectors = containerBounds.querySelectorAll(`[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined;
this.linkInProgressFromConnector = (containerBounds.querySelector(queryString) || undefined) as HTMLDivElement | undefined; this.linkInProgressFromConnector = nodeOutputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
const nodeInputConnectors = containerBounds.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined; const nodeInputConnectors = containerBounds.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined;
this.linkInProgressToConnector = nodeInputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined; this.linkInProgressToConnector = nodeInputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
this.disconnecting = { nodeId: nodeIdInt, inputIndex, linkIndex }; this.disconnecting = { nodeId: nodeIdInt, inputIndex, linkIndex };
@ -674,13 +696,16 @@ export default defineComponent({
if (outputNode && inputNode && outputConnectedNodeID && inputConnectedNodeID) { if (outputNode && inputNode && outputConnectedNodeID && inputConnectedNodeID) {
const inputNodeInPorts = Array.from(inputNode.querySelectorAll(`[data-port="input"]`)); const inputNodeInPorts = Array.from(inputNode.querySelectorAll(`[data-port="input"]`));
const outputNodeInPorts = Array.from(outputNode.querySelectorAll(`[data-port="output"]`));
const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(this.linkInProgressToConnector); const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(this.linkInProgressToConnector);
const outputNodeConnectionIndexSearch = outputNodeInPorts.indexOf(this.linkInProgressFromConnector);
const inputNodeConnectionIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined; const inputNodeConnectionIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined;
const outputNodeConnectionIndex = outputNodeConnectionIndexSearch > -1 ? outputNodeConnectionIndexSearch : undefined;
if (inputNodeConnectionIndex !== undefined) { if (inputNodeConnectionIndex !== undefined && outputNodeConnectionIndex !== undefined) {
// const oneBasedIndex = inputNodeConnectionIndex + 1; this.editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), outputNodeConnectionIndex, BigInt(inputConnectedNodeID), inputNodeConnectionIndex);
this.editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), BigInt(inputConnectedNodeID), inputNodeConnectionIndex);
} }
} }
} else if (this.draggingNodes) { } else if (this.draggingNodes) {
@ -728,8 +753,8 @@ export default defineComponent({
}); });
// If the node has been dragged on top of the link then connect it into the middle. // If the node has been dragged on top of the link then connect it into the middle.
if (link) { if (link) {
this.editor.instance.connectNodesByLink(link.linkStart, selectedNodeId, 0); this.editor.instance.connectNodesByLink(link.linkStart, 0, selectedNodeId, 0);
this.editor.instance.connectNodesByLink(selectedNodeId, link.linkEnd, Number(link.linkEndInputIndex)); this.editor.instance.connectNodesByLink(selectedNodeId, 0, link.linkEnd, Number(link.linkEndInputIndex));
this.editor.instance.shiftNode(selectedNodeId); this.editor.instance.shiftNode(selectedNodeId);
} }
} }

View file

@ -83,6 +83,12 @@ export class NodeGraphInput {
readonly name!: string; readonly name!: string;
} }
export class NodeGraphOutput {
readonly dataType!: FrontendGraphDataType;
readonly name!: string;
}
export class FrontendNode { export class FrontendNode {
readonly id!: bigint; readonly id!: bigint;
@ -92,12 +98,12 @@ export class FrontendNode {
readonly exposedInputs!: NodeGraphInput[]; readonly exposedInputs!: NodeGraphInput[];
readonly outputs!: FrontendGraphDataType[]; readonly outputs!: NodeGraphOutput[];
@TupleToVec2 @TupleToVec2
readonly position!: XY | undefined; readonly position!: XY | undefined;
readonly output!: boolean; readonly previewed!: boolean;
readonly disabled!: boolean; readonly disabled!: boolean;
} }
@ -105,6 +111,8 @@ export class FrontendNode {
export class FrontendNodeLink { export class FrontendNodeLink {
readonly linkStart!: bigint; readonly linkStart!: bigint;
readonly linkStartOutputIndex!: bigint;
readonly linkEnd!: bigint; readonly linkEnd!: bigint;
readonly linkEndInputIndex!: bigint; readonly linkEndInputIndex!: bigint;

View file

@ -578,9 +578,10 @@ impl JsEditorHandle {
/// Notifies the backend that the user connected a node's primary output to one of another node's inputs /// Notifies the backend that the user connected a node's primary output to one of another node's inputs
#[wasm_bindgen(js_name = connectNodesByLink)] #[wasm_bindgen(js_name = connectNodesByLink)]
pub fn connect_nodes_by_link(&self, output_node: u64, input_node: u64, input_node_connector_index: usize) { pub fn connect_nodes_by_link(&self, output_node: u64, output_node_connector_index: usize, input_node: u64, input_node_connector_index: usize) {
let message = NodeGraphMessage::ConnectNodesByLink { let message = NodeGraphMessage::ConnectNodesByLink {
output_node, output_node,
output_node_connector_index,
input_node, input_node,
input_node_connector_index, input_node_connector_index,
}; };

View file

@ -9,9 +9,9 @@ fn main() {
let network = NodeNetwork { let network = NodeNetwork {
inputs: vec![0], inputs: vec![0],
output: 0, outputs: vec![NodeOutput::new(0, 0)],
disabled: vec![], disabled: vec![],
previous_output: None, previous_outputs: None,
nodes: [( nodes: [(
0, 0,
DocumentNode { DocumentNode {
@ -39,9 +39,9 @@ fn main() {
fn add_network() -> NodeNetwork { fn add_network() -> NodeNetwork {
NodeNetwork { NodeNetwork {
inputs: vec![0, 0], inputs: vec![0, 0],
output: 1, outputs: vec![NodeOutput::new(1, 0)],
disabled: vec![], disabled: vec![],
previous_output: None, previous_outputs: None,
nodes: [ nodes: [
( (
0, 0,
@ -56,7 +56,7 @@ fn add_network() -> NodeNetwork {
1, 1,
DocumentNode { DocumentNode {
name: "Add".into(), name: "Add".into(),
inputs: vec![NodeInput::Node(0)], inputs: vec![NodeInput::node(0, 0)],
metadata: DocumentNodeMetadata::default(), metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
}, },

View file

@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0"
[features] [features]
std = ["dyn-any", "dyn-any/std"] std = ["dyn-any", "dyn-any/std"]
default = ["async", "serde", "kurbo", "log"] default = ["async", "serde", "kurbo", "log", "std"]
log = ["dep:log"] log = ["dep:log"]
serde = ["dep:serde", "glam/serde"] serde = ["dep:serde", "glam/serde"]
gpu = ["spirv-std", "bytemuck", "glam/bytemuck", "dyn-any"] gpu = ["spirv-std", "bytemuck", "glam/bytemuck", "dyn-any"]

View file

@ -275,7 +275,6 @@ mod test {
pub fn map_result() { pub fn map_result() {
let value: ClonedNode<Result<&u32, ()>> = ClonedNode(Ok(&4u32)); let value: ClonedNode<Result<&u32, ()>> = ClonedNode(Ok(&4u32));
assert_eq!(value.eval(()), Ok(&4u32)); assert_eq!(value.eval(()), Ok(&4u32));
static clone: &CloneNode<u32> = &CloneNode::new();
//let type_erased_clone = clone as &dyn for<'a> Node<'a, &'a u32, Output = u32>; //let type_erased_clone = clone as &dyn for<'a> Node<'a, &'a u32, Output = u32>;
let map_result = MapResultNode::new(ValueNode::new(FnNode::new(|x: &u32| x.clone()))); let map_result = MapResultNode::new(ValueNode::new(FnNode::new(|x: &u32| x.clone())));
//et type_erased = &map_result as &dyn for<'a> Node<'a, Result<&'a u32, ()>, Output = Result<u32, ()>>; //et type_erased = &map_result as &dyn for<'a> Node<'a, Result<&'a u32, ()>, Output = Result<u32, ()>>;

View file

@ -285,13 +285,14 @@ fn dimensions_node(input: ImageSlice<'input>) -> (u32, u32) {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub use image::{CollectNode, Image, ImageRefNode, MapImageSliceNode}; pub use image::{CollectNode, Image, ImageFrame, ImageRefNode, MapImageSliceNode};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
mod image { mod image {
use super::{Color, ImageSlice}; use super::{Color, ImageSlice};
use crate::Node; use crate::Node;
use alloc::vec::Vec; use alloc::vec::Vec;
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use glam::DAffine2;
#[derive(Clone, Debug, PartialEq, DynAny, Default, specta::Type, Hash)] #[derive(Clone, Debug, PartialEq, DynAny, Default, specta::Type, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -321,6 +322,15 @@ mod image {
let data = image_data.chunks_exact(4).map(|v| Color::from_rgba8(v[0], v[1], v[2], v[3])).collect(); let data = image_data.chunks_exact(4).map(|v| Color::from_rgba8(v[0], v[1], v[2], v[3])).collect();
Image { width, height, data } Image { width, height, data }
} }
/// Flattens each channel cast to a u8
pub fn as_flat_u8(self) -> (Vec<u8>, u32, u32) {
let Image { width, height, data } = self;
let result_bytes = data.into_iter().flat_map(|color| color.to_rgba8()).collect();
(result_bytes, width, height)
}
} }
impl IntoIterator for Image { impl IntoIterator for Image {
@ -363,6 +373,12 @@ mod image {
data, data,
} }
} }
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
pub struct ImageFrame {
pub image: Image,
pub transform: DAffine2,
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -38,6 +38,12 @@ pub struct DocumentNodeMetadata {
pub position: IVec2, pub position: IVec2,
} }
impl DocumentNodeMetadata {
pub fn position(position: impl Into<IVec2>) -> Self {
Self { position: position.into() }
}
}
#[derive(Clone, Debug, PartialEq, specta::Type)] #[derive(Clone, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DocumentNode { pub struct DocumentNode {
@ -48,7 +54,7 @@ pub struct DocumentNode {
} }
impl DocumentNode { impl DocumentNode {
pub fn populate_first_network_input(&mut self, node: NodeId, offset: usize) { pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize) {
let input = self let input = self
.inputs .inputs
.iter() .iter()
@ -58,7 +64,7 @@ impl DocumentNode {
.expect("no network input"); .expect("no network input");
let index = input.0; let index = input.0;
self.inputs[index] = NodeInput::Node(node); self.inputs[index] = NodeInput::Node { node_id, output_index };
} }
fn resolve_proto_node(mut self) -> ProtoNode { fn resolve_proto_node(mut self) -> ProtoNode {
@ -70,7 +76,10 @@ impl DocumentNode {
assert_eq!(self.inputs.len(), 0); assert_eq!(self.inputs.len(), 0);
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value)) (ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
} }
NodeInput::Node(id) => (ProtoNodeInput::Node(id), ConstructionArgs::Nodes(vec![])), NodeInput::Node { node_id, output_index } => {
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode.");
(ProtoNodeInput::Node(node_id), ConstructionArgs::Nodes(vec![]))
}
NodeInput::Network => (ProtoNodeInput::Network, ConstructionArgs::Nodes(vec![])), NodeInput::Network => (ProtoNodeInput::Network, ConstructionArgs::Nodes(vec![])),
}; };
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network)), "recieved non resolved parameter"); assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network)), "recieved non resolved parameter");
@ -83,7 +92,7 @@ impl DocumentNode {
if let ConstructionArgs::Nodes(nodes) = &mut args { if let ConstructionArgs::Nodes(nodes) = &mut args {
nodes.extend(self.inputs.iter().map(|input| match input { nodes.extend(self.inputs.iter().map(|input| match input {
NodeInput::Node(id) => *id, NodeInput::Node { node_id, .. } => *node_id,
_ => unreachable!(), _ => unreachable!(),
})); }));
} }
@ -105,11 +114,11 @@ impl DocumentNode {
P: Fn(String, usize) -> Option<NodeInput>, P: Fn(String, usize) -> Option<NodeInput>,
{ {
for (index, input) in self.inputs.iter_mut().enumerate() { for (index, input) in self.inputs.iter_mut().enumerate() {
let &mut NodeInput::Node(id) = input else { let &mut NodeInput::Node{node_id: id, output_index} = input else {
continue; continue;
}; };
if let Some(&new_id) = new_ids.get(&id) { if let Some(&new_id) = new_ids.get(&id) {
*input = NodeInput::Node(new_id); *input = NodeInput::Node { node_id: new_id, output_index };
} else if let Some(new_input) = default_input(self.name.clone(), index) { } else if let Some(new_input) = default_input(self.name.clone(), index) {
*input = new_input; *input = new_input;
} else { } else {
@ -123,23 +132,26 @@ impl DocumentNode {
#[derive(Clone, Debug, specta::Type)] #[derive(Clone, Debug, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum NodeInput { pub enum NodeInput {
Node(NodeId), Node { node_id: NodeId, output_index: usize },
Value { tagged_value: value::TaggedValue, exposed: bool }, Value { tagged_value: value::TaggedValue, exposed: bool },
Network, Network,
} }
impl NodeInput { impl NodeInput {
pub const fn node(node_id: NodeId, output_index: usize) -> Self {
Self::Node { node_id, output_index }
}
pub const fn value(tagged_value: value::TaggedValue, exposed: bool) -> Self { pub const fn value(tagged_value: value::TaggedValue, exposed: bool) -> Self {
Self::Value { tagged_value, exposed } Self::Value { tagged_value, exposed }
} }
fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) { fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {
if let NodeInput::Node(id) = self { if let &mut NodeInput::Node { node_id, output_index } = self {
*self = NodeInput::Node(f(*id)) *self = NodeInput::Node { node_id: f(node_id), output_index }
} }
} }
pub fn is_exposed(&self) -> bool { pub fn is_exposed(&self) -> bool {
match self { match self {
NodeInput::Node(_) => true, NodeInput::Node { .. } => true,
NodeInput::Value { exposed, .. } => *exposed, NodeInput::Value { exposed, .. } => *exposed,
NodeInput::Network => false, NodeInput::Network => false,
} }
@ -149,7 +161,7 @@ impl NodeInput {
impl PartialEq for NodeInput { impl PartialEq for NodeInput {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
match (&self, &other) { match (&self, &other) {
(Self::Node(n1), Self::Node(n2)) => n1 == n2, (Self::Node { node_id: n0, output_index: o0 }, Self::Node { node_id: n1, output_index: o1 }) => n0 == n1 && o0 == o1,
(Self::Value { tagged_value: v1, .. }, Self::Value { tagged_value: v2, .. }) => v1 == v2, (Self::Value { tagged_value: v1, .. }, Self::Value { tagged_value: v2, .. }) => v1 == v2,
_ => core::mem::discriminant(self) == core::mem::discriminant(other), _ => core::mem::discriminant(self) == core::mem::discriminant(other),
} }
@ -181,24 +193,38 @@ impl DocumentNodeImplementation {
} }
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, DynAny, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NodeOutput {
pub node_id: NodeId,
pub node_output_index: usize,
}
impl NodeOutput {
pub fn new(node_id: NodeId, node_output_index: usize) -> Self {
Self { node_id, node_output_index }
}
}
#[derive(Clone, Debug, Default, PartialEq, DynAny, specta::Type)] #[derive(Clone, Debug, Default, PartialEq, DynAny, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NodeNetwork { pub struct NodeNetwork {
pub inputs: Vec<NodeId>, pub inputs: Vec<NodeId>,
pub output: NodeId, pub outputs: Vec<NodeOutput>,
pub nodes: HashMap<NodeId, DocumentNode>, pub nodes: HashMap<NodeId, DocumentNode>,
/// These nodes are replaced with identity nodes when flattening /// These nodes are replaced with identity nodes when flattening
pub disabled: Vec<NodeId>, pub disabled: Vec<NodeId>,
/// In the case where a new node is chosen as output - what was the origional /// In the case where a new node is chosen as output - what was the original
pub previous_output: Option<NodeId>, pub previous_outputs: Option<Vec<NodeOutput>>,
} }
impl NodeNetwork { impl NodeNetwork {
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) { pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) {
self.inputs.iter_mut().for_each(|id| *id = f(*id)); self.inputs.iter_mut().for_each(|id| *id = f(*id));
self.output = f(self.output); self.outputs.iter_mut().for_each(|output| output.node_id = f(output.node_id));
self.disabled.iter_mut().for_each(|id| *id = f(*id)); self.disabled.iter_mut().for_each(|id| *id = f(*id));
self.previous_output = self.previous_output.map(f); self.previous_outputs
.iter_mut()
.for_each(|nodes| nodes.iter_mut().for_each(|output| output.node_id = f(output.node_id)));
let mut empty = HashMap::new(); let mut empty = HashMap::new();
std::mem::swap(&mut self.nodes, &mut empty); std::mem::swap(&mut self.nodes, &mut empty);
self.nodes = empty self.nodes = empty
@ -215,7 +241,7 @@ impl NodeNetwork {
let mut outwards_links: HashMap<u64, Vec<u64>> = HashMap::new(); let mut outwards_links: HashMap<u64, Vec<u64>> = HashMap::new();
for (node_id, node) in &self.nodes { for (node_id, node) in &self.nodes {
for input in &node.inputs { for input in &node.inputs {
if let NodeInput::Node(ref_id) = input { if let NodeInput::Node { node_id: ref_id, .. } = input {
outwards_links.entry(*ref_id).or_default().push(*node_id) outwards_links.entry(*ref_id).or_default().push(*node_id)
} }
} }
@ -223,6 +249,102 @@ impl NodeNetwork {
outwards_links outwards_links
} }
/// When a node has multiple outputs, we actually just duplicate the node and evaluate each output separately
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() {
// Recursivly 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() {
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
if network.outputs.is_empty() {
continue;
}
network.outputs = vec![network.outputs[0]];
}
}
}
/// Removes unused nodes from the graph. Returns a list of bools which represent if each of the inputs have been retained
pub fn remove_dead_nodes(&mut self) -> Vec<bool> {
// Take all the nodes out of the nodes list
let mut old_nodes = std::mem::take(&mut self.nodes);
let mut stack = self.outputs.iter().map(|output| output.node_id).collect::<Vec<_>>();
while let Some(node_id) = stack.pop() {
let Some((node_id, mut document_node)) = old_nodes.remove_entry(&node_id) else {
continue;
};
// Remove dead nodes from child networks
if let DocumentNodeImplementation::Network(network) = &mut document_node.implementation {
// Remove inputs to the parent node if they have been removed from the child
let mut retain_inputs = network.remove_dead_nodes().into_iter();
document_node.inputs.retain(|_| retain_inputs.next().unwrap_or(true))
}
// Visit all nodes that this node references
stack.extend(
document_node
.inputs
.iter()
.filter_map(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id) } else { None }),
);
// Add the node back to the list of nodes
self.nodes.insert(node_id, document_node);
}
// Check if inputs are used and store for return value
let are_inputs_used = self.inputs.iter().map(|input| self.nodes.contains_key(input)).collect();
// Remove unused inputs from graph
self.inputs.retain(|input| self.nodes.contains_key(input));
are_inputs_used
}
pub fn flatten(&mut self, node: NodeId) { pub fn flatten(&mut self, node: NodeId) {
self.flatten_with_fns(node, merge_ids, generate_uuid) self.flatten_with_fns(node, merge_ids, generate_uuid)
} }
@ -254,9 +376,9 @@ impl NodeNetwork {
for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) { for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) {
let offset = network_offsets.entry(network_input).or_insert(0); let offset = network_offsets.entry(network_input).or_insert(0);
match document_input { match document_input {
NodeInput::Node(node) => { NodeInput::Node { node_id, output_index } => {
let network_input = self.nodes.get_mut(network_input).unwrap(); let network_input = self.nodes.get_mut(network_input).unwrap();
network_input.populate_first_network_input(node, *offset); network_input.populate_first_network_input(node_id, output_index, *offset);
} }
NodeInput::Value { tagged_value, exposed } => { NodeInput::Value { tagged_value, exposed } => {
// Skip formatting very large values for seconds in performance speedup // Skip formatting very large values for seconds in performance speedup
@ -278,7 +400,7 @@ impl NodeNetwork {
assert!(!self.nodes.contains_key(&new_id)); assert!(!self.nodes.contains_key(&new_id));
self.nodes.insert(new_id, value_node); self.nodes.insert(new_id, value_node);
let network_input = self.nodes.get_mut(network_input).unwrap(); let network_input = self.nodes.get_mut(network_input).unwrap();
network_input.populate_first_network_input(new_id, *offset); network_input.populate_first_network_input(new_id, 0, *offset);
} }
NodeInput::Network => { NodeInput::Network => {
*network_offsets.get_mut(network_input).unwrap() += 1; *network_offsets.get_mut(network_input).unwrap() += 1;
@ -289,7 +411,14 @@ impl NodeNetwork {
} }
} }
node.implementation = DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])); node.implementation = DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]));
node.inputs = vec![NodeInput::Node(inner_network.output)]; node.inputs = inner_network
.outputs
.iter()
.map(|&NodeOutput { node_id, node_output_index }| NodeInput::Node {
node_id,
output_index: node_output_index,
})
.collect();
for node_id in new_nodes { for node_id in new_nodes {
self.flatten_with_fns(node_id, map_ids, gen_id); self.flatten_with_fns(node_id, map_ids, gen_id);
} }
@ -300,26 +429,28 @@ impl NodeNetwork {
self.nodes.insert(id, node); self.nodes.insert(id, node);
} }
pub fn into_proto_network(self) -> ProtoNetwork { pub fn into_proto_networks(self) -> impl Iterator<Item = ProtoNetwork> {
let mut nodes: Vec<_> = self.nodes.into_iter().map(|(id, node)| (id, node.resolve_proto_node())).collect(); let mut nodes: Vec<_> = self.nodes.into_iter().map(|(id, node)| (id, node.resolve_proto_node())).collect();
nodes.sort_unstable_by_key(|(i, _)| *i); nodes.sort_unstable_by_key(|(i, _)| *i);
ProtoNetwork {
inputs: self.inputs, // Create a network to evaluate each output
output: self.output, self.outputs.into_iter().map(move |output| ProtoNetwork {
nodes, inputs: self.inputs.clone(),
} output: output.node_id,
nodes: nodes.clone(),
})
} }
/// Get the original output node of this network, ignoring any preview node /// Get the original output nodes of this network, ignoring any preview node
pub fn original_output(&self) -> NodeId { pub fn original_outputs(&self) -> &Vec<NodeOutput> {
self.previous_output.unwrap_or(self.output) self.previous_outputs.as_ref().unwrap_or(&self.outputs)
} }
/// A graph with just an input and output node /// A graph with just an input and output node
pub fn new_network(output_offset: i32, output_node_id: NodeId) -> Self { pub fn new_network(output_offset: i32, output_node_id: NodeId) -> Self {
Self { Self {
inputs: vec![0], inputs: vec![0],
output: 1, outputs: vec![NodeOutput::new(1, 0)],
nodes: [ nodes: [
( (
0, 0,
@ -334,7 +465,7 @@ impl NodeNetwork {
1, 1,
DocumentNode { DocumentNode {
name: "Output".into(), name: "Output".into(),
inputs: vec![NodeInput::Node(output_node_id)], inputs: vec![NodeInput::node(output_node_id, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
metadata: DocumentNodeMetadata { position: (output_offset, 4).into() }, metadata: DocumentNodeMetadata { position: (output_offset, 4).into() },
}, },
@ -367,28 +498,27 @@ impl NodeNetwork {
} }
/// Check if the specified node id is connected to the output /// Check if the specified node id is connected to the output
pub fn connected_to_output(&self, node_id: NodeId) -> bool { pub fn connected_to_output(&self, target_node_id: NodeId) -> bool {
// If the node is the output then return true // If the node is the output then return true
if self.output == node_id { if self.outputs.iter().any(|&NodeOutput { node_id, .. }| node_id == target_node_id) {
return true; return true;
} }
// Get the output // Get the outputs
let Some(output_node) = self.nodes.get(&self.output) else { let Some(mut stack) = self.outputs.iter().map(|&output| self.nodes.get(&output.node_id)).collect::<Option<Vec<_>>>() else {
return false; return false;
}; };
let mut stack = vec![output_node];
let mut already_visited = HashSet::new(); let mut already_visited = HashSet::new();
already_visited.insert(self.output); already_visited.extend(self.outputs.iter().map(|output| output.node_id));
while let Some(node) = stack.pop() { while let Some(node) = stack.pop() {
for input in &node.inputs { for input in &node.inputs {
if let &NodeInput::Node(ref_id) = input { if let &NodeInput::Node { node_id: ref_id, .. } = input {
// Skip if already viewed // Skip if already viewed
if already_visited.contains(&ref_id) { if already_visited.contains(&ref_id) {
continue; continue;
} }
// If the target node is used as input then return true // If the target node is used as input then return true
if ref_id == node_id { if ref_id == target_node_id {
return true; return true;
} }
// Add the referenced node to the stack // Add the referenced node to the stack
@ -403,6 +533,21 @@ impl NodeNetwork {
false false
} }
/// Is the node being used directly as an output?
pub fn outputs_contain(&self, node_id: NodeId) -> bool {
self.outputs.iter().any(|output| output.node_id == node_id)
}
/// Is the node being used directly as an original output?
pub fn original_outputs_contain(&self, node_id: NodeId) -> bool {
self.original_outputs().iter().any(|output| output.node_id == node_id)
}
/// Is the node being used directly as a previous output?
pub fn previous_outputs_contain(&self, node_id: NodeId) -> Option<bool> {
self.previous_outputs.as_ref().map(|outputs| outputs.iter().any(|output| output.node_id == node_id))
}
} }
#[cfg(test)] #[cfg(test)]
@ -421,7 +566,7 @@ mod test {
fn add_network() -> NodeNetwork { fn add_network() -> NodeNetwork {
NodeNetwork { NodeNetwork {
inputs: vec![0, 0], inputs: vec![0, 0],
output: 1, outputs: vec![NodeOutput::new(1, 0)],
nodes: [ nodes: [
( (
0, 0,
@ -436,7 +581,7 @@ mod test {
1, 1,
DocumentNode { DocumentNode {
name: "Add".into(), name: "Add".into(),
inputs: vec![NodeInput::Node(0)], inputs: vec![NodeInput::node(0, 0)],
metadata: DocumentNodeMetadata::default(), metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
}, },
@ -454,7 +599,7 @@ mod test {
network.map_ids(|id| id + 1); network.map_ids(|id| id + 1);
let maped_add = NodeNetwork { let maped_add = NodeNetwork {
inputs: vec![1, 1], inputs: vec![1, 1],
output: 2, outputs: vec![NodeOutput::new(2, 0)],
nodes: [ nodes: [
( (
1, 1,
@ -469,7 +614,7 @@ mod test {
2, 2,
DocumentNode { DocumentNode {
name: "Add".into(), name: "Add".into(),
inputs: vec![NodeInput::Node(1)], inputs: vec![NodeInput::node(1, 0)],
metadata: DocumentNodeMetadata::default(), metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
}, },
@ -486,7 +631,7 @@ mod test {
fn flatten_add() { fn flatten_add() {
let mut network = NodeNetwork { let mut network = NodeNetwork {
inputs: vec![1], inputs: vec![1],
output: 1, outputs: vec![NodeOutput::new(1, 0)],
nodes: [( nodes: [(
1, 1,
DocumentNode { DocumentNode {
@ -518,7 +663,7 @@ mod test {
fn resolve_proto_node_add() { fn resolve_proto_node_add() {
let document_node = DocumentNode { let document_node = DocumentNode {
name: "Cons".into(), name: "Cons".into(),
inputs: vec![NodeInput::Network, NodeInput::Node(0)], inputs: vec![NodeInput::Network, NodeInput::node(0, 0)],
metadata: DocumentNodeMetadata::default(), metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
}; };
@ -568,23 +713,23 @@ mod test {
.collect(), .collect(),
}; };
let network = flat_network(); let network = flat_network();
let resolved_network = network.into_proto_network(); let resolved_network = network.into_proto_networks().collect::<Vec<_>>();
println!("{:#?}", resolved_network); println!("{:#?}", resolved_network[0]);
println!("{:#?}", construction_network); println!("{:#?}", construction_network);
assert_eq!(resolved_network, construction_network); assert_eq!(resolved_network[0], construction_network);
} }
fn flat_network() -> NodeNetwork { fn flat_network() -> NodeNetwork {
NodeNetwork { NodeNetwork {
inputs: vec![10], inputs: vec![10],
output: 1, outputs: vec![NodeOutput::new(1, 0)],
nodes: [ nodes: [
( (
1, 1,
DocumentNode { DocumentNode {
name: "Inc".into(), name: "Inc".into(),
inputs: vec![NodeInput::Node(11)], inputs: vec![NodeInput::node(11, 0)],
metadata: DocumentNodeMetadata::default(), metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
}, },
@ -593,7 +738,7 @@ mod test {
10, 10,
DocumentNode { DocumentNode {
name: "Cons".into(), name: "Cons".into(),
inputs: vec![NodeInput::Network, NodeInput::Node(14)], inputs: vec![NodeInput::Network, NodeInput::node(14, 0)],
metadata: DocumentNodeMetadata::default(), metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
}, },
@ -614,7 +759,7 @@ mod test {
11, 11,
DocumentNode { DocumentNode {
name: "Add".into(), name: "Add".into(),
inputs: vec![NodeInput::Node(10)], inputs: vec![NodeInput::node(10, 0)],
metadata: DocumentNodeMetadata::default(), metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
}, },
@ -625,4 +770,121 @@ mod test {
..Default::default() ..Default::default()
} }
} }
fn two_node_identity() -> NodeNetwork {
NodeNetwork {
inputs: vec![1, 2],
outputs: vec![NodeOutput::new(1, 0), NodeOutput::new(2, 0)],
nodes: [
(
1,
DocumentNode {
name: "Identity 1".into(),
inputs: vec![NodeInput::Network],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
},
),
(
2,
DocumentNode {
name: "Identity 2".into(),
inputs: vec![NodeInput::Network],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
},
),
]
.into_iter()
.collect(),
..Default::default()
}
}
fn output_duplicate(network_outputs: Vec<NodeOutput>, result_node_input: NodeInput) -> NodeNetwork {
let mut network = NodeNetwork {
inputs: Vec::new(),
outputs: network_outputs,
nodes: [
(
10,
DocumentNode {
name: "Nested network".into(),
inputs: vec![NodeInput::value(TaggedValue::F32(1.), false), NodeInput::value(TaggedValue::F32(2.), false)],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Network(two_node_identity()),
},
),
(
11,
DocumentNode {
name: "Result".into(),
inputs: vec![result_node_input],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
},
),
]
.into_iter()
.collect(),
..Default::default()
};
let mut new_ids = 101..;
network.duplicate_outputs(&mut || new_ids.next().unwrap());
network.remove_dead_nodes();
network
}
#[test]
fn simple_duplicate() {
let result = output_duplicate(vec![NodeOutput::new(10, 1)], NodeInput::node(10, 0));
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.nodes.keys().copied().collect::<Vec<_>>(), vec![101], "Should just call nested network");
let nested_network_node = result.nodes.get(&101).unwrap();
assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change");
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");
}
#[test]
fn out_of_order_duplicate() {
let result = output_duplicate(vec![NodeOutput::new(10, 1), NodeOutput::new(10, 0)], NodeInput::node(10, 0));
assert_eq!(result.outputs[0], NodeOutput::new(101, 0), "The first network output should be from a duplicated nested network");
assert_eq!(result.outputs[1], NodeOutput::new(10, 0), "The second network output should be from the original nested network");
assert!(
result.nodes.contains_key(&10) && result.nodes.contains_key(&101) && result.nodes.len() == 2,
"Network should contain two duplicated nodes"
);
for (node_id, input_value, inner_id) in [(10, 1., 1), (101, 2., 2)] {
let nested_network_node = result.nodes.get(&node_id).unwrap();
assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change");
assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(input_value), false)], "Input should be stable");
let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network");
assert_eq!(inner_network.inputs, vec![inner_id], "The input should be sent to the second node");
assert_eq!(inner_network.outputs, vec![NodeOutput::new(inner_id, 0)], "The output should be node id");
assert_eq!(inner_network.nodes.get(&inner_id).unwrap().name, format!("Identity {inner_id}"), "The node should be identity");
}
}
#[test]
fn using_other_node_duplicate() {
let result = output_duplicate(vec![NodeOutput::new(11, 0)], NodeInput::node(10, 1));
assert_eq!(result.outputs, vec![NodeOutput::new(11, 0)], "The network output should be the result node");
assert!(
result.nodes.contains_key(&11) && result.nodes.contains_key(&101) && result.nodes.len() == 2,
"Network should contain a duplicated node and a result node"
);
let result_node = result.nodes.get(&11).unwrap();
assert_eq!(result_node.inputs, vec![NodeInput::node(101, 0)], "Result node should refer to duplicate node as input");
let nested_network_node = result.nodes.get(&101).unwrap();
assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change");
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");
}
} }

View file

@ -1,7 +1,7 @@
pub use dyn_any::StaticType; pub use dyn_any::StaticType;
use dyn_any::{DynAny, Upcast}; use dyn_any::{DynAny, Upcast};
use dyn_clone::DynClone; use dyn_clone::DynClone;
pub use glam::DVec2; pub use glam::{DAffine2, DVec2};
use graphene_core::raster::LuminanceCalculation; use graphene_core::raster::LuminanceCalculation;
use graphene_core::Node; use graphene_core::Node;
use std::hash::Hash; use std::hash::Hash;
@ -22,6 +22,7 @@ pub enum TaggedValue {
Bool(bool), Bool(bool),
DVec2(DVec2), DVec2(DVec2),
OptionalDVec2(Option<DVec2>), OptionalDVec2(Option<DVec2>),
DAffine2(DAffine2),
Image(graphene_core::raster::Image), Image(graphene_core::raster::Image),
RcImage(Option<Arc<graphene_core::raster::Image>>), RcImage(Option<Arc<graphene_core::raster::Image>>),
Color(graphene_core::raster::color::Color), Color(graphene_core::raster::color::Color),
@ -68,44 +69,48 @@ impl Hash for TaggedValue {
8.hash(state); 8.hash(state);
Self::DVec2(*v).hash(state) Self::DVec2(*v).hash(state)
} }
Self::Image(i) => { Self::DAffine2(m) => {
9.hash(state); 9.hash(state);
i.hash(state) m.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
} }
Self::RcImage(i) => { Self::Image(i) => {
10.hash(state); 10.hash(state);
i.hash(state) i.hash(state)
} }
Self::Color(c) => { Self::RcImage(i) => {
11.hash(state); 11.hash(state);
i.hash(state)
}
Self::Color(c) => {
12.hash(state);
c.hash(state) c.hash(state)
} }
Self::Subpath(s) => { Self::Subpath(s) => {
12.hash(state);
s.hash(state)
}
Self::RcSubpath(s) => {
13.hash(state); 13.hash(state);
s.hash(state) s.hash(state)
} }
Self::LuminanceCalculation(l) => { Self::RcSubpath(s) => {
14.hash(state); 14.hash(state);
s.hash(state)
}
Self::LuminanceCalculation(l) => {
15.hash(state);
l.hash(state) l.hash(state)
} }
Self::ImaginateSamplingMethod(m) => { Self::ImaginateSamplingMethod(m) => {
15.hash(state); 16.hash(state);
m.hash(state) m.hash(state)
} }
Self::ImaginateMaskStartingFill(f) => { Self::ImaginateMaskStartingFill(f) => {
16.hash(state); 17.hash(state);
f.hash(state) f.hash(state)
} }
Self::ImaginateStatus(s) => { Self::ImaginateStatus(s) => {
17.hash(state); 18.hash(state);
s.hash(state) s.hash(state)
} }
Self::LayerPath(p) => { Self::LayerPath(p) => {
18.hash(state); 19.hash(state);
p.hash(state) p.hash(state)
} }
} }
@ -124,6 +129,7 @@ impl<'a> TaggedValue {
TaggedValue::Bool(x) => Box::new(x), TaggedValue::Bool(x) => Box::new(x),
TaggedValue::DVec2(x) => Box::new(x), TaggedValue::DVec2(x) => Box::new(x),
TaggedValue::OptionalDVec2(x) => Box::new(x), TaggedValue::OptionalDVec2(x) => Box::new(x),
TaggedValue::DAffine2(x) => Box::new(x),
TaggedValue::Image(x) => Box::new(x), TaggedValue::Image(x) => Box::new(x),
TaggedValue::RcImage(x) => Box::new(x), TaggedValue::RcImage(x) => Box::new(x),
TaggedValue::Color(x) => Box::new(x), TaggedValue::Color(x) => Box::new(x),

View file

@ -8,21 +8,29 @@ use crate::proto::ProtoNetwork;
pub struct Compiler {} pub struct Compiler {}
impl Compiler { impl Compiler {
pub fn compile(&self, mut network: NodeNetwork, resolve_inputs: bool) -> ProtoNetwork { pub fn compile(&self, mut network: NodeNetwork, resolve_inputs: bool) -> impl Iterator<Item = ProtoNetwork> {
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>(); let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
println!("flattening"); println!("flattening");
for id in node_ids { for id in node_ids {
network.flatten(id); network.flatten(id);
} }
let mut proto_network = network.into_proto_network(); let proto_networks = network.into_proto_networks();
if resolve_inputs { proto_networks.map(move |mut proto_network| {
println!("resolving inputs"); if resolve_inputs {
proto_network.resolve_inputs(); println!("resolving inputs");
} proto_network.resolve_inputs();
println!("reordering ids"); }
proto_network.reorder_ids(); proto_network.reorder_ids();
proto_network.generate_stable_node_ids(); proto_network.generate_stable_node_ids();
proto_network proto_network
})
}
pub fn compile_single(&self, network: NodeNetwork, resolve_inputs: bool) -> Result<ProtoNetwork, String> {
assert_eq!(network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
let Some(proto_network) = self.compile(network, resolve_inputs).next() else {
return Err("Failed to convert graph into proto graph".to_string());
};
Ok(proto_network)
} }
} }
pub type Any<'a> = Box<dyn DynAny<'a> + 'a>; pub type Any<'a> = Box<dyn DynAny<'a> + 'a>;

View file

@ -94,7 +94,7 @@ pub struct ProtoNetwork {
pub nodes: Vec<(NodeId, ProtoNode)>, pub nodes: Vec<(NodeId, ProtoNode)>,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum ConstructionArgs { pub enum ConstructionArgs {
Value(value::TaggedValue), Value(value::TaggedValue),
Nodes(Vec<NodeId>), Nodes(Vec<NodeId>),
@ -133,7 +133,7 @@ impl ConstructionArgs {
} }
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub struct ProtoNode { pub struct ProtoNode {
pub construction_args: ConstructionArgs, pub construction_args: ConstructionArgs,
pub input: ProtoNodeInput, pub input: ProtoNodeInput,

View file

@ -73,7 +73,7 @@ where
N: Node<'input, Any<'input>, Output = Any<'input>>, N: Node<'input, Any<'input>, Output = Any<'input>>,
{ {
let node_name = core::any::type_name::<N>(); let node_name = core::any::type_name::<N>();
let out = dyn_any::downcast(node.eval(input)).unwrap_or_else(|e| panic!("DynAnyNode Input {e} in:\n{node_name}")); let out = dyn_any::downcast(node.eval(input)).unwrap_or_else(|e| panic!("DowncastNode Input {e} in:\n{node_name}"));
*out *out
} }
@ -91,7 +91,7 @@ impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'i
fn eval<'node: 'input>(&'node self, input: I) -> Self::Output { fn eval<'node: 'input>(&'node self, input: I) -> Self::Output {
{ {
let input = Box::new(input); let input = Box::new(input);
let out = dyn_any::downcast(self.node.eval(input)).unwrap_or_else(|e| panic!("DynAnyNode Input {e}")); let out = dyn_any::downcast(self.node.eval(input)).unwrap_or_else(|e| panic!("DowncastBothNode Input {e}"));
*out *out
} }
} }
@ -118,7 +118,7 @@ impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'i
fn eval<'node: 'input>(&'node self, input: I) -> Self::Output { fn eval<'node: 'input>(&'node self, input: I) -> Self::Output {
{ {
let input = Box::new(input); let input = Box::new(input);
let out: Box<&_> = dyn_any::downcast::<&O>(self.node.eval(input)).unwrap_or_else(|e| panic!("DynAnyNode Input {e}")); let out: Box<&_> = dyn_any::downcast::<&O>(self.node.eval(input)).unwrap_or_else(|e| panic!("DowncastBothRefNode Input {e}"));
*out *out
} }
} }

View file

@ -1,5 +1,6 @@
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use glam::DAffine2;
use graphene_core::raster::{Color, Image}; use graphene_core::raster::{Color, Image};
use graphene_core::Node; use graphene_core::Node;
@ -117,6 +118,14 @@ fn imaginate(image: Image, cached: Option<std::sync::Arc<graphene_core::raster::
cached.map(|mut x| std::sync::Arc::make_mut(&mut x).clone()).unwrap_or(image) cached.map(|mut x| std::sync::Arc::make_mut(&mut x).clone()).unwrap_or(image)
} }
#[derive(Debug, Clone, Copy)]
pub struct ImageFrameNode<Transform> {
transform: Transform,
}
#[node_macro::node_fn(ImageFrameNode)]
fn image_frame(image: Image, transform: DAffine2) -> graphene_core::raster::ImageFrame {
graphene_core::raster::ImageFrame { image, transform }
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View file

@ -16,7 +16,7 @@ quantization = ["graphene-std/quantization"]
graphene-core = { path = "../gcore", features = ["async", "std" ] } graphene-core = { path = "../gcore", features = ["async", "std" ] }
graphene-std = { path = "../gstd" } graphene-std = { path = "../gstd" }
graph-craft = { path = "../graph-craft" } graph-craft = { path = "../graph-craft" }
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types"] } dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "glam"] }
num-traits = "0.2" num-traits = "0.2"
borrow_stack = { path = "../borrow_stack" } borrow_stack = { path = "../borrow_stack" }
dyn-clone = "1.0" dyn-clone = "1.0"

View file

@ -26,7 +26,7 @@ impl DynamicExecutor {
pub fn update(&mut self, proto_network: ProtoNetwork) { pub fn update(&mut self, proto_network: ProtoNetwork) {
self.output = proto_network.output; self.output = proto_network.output;
info!("setting output to {}", self.output); trace!("setting output to {}", self.output);
self.tree.update(proto_network); self.tree.update(proto_network);
} }
} }

View file

@ -52,7 +52,7 @@ mod tests {
fn add_network() -> NodeNetwork { fn add_network() -> NodeNetwork {
NodeNetwork { NodeNetwork {
inputs: vec![0, 0], inputs: vec![0, 0],
output: 1, outputs: vec![NodeOutput::new(1, 0)],
nodes: [ nodes: [
( (
0, 0,
@ -67,7 +67,7 @@ mod tests {
1, 1,
DocumentNode { DocumentNode {
name: "Add".into(), name: "Add".into(),
inputs: vec![NodeInput::Node(0)], inputs: vec![NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[concrete!("(u32, u32)")])), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[concrete!("(u32, u32)")])),
metadata: DocumentNodeMetadata::default(), metadata: DocumentNodeMetadata::default(),
}, },
@ -81,7 +81,7 @@ mod tests {
let network = NodeNetwork { let network = NodeNetwork {
inputs: vec![0], inputs: vec![0],
output: 0, outputs: vec![NodeOutput::new(0, 0)],
nodes: [( nodes: [(
0, 0,
DocumentNode { DocumentNode {
@ -106,7 +106,7 @@ mod tests {
use graph_craft::executor::{Compiler, Executor}; use graph_craft::executor::{Compiler, Executor};
let compiler = Compiler {}; let compiler = Compiler {};
let protograph = compiler.compile(network, true); let protograph = compiler.compile_single(network, true).expect("Graph should be generated");
let exec = DynamicExecutor::new(protograph); let exec = DynamicExecutor::new(protograph);

View file

@ -1,3 +1,4 @@
use glam::DAffine2;
use graphene_core::ops::{CloneNode, IdNode, TypeNode}; use graphene_core::ops::{CloneNode, IdNode, TypeNode};
use graphene_core::raster::color::Color; use graphene_core::raster::color::Color;
use graphene_core::raster::*; use graphene_core::raster::*;
@ -83,7 +84,7 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
( (
NodeIdentifier::new("graphene_std::raster::ImaginateNode<_>", &[concrete!("Image"), concrete!("Option<std::sync::Arc<Image>>")]), NodeIdentifier::new("graphene_std::raster::ImaginateNode<_>", &[concrete!("Image"), concrete!("Option<std::sync::Arc<Image>>")]),
|args| { |args| {
let cached = graphene_std::any::input_node::<Option<std::sync::Arc<Image>>>(args[15]); let cached = graphene_std::any::input_node::<Option<std::sync::Arc<Image>>>(args[16]);
let node = graphene_std::raster::ImaginateNode::new(cached); let node = graphene_std::raster::ImaginateNode::new(cached);
let any = DynAnyNode::new(ValueNode::new(node)); let any = DynAnyNode::new(ValueNode::new(node));
any.into_type_erased() any.into_type_erased()
@ -126,6 +127,7 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
any.into_type_erased() any.into_type_erased()
}), }),
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]),
register_node!(graphene_std::raster::ImageFrameNode<_>, input: Image, params: [DAffine2]),
/* /*
(NodeIdentifier::new("graphene_std::raster::ImageNode", &[concrete!("&str")]), |_proto_node, stack| { (NodeIdentifier::new("graphene_std::raster::ImageNode", &[concrete!("&str")]), |_proto_node, stack| {
stack.push_fn(|_nodes| { stack.push_fn(|_nodes| {

View file

@ -13,7 +13,7 @@ fn has_attribute(attrs: &[Attribute], target: &str) -> bool {
/// Make setting strings easier by allowing all types that `impl Into<String>` /// Make setting strings easier by allowing all types that `impl Into<String>`
/// ///
/// Returns the new input type and a conversion to the origional. /// Returns the new input type and a conversion to the original.
fn easier_string_assignment(field_ty: &Type, field_ident: &Ident) -> (TokenStream2, TokenStream2) { fn easier_string_assignment(field_ty: &Type, field_ident: &Ident) -> (TokenStream2, TokenStream2) {
if let Type::Path(type_path) = field_ty { if let Type::Path(type_path) = field_ty {
if let Some(last_segment) = type_path.path.segments.last() { if let Some(last_segment) = type_path.path.segments.last() {