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 GitHub
parent 70f4d60e66
commit e14618b234
35 changed files with 1172 additions and 553 deletions

View File

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

View File

@@ -107,6 +107,7 @@ pub enum DocumentMessage {
},
NodeGraphFrameImaginateRandom {
imaginate_node: Vec<NodeId>,
then_generate: bool,
},
NodeGraphFrameImaginateTerminate {
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::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(

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

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(())
}
}