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

* Multiple node outputs

* Add new nodes

* gcore use std by default to allow for testing

* Allow multiple node outputs

* Multiple outputs to frontend

* Add ImageFrameNode to node registry

* Minor cleanup

* Basic transform implementation

* Add some logging to image encoding

* Fix ImageFrameNode

* Add transform input to Imaginate node (#1014)

* Add transform input to imaginate node

* Force the resolution to be edited with no transform

* Add transform to imaginate generation

* Fix compilation

---------

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

* Add labels to node outputs

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

* Rename 'Input Multiple' node to 'Input'

* Code review

* Replicate to Svelte

* Show only the primary input chain in the Properties panel

---------

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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