From 233cbf5479b5f3ab72061aeedf53f3c28f5d3da8 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 22 Apr 2026 15:10:10 -0700 Subject: [PATCH 1/4] Rename 'Image Value' node to 'Image' and have its input value be Image not Table> --- .../document/document_message_handler.rs | 4 +--- .../graph_operation_message.rs | 8 ++++---- .../graph_operation_message_handler.rs | 9 ++------- .../document/graph_operation/utility_types.rs | 12 +++++------ .../messages/portfolio/document_migration.rs | 20 +++++++++++++++++-- .../graph_modification_utils.rs | 11 +++------- node-graph/graph-craft/src/document/value.rs | 1 + node-graph/nodes/raster/src/std_nodes.rs | 4 ++-- 8 files changed, 37 insertions(+), 32 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 2cd7744d93..7c83ae6c9e 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -34,10 +34,8 @@ use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork}; use graphene_std::math::quad::Quad; use graphene_std::path_bool_nodes::boolean_intersect; use graphene_std::raster::BlendMode; -use graphene_std::raster_types::Raster; use graphene_std::render_node::wgpu_available; use graphene_std::subpath::Subpath; -use graphene_std::table::Table; use graphene_std::vector::PointId; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::misc::dvec2_to_point; @@ -696,7 +694,7 @@ impl MessageHandler> for DocumentMes responses.add(DocumentMessage::AddTransaction); - let layer = graph_modification_utils::new_image_layer(Table::new_from_element(Raster::new_cpu(image)), layer_node_id, layer_parent, responses); + let layer = graph_modification_utils::new_image_layer(image, layer_node_id, layer_parent, responses); if let Some(name) = name { responses.add(NodeGraphMessage::SetDisplayName { diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs index a0828ba63a..7c0d8bb8e1 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs @@ -4,16 +4,16 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node use crate::messages::prelude::*; use glam::{DAffine2, IVec2}; use graph_craft::document::NodeId; +use graphene_std::Artboard; use graphene_std::brush::brush_stroke::BrushStroke; +use graphene_std::color::Color; use graphene_std::raster::BlendMode; -use graphene_std::raster_types::{CPU, Raster}; +use graphene_std::raster_types::Image; use graphene_std::subpath::Subpath; -use graphene_std::table::Table; use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::vector::PointId; use graphene_std::vector::VectorModificationType; use graphene_std::vector::style::{Fill, Stroke}; -use graphene_std::{Artboard, Color}; #[impl_message(Message, DocumentMessage, GraphOperation)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] @@ -70,7 +70,7 @@ pub enum GraphOperationMessage { }, NewBitmapLayer { id: NodeId, - image_frame: Table>, + image: Image, parent: LayerNodeIdentifier, insert_index: usize, }, diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index befb069156..db6148934c 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -160,15 +160,10 @@ impl MessageHandler> for responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] }); responses.add(NodeGraphMessage::RunDocumentGraph); } - GraphOperationMessage::NewBitmapLayer { - id, - image_frame, - parent, - insert_index, - } => { + GraphOperationMessage::NewBitmapLayer { id, image, parent, insert_index } => { let mut modify_inputs = ModifyInputsContext::new(network_interface, responses); let layer = modify_inputs.create_layer(id); - modify_inputs.insert_image_data(image_frame, layer); + modify_inputs.insert_image_data(image, layer); network_interface.move_layer_to_stack(layer, parent, insert_index, &[]); responses.add(NodeGraphMessage::RunDocumentGraph); } diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 388903c125..26903bd89e 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -9,7 +9,7 @@ use graph_craft::document::{NodeId, NodeInput}; use graph_craft::{ProtoNodeIdentifier, concrete}; use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::raster::BlendMode; -use graphene_std::raster_types::{CPU, Raster}; +use graphene_std::raster_types::Image; use graphene_std::subpath::Subpath; use graphene_std::table::Table; use graphene_std::text::{Font, TypesettingConfig}; @@ -303,14 +303,14 @@ impl<'a> ModifyInputsContext<'a> { self.network_interface.move_node_to_chain_start(&color_value_id, layer, &[], self.import); } - pub fn insert_image_data(&mut self, image_frame: Table>, layer: LayerNodeIdentifier) { + pub fn insert_image_data(&mut self, image: Image, layer: LayerNodeIdentifier) { let transform = resolve_network_node_type("Transform").expect("Transform node does not exist").default_node_template(); - let image = resolve_proto_node_type(graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER) - .expect("ImageValue node does not exist") - .node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::Raster(image_frame), false))]); + let image_node = resolve_proto_node_type(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER) + .expect("Image node does not exist") + .node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::ImageData(image), false))]); let image_id = NodeId::new(); - self.network_interface.insert_node(image_id, image, &[]); + self.network_interface.insert_node(image_id, image_node, &[]); self.network_interface.move_node_to_chain_start(&image_id, layer, &[], self.import); let transform_id = NodeId::new(); diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 1aa3e543cd..4bd1f0463a 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -584,8 +584,9 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ ], }, NodeReplacement { - node: graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER, + node: graphene_std::raster_nodes::std_nodes::image::IDENTIFIER, aliases: &[ + "raster_nodes::std_nodes::ImageValueNode", "graphene_raster_nodes::std_nodes::ImageValueNode", "graphene_std::raster::ImageValueNode", "graphene_std::raster::ImageNode", @@ -1384,7 +1385,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], .set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]); } - if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER) && inputs_count == 1 { + if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER) && inputs_count == 1 { let mut node_template = resolve_document_node_type(&reference)?.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template); @@ -1972,6 +1973,21 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], } } + // Migrate Image nodes that stored a Table> in input 1 to instead use bare Image via TaggedValue::ImageData + if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER) { + let input_1 = node.inputs.get(1)?; + if let NodeInput::Value { tagged_value, .. } = input_1 + && let TaggedValue::Raster(raster_table) = &**tagged_value + && !raster_table.is_empty() + { + let image = raster_table.iter().next()?.element.data().clone(); + + document + .network_interface + .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::ImageData(image), false), network_path); + } + } + // ================================== // PUT ALL MIGRATIONS ABOVE THIS LINE // ================================== diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 51ec764b0c..7aa346ddd3 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -10,7 +10,7 @@ use graph_craft::{ProtoNodeIdentifier, concrete}; use graphene_std::Color; use graphene_std::NodeInputDecleration; use graphene_std::raster::BlendMode; -use graphene_std::raster_types::{CPU, GPU, Raster}; +use graphene_std::raster_types::{CPU, GPU, Image, Raster}; use graphene_std::subpath::Subpath; use graphene_std::table::Table; use graphene_std::text::{Font, TypesettingConfig}; @@ -218,14 +218,9 @@ pub fn new_vector_layer(subpaths: Vec>, id: NodeId, parent: Lay } /// Create a new bitmap layer. -pub fn new_image_layer(image_frame: Table>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque) -> LayerNodeIdentifier { +pub fn new_image_layer(image: Image, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque) -> LayerNodeIdentifier { let insert_index = 0; - responses.add(GraphOperationMessage::NewBitmapLayer { - id, - image_frame, - parent, - insert_index, - }); + responses.add(GraphOperationMessage::NewBitmapLayer { id, image, parent, insert_index }); LayerNodeIdentifier::new_unchecked(id) } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 511710a597..565409937a 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -225,6 +225,7 @@ tagged_value! { Curve(raster_nodes::curve::Curve), Footprint(core_types::transform::Footprint), VectorModification(Box), + ImageData(Image), // ========== // ENUM TYPES // ========== diff --git a/node-graph/nodes/raster/src/std_nodes.rs b/node-graph/nodes/raster/src/std_nodes.rs index ebba6268de..b362074ecb 100644 --- a/node-graph/nodes/raster/src/std_nodes.rs +++ b/node-graph/nodes/raster/src/std_nodes.rs @@ -297,8 +297,8 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table) -> Tab } #[node_macro::node(category(""))] -pub fn image_value(_: impl Ctx, _primary: (), image: Table>) -> Table> { - image +pub fn image(_: impl Ctx, _primary: (), image: Image) -> Table> { + Table::new_from_element(Raster::new_cpu(image)) } #[node_macro::node(category("Raster: Pattern"))] From 8bffa125c05d3497d11830699a9f67faf7c5a42a Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 22 Apr 2026 15:10:35 -0700 Subject: [PATCH 2/4] Add a Properties panel widget labeling "width x height" for images in the Image node --- .../data_panel/data_panel_message_handler.rs | 4 ++-- .../document/node_graph/node_properties.rs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs index b767b60305..ef24204e7f 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs @@ -497,7 +497,7 @@ impl TableRowLayout for Raster { "Raster" } fn identifier(&self) -> String { - format!("Raster ({}x{})", self.width, self.height) + format!("Raster ({} x {})", self.width, self.height) } fn element_page(&self, _data: &mut LayoutData) -> Vec { let raster = self.data(); @@ -528,7 +528,7 @@ impl TableRowLayout for Raster { "Raster" } fn identifier(&self) -> String { - format!("Raster ({}x{})", self.data().width(), self.data().height()) + format!("Raster ({} x {})", self.data().width(), self.data().height()) } fn element_page(&self, _data: &mut LayoutData) -> Vec { let widgets = vec![TextLabel::new("Raster is a texture on the GPU and cannot currently be displayed here").widget_instance()]; diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index ad09378939..d0745c9a1d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -21,6 +21,7 @@ use graphene_std::raster::{ BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute, SelectiveColorChoice, }; +use graphene_std::raster_types::Image; use graphene_std::table::{Table, TableRow}; use graphene_std::text::{Font, TextAlign}; use graphene_std::transform::{Footprint, ReferencePoint, ScaleType, Transform}; @@ -231,6 +232,7 @@ pub(crate) fn property_from_type( Some(x) if x == TypeId::of::() => curve_widget(default_info), Some(x) if x == TypeId::of::() => footprint_widget(default_info, &mut extra_widgets), Some(x) if x == TypeId::of::>() => vector_modification_widget(default_info).into(), + Some(x) if x == TypeId::of::>() => image_data_widget(default_info).into(), // =============================== // MANUALLY IMPLEMENTED ENUM TYPES // =============================== @@ -420,6 +422,23 @@ pub fn vector_modification_widget(parameter_widgets_info: ParameterWidgetsInfo) widgets } +pub fn image_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec { + let ParameterWidgetsInfo { document_node, node_id: _, index, .. } = parameter_widgets_info; + + let mut widgets = start_widgets(parameter_widgets_info); + + let Some(document_node) = document_node else { return widgets }; + let Some(input) = document_node.inputs.get(index) else { return widgets }; + + if let Some(TaggedValue::ImageData(image)) = input.as_non_exposed_value() { + let label = format!("{} x {}", image.width, image.height); + + widgets.extend_from_slice(&[Separator::new(SeparatorStyle::Unrelated).widget_instance(), TextLabel::new(label).widget_instance()]); + } + + widgets +} + pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec) -> LayoutGroup { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; From 5b16ed8a1ff917013e19bd2b698a2cb6656ffd1d Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 22 Apr 2026 15:21:53 -0700 Subject: [PATCH 3/4] Add Image node registry entry for MonitorNode --- node-graph/interpreted-executor/src/node_registry.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 38512bece9..68770a9a03 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -71,6 +71,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Table>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Image]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => IVec2]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DVec2]), From 8a5535a2523c290803c0090fcf9081e814946b21 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 22 Apr 2026 15:43:26 -0700 Subject: [PATCH 4/4] Code review --- .../messages/portfolio/document_migration.rs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 4bd1f0463a..18919edd68 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -1974,18 +1974,16 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], } // Migrate Image nodes that stored a Table> in input 1 to instead use bare Image via TaggedValue::ImageData - if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER) { - let input_1 = node.inputs.get(1)?; - if let NodeInput::Value { tagged_value, .. } = input_1 - && let TaggedValue::Raster(raster_table) = &**tagged_value - && !raster_table.is_empty() - { - let image = raster_table.iter().next()?.element.data().clone(); + if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER) + && let Some(NodeInput::Value { tagged_value, .. }) = node.inputs.get(1) + && let TaggedValue::Raster(raster_table) = &**tagged_value + && let Some(row) = raster_table.iter().next() + { + let image = row.element.data().clone(); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::ImageData(image), false), network_path); - } + document + .network_interface + .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::ImageData(image), false), network_path); } // ==================================