Implement Infrastructure to reuse previous frames for brush drawing

Implement Infrastructuro to reuse the previous evaluation of the
node graph to blend the new stroke with instead of drawing the
entire trace from scratch.
This does not transition to a blending based approach because that still
caused regressions but allows the brush node to work with input data
natively.

Test Plan:
- Use the brush tool in the editor and check for regressions
- Evaluate the performance

Reviewers: Keavon

Pull Request: https://github.com/GraphiteEditor/Graphite/pull/1190
This commit is contained in:
Dennis Kobert
2023-05-03 13:14:41 +02:00
committed by Keavon Chambers
parent ebf67eaa82
commit 3adcc3031a
19 changed files with 282 additions and 172 deletions
+1
View File
@@ -170,6 +170,7 @@ impl Dispatcher {
self.message_handlers.portfolio_message_handler.active_document_id().unwrap(), self.message_handlers.portfolio_message_handler.active_document_id().unwrap(),
&self.message_handlers.input_preprocessor_message_handler, &self.message_handlers.input_preprocessor_message_handler,
&self.message_handlers.portfolio_message_handler.persistent_data, &self.message_handlers.portfolio_message_handler.persistent_data,
&self.message_handlers.portfolio_message_handler.executor,
), ),
); );
} else { } else {
@@ -106,6 +106,18 @@ fn static_nodes() -> Vec<DocumentNodeType> {
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)], outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)],
properties: |_document_node, _node_id, _context| node_properties::string_properties("The identity node simply returns the input"), properties: |_document_node, _node_id, _context| node_properties::string_properties("The identity node simply returns the input"),
}, },
DocumentNodeType {
name: "Monitor",
category: "General",
identifier: NodeImplementation::proto("graphene_core::ops::IdNode"),
inputs: vec![DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::General,
default: NodeInput::value(TaggedValue::None, true),
}],
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)],
properties: |_document_node, _node_id, _context| node_properties::string_properties("The Monitor node stores the value of its last evaluation"),
},
DocumentNodeType { DocumentNodeType {
name: "Downres", name: "Downres",
category: "Ignore", category: "Ignore",
@@ -457,6 +469,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
identifier: NodeImplementation::proto("graphene_std::brush::BrushNode"), identifier: NodeImplementation::proto("graphene_std::brush::BrushNode"),
inputs: vec![ inputs: vec![
DocumentInputType::value("None", TaggedValue::None, false), DocumentInputType::value("None", TaggedValue::None, false),
DocumentInputType::value("Background", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Trace", TaggedValue::VecDVec2((0..2).map(|x| DVec2::new(x as f64 * 10., 0.)).collect()), true), DocumentInputType::value("Trace", TaggedValue::VecDVec2((0..2).map(|x| DVec2::new(x as f64 * 10., 0.)).collect()), true),
DocumentInputType::value("Diameter", TaggedValue::F64(40.), false), DocumentInputType::value("Diameter", TaggedValue::F64(40.), false),
DocumentInputType::value("Hardness", TaggedValue::F64(50.), false), DocumentInputType::value("Hardness", TaggedValue::F64(50.), false),
@@ -588,11 +588,11 @@ pub fn blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _con
} }
pub fn brush_node_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub fn brush_node_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color = color_widget(document_node, node_id, 5, "Color", ColorInput::default(), true); let color = color_widget(document_node, node_id, 6, "Color", ColorInput::default(), true);
let size = number_widget(document_node, node_id, 2, "Diameter", NumberInput::default().min(1.).max(100.).unit(" px"), true); let size = number_widget(document_node, node_id, 3, "Diameter", NumberInput::default().min(1.).max(100.).unit(" px"), true);
let hardness = number_widget(document_node, node_id, 3, "Hardness", NumberInput::default().min(0.).max(100.).unit("%"), true); let hardness = number_widget(document_node, node_id, 4, "Hardness", NumberInput::default().min(0.).max(100.).unit("%"), true);
let flow = number_widget(document_node, node_id, 4, "Flow", NumberInput::default().min(1.).max(100.).unit("%"), true); let flow = number_widget(document_node, node_id, 5, "Flow", NumberInput::default().min(1.).max(100.).unit("%"), true);
vec![color, LayoutGroup::Row { widgets: size }, LayoutGroup::Row { widgets: hardness }, LayoutGroup::Row { widgets: flow }] vec![color, LayoutGroup::Row { widgets: size }, LayoutGroup::Row { widgets: hardness }, LayoutGroup::Row { widgets: flow }]
} }
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::utility_types::PersistentData; use super::utility_types::PersistentData;
use crate::application::generate_uuid; use crate::application::generate_uuid;
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION}; use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
@@ -25,7 +27,7 @@ pub struct PortfolioMessageHandler {
menu_bar_message_handler: MenuBarMessageHandler, menu_bar_message_handler: MenuBarMessageHandler,
documents: HashMap<u64, DocumentMessageHandler>, documents: HashMap<u64, DocumentMessageHandler>,
document_ids: Vec<u64>, document_ids: Vec<u64>,
executor: NodeGraphExecutor, pub executor: NodeGraphExecutor,
active_document_id: Option<u64>, active_document_id: Option<u64>,
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize], copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
pub persistent_data: PersistentData, pub persistent_data: PersistentData,
@@ -564,7 +566,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
} }
impl PortfolioMessageHandler { impl PortfolioMessageHandler {
pub fn introspect_node(&self, node_path: &[NodeId]) -> Option<String> { pub fn introspect_node(&self, node_path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
self.executor.introspect_node(node_path) self.executor.introspect_node(node_path)
} }
@@ -7,6 +7,7 @@ use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::utility_types::ToolType; use crate::messages::tool::utility_types::ToolType;
use crate::node_graph_executor::NodeGraphExecutor;
use document_legacy::layers::style::RenderData; use document_legacy::layers::style::RenderData;
use graphene_core::raster::color::Color; use graphene_core::raster::color::Color;
@@ -19,13 +20,13 @@ pub struct ToolMessageHandler {
pub shape_editor: ShapeState, pub shape_editor: ShapeState,
} }
impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocessorMessageHandler, &PersistentData)> for ToolMessageHandler { impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocessorMessageHandler, &PersistentData, &NodeGraphExecutor)> for ToolMessageHandler {
#[remain::check] #[remain::check]
fn process_message( fn process_message(
&mut self, &mut self,
message: ToolMessage, message: ToolMessage,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
(document, document_id, input, persistent_data): (&DocumentMessageHandler, u64, &InputPreprocessorMessageHandler, &PersistentData), (document, document_id, input, persistent_data, node_graph): (&DocumentMessageHandler, u64, &InputPreprocessorMessageHandler, &PersistentData, &NodeGraphExecutor),
) { ) {
let render_data = RenderData::new(&persistent_data.font_cache, document.view_mode, None); let render_data = RenderData::new(&persistent_data.font_cache, document.view_mode, None);
@@ -94,6 +95,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
render_data: &render_data, render_data: &render_data,
shape_overlay: &mut self.shape_overlay, shape_overlay: &mut self.shape_overlay,
shape_editor: &mut self.shape_editor, shape_editor: &mut self.shape_editor,
node_graph,
}; };
if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort { if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort {
tool.process_message(tool_abort_message, responses, &mut data); tool.process_message(tool_abort_message, responses, &mut data);
@@ -173,6 +175,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
render_data: &render_data, render_data: &render_data,
shape_overlay: &mut self.shape_overlay, shape_overlay: &mut self.shape_overlay,
shape_editor: &mut self.shape_editor, shape_editor: &mut self.shape_editor,
node_graph,
}; };
// Set initial hints and cursor // Set initial hints and cursor
@@ -243,6 +246,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
render_data: &render_data, render_data: &render_data,
shape_overlay: &mut self.shape_overlay, shape_overlay: &mut self.shape_overlay,
shape_editor: &mut self.shape_editor, shape_editor: &mut self.shape_editor,
node_graph,
}; };
if matches!(tool_message, ToolMessage::UpdateHints) { if matches!(tool_message, ToolMessage::UpdateHints) {
if self.transform_layer_handler.is_transforming() { if self.transform_layer_handler.is_transforming() {
@@ -8,14 +8,18 @@ use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use crate::node_graph_executor::NodeGraphExecutor;
use document_legacy::LayerId; use document_legacy::LayerId;
use dyn_any::downcast_ref;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork}; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
use graph_craft::{concrete, Type, TypeDescriptor};
use graphene_core::Cow; use graphene_core::Color;
use glam::DVec2; use glam::DVec2;
use graphene_core::raster::ImageFrame;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
@@ -172,7 +176,7 @@ impl ToolTransition for BrushTool {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct BrushToolData { struct BrushToolData {
points: Vec<DVec2>, points: Vec<Vec<DVec2>>,
diameter: f64, diameter: f64,
hardness: f64, hardness: f64,
flow: f64, flow: f64,
@@ -181,12 +185,28 @@ struct BrushToolData {
impl BrushToolData { impl BrushToolData {
fn update_points(&self, responses: &mut VecDeque<Message>) { fn update_points(&self, responses: &mut VecDeque<Message>) {
if let Some(layer_path) = self.path.clone() {
let points = self.points.iter().flatten().cloned().collect();
responses.add(NodeGraphMessage::SetQualifiedInputValue {
layer_path,
node_path: vec![0],
input_index: 2,
value: TaggedValue::VecDVec2(points),
});
}
}
fn update_image(&self, node_graph: &NodeGraphExecutor, responses: &mut VecDeque<Message>) {
let Some(image) = node_graph.introspect_node(&[1]) else { return; };
let image: &ImageFrame<Color> = image.downcast_ref().unwrap();
self.set_image(image.clone(), responses)
}
fn set_image(&self, image_frame: ImageFrame<Color>, responses: &mut VecDeque<Message>) {
if let Some(layer_path) = self.path.clone() { if let Some(layer_path) = self.path.clone() {
responses.add(NodeGraphMessage::SetQualifiedInputValue { responses.add(NodeGraphMessage::SetQualifiedInputValue {
layer_path, layer_path,
node_path: vec![0], node_path: vec![0],
input_index: 1, input_index: 1,
value: TaggedValue::VecDVec2(self.points.clone()), value: TaggedValue::ImageFrame(image_frame),
}); });
} }
} }
@@ -201,7 +221,11 @@ impl Fsm for BrushToolFsmState {
event: ToolMessage, event: ToolMessage,
tool_data: &mut Self::ToolData, tool_data: &mut Self::ToolData,
ToolActionHandlerData { ToolActionHandlerData {
document, global_tool_data, input, .. document,
global_tool_data,
input,
node_graph,
..
}: &mut ToolActionHandlerData, }: &mut ToolActionHandlerData,
tool_options: &Self::ToolOptions, tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
@@ -217,9 +241,12 @@ impl Fsm for BrushToolFsmState {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
let existing_points = load_existing_points(document); let existing_points = load_existing_points(document);
let new_layer = existing_points.is_none(); let new_layer = existing_points.is_none();
if let Some((layer_path, points)) = existing_points { if let Some((layer_path, points, image)) = existing_points {
tool_data.path = Some(layer_path); tool_data.path = Some(layer_path);
tool_data.points = points; //tool_data.set_image(image, responses);
if tool_data.points.is_empty() {
tool_data.points.push(points);
}
} else { } else {
responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::DeselectAllLayers);
tool_data.path = Some(document.get_path_for_new_layer()); tool_data.path = Some(document.get_path_for_new_layer());
@@ -227,7 +254,7 @@ impl Fsm for BrushToolFsmState {
let pos = transform.inverse().transform_point2(input.mouse.position); let pos = transform.inverse().transform_point2(input.mouse.position);
tool_data.points.push(pos); tool_data.points.push(vec![pos]);
tool_data.diameter = tool_options.diameter; tool_data.diameter = tool_options.diameter;
tool_data.hardness = tool_options.hardness; tool_data.hardness = tool_options.hardness;
@@ -236,6 +263,7 @@ impl Fsm for BrushToolFsmState {
if new_layer { if new_layer {
add_brush_render(tool_data, global_tool_data, responses); add_brush_render(tool_data, global_tool_data, responses);
} else { } else {
//tool_data.update_image(node_graph, responses);
tool_data.update_points(responses); tool_data.update_points(responses);
} }
@@ -244,15 +272,21 @@ impl Fsm for BrushToolFsmState {
(Drawing, PointerMove) => { (Drawing, PointerMove) => {
let pos = transform.inverse().transform_point2(input.mouse.position); let pos = transform.inverse().transform_point2(input.mouse.position);
if tool_data.points.last() != Some(&pos) { if tool_data.points.last().and_then(|x| x.last()) != Some(&pos) {
// Linear interpolation for when the mouse has moved a lot between frames // Linear interpolation for when the mouse has moved a lot between frames
if let Some(&last_point) = tool_data.points.last() { if let Some(&last_point) = tool_data.points.last().and_then(|x| x.last()) {
let distance = (last_point - pos).length(); let distance = (last_point - pos).length();
let extra_points = (distance / (tool_data.diameter / 2.)).floor() as usize; let extra_points = (distance / (tool_data.diameter / 2.)).floor() as usize;
tool_data.points.extend((0..extra_points).map(|i| last_point.lerp(pos, (i as f64 + 1.) / (extra_points as f64 + 1.)))); tool_data
.points
.last_mut()
.unwrap()
.extend((0..extra_points).map(|i| last_point.lerp(pos, (i as f64 + 1.) / (extra_points as f64 + 1.))));
} }
tool_data.points.push(pos); if let Some(x) = tool_data.points.last_mut() {
x.push(pos)
}
} }
tool_data.update_points(responses); tool_data.update_points(responses);
@@ -266,8 +300,8 @@ impl Fsm for BrushToolFsmState {
responses.add(DocumentMessage::AbortTransaction); responses.add(DocumentMessage::AbortTransaction);
} }
tool_data.path = None;
tool_data.points.clear(); tool_data.points.clear();
tool_data.path = None;
Ready Ready
} }
@@ -294,11 +328,13 @@ impl Fsm for BrushToolFsmState {
fn add_brush_render(data: &BrushToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) { fn add_brush_render(data: &BrushToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
let layer_path = data.path.clone().unwrap(); let layer_path = data.path.clone().unwrap();
let brush_node = DocumentNode { let brush_node = DocumentNode {
name: "Brush".to_string(), name: "Brush".to_string(),
inputs: vec![ inputs: vec![
NodeInput::ShortCircut(concrete!(())), NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::VecDVec2(data.points.clone()), false), NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
NodeInput::value(TaggedValue::VecDVec2(data.points.last().cloned().unwrap_or_default()), false),
// Diameter // Diameter
NodeInput::value(TaggedValue::F64(data.diameter), false), NodeInput::value(TaggedValue::F64(data.diameter), false),
// Hardness // Hardness
@@ -312,12 +348,18 @@ fn add_brush_render(data: &BrushToolData, tool_data: &DocumentToolData, response
metadata: graph_craft::document::DocumentNodeMetadata { position: (8, 4).into() }, metadata: graph_craft::document::DocumentNodeMetadata { position: (8, 4).into() },
..Default::default() ..Default::default()
}; };
let monitor_node = DocumentNode {
name: "Monitor".to_string(),
implementation: DocumentNodeImplementation::Unresolved("graphene_std::memo::MonitorNode<_>".into()),
..Default::default()
};
let mut network = NodeNetwork::value_network(brush_node); let mut network = NodeNetwork::value_network(brush_node);
//network.push_node(monitor_node, true);
network.push_output_node(); network.push_output_node();
graph_modification_utils::new_custom_layer(network, layer_path, responses); graph_modification_utils::new_custom_layer(network, layer_path, responses);
} }
fn load_existing_points(document: &DocumentMessageHandler) -> Option<(Vec<LayerId>, Vec<DVec2>)> { fn load_existing_points(document: &DocumentMessageHandler) -> Option<(Vec<LayerId>, Vec<DVec2>, ImageFrame<Color>)> {
if document.selected_layers().count() != 1 { if document.selected_layers().count() != 1 {
return None; return None;
} }
@@ -327,11 +369,16 @@ fn load_existing_points(document: &DocumentMessageHandler) -> Option<(Vec<LayerI
if brush_node.implementation != DocumentNodeImplementation::Unresolved("graphene_std::brush::BrushNode".into()) { if brush_node.implementation != DocumentNodeImplementation::Unresolved("graphene_std::brush::BrushNode".into()) {
return None; return None;
} }
let points_input = brush_node.inputs.get(1)?; let image_input = brush_node.inputs.get(1)?;
let NodeInput::Value {
tagged_value: TaggedValue::ImageFrame(image_frame),
..
} = image_input else { return None };
let points_input = brush_node.inputs.get(2)?;
let NodeInput::Value { let NodeInput::Value {
tagged_value: TaggedValue::VecDVec2(points), tagged_value: TaggedValue::VecDVec2(points),
.. ..
} = points_input else { return None }; } = points_input else { return None };
Some((layer_path, points.clone())) Some((layer_path, points.clone(), image_frame.clone()))
} }
@@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widgets::button_widgets::IconButton;
use crate::messages::layout::utility_types::widgets::input_widgets::SwatchPairInput; use crate::messages::layout::utility_types::widgets::input_widgets::SwatchPairInput;
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType}; use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::node_graph_executor::NodeGraphExecutor;
use document_legacy::layers::style::RenderData; use document_legacy::layers::style::RenderData;
use graphene_core::raster::color::Color; use graphene_core::raster::color::Color;
@@ -27,6 +28,7 @@ pub struct ToolActionHandlerData<'a> {
pub render_data: &'a RenderData<'a>, pub render_data: &'a RenderData<'a>,
pub shape_overlay: &'a mut OverlayRenderer, pub shape_overlay: &'a mut OverlayRenderer,
pub shape_editor: &'a mut ShapeState, pub shape_editor: &'a mut ShapeState,
pub node_graph: &'a NodeGraphExecutor,
} }
impl<'a> ToolActionHandlerData<'a> { impl<'a> ToolActionHandlerData<'a> {
pub fn new( pub fn new(
@@ -37,6 +39,7 @@ impl<'a> ToolActionHandlerData<'a> {
render_data: &'a RenderData<'a>, render_data: &'a RenderData<'a>,
shape_overlay: &'a mut OverlayRenderer, shape_overlay: &'a mut OverlayRenderer,
shape_editor: &'a mut ShapeState, shape_editor: &'a mut ShapeState,
node_graph: &'a NodeGraphExecutor,
) -> Self { ) -> Self {
Self { Self {
document, document,
@@ -46,6 +49,7 @@ impl<'a> ToolActionHandlerData<'a> {
render_data, render_data,
shape_overlay, shape_overlay,
shape_editor, shape_editor,
node_graph,
} }
} }
} }
+3 -1
View File
@@ -17,6 +17,7 @@ use interpreted_executor::executor::DynamicExecutor;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use std::borrow::Cow; use std::borrow::Cow;
use std::sync::Arc;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct NodeGraphExecutor { pub struct NodeGraphExecutor {
@@ -37,6 +38,7 @@ impl NodeGraphExecutor {
assert_eq!(scoped_network.outputs.len(), 1, "Graph with multiple outputs not yet handled"); assert_eq!(scoped_network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
let c = Compiler {}; let c = Compiler {};
let proto_network = c.compile_single(scoped_network, true)?; let proto_network = c.compile_single(scoped_network, true)?;
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?"); assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
if let Err(e) = self.executor.update(proto_network) { if let Err(e) = self.executor.update(proto_network) {
error!("Failed to update executor:\n{}", e); error!("Failed to update executor:\n{}", e);
@@ -53,7 +55,7 @@ impl NodeGraphExecutor {
} }
} }
pub fn introspect_node(&self, path: &[NodeId]) -> Option<String> { pub fn introspect_node(&self, path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
self.executor.introspect(path).flatten() self.executor.introspect(path).flatten()
} }
+1 -1
View File
@@ -19,7 +19,7 @@
"build-wasm": "wasm-pack build ./wasm --dev --target=web", "build-wasm": "wasm-pack build ./wasm --dev --target=web",
"build-wasm-prod": "wasm-pack build ./wasm --release --target=web", "build-wasm-prod": "wasm-pack build ./wasm --release --target=web",
"tauri:build-wasm": "wasm-pack build ./wasm --release --target=web -- --features tauri", "tauri:build-wasm": "wasm-pack build ./wasm --release --target=web -- --features tauri",
"watch:wasm": "cargo watch --postpone --workdir wasm --shell \"wasm-pack build . --dev --target=web -- --color always\"", "watch:wasm": "cargo watch --postpone --watch-when-idle --workdir wasm --shell \"wasm-pack build . --dev --target=web -- --color always\"",
"--------------------": "", "--------------------": "",
"print-building-help": "echo 'Graphite project failed to build. Did you remember to `npm install` the dependencies in `/frontend`?'", "print-building-help": "echo 'Graphite project failed to build. Did you remember to `npm install` the dependencies in `/frontend`?'",
"print-linting-help": "echo 'Graphite project had lint errors, or may have otherwise failed. In the latter case, did you remember to `npm install` the dependencies in `/frontend`?'" "print-linting-help": "echo 'Graphite project had lint errors, or may have otherwise failed. In the latter case, did you remember to `npm install` the dependencies in `/frontend`?'"
+9 -4
View File
@@ -708,21 +708,26 @@ impl JsEditorHandle {
/// Returns the string representation of the nodes contents /// Returns the string representation of the nodes contents
#[wasm_bindgen(js_name = introspectNode)] #[wasm_bindgen(js_name = introspectNode)]
pub fn introspect_node(&self, node_path: Vec<NodeId>) -> Option<String> { pub fn introspect_node(&self, node_path: Vec<NodeId>) -> JsValue {
let frontend_messages = EDITOR_INSTANCES.with(|instances| { let frontend_messages = EDITOR_INSTANCES.with(|instances| {
// Mutably borrow the editors, and if successful, we can access them in the closure // Mutably borrow the editors, and if successful, we can access them in the closure
instances.try_borrow_mut().map(|mut editors| { instances.try_borrow_mut().map(|mut editors| {
// Get the editor instance for this editor ID, then dispatch the message to the backend, and return its response `FrontendMessage` queue // Get the editor instance for this editor ID, then dispatch the message to the backend, and return its response `FrontendMessage` queue
editors let image = editors
.get_mut(&self.editor_id) .get_mut(&self.editor_id)
.expect("EDITOR_INSTANCES does not contain the current editor_id") .expect("EDITOR_INSTANCES does not contain the current editor_id")
.dispatcher .dispatcher
.message_handlers .message_handlers
.portfolio_message_handler .portfolio_message_handler
.introspect_node(&node_path) .introspect_node(&node_path);
let image = image?;
let image = image.downcast_ref::<graphene_core::raster::ImageFrame<Color>>()?;
let serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);
let message_data = image.serialize(&serializer).expect("Failed to serialize FrontendMessage");
Some(message_data)
}) })
}); });
frontend_messages.unwrap() frontend_messages.unwrap().unwrap_or_default().into()
} }
} }
+3 -2
View File
@@ -39,8 +39,9 @@ pub trait Node<'i, Input: 'i>: 'i {
type Output: 'i; type Output: 'i;
fn eval(&'i self, input: Input) -> Self::Output; fn eval(&'i self, input: Input) -> Self::Output;
fn reset(self: Pin<&mut Self>) {} fn reset(self: Pin<&mut Self>) {}
#[cfg(feature = "alloc")] #[cfg(feature = "std")]
fn serialize(&self) -> Option<String> { fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
log::warn!("Node::serialize not implemented for {}", core::any::type_name::<Self>());
None None
} }
} }
+1
View File
@@ -139,6 +139,7 @@ pub trait UnassociatedAlpha: RGB + Alpha {
pub trait Alpha { pub trait Alpha {
type AlphaChannel: Channel; type AlphaChannel: Channel;
const TRANSPARENT: Self;
fn alpha(&self) -> Self::AlphaChannel; fn alpha(&self) -> Self::AlphaChannel;
fn a(&self) -> Self::AlphaChannel { fn a(&self) -> Self::AlphaChannel {
self.alpha() self.alpha()
+2
View File
@@ -96,6 +96,8 @@ impl Pixel for Color {
impl Alpha for Color { impl Alpha for Color {
type AlphaChannel = f32; type AlphaChannel = f32;
const TRANSPARENT: Self = Self::TRANSPARENT;
fn alpha(&self) -> f32 { fn alpha(&self) -> f32 {
self.alpha self.alpha
} }
+38 -10
View File
@@ -9,15 +9,45 @@ pub struct DynAnyNode<I, O, Node> {
_i: PhantomData<I>, _i: PhantomData<I>,
_o: PhantomData<O>, _o: PhantomData<O>,
} }
#[node_macro::node_fn(DynAnyNode<_I, _O>)]
fn any_node<_I: StaticType, _O: StaticType, N>(input: Any<'input>, node: &'any_input N) -> Any<'input> impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input, S0: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, S0>
where where
N: for<'any_input> Node<'any_input, _I, Output = _O>, N: for<'any_input> Node<'any_input, _I, Output = _O>,
S0: for<'any_input> Node<'any_input, (), Output = &'any_input N>,
{ {
let node_name = core::any::type_name::<N>(); type Output = Any<'input>;
let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyNode Input, {e} in:\n{node_name}")); #[inline]
Box::new(node.eval(*input)) fn eval(&'input self, input: Any<'input>) -> Self::Output {
let node = self.node.eval(());
{
let node_name = core::any::type_name::<N>();
let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyNode Input, {0} in:\n{1}", e, node_name));
Box::new(node.eval(*input))
}
}
fn reset(self: std::pin::Pin<&mut Self>) {
let wrapped_node = unsafe { self.map_unchecked_mut(|e| &mut e.node) };
Node::reset(wrapped_node);
}
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
self.node.eval(()).serialize()
}
} }
impl<'input, _I: StaticType, _O: StaticType, N, S0: 'input> DynAnyNode<_I, _O, S0>
where
S0: for<'any_input> Node<'any_input, (), Output = &'any_input N>,
{
pub const fn new(node: S0) -> Self {
Self {
node,
_i: core::marker::PhantomData,
_o: core::marker::PhantomData,
}
}
}
pub struct DynAnyRefNode<I, O, Node> { pub struct DynAnyRefNode<I, O, Node> {
node: Node, node: Node,
_i: PhantomData<(I, O)>, _i: PhantomData<(I, O)>,
@@ -28,11 +58,9 @@ where
{ {
type Output = Any<'input>; type Output = Any<'input>;
fn eval(&'input self, input: Any<'input>) -> Self::Output { fn eval(&'input self, input: Any<'input>) -> Self::Output {
{ let node_name = core::any::type_name::<N>();
let node_name = core::any::type_name::<N>(); let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyRefNode Input, {e} in:\n{node_name}"));
let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyRefNode Input, {e} in:\n{node_name}")); Box::new(self.node.eval(*input))
Box::new(self.node.eval(*input))
}
} }
fn reset(self: std::pin::Pin<&mut Self>) { fn reset(self: std::pin::Pin<&mut Self>) {
let wrapped_node = unsafe { self.map_unchecked_mut(|e| &mut e.node) }; let wrapped_node = unsafe { self.map_unchecked_mut(|e| &mut e.node) };
-34
View File
@@ -7,40 +7,6 @@ use graphene_core::vector::VectorData;
use graphene_core::Node; use graphene_core::Node;
use node_macro::node_fn; use node_macro::node_fn;
// Spacing is a consistent 0.2 apart, even when tiled across pixels (from 0.9 to the neighboring 0.1), to avoid bias
const MULTISAMPLE_GRID: [(f64, f64); 25] = [
// Row 1
(0.1, 0.1),
(0.1, 0.3),
(0.1, 0.5),
(0.1, 0.7),
(0.1, 0.9),
// Row 2
(0.3, 0.1),
(0.3, 0.3),
(0.3, 0.5),
(0.3, 0.7),
(0.3, 0.9),
// Row 3
(0.5, 0.1),
(0.5, 0.3),
(0.5, 0.5),
(0.5, 0.7),
(0.5, 0.9),
// Row 4
(0.7, 0.1),
(0.7, 0.3),
(0.7, 0.5),
(0.7, 0.7),
(0.7, 0.9),
// Row 5
(0.9, 0.1),
(0.9, 0.3),
(0.9, 0.5),
(0.9, 0.7),
(0.9, 0.9),
];
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ReduceNode<Initial, Lambda> { pub struct ReduceNode<Initial, Lambda> {
pub initial: Initial, pub initial: Initial,
+13 -19
View File
@@ -1,11 +1,12 @@
use graphene_core::Node; use graphene_core::Node;
#[cfg(feature = "serde")]
use serde::Serialize; use serde::Serialize;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::Mutex; use std::sync::{Arc, Mutex};
use xxhash_rust::xxh3::Xxh3; use xxhash_rust::xxh3::Xxh3;
/// Caches the output of a given Node and acts as a proxy /// Caches the output of a given Node and acts as a proxy
@@ -53,32 +54,25 @@ impl<T, CachedNode> CacheNode<T, CachedNode> {
/// Caches the output of the last graph evaluation for introspection /// Caches the output of the last graph evaluation for introspection
#[derive(Default)] #[derive(Default)]
pub struct MonitorNode<T, CachedNode> { pub struct MonitorNode<T> {
output: Mutex<Option<T>>, output: Mutex<Option<Arc<T>>>,
node: CachedNode,
} }
impl<'i, T: 'i + Serialize + Clone, I: 'i + Hash, CachedNode: 'i> Node<'i, I> for MonitorNode<T, CachedNode> impl<'i, T: 'static + Clone> Node<'i, T> for MonitorNode<T> {
where
CachedNode: for<'any_input> Node<'any_input, I, Output = T>,
{
type Output = T; type Output = T;
fn eval(&'i self, input: I) -> Self::Output { fn eval(&'i self, input: T) -> Self::Output {
let output = self.node.eval(input); *self.output.lock().unwrap() = Some(Arc::new(input.clone()));
*self.output.lock().unwrap() = Some(output.clone()); input
output
} }
fn serialize(&self) -> Option<String> { fn serialize(&self) -> Option<Arc<dyn core::any::Any>> {
let output = self.output.lock().unwrap(); let output = self.output.lock().unwrap();
(*output).as_ref().and_then(|output| serde_json::to_string(output).ok()) (*output).as_ref().and_then(|output| Some(output.clone() as Arc<dyn core::any::Any>))
} }
} }
impl<T, CachedNode> std::marker::Unpin for MonitorNode<T, CachedNode> {} impl<T> MonitorNode<T> {
pub const fn new() -> MonitorNode<T> {
impl<T, CachedNode> MonitorNode<T, CachedNode> { MonitorNode { output: Mutex::new(None) }
pub const fn new(node: CachedNode) -> MonitorNode<T, CachedNode> {
MonitorNode { output: Mutex::new(None), node }
} }
} }
+85 -59
View File
@@ -1,9 +1,10 @@
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene_core::raster::{Alpha, Channel, Image, ImageFrame, Luminance, Pixel, RasterMut, Sample}; use graphene_core::raster::{Alpha, BlendMode, BlendNode, Channel, Image, ImageFrame, Luminance, Pixel, RasterMut, Sample};
use graphene_core::transform::Transform; use graphene_core::transform::Transform;
use graphene_core::Node; use graphene_core::value::CopiedNode;
use graphene_core::{Color, Node};
use std::fmt::Debug; use std::fmt::Debug;
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -48,51 +49,6 @@ fn buffer_node<R: std::io::Read>(reader: R) -> Result<Vec<u8>, Error> {
Ok(std::io::Read::bytes(reader).collect::<Result<Vec<_>, _>>()?) Ok(std::io::Read::bytes(reader).collect::<Result<Vec<_>, _>>()?)
} }
/*
pub fn file_node<'i, 's: 'i, P: AsRef<Path> + 'i>() -> impl Node<'i, 's, P, Output = Result<Vec<u8>, Error>> {
let fs = ValueNode(StdFs).then(CloneNode::new());
let file = FileNode::new(fs);
file.then(FlatMapResultNode::new(ValueNode::new(BufferNode)))
}
pub fn image_node<'i, 's: 'i, P: AsRef<Path> + 'i>() -> impl Node<'i, 's, P, Output = Result<Image, Error>> {
let file = file_node();
let image_loader = FnNode::new(|data: Vec<u8>| image::load_from_memory(&data).map_err(Error::Image).map(|image| image.into_rgba32f()));
let image = file.then(FlatMapResultNode::new(ValueNode::new(image_loader)));
let convert_image = FnNode::new(|image: image::ImageBuffer<_, _>| {
let data = image
.enumerate_pixels()
.map(|(_, _, pixel): (_, _, &image::Rgba<f32>)| {
let c = pixel.channels();
Color::from_rgbaf32(c[0], c[1], c[2], c[3]).unwrap()
})
.collect();
Image {
width: image.width(),
height: image.height(),
data,
}
});
image.then(MapResultNode::new(convert_image))
}
pub fn export_image_node<'i, 's: 'i>() -> impl Node<'i, 's, (Image, &'i str), Output = Result<(), Error>> {
FnNode::new(|input: (Image, &str)| {
let (image, path) = input;
let mut new_image = image::ImageBuffer::new(image.width, image.height);
for ((x, y, pixel), color) in new_image.enumerate_pixels_mut().zip(image.data.iter()) {
let color: Color = *color;
assert!(x < image.width);
assert!(y < image.height);
*pixel = image::Rgba(color.to_rgba8())
}
new_image.save(path).map_err(Error::Image)
})
}
*/
pub struct DownresNode<P> { pub struct DownresNode<P> {
_p: PhantomData<P>, _p: PhantomData<P>,
} }
@@ -168,6 +124,17 @@ impl AxisAlignedBbox {
end: DVec2::new(self.end.x.max(other.end.x), self.end.y.max(other.end.y)), end: DVec2::new(self.end.x.max(other.end.x), self.end.y.max(other.end.y)),
} }
} }
pub fn union_non_empty(&self, other: &AxisAlignedBbox) -> Option<AxisAlignedBbox> {
match (self.size() == DVec2::ZERO, other.size() == DVec2::ZERO) {
(true, true) => None,
(true, _) => Some(other.clone()),
(_, true) => Some(self.clone()),
_ => Some(AxisAlignedBbox {
start: DVec2::new(self.start.x.min(other.start.x), self.start.y.min(other.start.y)),
end: DVec2::new(self.end.x.max(other.end.x), self.end.y.max(other.end.y)),
}),
}
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -266,7 +233,7 @@ pub struct BlendImageTupleNode<P, Fg, MapFn> {
} }
#[node_macro::node_fn(BlendImageTupleNode<_P, _Fg>)] #[node_macro::node_fn(BlendImageTupleNode<_P, _Fg>)]
fn blend_image_tuple<_P: Pixel + Debug, MapFn, _Fg: Sample<Pixel = _P> + Transform>(images: (ImageFrame<_P>, _Fg), map_fn: &'any_input MapFn) -> ImageFrame<_P> fn blend_image_tuple<_P: Alpha + Pixel + Debug, MapFn, _Fg: Sample<Pixel = _P> + Transform>(images: (ImageFrame<_P>, _Fg), map_fn: &'any_input MapFn) -> ImageFrame<_P>
where where
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input + Clone, MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input + Clone,
{ {
@@ -282,25 +249,67 @@ pub struct BlendImageNode<P, Background, MapFn> {
_p: PhantomData<P>, _p: PhantomData<P>,
} }
// TODO: Implement proper blending
#[node_macro::node_fn(BlendImageNode<_P>)] #[node_macro::node_fn(BlendImageNode<_P>)]
fn blend_image_node<_P: Clone, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: RasterMut<Pixel = _P> + Transform>( fn blend_image_node<_P: Alpha + Pixel + Debug, MapFn, Forground: Sample<Pixel = _P> + Transform>(foreground: Forground, background: ImageFrame<_P>, map_fn: &'any_input MapFn) -> ImageFrame<_P>
foreground: Frame,
background: Background,
map_fn: &'any_input MapFn,
) -> Background
where where
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input, MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input,
{ {
blend_image(foreground, background, map_fn) blend_new_image(foreground, background, map_fn)
} }
fn blend_image<_P: Clone, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: RasterMut<Pixel = _P> + Transform>(foreground: Frame, mut background: Background, map_fn: &MapFn) -> Background #[derive(Debug, Clone, Copy)]
pub struct BlendReverseImageNode<P, Background, MapFn> {
background: Background,
map_fn: MapFn,
_p: PhantomData<P>,
}
#[node_macro::node_fn(BlendReverseImageNode<_P>)]
fn blend_image_node<_P: Alpha + Pixel + Debug, MapFn, Background: Transform + Sample<Pixel = _P>>(foreground: ImageFrame<_P>, background: Background, map_fn: &'any_input MapFn) -> ImageFrame<_P>
where
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input,
{
blend_new_image(background, foreground, map_fn)
}
fn blend_new_image<_P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform>(foreground: Frame, background: ImageFrame<_P>, map_fn: &MapFn) -> ImageFrame<_P>
where
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P>,
{
let foreground_aabb = compute_transformed_bounding_box(foreground.transform()).axis_aligned_bbox();
let background_aabb = compute_transformed_bounding_box(background.transform()).axis_aligned_bbox();
let Some(aabb) = foreground_aabb.union_non_empty(&background_aabb) else {return ImageFrame::empty()};
if background_aabb.contains(foreground_aabb.start) && background_aabb.contains(foreground_aabb.end) {
return blend_image(foreground, background, map_fn);
}
// Clamp the foreground image to the background image
let start = aabb.start.as_uvec2();
let end = aabb.end.as_uvec2();
let new_background = Image::new(end.x - start.x, end.y - start.y, _P::TRANSPARENT);
let size = DVec2::new(new_background.width as f64, new_background.height as f64);
let transfrom = DAffine2::from_scale_angle_translation(size, 0., start.as_dvec2());
let mut new_background = ImageFrame {
image: new_background,
transform: transfrom,
};
new_background = blend_image(background, new_background, map_fn);
blend_image(foreground, new_background, map_fn)
}
fn blend_image<_P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: RasterMut<Pixel = _P> + Transform + Sample<Pixel = _P>>(
foreground: Frame,
mut background: Background,
map_fn: &MapFn,
) -> Background
where where
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P>, MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P>,
{ {
let background_size = DVec2::new(background.width() as f64, background.height() as f64); let background_size = DVec2::new(background.width() as f64, background.height() as f64);
// Transforms a point from the background image to the forground image // Transforms a point from the background image to the forground image
let bg_to_fg = background.transform() * DAffine2::from_scale(1. / background_size); let bg_to_fg = background.transform() * DAffine2::from_scale(1. / background_size);
@@ -319,7 +328,7 @@ where
if let Some(src_pixel) = foreground.sample(fg_point, area) { if let Some(src_pixel) = foreground.sample(fg_point, area) {
if let Some(dst_pixel) = background.get_pixel_mut(x, y) { if let Some(dst_pixel) = background.get_pixel_mut(x, y) {
*dst_pixel = map_fn.eval((src_pixel, dst_pixel.clone())); *dst_pixel = map_fn.eval((src_pixel, *dst_pixel));
} }
} }
} }
@@ -328,6 +337,23 @@ where
background background
} }
#[derive(Debug, Clone, Copy)]
pub struct ExtendImageNode<Background> {
background: Background,
}
#[node_macro::node_fn(ExtendImageNode)]
fn extend_image_node(foreground: ImageFrame<Color>, background: ImageFrame<Color>) -> ImageFrame<Color> {
let foreground_aabb = compute_transformed_bounding_box(foreground.transform()).axis_aligned_bbox();
let background_aabb = compute_transformed_bounding_box(background.transform()).axis_aligned_bbox();
if foreground_aabb.contains(background_aabb.start) && foreground_aabb.contains(background_aabb.end) {
return foreground;
}
blend_image(foreground, background, &BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.)))
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct MergeBoundingBoxNode<Data> { pub struct MergeBoundingBoxNode<Data> {
_data: PhantomData<Data>, _data: PhantomData<Data>,
@@ -340,7 +366,7 @@ fn merge_bounding_box_node<_Data: Transform>(input: (Option<AxisAlignedBbox>, _D
let snd_aabb = compute_transformed_bounding_box(data.transform()).axis_aligned_bbox(); let snd_aabb = compute_transformed_bounding_box(data.transform()).axis_aligned_bbox();
if let Some(fst_aabb) = initial_aabb { if let Some(fst_aabb) = initial_aabb {
Some(fst_aabb.union(&snd_aabb)) fst_aabb.union_non_empty(&snd_aabb)
} else { } else {
Some(snd_aabb) Some(snd_aabb)
} }
@@ -17,6 +17,8 @@ pub struct DynamicExecutor {
output: NodeId, output: NodeId,
tree: BorrowTree, tree: BorrowTree,
typing_context: TypingContext, typing_context: TypingContext,
// This allows us to keep the nodes around for one more frame which is used for introspection
orphaned_nodes: Vec<NodeId>,
} }
impl Default for DynamicExecutor { impl Default for DynamicExecutor {
@@ -25,6 +27,7 @@ impl Default for DynamicExecutor {
output: Default::default(), output: Default::default(),
tree: Default::default(), tree: Default::default(),
typing_context: TypingContext::new(&node_registry::NODE_REGISTRY), typing_context: TypingContext::new(&node_registry::NODE_REGISTRY),
orphaned_nodes: Vec::new(),
} }
} }
} }
@@ -36,21 +39,27 @@ impl DynamicExecutor {
let output = proto_network.output; let output = proto_network.output;
let tree = BorrowTree::new(proto_network, &typing_context)?; let tree = BorrowTree::new(proto_network, &typing_context)?;
Ok(Self { tree, output, typing_context }) Ok(Self {
tree,
output,
typing_context,
orphaned_nodes: Vec::new(),
})
} }
pub fn update(&mut self, proto_network: ProtoNetwork) -> Result<(), String> { pub fn update(&mut self, proto_network: ProtoNetwork) -> Result<(), String> {
self.output = proto_network.output; self.output = proto_network.output;
self.typing_context.update(&proto_network)?; self.typing_context.update(&proto_network)?;
trace!("setting output to {}", self.output); trace!("setting output to {}", self.output);
let orphans = self.tree.update(proto_network, &self.typing_context)?; let mut orphans = self.tree.update(proto_network, &self.typing_context)?;
core::mem::swap(&mut self.orphaned_nodes, &mut orphans);
for node_id in orphans { for node_id in orphans {
self.tree.free_node(node_id) self.tree.free_node(node_id)
} }
Ok(()) Ok(())
} }
pub fn introspect(&self, node_path: &[NodeId]) -> Option<Option<String>> { pub fn introspect(&self, node_path: &[NodeId]) -> Option<Option<Arc<dyn std::any::Any>>> {
self.tree.introspect(node_path) self.tree.introspect(node_path)
} }
@@ -128,7 +137,7 @@ impl BorrowTree {
node.reset(); node.reset();
} }
old_nodes.remove(&id); old_nodes.remove(&id);
self.source_map.retain(|_, nid| *nid != id); self.source_map.retain(|_, nid| !old_nodes.contains(nid));
} }
Ok(old_nodes.into_iter().collect()) Ok(old_nodes.into_iter().collect())
} }
@@ -145,7 +154,7 @@ impl BorrowTree {
node node
} }
pub fn introspect(&self, node_path: &[NodeId]) -> Option<Option<String>> { pub fn introspect(&self, node_path: &[NodeId]) -> Option<Option<Arc<dyn std::any::Any>>> {
let id = self.source_map.get(node_path)?; let id = self.source_map.get(node_path)?;
let node = self.nodes.get(id)?; let node = self.nodes.get(id)?;
let reader = node.read().unwrap(); let reader = node.read().unwrap();
@@ -154,7 +154,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]), register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]), register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]), register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
register_node!(graphene_std::memo::MonitorNode<_, _>, input: (), params: [ImageFrame<Color>]), register_node!(graphene_std::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image<Color>, params: [String]), register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image<Color>, params: [String]),
vec![( vec![(
@@ -175,14 +175,16 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
vec![( vec![(
NodeIdentifier::new("graphene_std::brush::BrushNode"), NodeIdentifier::new("graphene_std::brush::BrushNode"),
|args| { |args| {
use graphene_core::structural::*;
use graphene_core::value::*; use graphene_core::value::*;
use graphene_std::brush::*; use graphene_std::brush::*;
let trace: DowncastBothNode<(), Vec<DVec2>> = DowncastBothNode::new(args[0]); let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
let diameter: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1]); let trace: DowncastBothNode<(), Vec<DVec2>> = DowncastBothNode::new(args[1]);
let hardness: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]); let diameter: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]);
let flow: DowncastBothNode<(), f64> = DowncastBothNode::new(args[3]); let hardness: DowncastBothNode<(), f64> = DowncastBothNode::new(args[3]);
let color: DowncastBothNode<(), Color> = DowncastBothNode::new(args[4]); let flow: DowncastBothNode<(), f64> = DowncastBothNode::new(args[4]);
let color: DowncastBothNode<(), Color> = DowncastBothNode::new(args[5]);
let stamp = BrushStampGeneratorNode::new(color, CopiedNode::new(hardness.eval(())), CopiedNode::new(flow.eval(()))); let stamp = BrushStampGeneratorNode::new(color, CopiedNode::new(hardness.eval(())), CopiedNode::new(flow.eval(())));
let stamp = stamp.eval(diameter.eval(())); let stamp = stamp.eval(diameter.eval(()));
@@ -191,16 +193,19 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
let frames = MapNode::new(ValueNode::new(frames)); let frames = MapNode::new(ValueNode::new(frames));
let frames = frames.eval(trace.eval(()).into_iter()).collect::<Vec<_>>(); let frames = frames.eval(trace.eval(()).into_iter()).collect::<Vec<_>>();
let background_bounds = ReduceNode::new(DebugClonedNode::new(None), ValueNode::new(MergeBoundingBoxNode::new())); let background_bounds = ReduceNode::new(ClonedNode::new(None), ValueNode::new(MergeBoundingBoxNode::new()));
let background_bounds = background_bounds.eval(frames.clone().into_iter()); let background_bounds = background_bounds.eval(frames.clone().into_iter());
let background_bounds = DebugClonedNode::new(background_bounds.unwrap().to_transform()); let background_bounds = MergeBoundingBoxNode::new().eval((background_bounds, image.eval(())));
let background_bounds = ClonedNode::new(background_bounds.unwrap().to_transform());
let background_image = background_bounds.then(EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT))); let background_image = background_bounds.then(EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)));
let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.)); let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
let background = ExtendImageNode::new(background_image);
let background_image = image.then(background);
let final_image = ReduceNode::new(background_image, ValueNode::new(BlendImageTupleNode::new(ValueNode::new(blend_node)))); let final_image = ReduceNode::new(background_image, ValueNode::new(BlendImageTupleNode::new(ValueNode::new(blend_node))));
let final_image = DebugClonedNode::new(frames.into_iter()).then(final_image); let final_image = ClonedNode::new(frames.into_iter()).then(final_image);
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(final_image)); let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(final_image));
Box::pin(any) Box::pin(any)
@@ -208,7 +213,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIOTypes::new( NodeIOTypes::new(
concrete!(()), concrete!(()),
concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>),
vec![value_fn!(Vec<DVec2>), value_fn!(f64), value_fn!(f64), value_fn!(f64), value_fn!(Color)], vec![value_fn!(ImageFrame<Color>), value_fn!(Vec<DVec2>), value_fn!(f64), value_fn!(f64), value_fn!(f64), value_fn!(Color)],
), ),
)], )],
vec![( vec![(