mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
Node graph improvements (#855)
* Selecting multiple nodes * Improve logs * Log bad types in dyn any * Add (broken) node links * New topological sort * Fix reorder ids function * Input and output node * Add nodes that operate on images * Fixups * Show node parameters together with layer properties * New nodes don't crash editor * Fix tests * Node positions backend * Generate node graph on value change * Add expose input message * Fix tests Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
bbe98d3fe3
commit
2994afa6b8
23 changed files with 734 additions and 331 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -223,6 +223,7 @@ name = "dyn-any"
|
|||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"dyn-any-derive",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -398,6 +399,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
use crate::messages::prelude::*;
|
||||
use graph_craft::document::{value::TaggedValue, NodeId};
|
||||
use graph_craft::proto::NodeIdentifier;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, NodeGraph)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum NodeGraphMessage {
|
||||
// Messages
|
||||
AddLink {
|
||||
from: NodeId,
|
||||
to: NodeId,
|
||||
to_index: usize,
|
||||
},
|
||||
CloseNodeGraph,
|
||||
ConnectNodesByLink {
|
||||
output_node: u64,
|
||||
input_node: u64,
|
||||
input_node_connector_index: u32,
|
||||
input_node_connector_index: usize,
|
||||
},
|
||||
CreateNode {
|
||||
// Having the caller generate the id means that we don't have to return it. This can be a random u64.
|
||||
node_id: NodeId,
|
||||
// I don't really know what this is for (perhaps a user identifiable name).
|
||||
name: String,
|
||||
// The node identifier must mach that found in `node-graph/graph-craft/src/node_registry.rs` e.g. "graphene_core::raster::GrayscaleNode
|
||||
identifier: NodeIdentifier,
|
||||
num_inputs: u32,
|
||||
node_type: String,
|
||||
},
|
||||
DeleteNode {
|
||||
node_id: NodeId,
|
||||
},
|
||||
ExposeInput {
|
||||
node_id: NodeId,
|
||||
input_index: usize,
|
||||
new_exposed: bool,
|
||||
},
|
||||
MoveSelectedNodes {
|
||||
displacement_x: i32,
|
||||
displacement_y: i32,
|
||||
},
|
||||
OpenNodeGraph {
|
||||
layer_path: Vec<graphene::LayerId>,
|
||||
},
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetCallback, WidgetHolder};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, NumberInputMode};
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
|
||||
use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeInput, NodeNetwork};
|
||||
use graphene::document::Document;
|
||||
use graphene::layers::layer_info::LayerDataType;
|
||||
use graphene::layers::nodegraph_layer::NodeGraphFrameLayer;
|
||||
|
||||
mod document_node_types;
|
||||
mod node_properties;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DataType {
|
||||
Raster,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FrontendNode {
|
||||
pub id: graph_craft::document::NodeId,
|
||||
#[serde(rename = "displayName")]
|
||||
pub display_name: String,
|
||||
#[serde(rename = "exposedInputs")]
|
||||
pub exposed_inputs: Vec<DataType>,
|
||||
pub outputs: Vec<DataType>,
|
||||
pub position: (i32, i32),
|
||||
}
|
||||
|
||||
// (link_start, link_end, link_end_input_index)
|
||||
|
@ -55,111 +64,11 @@ impl NodeGraphMessageHandler {
|
|||
let network = &node_graph_frame.network;
|
||||
let mut section = Vec::new();
|
||||
for node_id in &self.selected_nodes {
|
||||
let node = *node_id;
|
||||
let Some(document_node) = network.nodes.get(node_id) else {
|
||||
continue;
|
||||
};
|
||||
let name = format!("Node {} Properties", document_node.name);
|
||||
let layout = match &document_node.implementation {
|
||||
DocumentNodeImplementation::Network(_) => match document_node.name.as_str() {
|
||||
"Hue Shift Color" => vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Shift degrees".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some({
|
||||
let NodeInput::Value (TaggedValue::F32(x)) = document_node.inputs[1] else {
|
||||
panic!("Hue rotate should be f32")
|
||||
};
|
||||
x as f64
|
||||
}),
|
||||
unit: "°".into(),
|
||||
mode: NumberInputMode::Range,
|
||||
range_min: Some(-180.),
|
||||
range_max: Some(180.),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node,
|
||||
input_index: 1,
|
||||
value: TaggedValue::F32(number_input.value.unwrap() as f32),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
],
|
||||
}],
|
||||
"Brighten Color" => vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Brighten Amount".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some({
|
||||
let NodeInput::Value (TaggedValue::F32(x)) = document_node.inputs[1] else {
|
||||
panic!("Brighten amount should be f32")
|
||||
};
|
||||
x as f64
|
||||
}),
|
||||
mode: NumberInputMode::Range,
|
||||
range_min: Some(-255.),
|
||||
range_max: Some(255.),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node,
|
||||
input_index: 1,
|
||||
value: TaggedValue::F32(number_input.value.unwrap() as f32),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
],
|
||||
}],
|
||||
_ => vec![LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: format!("Cannot currently display properties for network {}", document_node.name),
|
||||
..Default::default()
|
||||
}))],
|
||||
}],
|
||||
},
|
||||
DocumentNodeImplementation::Unresolved(identifier) => match identifier.name.as_ref() {
|
||||
"graphene_std::raster::MapImageNode" | "graphene_core::ops::IdNode" => vec![LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: format!("{} requires no properties", document_node.name),
|
||||
..Default::default()
|
||||
}))],
|
||||
}],
|
||||
unknown => {
|
||||
vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: format!("TODO: {} properties", unknown),
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Add in editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs".to_string(),
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
};
|
||||
section.push(LayoutGroup::Section { name, layout });
|
||||
|
||||
section.push(node_properties::generate_node_properties(document_node, *node_id));
|
||||
}
|
||||
|
||||
section
|
||||
|
@ -173,7 +82,7 @@ impl NodeGraphMessageHandler {
|
|||
let links = network
|
||||
.nodes
|
||||
.iter()
|
||||
.flat_map(|(link_end, node)| node.inputs.iter().enumerate().map(move |(index, input)| (input, link_end, index)))
|
||||
.flat_map(|(link_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, link_end, index)))
|
||||
.filter_map(|(input, &link_end, link_end_input_index)| {
|
||||
if let NodeInput::Node(link_start) = *input {
|
||||
Some(FrontendNodeLink {
|
||||
|
@ -192,6 +101,9 @@ impl NodeGraphMessageHandler {
|
|||
nodes.push(FrontendNode {
|
||||
id: *id,
|
||||
display_name: node.name.clone(),
|
||||
exposed_inputs: node.inputs.iter().filter(|input| input.is_exposed()).map(|_| DataType::Raster).collect(),
|
||||
outputs: vec![DataType::Raster],
|
||||
position: node.metadata.position,
|
||||
})
|
||||
}
|
||||
log::debug!("Nodes:\n{:#?}\n\nFrontend Nodes:\n{:#?}\n\nLinks:\n{:#?}", network.nodes, nodes, links);
|
||||
|
@ -204,19 +116,6 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
fn process_message(&mut self, message: NodeGraphMessage, (document, _ipp): (&mut Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
NodeGraphMessage::AddLink { from, to, to_index } => {
|
||||
log::debug!("Connect primary output from node {from} to input of index {to_index} on node {to}.");
|
||||
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
if let Some(to) = network.nodes.get_mut(&to) {
|
||||
// Extend number of inputs if not already large enough
|
||||
if to_index >= to.inputs.len() {
|
||||
to.inputs.extend(((to.inputs.len() - 1)..to_index).map(|_| NodeInput::Network));
|
||||
}
|
||||
to.inputs[to_index] = NodeInput::Node(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::CloseNodeGraph => {
|
||||
if let Some(_old_layer_path) = self.layer_path.take() {
|
||||
info!("Closing node graph");
|
||||
|
@ -231,45 +130,103 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
input_node_connector_index,
|
||||
} => {
|
||||
log::debug!("Connect primary output from node {output_node} to input of index {input_node_connector_index} on node {input_node}.");
|
||||
|
||||
let Some(network) = self.get_active_network_mut(document) else {
|
||||
error!("No network");
|
||||
return;
|
||||
};
|
||||
let Some(input_node) = network.nodes.get_mut(&input_node) else {
|
||||
error!("No to");
|
||||
return;
|
||||
};
|
||||
let Some((actual_index, _)) = input_node.inputs.iter().enumerate().filter(|input|input.1.is_exposed()).nth(input_node_connector_index) else {
|
||||
error!("Failed to find actual index of connector indes {input_node_connector_index} on node {input_node:#?}");
|
||||
return;
|
||||
};
|
||||
input_node.inputs[actual_index] = NodeInput::Node(output_node);
|
||||
|
||||
info!("Inputs: {:?}", input_node.inputs);
|
||||
Self::send_graph(network, responses);
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||
}
|
||||
NodeGraphMessage::CreateNode {
|
||||
node_id,
|
||||
name,
|
||||
identifier,
|
||||
num_inputs,
|
||||
} => {
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
let inner_network = NodeNetwork {
|
||||
inputs: (0..num_inputs).map(|_| 0).collect(),
|
||||
output: 0,
|
||||
nodes: [(
|
||||
node_id,
|
||||
DocumentNode {
|
||||
name: format!("{}_impl", name),
|
||||
// TODO: Allow inserting nodes that contain other nodes.
|
||||
implementation: DocumentNodeImplementation::Unresolved(identifier),
|
||||
inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
network.nodes.insert(
|
||||
node_id,
|
||||
NodeGraphMessage::CreateNode { node_id, node_type } => {
|
||||
let Some(network) = self.get_active_network_mut(document) else{
|
||||
warn!("No network");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(document_node_type) = document_node_types::resolve_document_node_type(&node_type) else{
|
||||
responses.push_back(DialogMessage::DisplayDialogError { title: "Cannot insert node".to_string(), description: format!("The document node '{node_type}' does not exist in the document node list") }.into());
|
||||
return;
|
||||
};
|
||||
|
||||
let num_inputs = document_node_type.default_inputs.len();
|
||||
|
||||
let inner_network = NodeNetwork {
|
||||
inputs: (0..num_inputs).map(|_| 0).collect(),
|
||||
output: 0,
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
name,
|
||||
inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(),
|
||||
name: format!("{}_impl", document_node_type.name),
|
||||
// TODO: Allow inserting nodes that contain other nodes.
|
||||
implementation: DocumentNodeImplementation::Network(inner_network),
|
||||
implementation: DocumentNodeImplementation::Unresolved(document_node_type.identifier.clone()),
|
||||
inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
);
|
||||
Self::send_graph(network, responses);
|
||||
}
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
network.nodes.insert(
|
||||
node_id,
|
||||
DocumentNode {
|
||||
name: node_type.clone(),
|
||||
inputs: document_node_type.default_inputs.to_vec(),
|
||||
// TODO: Allow inserting nodes that contain other nodes.
|
||||
implementation: DocumentNodeImplementation::Network(inner_network),
|
||||
metadata: graph_craft::document::DocumentNodeMetadata {
|
||||
// TODO: Better position default
|
||||
position: (node_id as i32 * 7 - 41, node_id as i32 * 2 - 10),
|
||||
},
|
||||
},
|
||||
);
|
||||
Self::send_graph(network, responses);
|
||||
}
|
||||
NodeGraphMessage::DeleteNode { node_id } => {
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
network.nodes.remove(&node_id);
|
||||
// TODO: Update UI if it is not already updated.
|
||||
Self::send_graph(network, responses);
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
|
||||
let Some(network) = self.get_active_network_mut(document) else{
|
||||
warn!("No network");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(node) = network.nodes.get_mut(&node_id) else {
|
||||
warn!("No node");
|
||||
return;
|
||||
};
|
||||
|
||||
if let NodeInput::Value { exposed, .. } = &mut node.inputs[input_index] {
|
||||
*exposed = new_exposed;
|
||||
}
|
||||
Self::send_graph(network, responses);
|
||||
}
|
||||
NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y } => {
|
||||
let Some(network) = self.get_active_network_mut(document) else{
|
||||
warn!("No network");
|
||||
return;
|
||||
};
|
||||
|
||||
for node_id in &self.selected_nodes {
|
||||
if let Some(node) = network.nodes.get_mut(node_id) {
|
||||
node.metadata.position.0 += displacement_x;
|
||||
node.metadata.position.1 += displacement_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::OpenNodeGraph { layer_path } => {
|
||||
|
@ -283,20 +240,8 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
|
||||
Self::send_graph(network, responses);
|
||||
|
||||
// TODO: Dynamic node library
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateNodeTypes {
|
||||
node_types: vec![
|
||||
FrontendNodeType::new("Identity"),
|
||||
FrontendNodeType::new("Grayscale Color"),
|
||||
FrontendNodeType::new("Brighten Color"),
|
||||
FrontendNodeType::new("Hue Shift Color"),
|
||||
FrontendNodeType::new("Add"),
|
||||
FrontendNodeType::new("Map Image"),
|
||||
],
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let node_types = document_node_types::collect_node_types();
|
||||
responses.push_back(FrontendMessage::UpdateNodeTypes { node_types }.into());
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::SelectNodes { nodes } => {
|
||||
|
@ -310,7 +255,8 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
if input_index >= node.inputs.len() {
|
||||
node.inputs.extend(((node.inputs.len() - 1)..input_index).map(|_| NodeInput::Network));
|
||||
}
|
||||
node.inputs[input_index] = NodeInput::Value(value);
|
||||
node.inputs[input_index] = NodeInput::Value { tagged_value: value, exposed: false };
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::proto::{NodeIdentifier, Type};
|
||||
use graphene_std::raster::Image;
|
||||
|
||||
use super::FrontendNodeType;
|
||||
|
||||
pub struct DocumentNodeType {
|
||||
pub name: &'static str,
|
||||
pub identifier: NodeIdentifier,
|
||||
pub default_inputs: &'static [NodeInput],
|
||||
}
|
||||
|
||||
// TODO: Dynamic node library
|
||||
static DOCUMENT_NODE_TYPES: [DocumentNodeType; 5] = [
|
||||
DocumentNodeType {
|
||||
name: "Identity",
|
||||
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
||||
default_inputs: &[NodeInput::Node(0)],
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Grayscale Image",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::GrayscaleImageNode", &[]),
|
||||
default_inputs: &[NodeInput::Value {
|
||||
tagged_value: TaggedValue::Image(Image {
|
||||
width: 0,
|
||||
height: 0,
|
||||
data: Vec::new(),
|
||||
}),
|
||||
exposed: true,
|
||||
}],
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Brighten Image",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
default_inputs: &[
|
||||
NodeInput::Value {
|
||||
tagged_value: TaggedValue::Image(Image {
|
||||
width: 0,
|
||||
height: 0,
|
||||
data: Vec::new(),
|
||||
}),
|
||||
exposed: true,
|
||||
},
|
||||
NodeInput::Value {
|
||||
tagged_value: TaggedValue::F32(10.),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Hue Shift Image",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
default_inputs: &[
|
||||
NodeInput::Value {
|
||||
tagged_value: TaggedValue::Image(Image {
|
||||
width: 0,
|
||||
height: 0,
|
||||
data: Vec::new(),
|
||||
}),
|
||||
exposed: true,
|
||||
},
|
||||
NodeInput::Value {
|
||||
tagged_value: TaggedValue::F32(50.),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Add",
|
||||
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("u32")), Type::Concrete(Cow::Borrowed("u32"))]),
|
||||
default_inputs: &[
|
||||
NodeInput::Value {
|
||||
tagged_value: TaggedValue::U32(0),
|
||||
exposed: false,
|
||||
},
|
||||
NodeInput::Value {
|
||||
tagged_value: TaggedValue::U32(0),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
pub fn resolve_document_node_type(name: &str) -> Option<&DocumentNodeType> {
|
||||
DOCUMENT_NODE_TYPES.iter().find(|node| node.name == name)
|
||||
}
|
||||
|
||||
pub fn collect_node_types() -> Vec<FrontendNodeType> {
|
||||
DOCUMENT_NODE_TYPES.iter().map(|node_type| FrontendNodeType { name: node_type.name.to_string() }).collect()
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetCallback, WidgetHolder};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, NumberInputMode};
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
|
||||
use crate::messages::prelude::NodeGraphMessage;
|
||||
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||
|
||||
pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId) -> LayoutGroup {
|
||||
let name = document_node.name.clone();
|
||||
let layout = match &document_node.implementation {
|
||||
DocumentNodeImplementation::Network(_) => match document_node.name.as_str() {
|
||||
"Hue Shift Image" => vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Shift Degrees".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some({
|
||||
let NodeInput::Value {tagged_value: TaggedValue::F32(x), ..} = document_node.inputs[1] else {
|
||||
panic!("Hue rotate should be f32")
|
||||
};
|
||||
x as f64
|
||||
}),
|
||||
unit: "°".into(),
|
||||
mode: NumberInputMode::Range,
|
||||
range_min: Some(-180.),
|
||||
range_max: Some(180.),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node: node_id,
|
||||
input_index: 1,
|
||||
value: TaggedValue::F32(number_input.value.unwrap() as f32),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
],
|
||||
}],
|
||||
"Brighten Image" => vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Brighten Amount".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some({
|
||||
let NodeInput::Value {tagged_value: TaggedValue::F32(x), ..} = document_node.inputs[1] else {
|
||||
panic!("Brighten amount should be f32")
|
||||
};
|
||||
x as f64
|
||||
}),
|
||||
mode: NumberInputMode::Range,
|
||||
range_min: Some(-255.),
|
||||
range_max: Some(255.),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node: node_id,
|
||||
input_index: 1,
|
||||
value: TaggedValue::F32(number_input.value.unwrap() as f32),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
],
|
||||
}],
|
||||
_ => vec![LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: format!("Cannot currently display parameters for network {}", document_node.name),
|
||||
..Default::default()
|
||||
}))],
|
||||
}],
|
||||
},
|
||||
DocumentNodeImplementation::Unresolved(identifier) => match identifier.name.as_ref() {
|
||||
"graphene_std::raster::MapImageNode" | "graphene_core::ops::IdNode" => vec![LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: format!("{} exposes no parameters", document_node.name),
|
||||
..Default::default()
|
||||
}))],
|
||||
}],
|
||||
unknown => {
|
||||
vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: format!("TODO: {} parameters", unknown),
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Add in editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs".to_string(),
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
};
|
||||
LayoutGroup::Section { name, layout }
|
||||
}
|
|
@ -269,7 +269,10 @@ pub fn register_artwork_layer_properties(
|
|||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: LayerDataTypeDiscriminant::from(&layer.data).to_string(),
|
||||
value: match &layer.data {
|
||||
LayerDataType::NodeGraphFrame(_) => "Node Graph Frame".into(),
|
||||
other => LayerDataTypeDiscriminant::from(other).to_string(),
|
||||
},
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
|
@ -322,14 +325,16 @@ pub fn register_artwork_layer_properties(
|
|||
LayerDataType::NodeGraphFrame(node_graph_frame) => {
|
||||
let is_graph_open = node_graph_message_handler.layer_path.as_ref().filter(|node_graph| *node_graph == &layer_path).is_some();
|
||||
let selected_nodes = &node_graph_message_handler.selected_nodes;
|
||||
|
||||
let mut properties_sections = vec![
|
||||
node_section_transform(layer, persistent_data),
|
||||
node_section_node_graph_frame(layer_path, node_graph_frame, is_graph_open),
|
||||
];
|
||||
if !selected_nodes.is_empty() && is_graph_open {
|
||||
node_graph_message_handler.collate_properties(&node_graph_frame)
|
||||
} else {
|
||||
vec![
|
||||
node_section_transform(layer, persistent_data),
|
||||
node_section_node_graph_frame(layer_path, node_graph_frame, is_graph_open),
|
||||
]
|
||||
let parameters_sections = node_graph_message_handler.collate_properties(node_graph_frame);
|
||||
properties_sections.extend(parameters_sections.into_iter());
|
||||
}
|
||||
properties_sections
|
||||
}
|
||||
LayerDataType::Folder(_) => {
|
||||
vec![node_section_transform(layer, persistent_data)]
|
||||
|
|
|
@ -437,7 +437,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||
// Execute the node graph
|
||||
|
||||
let mut network = node_graph_frame.network.clone();
|
||||
info!("Network {network:?}");
|
||||
info!("Executing network {network:#?}");
|
||||
|
||||
let stack = borrow_stack::FixedSizeStack::new(256);
|
||||
for node_id in node_graph_frame.network.nodes.keys() {
|
||||
|
@ -447,14 +447,18 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||
let mut proto_network = network.into_proto_network();
|
||||
proto_network.reorder_ids();
|
||||
|
||||
info!("proto_network with reordered ids: {proto_network:#?}");
|
||||
|
||||
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
|
||||
for (_id, node) in proto_network.nodes {
|
||||
info!("Node {:?}", node);
|
||||
info!("Inserting proto node {:?}", node);
|
||||
graph_craft::node_registry::push_node(node, &stack);
|
||||
}
|
||||
|
||||
use borrow_stack::BorrowStack;
|
||||
use dyn_any::IntoDynAny;
|
||||
use graphene_core::Node;
|
||||
|
||||
let result = unsafe { stack.get().last().unwrap().eval(image.into_dyn()) };
|
||||
let result = *dyn_any::downcast::<Image>(result).unwrap();
|
||||
|
||||
|
@ -491,7 +495,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||
}
|
||||
Err(description) => responses.push_back(
|
||||
DialogMessage::DisplayDialogError {
|
||||
title: "Failed to update image".to_string(),
|
||||
title: "Failed to update node graph".to_string(),
|
||||
description,
|
||||
}
|
||||
.into(),
|
||||
|
|
|
@ -35,8 +35,8 @@
|
|||
class="node"
|
||||
:class="{ selected: selected.includes(node.id) }"
|
||||
:style="{
|
||||
'--offset-left': 8 + Number(node.id < 9n ? node.id : node.id - 9n) * 7,
|
||||
'--offset-top': 4 + Number(node.id < 9n ? node.id : node.id - 9n) * 2,
|
||||
'--offset-left': node.position?.x || 0,
|
||||
'--offset-top': node.position?.y || 0,
|
||||
'--data-color': 'var(--color-data-raster)',
|
||||
'--data-color-dim': 'var(--color-data-raster-dim)',
|
||||
}"
|
||||
|
@ -438,11 +438,17 @@ export default defineComponent({
|
|||
const nodeId = node?.getAttribute("data-node") || undefined;
|
||||
if (nodeId) {
|
||||
const id = BigInt(nodeId);
|
||||
this.editor.instance.selectNodes(new BigUint64Array([id]));
|
||||
this.selected = [id];
|
||||
if (e.shiftKey || e.ctrlKey) {
|
||||
if (this.selected.includes(id)) this.selected.splice(this.selected.lastIndexOf(id), 1);
|
||||
else this.selected.push(id);
|
||||
} else {
|
||||
this.selected = [id];
|
||||
}
|
||||
|
||||
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
||||
} else {
|
||||
this.editor.instance.selectNodes(new BigUint64Array([]));
|
||||
this.selected = [];
|
||||
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
||||
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
||||
graphDiv?.setPointerCapture(e.pointerId);
|
||||
|
||||
|
@ -483,9 +489,9 @@ export default defineComponent({
|
|||
const inputNodeConnectionIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined;
|
||||
|
||||
if (inputNodeConnectionIndex !== undefined) {
|
||||
const oneBasedIndex = inputNodeConnectionIndex + 1;
|
||||
// const oneBasedIndex = inputNodeConnectionIndex + 1;
|
||||
|
||||
this.editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), BigInt(inputConnectedNodeID), oneBasedIndex);
|
||||
this.editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), BigInt(inputConnectedNodeID), inputNodeConnectionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,11 +88,11 @@
|
|||
|
||||
.widget-row {
|
||||
&:first-child {
|
||||
margin-top: -1px;
|
||||
margin-top: calc(4px - 1px);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: -1px;
|
||||
margin-bottom: calc(4px - 1px);
|
||||
}
|
||||
|
||||
> .text-label:first-of-type {
|
||||
|
|
|
@ -12,6 +12,11 @@ export class JsMessage {
|
|||
static readonly jsMessageMarker = true;
|
||||
}
|
||||
|
||||
const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] }));
|
||||
const BigIntTupleToVec2 = Transform(({ value }: { value: [bigint, bigint] | undefined }) => (value === undefined ? undefined : { x: Number(value[0]), y: Number(value[1]) }));
|
||||
|
||||
export type XY = { x: number; y: number };
|
||||
|
||||
// ============================================================================
|
||||
// Add additional classes below to replicate Rust's `FrontendMessage`s and data structures.
|
||||
//
|
||||
|
@ -64,10 +69,19 @@ export class FrontendDocumentDetails extends DocumentDetails {
|
|||
readonly id!: bigint;
|
||||
}
|
||||
|
||||
export type DataType = "Raster" | "Color" | "Image" | "F32";
|
||||
|
||||
export class FrontendNode {
|
||||
readonly id!: bigint;
|
||||
|
||||
readonly displayName!: string;
|
||||
|
||||
readonly exposedInputs!: DataType[];
|
||||
|
||||
readonly outputs!: DataType[];
|
||||
|
||||
@TupleToVec2
|
||||
readonly position!: XY | undefined;
|
||||
}
|
||||
|
||||
export class FrontendNodeLink {
|
||||
|
@ -403,11 +417,6 @@ export class UpdateDocumentArtboards extends JsMessage {
|
|||
readonly svg!: string;
|
||||
}
|
||||
|
||||
const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] }));
|
||||
const BigIntTupleToVec2 = Transform(({ value }: { value: [bigint, bigint] | undefined }) => (value === undefined ? undefined : { x: Number(value[0]), y: Number(value[1]) }));
|
||||
|
||||
export type XY = { x: number; y: number };
|
||||
|
||||
export class UpdateDocumentScrollbars extends JsMessage {
|
||||
@TupleToVec2
|
||||
readonly position!: XY;
|
||||
|
|
|
@ -541,7 +541,7 @@ impl JsEditorHandle {
|
|||
|
||||
/// Notifies the backend that the user connected a node's primary output to one of another node's inputs
|
||||
#[wasm_bindgen(js_name = connectNodesByLink)]
|
||||
pub fn connect_nodes_by_link(&self, output_node: u64, input_node: u64, input_node_connector_index: u32) {
|
||||
pub fn connect_nodes_by_link(&self, output_node: u64, input_node: u64, input_node_connector_index: usize) {
|
||||
let message = NodeGraphMessage::ConnectNodesByLink {
|
||||
output_node,
|
||||
input_node,
|
||||
|
@ -553,9 +553,6 @@ impl JsEditorHandle {
|
|||
/// Creates a new document node in the node graph
|
||||
#[wasm_bindgen(js_name = createNode)]
|
||||
pub fn create_node(&self, node_type: String) {
|
||||
use graph_craft::proto::{NodeIdentifier, Type};
|
||||
use std::borrow::Cow;
|
||||
|
||||
fn generate_node_id() -> u64 {
|
||||
static mut NODE_ID: u64 = 10;
|
||||
unsafe {
|
||||
|
@ -564,24 +561,9 @@ impl JsEditorHandle {
|
|||
}
|
||||
}
|
||||
|
||||
let (ident, args) = match node_type.as_str() {
|
||||
"Identity" => (NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), 1),
|
||||
"Grayscale Color" => (NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), 1),
|
||||
"Brighten Color" => (NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), 1),
|
||||
"Hue Shift Color" => (NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), 1),
|
||||
"Add" => (
|
||||
NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("u32")), Type::Concrete(Cow::Borrowed("u32"))]),
|
||||
2,
|
||||
),
|
||||
"Map Image" => (NodeIdentifier::new("graphene_std::raster::MapImageNode", &[]), 2),
|
||||
_ => panic!("Invalid node type: {}", node_type),
|
||||
};
|
||||
|
||||
let message = NodeGraphMessage::CreateNode {
|
||||
node_id: generate_node_id(),
|
||||
name: node_type,
|
||||
identifier: ident,
|
||||
num_inputs: args,
|
||||
node_type,
|
||||
};
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
@ -593,6 +575,13 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Notifies the backend that the selected nodes have been moved
|
||||
#[wasm_bindgen(js_name = moveSelectedNodes)]
|
||||
pub fn move_selected_nodes(&self, displacement_x: i32, displacement_y: i32) {
|
||||
let message = NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Pastes an image
|
||||
#[wasm_bindgen(js_name = pasteImage)]
|
||||
pub fn paste_image(&self, mime: String, image_data: Vec<u8>, mouse_x: Option<f64>, mouse_y: Option<f64>) {
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::layers::text_layer::FontCache;
|
|||
use crate::LayerId;
|
||||
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graph_craft::proto::Type;
|
||||
use kurbo::{Affine, BezPath, Shape as KurboShape};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
|
@ -124,12 +125,13 @@ impl Default for NodeGraphFrameLayer {
|
|||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "brighten".into(),
|
||||
name: "Brighten Image Node".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||
"graphene_core::raster::BrightenColorNode",
|
||||
"graphene_std::raster::BrightenImageNode",
|
||||
&[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))],
|
||||
)),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
|
@ -142,12 +144,13 @@ impl Default for NodeGraphFrameLayer {
|
|||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "hue shift".into(),
|
||||
name: "Hue Shift Image Node".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||
"graphene_core::raster::HueShiftNode",
|
||||
"graphene_std::raster::HueShiftImage",
|
||||
&[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))],
|
||||
)),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
|
@ -157,31 +160,55 @@ impl Default for NodeGraphFrameLayer {
|
|||
Self {
|
||||
mime: String::new(),
|
||||
network: NodeNetwork {
|
||||
inputs: vec![2, 1],
|
||||
output: 2,
|
||||
inputs: vec![0],
|
||||
output: 3,
|
||||
nodes: [
|
||||
(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Hue Shift Color".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Value(value::TaggedValue::F32(50.))],
|
||||
implementation: DocumentNodeImplementation::Network(hue_shift_network),
|
||||
name: "Input".into(),
|
||||
inputs: vec![NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])),
|
||||
metadata: DocumentNodeMetadata { position: (8, 4) },
|
||||
},
|
||||
),
|
||||
(
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "Brighten Color".into(),
|
||||
inputs: vec![NodeInput::Node(0), NodeInput::Value(value::TaggedValue::F32(10.))],
|
||||
implementation: DocumentNodeImplementation::Network(brighten_network),
|
||||
name: "Hue Shift Image".into(),
|
||||
inputs: vec![
|
||||
NodeInput::Node(0),
|
||||
NodeInput::Value {
|
||||
tagged_value: value::TaggedValue::F32(50.),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
implementation: DocumentNodeImplementation::Network(hue_shift_network),
|
||||
metadata: DocumentNodeMetadata { position: (8 + 7, 4 + 2) },
|
||||
},
|
||||
),
|
||||
(
|
||||
2,
|
||||
DocumentNode {
|
||||
name: "Map Image".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Node(1)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::raster::MapImageNode", &[])),
|
||||
name: "Brighten Image".into(),
|
||||
inputs: vec![
|
||||
NodeInput::Node(1),
|
||||
NodeInput::Value {
|
||||
tagged_value: value::TaggedValue::F32(10.),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
implementation: DocumentNodeImplementation::Network(brighten_network),
|
||||
metadata: DocumentNodeMetadata { position: (8 + 7 * 2, 4 + 2 * 2) },
|
||||
},
|
||||
),
|
||||
(
|
||||
3,
|
||||
DocumentNode {
|
||||
name: "Output".into(),
|
||||
inputs: vec![NodeInput::Node(2)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])),
|
||||
metadata: DocumentNodeMetadata { position: (8 + 7 * 3, 4) },
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -13,9 +13,11 @@ documentation = "https://docs.rs/dyn-any"
|
|||
|
||||
[dependencies]
|
||||
dyn-any-derive = { path = "derive", version = "0.2.0", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
[features]
|
||||
derive = ["dyn-any-derive"]
|
||||
log-bad-types = ["log"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
@ -50,12 +50,18 @@ impl<'a, T: DynAny<'a> + 'a> UpcastFrom<T> for dyn DynAny<'a> + 'a {
|
|||
|
||||
pub trait DynAny<'a> {
|
||||
fn type_id(&self) -> TypeId;
|
||||
#[cfg(feature = "log-bad-types")]
|
||||
fn type_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
impl<'a, T: StaticType> DynAny<'a> for T {
|
||||
fn type_id(&self) -> std::any::TypeId {
|
||||
std::any::TypeId::of::<T::Static>()
|
||||
}
|
||||
#[cfg(feature = "log-bad-types")]
|
||||
fn type_name(&self) -> &'static str {
|
||||
std::any::type_name::<T>()
|
||||
}
|
||||
}
|
||||
pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> {
|
||||
if i.type_id() == std::any::TypeId::of::<<V as StaticType>::Static>() {
|
||||
|
@ -74,7 +80,11 @@ pub fn downcast<'a, V: StaticType>(i: Box<dyn DynAny<'a> + 'a>) -> Option<Box<V>
|
|||
let ptr = Box::into_raw(i) as *mut dyn DynAny<'a> as *mut V;
|
||||
Some(unsafe { Box::from_raw(ptr) })
|
||||
} else {
|
||||
// TODO: add log error
|
||||
#[cfg(feature = "log-bad-types")]
|
||||
{
|
||||
log::error!("Tried to downcast a {} to a {}", DynAny::type_name(i.as_ref()), core::any::type_name::<V>());
|
||||
}
|
||||
|
||||
if type_id == std::any::TypeId::of::<&dyn DynAny<'static>>() {
|
||||
panic!("downcast error: type_id == std::any::TypeId::of::<dyn DynAny<'a>>()");
|
||||
}
|
||||
|
@ -192,9 +202,12 @@ impl_type!(Option<T>,Result<T, E>,Cell<T>,UnsafeCell<T>,RefCell<T>,MaybeUninit<T
|
|||
impl<T: crate::StaticType + ?Sized> crate::StaticType for Box<T> {
|
||||
type Static = Box<<T as crate::StaticType>::Static>;
|
||||
}
|
||||
fn test() {
|
||||
let foo = (Box::new(&1 as &dyn DynAny<'static>), Box::new(&2 as &dyn DynAny<'static>));
|
||||
//let bar = &foo as &dyn DynAny<'static>;
|
||||
#[test]
|
||||
fn test_tuple_of_boxes() {
|
||||
let tuple = (Box::new(&1 as &dyn DynAny<'static>), Box::new(&2 as &dyn DynAny<'static>));
|
||||
let dyn_any = &tuple as &dyn DynAny;
|
||||
assert_eq!(&1, downcast_ref(*downcast_ref::<(Box<&dyn DynAny>, Box<&dyn DynAny>)>(dyn_any).unwrap().0).unwrap());
|
||||
assert_eq!(&2, downcast_ref(*downcast_ref::<(Box<&dyn DynAny>, Box<&dyn DynAny>)>(dyn_any).unwrap().1).unwrap());
|
||||
}
|
||||
|
||||
macro_rules! impl_tuple {
|
||||
|
|
|
@ -5,16 +5,16 @@ use self::color::Color;
|
|||
pub mod color;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GrayscaleNode;
|
||||
pub struct GrayscaleColorNode;
|
||||
|
||||
impl Node<Color> for GrayscaleNode {
|
||||
impl Node<Color> for GrayscaleColorNode {
|
||||
type Output = Color;
|
||||
fn eval(self, color: Color) -> Color {
|
||||
let avg = (color.r() + color.g() + color.b()) / 3.0;
|
||||
Color::from_rgbaf32_unchecked(avg, avg, avg, color.a())
|
||||
}
|
||||
}
|
||||
impl<'n> Node<Color> for &'n GrayscaleNode {
|
||||
impl<'n> Node<Color> for &'n GrayscaleColorNode {
|
||||
type Output = Color;
|
||||
fn eval(self, color: Color) -> Color {
|
||||
let avg = (color.r() + color.g() + color.b()) / 3.0;
|
||||
|
@ -49,9 +49,9 @@ impl<N: Node<(), Output = f32> + Copy> BrightenColorNode<N> {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HueShiftNode<N: Node<(), Output = f32>>(N);
|
||||
pub struct HueShiftColorNode<N: Node<(), Output = f32>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f32>> Node<Color> for HueShiftNode<N> {
|
||||
impl<N: Node<(), Output = f32>> Node<Color> for HueShiftColorNode<N> {
|
||||
type Output = Color;
|
||||
fn eval(self, color: Color) -> Color {
|
||||
let hue_shift = self.0.eval(());
|
||||
|
@ -59,7 +59,7 @@ impl<N: Node<(), Output = f32>> Node<Color> for HueShiftNode<N> {
|
|||
Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha)
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f32> + Copy> Node<Color> for &HueShiftNode<N> {
|
||||
impl<N: Node<(), Output = f32> + Copy> Node<Color> for &HueShiftColorNode<N> {
|
||||
type Output = Color;
|
||||
fn eval(self, color: Color) -> Color {
|
||||
let hue_shift = self.0.eval(());
|
||||
|
@ -68,7 +68,7 @@ impl<N: Node<(), Output = f32> + Copy> Node<Color> for &HueShiftNode<N> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f32> + Copy> HueShiftNode<N> {
|
||||
impl<N: Node<(), Output = f32> + Copy> HueShiftColorNode<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ mod test {
|
|||
#[test]
|
||||
fn map_node() {
|
||||
// let array = &mut [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()];
|
||||
(&GrayscaleNode).eval(Color::from_rgbf32_unchecked(1., 0., 0.));
|
||||
(&GrayscaleColorNode).eval(Color::from_rgbf32_unchecked(1., 0., 0.));
|
||||
/*let map = ForEachNode(MutWrapper(GrayscaleNode));
|
||||
(&map).eval(array.iter_mut());
|
||||
assert_eq!(array[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());*/
|
||||
|
|
|
@ -9,14 +9,13 @@ license = "MIT OR Apache-2.0"
|
|||
[dependencies]
|
||||
graphene-core = { path = "../gcore", features = ["async", "std"] }
|
||||
graphene-std = { path = "../gstd" }
|
||||
dyn-any = { path = "../../libraries/dyn-any" }
|
||||
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types"] }
|
||||
num-traits = "0.2"
|
||||
borrow_stack = { path = "../borrow_stack" }
|
||||
dyn-clone = "1.0"
|
||||
rand_chacha = "0.3.1"
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
features = ["derive"]
|
||||
[features]
|
||||
serde = ["dep:serde", "graphene-std/serde"]
|
||||
|
|
|
@ -28,12 +28,19 @@ fn merge_ids(a: u64, b: u64) -> u64 {
|
|||
hasher.finish()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DocumentNodeMetadata {
|
||||
pub position: (i32, i32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DocumentNode {
|
||||
pub name: String,
|
||||
pub inputs: Vec<NodeInput>,
|
||||
pub implementation: DocumentNodeImplementation,
|
||||
pub metadata: DocumentNodeMetadata,
|
||||
}
|
||||
|
||||
impl DocumentNode {
|
||||
|
@ -54,7 +61,7 @@ impl DocumentNode {
|
|||
let first = self.inputs.remove(0);
|
||||
if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation {
|
||||
let (input, mut args) = match first {
|
||||
NodeInput::Value(tagged_value) => {
|
||||
NodeInput::Value { tagged_value, .. } => {
|
||||
assert_eq!(self.inputs.len(), 0);
|
||||
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value.to_value()))
|
||||
}
|
||||
|
@ -63,7 +70,7 @@ impl DocumentNode {
|
|||
};
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network)), "recieved non resolved parameter");
|
||||
assert!(
|
||||
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value(_))),
|
||||
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
|
||||
"recieved value as parameter. inupts: {:#?}, construction_args: {:#?}",
|
||||
&self.inputs,
|
||||
&args
|
||||
|
@ -90,7 +97,7 @@ impl DocumentNode {
|
|||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum NodeInput {
|
||||
Node(NodeId),
|
||||
Value(value::TaggedValue),
|
||||
Value { tagged_value: value::TaggedValue, exposed: bool },
|
||||
Network,
|
||||
}
|
||||
|
||||
|
@ -100,13 +107,20 @@ impl NodeInput {
|
|||
*self = NodeInput::Node(f(*id))
|
||||
}
|
||||
}
|
||||
pub fn is_exposed(&self) -> bool {
|
||||
if let NodeInput::Value { exposed, .. } = self {
|
||||
*exposed
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NodeInput {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (&self, &other) {
|
||||
(Self::Node(n1), Self::Node(n2)) => n1 == n2,
|
||||
(Self::Value(v1), Self::Value(v2)) => v1 == v2,
|
||||
(Self::Value { tagged_value: v1, .. }, Self::Value { tagged_value: v2, .. }) => v1 == v2,
|
||||
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
||||
}
|
||||
}
|
||||
|
@ -169,13 +183,14 @@ impl NodeNetwork {
|
|||
let network_input = self.nodes.get_mut(network_input).unwrap();
|
||||
network_input.populate_first_network_input(node, *offset);
|
||||
}
|
||||
NodeInput::Value(value) => {
|
||||
let name = format!("Value: {:?}", value.clone().to_value());
|
||||
NodeInput::Value { tagged_value, exposed } => {
|
||||
let name = format!("Value: {:?}", tagged_value.clone().to_value());
|
||||
let new_id = map_ids(id, gen_id());
|
||||
let value_node = DocumentNode {
|
||||
name: name.clone(),
|
||||
inputs: vec![NodeInput::Value(value)],
|
||||
inputs: vec![NodeInput::Value { tagged_value, exposed }],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
};
|
||||
assert!(!self.nodes.contains_key(&new_id));
|
||||
self.nodes.insert(new_id, value_node);
|
||||
|
@ -235,17 +250,19 @@ mod test {
|
|||
(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "cons".into(),
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
),
|
||||
(
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "add".into(),
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::Node(0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -265,17 +282,19 @@ mod test {
|
|||
(
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "cons".into(),
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
),
|
||||
(
|
||||
2,
|
||||
DocumentNode {
|
||||
name: "add".into(),
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::Node(1)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -294,8 +313,15 @@ mod test {
|
|||
1,
|
||||
DocumentNode {
|
||||
name: "Inc".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Value(value::TaggedValue::U32(2))],
|
||||
inputs: vec![
|
||||
NodeInput::Network,
|
||||
NodeInput::Value {
|
||||
tagged_value: value::TaggedValue::U32(2),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
implementation: DocumentNodeImplementation::Network(add_network()),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
|
@ -312,9 +338,10 @@ mod test {
|
|||
#[test]
|
||||
fn resolve_proto_node_add() {
|
||||
let document_node = DocumentNode {
|
||||
name: "cons".into(),
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Node(0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
};
|
||||
|
||||
let proto_node = document_node.resolve_proto_node();
|
||||
|
@ -380,30 +407,37 @@ mod test {
|
|||
name: "Inc".into(),
|
||||
inputs: vec![NodeInput::Node(11)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
),
|
||||
(
|
||||
10,
|
||||
DocumentNode {
|
||||
name: "cons".into(),
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Node(14)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
),
|
||||
(
|
||||
14,
|
||||
DocumentNode {
|
||||
name: "Value: 2".into(),
|
||||
inputs: vec![NodeInput::Value(value::TaggedValue::U32(2))],
|
||||
inputs: vec![NodeInput::Value {
|
||||
tagged_value: value::TaggedValue::U32(2),
|
||||
exposed: false,
|
||||
}],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
),
|
||||
(
|
||||
11,
|
||||
DocumentNode {
|
||||
name: "add".into(),
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::Node(10)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -11,7 +11,7 @@ pub enum TaggedValue {
|
|||
String(String),
|
||||
U32(u32),
|
||||
F32(f32),
|
||||
//Image(graphene_std::raster::Image),
|
||||
Image(graphene_std::raster::Image),
|
||||
Color(graphene_core::raster::color::Color),
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ impl TaggedValue {
|
|||
TaggedValue::String(x) => Box::new(x),
|
||||
TaggedValue::U32(x) => Box::new(x),
|
||||
TaggedValue::F32(x) => Box::new(x),
|
||||
TaggedValue::Image(x) => Box::new(x),
|
||||
TaggedValue::Color(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,23 +68,25 @@ mod tests {
|
|||
(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "cons".into(),
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||
"graphene_core::structural::ConsNode",
|
||||
&[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||
)),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
),
|
||||
(
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "add".into(),
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::Node(0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||
"graphene_core::ops::AddNode",
|
||||
&[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||
)),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -100,8 +102,15 @@ mod tests {
|
|||
0,
|
||||
DocumentNode {
|
||||
name: "Inc".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Value(value::TaggedValue::U32(1))],
|
||||
inputs: vec![
|
||||
NodeInput::Network,
|
||||
NodeInput::Value {
|
||||
tagged_value: value::TaggedValue::U32(1),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
implementation: DocumentNodeImplementation::Network(add_network()),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
|
|
|
@ -21,17 +21,25 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
NodeIdentifier::new("graphene_core::ops::IdNode", &[Concrete(std::borrow::Cow::Borrowed("Any<'_>"))]),
|
||||
|proto_node, stack| {
|
||||
stack.push_fn(|nodes| {
|
||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
||||
let node = pre_node.then(graphene_core::ops::IdNode);
|
||||
node.into_type_erased()
|
||||
if let ProtoNodeInput::Node(pre_id) = proto_node.input {
|
||||
let pre_node = nodes.get(pre_id as usize).unwrap();
|
||||
let node = pre_node.then(graphene_core::ops::IdNode);
|
||||
node.into_type_erased()
|
||||
} else {
|
||||
graphene_core::ops::IdNode.into_type_erased()
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic]), |proto_node, stack| {
|
||||
stack.push_fn(|nodes| {
|
||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
||||
let node = pre_node.then(graphene_core::ops::IdNode);
|
||||
node.into_type_erased()
|
||||
if let ProtoNodeInput::Node(pre_id) = proto_node.input {
|
||||
let pre_node = nodes.get(pre_id as usize).unwrap();
|
||||
let node = pre_node.then(graphene_core::ops::IdNode);
|
||||
node.into_type_erased()
|
||||
} else {
|
||||
graphene_core::ops::IdNode.into_type_erased()
|
||||
}
|
||||
})
|
||||
}),
|
||||
(
|
||||
|
@ -190,9 +198,9 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
}
|
||||
})
|
||||
}),
|
||||
(NodeIdentifier::new("graphene_core::raster::GrayscaleNode", &[]), |proto_node, stack| {
|
||||
(NodeIdentifier::new("graphene_core::raster::GrayscaleColorNode", &[]), |proto_node, stack| {
|
||||
stack.push_fn(|nodes| {
|
||||
let node = DynAnyNode::new(graphene_core::raster::GrayscaleNode);
|
||||
let node = DynAnyNode::new(graphene_core::raster::GrayscaleColorNode);
|
||||
|
||||
if let ProtoNodeInput::Node(pre_id) = proto_node.input {
|
||||
let pre_node = nodes.get(pre_id as usize).unwrap();
|
||||
|
@ -222,14 +230,14 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
},
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_core::raster::HueShiftNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
NodeIdentifier::new("graphene_core::raster::HueShiftColorNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
|proto_node, stack| {
|
||||
info!("proto node {:?}", proto_node);
|
||||
stack.push_fn(|nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Hue Shift Color Node constructed with out shift input node") };
|
||||
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
||||
let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node);
|
||||
let node = DynAnyNode::new(graphene_core::raster::HueShiftNode::new(input_node));
|
||||
let node = DynAnyNode::new(graphene_core::raster::HueShiftColorNode::new(input_node));
|
||||
|
||||
if let ProtoNodeInput::Node(pre_id) = proto_node.input {
|
||||
let pre_node = nodes.get(pre_id as usize).unwrap();
|
||||
|
@ -243,6 +251,7 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
(NodeIdentifier::new("graphene_std::raster::MapImageNode", &[]), |proto_node, stack| {
|
||||
if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args {
|
||||
stack.push_fn(move |nodes| {
|
||||
info!("Map image Depending upon id {:?}", operation_node_id);
|
||||
let operation_node = nodes.get(operation_node_id[0] as usize).unwrap();
|
||||
let operation_node: DowncastBothNode<_, Color, Color> = DowncastBothNode::new(operation_node);
|
||||
let map_node = DynAnyNode::new(graphene_std::raster::MapImageNode::new(operation_node));
|
||||
|
@ -258,45 +267,54 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
unimplemented!()
|
||||
}
|
||||
}),
|
||||
(NodeIdentifier::new("graph_craft::node_registry::GrayscaleImage", &[]), |proto_node, stack| {
|
||||
(NodeIdentifier::new("graphene_std::raster::GrayscaleImageNode", &[]), |proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let grayscale_node = DynAnyNode::new(FnNode::new(|mut image: Image| {
|
||||
for pixel in &mut image.data {
|
||||
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
||||
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
||||
}
|
||||
image
|
||||
}));
|
||||
let node = DynAnyNode::new(graphene_std::raster::GrayscaleImageNode);
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
(pre_node).then(grayscale_node).into_type_erased()
|
||||
(pre_node).then(node).into_type_erased()
|
||||
} else {
|
||||
grayscale_node.into_type_erased()
|
||||
node.into_type_erased()
|
||||
}
|
||||
})
|
||||
}),
|
||||
(NodeIdentifier::new("graph_craft::node_registry::HueShiftImage", &[]), |_proto_node, _stack| {
|
||||
todo!();
|
||||
// stack.push_fn(move |nodes| {
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
|proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Hue Shift Image Node constructed with out shift input node") };
|
||||
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
||||
let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node);
|
||||
let node = DynAnyNode::new(graphene_std::raster::HueShiftImage::new(input_node));
|
||||
|
||||
// let hue_shift_node = DynAnyNode::new(FnNode::new(|(mut image, amount): (Image, f32)| {
|
||||
// for pixel in &mut image.data {
|
||||
// let [mut hue, saturation, luminance, alpha] = pixel.to_hsla();
|
||||
// hue += amount;
|
||||
// *pixel = Color::from_hsla(hue, saturation, luminance, alpha);
|
||||
// }
|
||||
// image
|
||||
// }));
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
(pre_node).then(node).into_type_erased()
|
||||
} else {
|
||||
node.into_type_erased()
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
|proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Brighten Image Node constructed with out brighten input node") };
|
||||
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
||||
let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node);
|
||||
let node = DynAnyNode::new(graphene_std::raster::BrightenImageNode::new(input_node));
|
||||
|
||||
// if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
// let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
// (pre_node).then(hue_shift_node).into_type_erased()
|
||||
// } else {
|
||||
// hue_shift_node.into_type_erased()
|
||||
// }
|
||||
// })
|
||||
}),
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
(pre_node).then(node).into_type_erased()
|
||||
} else {
|
||||
node.into_type_erased()
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::raster::ImageNode", &[Concrete(std::borrow::Cow::Borrowed("&str"))]),
|
||||
|_proto_node, stack| {
|
||||
|
@ -401,7 +419,7 @@ mod protograph_testing {
|
|||
let grayscale_protonode = ProtoNode {
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
input: ProtoNodeInput::Node(0),
|
||||
identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleNode", &[]),
|
||||
identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleColorNode", &[]),
|
||||
};
|
||||
push_node(grayscale_protonode, &stack);
|
||||
|
||||
|
@ -438,7 +456,7 @@ mod protograph_testing {
|
|||
let grayscale_protonode = ProtoNode {
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
input: ProtoNodeInput::None,
|
||||
identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleNode", &[]),
|
||||
identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleColorNode", &[]),
|
||||
};
|
||||
push_node(grayscale_protonode, &stack);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::document::value;
|
||||
use crate::document::NodeId;
|
||||
|
@ -165,7 +165,37 @@ impl ProtoNetwork {
|
|||
edges
|
||||
}
|
||||
|
||||
// Based on https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
|
||||
// Based on https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
|
||||
// This approach excludes nodes that are not connected
|
||||
pub fn topological_sort(&self) -> Vec<NodeId> {
|
||||
let mut sorted = Vec::new();
|
||||
let inwards_edges = self.collect_inwards_edges();
|
||||
fn visit(node_id: NodeId, temp_marks: &mut HashSet<NodeId>, sorted: &mut Vec<NodeId>, inwards_edges: &HashMap<NodeId, Vec<NodeId>>) {
|
||||
if sorted.contains(&node_id) {
|
||||
return;
|
||||
};
|
||||
if temp_marks.contains(&node_id) {
|
||||
panic!("Cycle detected");
|
||||
}
|
||||
info!("Visiting {node_id}");
|
||||
|
||||
if let Some(dependencies) = inwards_edges.get(&node_id) {
|
||||
temp_marks.insert(node_id);
|
||||
for &dependant in dependencies {
|
||||
visit(dependant, temp_marks, sorted, inwards_edges);
|
||||
}
|
||||
temp_marks.remove(&node_id);
|
||||
}
|
||||
sorted.push(node_id);
|
||||
}
|
||||
assert!(self.nodes.iter().any(|(id, _)| *id == self.output), "Output id {} does not exist", self.output);
|
||||
visit(self.output, &mut HashSet::new(), &mut sorted, &inwards_edges);
|
||||
|
||||
info!("Sorted order {sorted:?}");
|
||||
sorted
|
||||
}
|
||||
|
||||
/*// Based on https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
|
||||
pub fn topological_sort(&self) -> Vec<NodeId> {
|
||||
let mut sorted = Vec::new();
|
||||
let outwards_edges = self.collect_outwards_edges();
|
||||
|
@ -187,21 +217,24 @@ impl ProtoNetwork {
|
|||
}
|
||||
}
|
||||
}
|
||||
info!("Sorted order {sorted:?}");
|
||||
sorted
|
||||
}
|
||||
}*/
|
||||
|
||||
pub fn reorder_ids(&mut self) {
|
||||
let order = self.topological_sort();
|
||||
let lookup = self
|
||||
.nodes
|
||||
// Map of node ids to indexes (which become the node ids as they are inserted into the borrow stack)
|
||||
let lookup: HashMap<_, _> = order.iter().enumerate().map(|(pos, id)| (*id, pos as NodeId)).collect();
|
||||
info!("Order {order:?}");
|
||||
self.nodes = order
|
||||
.iter()
|
||||
.map(|(id, _)| (*id, order.iter().position(|x| x == id).unwrap() as u64))
|
||||
.collect::<HashMap<u64, u64>>();
|
||||
self.nodes.sort_by_key(|(id, _)| lookup.get(id).unwrap());
|
||||
self.nodes.iter_mut().for_each(|(id, node)| {
|
||||
node.map_ids(|id| *lookup.get(&id).unwrap());
|
||||
*id = *lookup.get(id).unwrap()
|
||||
});
|
||||
.map(|id| {
|
||||
let mut node = self.nodes.swap_remove(self.nodes.iter().position(|(test_id, _)| test_id == id).unwrap()).1;
|
||||
node.map_ids(|id| *lookup.get(&id).unwrap());
|
||||
(*lookup.get(id).unwrap(), node)
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(order.len(), self.nodes.len());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,6 +250,14 @@ mod test {
|
|||
inputs: vec![10],
|
||||
output: 1,
|
||||
nodes: [
|
||||
(
|
||||
7,
|
||||
ProtoNode {
|
||||
identifier: "id".into(),
|
||||
input: ProtoNodeInput::Node(11),
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
},
|
||||
),
|
||||
(
|
||||
1,
|
||||
ProtoNode {
|
||||
|
|
|
@ -26,3 +26,8 @@ proc-macro2 = {version = "1.0", default-features = false, features = ["proc-macr
|
|||
quote = {version = "1.0", default-features = false }
|
||||
image = "*"
|
||||
dyn-clone = "1.0"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
features = ["derive"]
|
||||
|
|
|
@ -98,7 +98,8 @@ impl<Reader: std::io::Read> Node<Reader> for BufferNode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, DynAny)]
|
||||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Image {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
|
@ -165,16 +166,104 @@ pub fn export_image_node<'n>() -> impl Node<(Image, &'n str), Output = Result<()
|
|||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GrayscaleImageNode;
|
||||
|
||||
impl Node<Image> for GrayscaleImageNode {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
for pixel in &mut image.data {
|
||||
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
||||
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
impl Node<Image> for &GrayscaleImageNode {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
for pixel in &mut image.data {
|
||||
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
||||
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BrightenImageNode<N: Node<(), Output = f32>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f32>> Node<Image> for BrightenImageNode<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let brightness = self.0.eval(());
|
||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(per_channel(pixel.r()), per_channel(pixel.g()), per_channel(pixel.b()), pixel.a());
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f32> + Copy> Node<Image> for &BrightenImageNode<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let brightness = self.0.eval(());
|
||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(per_channel(pixel.r()), per_channel(pixel.g()), per_channel(pixel.b()), pixel.a());
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f32> + Copy> BrightenImageNode<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HueShiftImage<N: Node<(), Output = f32>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f32>> Node<Image> for HueShiftImage<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let hue_shift = self.0.eval(());
|
||||
for pixel in &mut image.data {
|
||||
let [hue, saturation, luminance, alpha] = pixel.to_hsla();
|
||||
*pixel = Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha);
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f32> + Copy> Node<Image> for &HueShiftImage<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let hue_shift = self.0.eval(());
|
||||
for pixel in &mut image.data {
|
||||
let [hue, saturation, luminance, alpha] = pixel.to_hsla();
|
||||
*pixel = Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha);
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f32> + Copy> HueShiftImage<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::raster::GrayscaleNode;
|
||||
use graphene_core::raster::GrayscaleColorNode;
|
||||
|
||||
#[test]
|
||||
fn map_node() {
|
||||
let array = [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()];
|
||||
let map = MapNode(GrayscaleNode, PhantomData);
|
||||
let map = MapNode(GrayscaleColorNode, PhantomData);
|
||||
let values = map.eval(array.into_iter());
|
||||
assert_eq!(values[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());
|
||||
}
|
||||
|
@ -182,7 +271,7 @@ mod test {
|
|||
#[test]
|
||||
fn load_image() {
|
||||
let image = image_node::<&str>();
|
||||
let gray = MapImageNode::new(GrayscaleNode);
|
||||
let gray = MapImageNode::new(GrayscaleColorNode);
|
||||
|
||||
let grayscale_picture = image.then(MapResultNode::new(&gray));
|
||||
let export = export_image_node();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue