mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 10:58:04 +08:00
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:
@@ -11,5 +11,6 @@ pub mod application;
|
||||
pub mod consts;
|
||||
pub mod dispatcher;
|
||||
pub mod messages;
|
||||
pub mod node_graph_executor;
|
||||
pub mod test_utils;
|
||||
pub mod utility_traits;
|
||||
|
||||
@@ -107,6 +107,7 @@ pub enum DocumentMessage {
|
||||
},
|
||||
NodeGraphFrameImaginateRandom {
|
||||
imaginate_node: Vec<NodeId>,
|
||||
then_generate: bool,
|
||||
},
|
||||
NodeGraphFrameImaginateTerminate {
|
||||
layer_path: Vec<LayerId>,
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::messages::portfolio::document::utility_types::vectorize_layer_metadat
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use document_legacy::boolean_ops::BooleanOperationError;
|
||||
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]
|
||||
fn process_message(
|
||||
&mut self,
|
||||
message: DocumentMessage,
|
||||
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::*;
|
||||
|
||||
@@ -203,6 +204,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
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())),
|
||||
node_graph_message_handler: &self.node_graph_handler,
|
||||
executor,
|
||||
};
|
||||
self.properties_panel_message_handler
|
||||
.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);
|
||||
}
|
||||
}
|
||||
NodeGraphFrameImaginateRandom { imaginate_node } => {
|
||||
NodeGraphFrameImaginateRandom { imaginate_node, then_generate } => {
|
||||
// Set a random seed input
|
||||
responses.push_back(
|
||||
NodeGraphMessage::SetInputValue {
|
||||
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),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
// 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 } => {
|
||||
responses.push_back(
|
||||
@@ -599,8 +605,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
let path = vec![generate_uuid()];
|
||||
let image_node_id = 2;
|
||||
let mut network = graph_craft::document::NodeNetwork::new_network(32, image_node_id);
|
||||
let image_node_id = 100;
|
||||
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 {
|
||||
warn!("Image node should be in registry");
|
||||
@@ -609,12 +615,10 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
|
||||
network.nodes.insert(
|
||||
image_node_id,
|
||||
graph_craft::document::DocumentNode {
|
||||
name: image_node_type.name.to_string(),
|
||||
inputs: vec![graph_craft::document::NodeInput::value(graph_craft::document::value::TaggedValue::Image(image), false)],
|
||||
implementation: image_node_type.generate_implementation(),
|
||||
metadata: graph_craft::document::DocumentNodeMetadata { position: (20, 4).into() },
|
||||
},
|
||||
image_node_type.to_document_node(
|
||||
[graph_craft::document::NodeInput::value(graph_craft::document::value::TaggedValue::Image(image), false)],
|
||||
graph_craft::document::DocumentNodeMetadata::position((20, 4)),
|
||||
),
|
||||
);
|
||||
|
||||
responses.push_back(
|
||||
|
||||
@@ -12,6 +12,7 @@ pub enum NodeGraphMessage {
|
||||
CloseNodeGraph,
|
||||
ConnectNodesByLink {
|
||||
output_node: u64,
|
||||
output_node_connector_index: usize,
|
||||
input_node: u64,
|
||||
input_node_connector_index: usize,
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
|
||||
use document_legacy::layers::nodegraph_layer::NodeGraphFrameLayer;
|
||||
use document_legacy::LayerId;
|
||||
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 node_properties;
|
||||
|
||||
@@ -39,7 +39,7 @@ impl FrontendGraphDataType {
|
||||
pub const fn with_tagged_value(value: &TaggedValue) -> Self {
|
||||
match value {
|
||||
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::DVec2(_) => Self::Vector,
|
||||
TaggedValue::Image(_) => Self::Raster,
|
||||
@@ -57,6 +57,13 @@ pub struct NodeGraphInput {
|
||||
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)]
|
||||
pub struct FrontendNode {
|
||||
pub id: graph_craft::document::NodeId,
|
||||
@@ -66,10 +73,10 @@ pub struct FrontendNode {
|
||||
pub primary_input: Option<FrontendGraphDataType>,
|
||||
#[serde(rename = "exposedInputs")]
|
||||
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 disabled: bool,
|
||||
pub output: bool,
|
||||
pub previewed: bool,
|
||||
}
|
||||
|
||||
// (link_start, link_end, link_end_input_index)
|
||||
@@ -77,6 +84,8 @@ pub struct FrontendNode {
|
||||
pub struct FrontendNodeLink {
|
||||
#[serde(rename = "linkStart")]
|
||||
pub link_start: u64,
|
||||
#[serde(rename = "linkStartOutputIndex")]
|
||||
pub link_start_output_index: usize,
|
||||
#[serde(rename = "linkEnd")]
|
||||
pub link_end: u64,
|
||||
#[serde(rename = "linkEndInputIndex")]
|
||||
@@ -176,7 +185,7 @@ impl NodeGraphMessageHandler {
|
||||
let mut widgets = Vec::new();
|
||||
|
||||
// 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 selected_nodes.next().is_some() {
|
||||
@@ -187,13 +196,11 @@ impl NodeGraphMessageHandler {
|
||||
let multiple_nodes = selected_nodes.next().is_some();
|
||||
|
||||
// Generate the enable or disable button accordingly
|
||||
let hide_button = WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
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_shortcut: action_keys!(NodeGraphMessageDiscriminant::ToggleHidden),
|
||||
on_update: WidgetCallback::new(move |_| NodeGraphMessage::ToggleHidden.into()),
|
||||
..Default::default()
|
||||
}));
|
||||
let hide_button = TextButton::new(if is_hidden { "Show" } else { "Hide" })
|
||||
.tooltip(if is_hidden { "Show node" } else { "Hide node" }.to_string() + if multiple_nodes { "s" } else { "" })
|
||||
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleHidden))
|
||||
.on_update(move |_| NodeGraphMessage::ToggleHidden.into())
|
||||
.widget_holder();
|
||||
widgets.push(hide_button);
|
||||
}
|
||||
|
||||
@@ -201,13 +208,13 @@ impl NodeGraphMessageHandler {
|
||||
if self.selected_nodes.len() == 1 {
|
||||
let node_id = self.selected_nodes[0];
|
||||
// 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
|
||||
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 {
|
||||
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()),
|
||||
..Default::default()
|
||||
}));
|
||||
@@ -220,6 +227,7 @@ impl NodeGraphMessageHandler {
|
||||
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>) {
|
||||
let mut network = &node_graph_frame.network;
|
||||
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 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();
|
||||
while let Some(node_id) = stack.pop() {
|
||||
let Some(document_node) = network.nodes.get(&node_id) else {
|
||||
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));
|
||||
}
|
||||
for &(document_node, node_id) in nodes.iter().rev() {
|
||||
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 {
|
||||
let Some(document_node) = network.nodes.get(node_id) else {
|
||||
continue;
|
||||
@@ -260,9 +275,14 @@ impl NodeGraphMessageHandler {
|
||||
.iter()
|
||||
.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)| {
|
||||
if let NodeInput::Node(link_start) = *input {
|
||||
if let NodeInput::Node {
|
||||
node_id: link_start,
|
||||
output_index: link_start_index,
|
||||
} = *input
|
||||
{
|
||||
Some(FrontendNodeLink {
|
||||
link_start,
|
||||
link_start_output_index: link_start_index,
|
||||
link_end,
|
||||
link_end_input_index: link_end_input_index as u64,
|
||||
})
|
||||
@@ -298,9 +318,16 @@ impl NodeGraphMessageHandler {
|
||||
name: input_type.name.to_string(),
|
||||
})
|
||||
.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(),
|
||||
output: network.output == *id,
|
||||
previewed: network.outputs_contain(*id),
|
||||
disabled: network.disabled.contains(id),
|
||||
})
|
||||
}
|
||||
@@ -318,24 +345,24 @@ impl NodeGraphMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
fn remove_references_from_network(network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
||||
if network.inputs.iter().any(|&id| id == node_id) {
|
||||
fn remove_references_from_network(network: &mut NodeNetwork, deleting_node_id: NodeId) -> bool {
|
||||
if network.inputs.contains(&deleting_node_id) {
|
||||
warn!("Deleting input node");
|
||||
return false;
|
||||
}
|
||||
if network.output == node_id {
|
||||
if network.outputs_contain(deleting_node_id) {
|
||||
warn!("Deleting the output node!");
|
||||
return false;
|
||||
}
|
||||
for (id, node) in network.nodes.iter_mut() {
|
||||
if *id == node_id {
|
||||
for (node_id, node) in network.nodes.iter_mut() {
|
||||
if *node_id == deleting_node_id {
|
||||
continue;
|
||||
}
|
||||
for (input_index, input) in node.inputs.iter_mut().enumerate() {
|
||||
let NodeInput::Node(id) = input else {
|
||||
let NodeInput::Node{ node_id, .. } = input else {
|
||||
continue;
|
||||
};
|
||||
if *id != node_id {
|
||||
if *node_id != deleting_node_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -344,19 +371,17 @@ impl NodeGraphMessageHandler {
|
||||
return false;
|
||||
};
|
||||
if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default {
|
||||
*input = NodeInput::Value {
|
||||
tagged_value: tagged_value.clone(),
|
||||
exposed: true,
|
||||
};
|
||||
*input = NodeInput::value(tagged_value.clone(), true);
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
/// Tries to remove a node from the network, returning true on success.
|
||||
fn remove_node(&mut self, network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
||||
if Self::remove_references_from_network(network, 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 {
|
||||
new_ids
|
||||
.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())))
|
||||
.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 {
|
||||
output_node,
|
||||
output_node_connector_index,
|
||||
input_node,
|
||||
input_node_connector_index,
|
||||
} => {
|
||||
@@ -422,7 +448,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
||||
|
||||
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());
|
||||
|
||||
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());
|
||||
|
||||
let document_node = DocumentNode {
|
||||
name: node_type.clone(),
|
||||
inputs: document_node_type.inputs.iter().map(|input| input.default.clone()).collect(),
|
||||
implementation: document_node_type.generate_implementation(),
|
||||
metadata: graph_craft::document::DocumentNodeMetadata { position: (x, y).into() },
|
||||
};
|
||||
let document_node = document_node_type.to_document_node(
|
||||
document_node_type.inputs.iter().map(|input| input.default.clone()),
|
||||
graph_craft::document::DocumentNodeMetadata::position((x, y)),
|
||||
);
|
||||
responses.push_back(NodeGraphMessage::InsertNode { node_id, document_node }.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)
|
||||
.map_or(&Vec::new(), |node| &node.inputs)
|
||||
.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<_>>();
|
||||
|
||||
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
|
||||
network.disabled.retain(|id| !self.selected_nodes.contains(id));
|
||||
} 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)
|
||||
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);
|
||||
|
||||
@@ -831,12 +857,12 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
||||
}
|
||||
NodeGraphMessage::TogglePreviewImpl { node_id } => {
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
// Check if the node is not already
|
||||
if network.output != node_id {
|
||||
network.previous_output = Some(network.previous_output.unwrap_or(network.output));
|
||||
network.output = node_id;
|
||||
} else if let Some(output) = network.previous_output.take() {
|
||||
network.output = output
|
||||
// Check if the node is not already being previewed
|
||||
if !network.outputs_contain(node_id) {
|
||||
network.previous_outputs = Some(network.previous_outputs.to_owned().unwrap_or_else(|| network.outputs.clone()));
|
||||
network.outputs[0] = NodeOutput::new(node_id, 0);
|
||||
} else if let Some(outputs) = network.previous_outputs.take() {
|
||||
network.outputs = outputs
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::{node_properties, FrontendGraphDataType, FrontendNodeType};
|
||||
use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use graph_craft::document::value::*;
|
||||
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 persistent_data: &'a crate::messages::portfolio::utility_types::PersistentData,
|
||||
pub document: &'a document_legacy::document::Document,
|
||||
pub responses: &'a mut VecDeque<crate::messages::prelude::Message>,
|
||||
pub layer_path: &'a [document_legacy::LayerId],
|
||||
pub nested_path: &'a [NodeId],
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
pub network: &'a NodeNetwork,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -58,25 +72,24 @@ pub struct DocumentNodeType {
|
||||
pub category: &'static str,
|
||||
pub identifier: NodeImplementation,
|
||||
pub inputs: &'static [DocumentInputType],
|
||||
pub outputs: &'static [FrontendGraphDataType],
|
||||
pub outputs: &'static [DocumentOutputType],
|
||||
pub properties: fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup>,
|
||||
}
|
||||
|
||||
fn document_node_types() -> Vec<DocumentNodeType> {
|
||||
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("Radius", TaggedValue::U32(3), false),
|
||||
DocumentInputType::new("Sigma", TaggedValue::F64(1.), false),
|
||||
];
|
||||
|
||||
let blur = DocumentNodeType {
|
||||
name: "Gaussian Blur",
|
||||
category: "Image Filters",
|
||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||
inputs: vec![0, 1, 1],
|
||||
output: 1,
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: vec![
|
||||
(
|
||||
0,
|
||||
@@ -91,7 +104,7 @@ fn document_node_types() -> Vec<DocumentNodeType> {
|
||||
1,
|
||||
DocumentNode {
|
||||
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")])),
|
||||
metadata: Default::default(),
|
||||
},
|
||||
@@ -101,11 +114,64 @@ fn document_node_types() -> Vec<DocumentNodeType> {
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: INPUTS,
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
inputs: GAUSSIAN_BLUR_NODE_INPUTS,
|
||||
outputs: &[DocumentOutputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
}],
|
||||
properties: node_properties::blur_image_properties,
|
||||
};
|
||||
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
|
||||
}
|
||||
|
||||
@@ -122,9 +188,9 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
inputs: &[DocumentInputType {
|
||||
name: "In",
|
||||
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"),
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -132,21 +198,21 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
category: "Ignore",
|
||||
identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]),
|
||||
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"),
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Input",
|
||||
category: "Ignore",
|
||||
identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]),
|
||||
inputs: &[DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::Network,
|
||||
}],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: node_properties::input_properties,
|
||||
},
|
||||
// DocumentNodeType {
|
||||
// name: "Input",
|
||||
// category: "Ignore",
|
||||
// identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]),
|
||||
// inputs: &[DocumentInputType {
|
||||
// name: "In",
|
||||
// data_type: FrontendGraphDataType::Raster,
|
||||
// default: NodeInput::Network,
|
||||
// }],
|
||||
// outputs: &[DocumentOutputType::new("Out", FrontendGraphDataType::Raster)],
|
||||
// properties: node_properties::input_properties,
|
||||
// },
|
||||
DocumentNodeType {
|
||||
name: "Output",
|
||||
category: "Ignore",
|
||||
@@ -157,7 +223,18 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
}],
|
||||
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 {
|
||||
name: "Grayscale",
|
||||
@@ -217,7 +294,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
default: NodeInput::value(TaggedValue::F64(50.), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::grayscale_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -228,7 +305,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::luminance_properties,
|
||||
},
|
||||
#[cfg(feature = "gpu")]
|
||||
@@ -244,7 +321,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
default: NodeInput::value(TaggedValue::String(String::new()), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::gpu_map_properties,
|
||||
},
|
||||
#[cfg(feature = "quantization")]
|
||||
@@ -269,7 +346,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
default: NodeInput::value(TaggedValue::U32(0), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::quantize_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -277,7 +354,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
category: "Structural",
|
||||
identifier: NodeImplementation::proto("graphene_std::memo::CacheNode", &[concrete!("Image")]),
|
||||
inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -285,7 +362,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
category: "Image Adjustments",
|
||||
identifier: NodeImplementation::proto("graphene_core::raster::InvertRGBNode", &[concrete!("Image")]),
|
||||
inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -301,7 +378,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Saturation 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,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -313,7 +390,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Brightness", 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,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -325,7 +402,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
|
||||
DocumentInputType::new("Threshold", TaggedValue::F64(50.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::adjust_threshold_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -336,7 +413,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Vibrance", TaggedValue::F64(0.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::adjust_vibrance_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -347,7 +424,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Factor", TaggedValue::F64(100.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::multiply_opacity,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -358,7 +435,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Value", TaggedValue::F64(4.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::posterize_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -374,7 +451,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Offset", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::new("Gamma Correction", TaggedValue::F64(1.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::exposure_properties,
|
||||
},
|
||||
IMAGINATE_NODE,
|
||||
@@ -386,7 +463,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Input", 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,
|
||||
},
|
||||
/*DocumentNodeType {
|
||||
@@ -394,7 +471,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]),
|
||||
inputs: &[DocumentInputType::none()],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -402,7 +479,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]),
|
||||
inputs: &[DocumentInputType::none()],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -414,7 +491,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
data_type: FrontendGraphDataType::Subpath,
|
||||
default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), false),
|
||||
}],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -428,7 +505,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Scale", TaggedValue::DVec2(DVec2::ONE), false),
|
||||
DocumentInputType::new("Skew", TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::transform_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
@@ -439,7 +516,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
||||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Subpath", TaggedValue::Subpath(Subpath::empty()), true),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Raster)],
|
||||
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>>")]),
|
||||
inputs: &[
|
||||
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("Samples", TaggedValue::F64(30.), 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("Negative Prompt", TaggedValue::String(String::new()), 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("Status", TaggedValue::ImaginateStatus(ImaginateStatus::Idle), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::imaginate_properties,
|
||||
};
|
||||
|
||||
@@ -495,7 +573,7 @@ impl DocumentNodeType {
|
||||
NodeImplementation::ProtoNode(ident) => {
|
||||
NodeNetwork {
|
||||
inputs: (0..num_inputs).map(|_| 0).collect(),
|
||||
output: 0,
|
||||
outputs: vec![NodeOutput::new(0, 0)],
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
@@ -515,4 +593,37 @@ impl DocumentNodeType {
|
||||
};
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
|
||||
use document_legacy::Operation;
|
||||
use glam::DVec2;
|
||||
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 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> {
|
||||
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 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 resolution_index = resolve_input("Resolution");
|
||||
let samples_index = resolve_input("Samples");
|
||||
@@ -478,6 +478,9 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
let transform_not_connected = matches!(document_node.inputs[transform_index], NodeInput::Value { .. });
|
||||
|
||||
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
|
||||
let mut interpreted_status = imaginate_status;
|
||||
@@ -527,12 +530,15 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
||||
widgets.push(
|
||||
TextButton::new("Terminate")
|
||||
.tooltip("Cancel the in-progress image generation and keep the latest progress")
|
||||
.on_update(move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginateTerminate {
|
||||
layer_path: layer_path.clone(),
|
||||
node_path: imaginate_node.clone(),
|
||||
.on_update({
|
||||
let imaginate_node = imaginate_node.clone();
|
||||
move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginateTerminate {
|
||||
layer_path: layer_path.clone(),
|
||||
node_path: imaginate_node.clone(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.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(&[
|
||||
IconButton::new("Random", 24)
|
||||
.tooltip("Generate with a new random seed")
|
||||
.on_update(move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginateRandom {
|
||||
imaginate_node: imaginate_node.clone(),
|
||||
.on_update({
|
||||
let imaginate_node = imaginate_node.clone();
|
||||
move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginateRandom {
|
||||
imaginate_node: imaginate_node.clone(),
|
||||
then_generate: true,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
TextButton::new("Generate")
|
||||
.tooltip("Fill layer frame by generating a new image")
|
||||
.on_update(move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginate {
|
||||
imaginate_node: imaginate_node_1.clone(),
|
||||
.on_update({
|
||||
let imaginate_node = imaginate_node.clone();
|
||||
move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginate {
|
||||
imaginate_node: imaginate_node.clone(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
WidgetHolder::related_separator(),
|
||||
@@ -590,7 +603,16 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
||||
WidgetHolder::unrelated_separator(),
|
||||
IconButton::new("Regenerate", 24)
|
||||
.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(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
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")
|
||||
};
|
||||
|
||||
// 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 = {
|
||||
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 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 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(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
CheckboxInput::new(!dimensions_is_auto)
|
||||
CheckboxInput::new(!dimensions_is_auto || transform_not_connected)
|
||||
.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(
|
||||
move |checkbox_input: &CheckboxInput| {
|
||||
if checkbox_input.checked {
|
||||
@@ -663,7 +706,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
||||
NumberInput::new(Some(vec2.x))
|
||||
.label("W")
|
||||
.unit(" px")
|
||||
.disabled(dimensions_is_auto)
|
||||
.disabled(dimensions_is_auto && !transform_not_connected)
|
||||
.on_update(update_value(
|
||||
move |number_input: &NumberInput| TaggedValue::OptionalDVec2(round(DVec2::new(number_input.value.unwrap(), vec2.y))),
|
||||
node_id,
|
||||
@@ -674,7 +717,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
||||
NumberInput::new(Some(vec2.y))
|
||||
.label("H")
|
||||
.unit(" px")
|
||||
.disabled(dimensions_is_auto)
|
||||
.disabled(dimensions_is_auto && !transform_not_connected)
|
||||
.on_update(update_value(
|
||||
move |number_input: &NumberInput| TaggedValue::OptionalDVec2(round(DVec2::new(vec2.x, number_input.value.unwrap()))),
|
||||
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_type = layer_reference_input_layer.as_ref().map(|(_, layer_type)| layer_type);
|
||||
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_separator(),
|
||||
LayerReferenceInput::new(layer_path.clone(), layer_reference_input_layer_name.cloned(), layer_reference_input_layer_type.cloned())
|
||||
.disabled(!use_base_image)
|
||||
.on_update(update_value(|input: &LayerReferenceInput| TaggedValue::LayerPath(input.value.clone()), node_id, mask_index))
|
||||
.widget_holder(),
|
||||
]);
|
||||
widgets.push(WidgetHolder::unrelated_separator());
|
||||
if !transform_not_connected {
|
||||
widgets.push(
|
||||
LayerReferenceInput::new(layer_path.clone(), layer_reference_input_layer_name.cloned(), layer_reference_input_layer_type.cloned())
|
||||
.disabled(!use_base_image)
|
||||
.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(
|
||||
"Reference to a layer or folder which masks parts of the input image. Image generation is constrained to masked areas.\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.)",
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
||||
artboard_document,
|
||||
selected_layers,
|
||||
node_graph_message_handler,
|
||||
executor,
|
||||
} = data;
|
||||
let get_document = |document_selector: TargetDocument| match document_selector {
|
||||
TargetDocument::Artboard => artboard_document,
|
||||
@@ -166,7 +167,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
||||
let layer = document.layer(&path).unwrap();
|
||||
match target_document {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant};
|
||||
@@ -246,6 +247,7 @@ pub fn register_artwork_layer_properties(
|
||||
responses: &mut VecDeque<Message>,
|
||||
persistent_data: &PersistentData,
|
||||
node_graph_message_handler: &NodeGraphMessageHandler,
|
||||
executor: &mut NodeGraphExecutor,
|
||||
) {
|
||||
let options_bar = vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
@@ -323,6 +325,8 @@ pub fn register_artwork_layer_properties(
|
||||
responses,
|
||||
nested_path: &node_graph_message_handler.nested_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);
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ use document_legacy::LayerId;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::messages::prelude::NodeGraphMessageHandler;
|
||||
use crate::{messages::prelude::NodeGraphMessageHandler, node_graph_executor::NodeGraphExecutor};
|
||||
|
||||
pub struct PropertiesPanelMessageHandlerData<'a> {
|
||||
pub artwork_document: &'a DocumentLegacy,
|
||||
pub artboard_document: &'a DocumentLegacy,
|
||||
pub selected_layers: &'a mut dyn Iterator<Item = &'a [LayerId]>,
|
||||
pub node_graph_message_handler: &'a NodeGraphMessageHandler,
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, specta::Type)]
|
||||
|
||||
@@ -2,37 +2,29 @@ use super::utility_types::PersistentData;
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
||||
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::misc::LayoutTarget;
|
||||
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::misc::DocumentRenderMode;
|
||||
use crate::messages::portfolio::utility_types::ImaginateServerStatus;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup};
|
||||
use document_legacy::document::pick_safe_imaginate_resolution;
|
||||
use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
|
||||
use document_legacy::layers::style::RenderData;
|
||||
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::NodeId;
|
||||
use graph_craft::document::{NodeInput, NodeNetwork};
|
||||
use graph_craft::executor::Compiler;
|
||||
use graphene_core::raster::Image;
|
||||
|
||||
use glam::DVec2;
|
||||
use interpreted_executor::executor::DynamicExecutor;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PortfolioMessageHandler {
|
||||
menu_bar_message_handler: MenuBarMessageHandler,
|
||||
documents: HashMap<u64, DocumentMessageHandler>,
|
||||
document_ids: Vec<u64>,
|
||||
executor: interpreted_executor::executor::DynamicExecutor,
|
||||
executor: NodeGraphExecutor,
|
||||
active_document_id: Option<u64>,
|
||||
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
||||
pub persistent_data: PersistentData,
|
||||
@@ -50,7 +42,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||
PortfolioMessage::Document(message) => {
|
||||
if let Some(document_id) = self.active_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]
|
||||
PortfolioMessage::DocumentPassMessage { document_id, message } => {
|
||||
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 => {
|
||||
@@ -489,7 +481,14 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||
size,
|
||||
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(
|
||||
DialogMessage::DisplayDialogError {
|
||||
title: "Unable to update node graph".to_string(),
|
||||
@@ -681,227 +680,4 @@ impl PortfolioMessageHandler {
|
||||
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")
|
||||
}
|
||||
|
||||
/// 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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
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::tool::common_functionality::resize::Resize;
|
||||
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 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 mut network = NodeNetwork::new_network(32, imaginate_node_id);
|
||||
let imaginate_node_id = 100;
|
||||
let mut network = node_graph::new_image_network(32, imaginate_node_id);
|
||||
network.nodes.insert(
|
||||
imaginate_node_id,
|
||||
DocumentNode {
|
||||
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() },
|
||||
},
|
||||
imaginate_node_type.to_document_node(imaginate_inputs, graph_craft::document::DocumentNodeMetadata::position((20, 3))),
|
||||
);
|
||||
|
||||
responses.push_back(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::portfolio::document::node_graph;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
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());
|
||||
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(
|
||||
Operation::AddNodeGraphFrame {
|
||||
|
||||
277
editor/src/node_graph_executor.rs
Normal file
277
editor/src/node_graph_executor.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user