mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 16:08:13 +08:00
Implement the Brush tool (#1099)
* Implement Brush Node * Add color Input * Add VectorPointsNode * Add Erase Node * Adapt compilation infrastructure to allow non Image Frame inputs * Remove debug output from TransformNode * Fix transform calculation * Fix Blending by making the brush texture use associated alpha * Code improvements and UX polish * Rename Opacity to Flow * Add erase option to brush node + fix freehand tool * Fix crash * Revert erase implementation * Fix flattening id calculation * Fix some transformation issues * Fix changing the pivot location * Fix vector data modify bounds * Minor fn name cleanup * Fix some tests * Fix tests --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
hypercube
parent
758f757775
commit
589ff9a2d3
@@ -205,6 +205,11 @@ pub fn default_mapping() -> Mapping {
|
|||||||
entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::LeftPointerDown),
|
entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::LeftPointerDown),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::RightPointerDown),
|
entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::RightPointerDown),
|
||||||
//
|
//
|
||||||
|
// BrushToolMessage
|
||||||
|
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
|
||||||
|
entry!(KeyDown(Lmb); action_dispatch=BrushToolMessage::DragStart),
|
||||||
|
entry!(KeyUp(Lmb); action_dispatch=BrushToolMessage::DragStop),
|
||||||
|
//
|
||||||
// ToolMessage
|
// ToolMessage
|
||||||
entry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect),
|
entry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect),
|
||||||
entry!(KeyDown(KeyZ); action_dispatch=ToolMessage::ActivateToolNavigate),
|
entry!(KeyDown(KeyZ); action_dispatch=ToolMessage::ActivateToolNavigate),
|
||||||
@@ -219,6 +224,7 @@ pub fn default_mapping() -> Mapping {
|
|||||||
entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolRectangle),
|
entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolRectangle),
|
||||||
entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolEllipse),
|
entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolEllipse),
|
||||||
entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape),
|
entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape),
|
||||||
|
entry!(KeyDown(KeyB); action_dispatch=ToolMessage::ActivateToolBrush),
|
||||||
entry!(KeyDown(KeyX); modifiers=[Shift, Accel], action_dispatch=ToolMessage::ResetColors),
|
entry!(KeyDown(KeyX); modifiers=[Shift, Accel], action_dispatch=ToolMessage::ResetColors),
|
||||||
entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors),
|
entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors),
|
||||||
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomPrimaryColor),
|
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomPrimaryColor),
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ use document_legacy::layers::style::{Fill, RenderData, ViewMode};
|
|||||||
use document_legacy::layers::text_layer::Font;
|
use document_legacy::layers::text_layer::Font;
|
||||||
use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
|
use graph_craft::{concrete, Type, TypeDescriptor};
|
||||||
use graphene_core::raster::{Color, ImageFrame};
|
use graphene_core::raster::{Color, ImageFrame};
|
||||||
|
use graphene_core::Cow;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -1050,39 +1052,39 @@ impl DocumentMessageHandler {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Skip processing under node graph frame input if not connected
|
// Find the primary input type of the node graph
|
||||||
if !node_network.connected_to_output(node_network.inputs[0], false) {
|
let primary_input_type = node_network.input_types().next().clone();
|
||||||
return Some(
|
let response = match primary_input_type {
|
||||||
PortfolioMessage::ProcessNodeGraphFrame {
|
// Only calclate the frame if the primary input is an image
|
||||||
|
Some(ty) if ty == concrete!(ImageFrame) => {
|
||||||
|
// Calculate the size of the region to be exported
|
||||||
|
let old_transforms = self.remove_document_transform();
|
||||||
|
let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap();
|
||||||
|
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
||||||
|
|
||||||
|
let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
|
||||||
|
self.restore_document_transform(old_transforms);
|
||||||
|
|
||||||
|
FrontendMessage::TriggerNodeGraphFrameGenerate {
|
||||||
document_id,
|
document_id,
|
||||||
layer_path,
|
layer_path,
|
||||||
image_data: Default::default(),
|
svg,
|
||||||
size: (0, 0),
|
size,
|
||||||
imaginate_node,
|
imaginate_node,
|
||||||
}
|
}
|
||||||
.into(),
|
.into()
|
||||||
);
|
}
|
||||||
}
|
// Skip processing under node graph frame input if not connected
|
||||||
|
_ => PortfolioMessage::ProcessNodeGraphFrame {
|
||||||
// Calculate the size of the region to be exported
|
|
||||||
|
|
||||||
let old_transforms = self.remove_document_transform();
|
|
||||||
let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap();
|
|
||||||
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
|
||||||
|
|
||||||
let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
|
|
||||||
self.restore_document_transform(old_transforms);
|
|
||||||
|
|
||||||
Some(
|
|
||||||
FrontendMessage::TriggerNodeGraphFrameGenerate {
|
|
||||||
document_id,
|
document_id,
|
||||||
layer_path,
|
layer_path,
|
||||||
svg,
|
image_data: Default::default(),
|
||||||
size,
|
size: (0, 0),
|
||||||
imaginate_node,
|
imaginate_node,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
)
|
};
|
||||||
|
Some(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the artwork and artboard pan/tilt/zoom to render it without the user's viewport navigation, and save it to be restored at the end
|
/// Remove the artwork and artboard pan/tilt/zoom to render it without the user's viewport navigation, and save it to be restored at the end
|
||||||
|
|||||||
+9
-6
@@ -119,7 +119,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, skip_rerender: bool) {
|
fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds, skip_rerender: bool) {
|
||||||
self.modify_inputs("Transform", skip_rerender, |inputs| {
|
self.modify_inputs("Transform", skip_rerender, |inputs| {
|
||||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||||
let to = match transform_in {
|
let to = match transform_in {
|
||||||
@@ -127,7 +127,8 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
TransformIn::Scope { scope } => scope * parent_transform,
|
TransformIn::Scope { scope } => scope * parent_transform,
|
||||||
TransformIn::Viewport => parent_transform,
|
TransformIn::Viewport => parent_transform,
|
||||||
};
|
};
|
||||||
let transform = to.inverse() * transform * to * layer_transform;
|
let pivot = DAffine2::from_translation(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs)));
|
||||||
|
let transform = pivot.inverse() * to.inverse() * transform * to * pivot * layer_transform;
|
||||||
transform_utils::update_transform(inputs, transform);
|
transform_utils::update_transform(inputs, transform);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -140,7 +141,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
TransformIn::Viewport => parent_transform,
|
TransformIn::Viewport => parent_transform,
|
||||||
};
|
};
|
||||||
let pivot = DAffine2::from_translation(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs)));
|
let pivot = DAffine2::from_translation(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs)));
|
||||||
let transform = to.inverse() * transform * pivot;
|
let transform = pivot.inverse() * to.inverse() * transform * pivot;
|
||||||
transform_utils::update_transform(inputs, transform);
|
transform_utils::update_transform(inputs, transform);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -150,7 +151,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||||
let old_pivot_transform = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalized_pivot(inputs)));
|
let old_pivot_transform = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalized_pivot(inputs)));
|
||||||
let new_pivot_transform = DAffine2::from_translation(bounds.local_pivot(new_pivot));
|
let new_pivot_transform = DAffine2::from_translation(bounds.local_pivot(new_pivot));
|
||||||
let transform = layer_transform * old_pivot_transform.inverse() * new_pivot_transform;
|
let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform;
|
||||||
transform_utils::update_transform(inputs, transform);
|
transform_utils::update_transform(inputs, transform);
|
||||||
inputs[5] = NodeInput::value(TaggedValue::DVec2(new_pivot), false);
|
inputs[5] = NodeInput::value(TaggedValue::DVec2(new_pivot), false);
|
||||||
});
|
});
|
||||||
@@ -184,6 +185,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
|
|
||||||
[new_bounds_min, new_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
[new_bounds_min, new_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.modify_inputs("Transform", false, |inputs| {
|
self.modify_inputs("Transform", false, |inputs| {
|
||||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||||
let normalized_pivot = transform_utils::get_current_normalized_pivot(inputs);
|
let normalized_pivot = transform_utils::get_current_normalized_pivot(inputs);
|
||||||
@@ -193,7 +195,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
let new_pivot_transform = DAffine2::from_translation(new_layerspace_pivot);
|
let new_pivot_transform = DAffine2::from_translation(new_layerspace_pivot);
|
||||||
let old_pivot_transform = DAffine2::from_translation(old_layerspace_pivot);
|
let old_pivot_transform = DAffine2::from_translation(old_layerspace_pivot);
|
||||||
|
|
||||||
let transform = layer_transform * old_pivot_transform.inverse() * new_pivot_transform;
|
let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform;
|
||||||
transform_utils::update_transform(inputs, transform);
|
transform_utils::update_transform(inputs, transform);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -225,8 +227,9 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
|||||||
skip_rerender,
|
skip_rerender,
|
||||||
} => {
|
} => {
|
||||||
let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
|
let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
|
||||||
|
let bounds = LayerBounds::new(document, &layer);
|
||||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||||
modify_inputs.transform_change(transform, transform_in, parent_transform, skip_rerender);
|
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender);
|
||||||
}
|
}
|
||||||
|
|
||||||
let transform = transform.to_cols_array();
|
let transform = transform.to_cols_array();
|
||||||
|
|||||||
+56
-9
@@ -8,6 +8,7 @@ use graph_craft::document::*;
|
|||||||
use graph_craft::imaginate_input::ImaginateSamplingMethod;
|
use graph_craft::imaginate_input::ImaginateSamplingMethod;
|
||||||
use graph_craft::NodeIdentifier;
|
use graph_craft::NodeIdentifier;
|
||||||
use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation};
|
use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation};
|
||||||
|
use graphene_core::vector::VectorData;
|
||||||
use graphene_core::*;
|
use graphene_core::*;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
@@ -448,6 +449,35 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||||||
}],
|
}],
|
||||||
properties: node_properties::blur_image_properties,
|
properties: node_properties::blur_image_properties,
|
||||||
},
|
},
|
||||||
|
DocumentNodeType {
|
||||||
|
name: "Brush",
|
||||||
|
category: "Brush",
|
||||||
|
identifier: NodeImplementation::proto("graphene_std::brush::BrushNode"),
|
||||||
|
inputs: vec![
|
||||||
|
DocumentInputType::value("None", TaggedValue::None, false),
|
||||||
|
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("Hardness", TaggedValue::F64(50.), false),
|
||||||
|
DocumentInputType::value("Flow", TaggedValue::F64(100.), false),
|
||||||
|
DocumentInputType::value("Color", TaggedValue::Color(Color::BLACK), false),
|
||||||
|
],
|
||||||
|
outputs: vec![DocumentOutputType {
|
||||||
|
name: "Image",
|
||||||
|
data_type: FrontendGraphDataType::Raster,
|
||||||
|
}],
|
||||||
|
properties: node_properties::brush_node_properties,
|
||||||
|
},
|
||||||
|
DocumentNodeType {
|
||||||
|
name: "Extract Vector Points",
|
||||||
|
category: "Brush",
|
||||||
|
identifier: NodeImplementation::proto("graphene_std::brush::VectorPointsNode"),
|
||||||
|
inputs: vec![DocumentInputType::value("VectorData", TaggedValue::VectorData(VectorData::empty()), true)],
|
||||||
|
outputs: vec![DocumentOutputType {
|
||||||
|
name: "Vector Points",
|
||||||
|
data_type: FrontendGraphDataType::General,
|
||||||
|
}],
|
||||||
|
properties: node_properties::no_properties,
|
||||||
|
},
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Cache",
|
name: "Cache",
|
||||||
category: "Structural",
|
category: "Structural",
|
||||||
@@ -490,6 +520,14 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"),
|
properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"),
|
||||||
},
|
},
|
||||||
|
DocumentNodeType {
|
||||||
|
name: "Ref",
|
||||||
|
category: "Structural",
|
||||||
|
identifier: NodeImplementation::proto("graphene_std::memo::CacheNode"),
|
||||||
|
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
|
||||||
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
|
properties: node_properties::no_properties,
|
||||||
|
},
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "GpuImage",
|
name: "GpuImage",
|
||||||
@@ -841,8 +879,18 @@ impl DocumentNodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
|
pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
|
||||||
|
// if the network has no inputs, it doesn't need to be wrapped in a scope
|
||||||
|
if network.inputs.is_empty() {
|
||||||
|
return network;
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(network.inputs.len(), 1, "Networks wrapped in scope must have exactly one input");
|
assert_eq!(network.inputs.len(), 1, "Networks wrapped in scope must have exactly one input");
|
||||||
let input_type = network.nodes[&network.inputs[0]].inputs.iter().find(|&i| matches!(i, NodeInput::Network(_))).unwrap().clone();
|
let input = network.nodes[&network.inputs[0]].inputs.iter().find(|&i| matches!(i, NodeInput::Network(_))).cloned();
|
||||||
|
|
||||||
|
// if the network has no network inputs, it doesn't need to be wrapped in a scope either
|
||||||
|
let Some(input_type) = input else {
|
||||||
|
return network;
|
||||||
|
};
|
||||||
|
|
||||||
let inner_network = DocumentNode {
|
let inner_network = DocumentNode {
|
||||||
name: "Scope".to_string(),
|
name: "Scope".to_string(),
|
||||||
@@ -850,6 +898,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
|
|||||||
inputs: vec![NodeInput::node(0, 1)],
|
inputs: vec![NodeInput::node(0, 1)],
|
||||||
metadata: DocumentNodeMetadata::default(),
|
metadata: DocumentNodeMetadata::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// wrap the inner network in a scope
|
// wrap the inner network in a scope
|
||||||
let nodes = vec![
|
let nodes = vec![
|
||||||
resolve_document_node_type("Begin Scope")
|
resolve_document_node_type("Begin Scope")
|
||||||
@@ -890,7 +939,6 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_vector_network(subpaths: Vec<bezier_rs::Subpath<uuid::ManipulatorGroupId>>) -> NodeNetwork {
|
pub fn new_vector_network(subpaths: Vec<bezier_rs::Subpath<uuid::ManipulatorGroupId>>) -> NodeNetwork {
|
||||||
let input = resolve_document_node_type("Input Frame").expect("Input Frame node does not exist");
|
|
||||||
let path_generator = resolve_document_node_type("Path Generator").expect("Path Generator node does not exist");
|
let path_generator = resolve_document_node_type("Path Generator").expect("Path Generator node does not exist");
|
||||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist");
|
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist");
|
||||||
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist");
|
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist");
|
||||||
@@ -905,15 +953,14 @@ pub fn new_vector_network(subpaths: Vec<bezier_rs::Subpath<uuid::ManipulatorGrou
|
|||||||
};
|
};
|
||||||
|
|
||||||
NodeNetwork {
|
NodeNetwork {
|
||||||
inputs: vec![0],
|
inputs: vec![],
|
||||||
outputs: vec![NodeOutput::new(5, 0)],
|
outputs: vec![NodeOutput::new(4, 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
input.to_document_node_default_inputs([], next_pos()),
|
|
||||||
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], next_pos()),
|
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], next_pos()),
|
||||||
transform.to_document_node_default_inputs([Some(NodeInput::node(1, 0))], next_pos()),
|
transform.to_document_node_default_inputs([Some(NodeInput::node(0, 0))], next_pos()),
|
||||||
fill.to_document_node_default_inputs([Some(NodeInput::node(2, 0))], next_pos()),
|
fill.to_document_node_default_inputs([Some(NodeInput::node(1, 0))], next_pos()),
|
||||||
stroke.to_document_node_default_inputs([Some(NodeInput::node(3, 0))], next_pos()),
|
stroke.to_document_node_default_inputs([Some(NodeInput::node(2, 0))], next_pos()),
|
||||||
output.to_document_node_default_inputs([Some(NodeInput::node(4, 0))], next_pos()),
|
output.to_document_node_default_inputs([Some(NodeInput::node(3, 0))], next_pos()),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
|||||||
+10
@@ -515,6 +515,16 @@ pub fn blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _con
|
|||||||
vec![LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: sigma }]
|
vec![LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: sigma }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 size = number_widget(document_node, node_id, 2, "Diameter", NumberInput::default().min(0.).max(100.).unit(" px"), true);
|
||||||
|
let hardness = number_widget(document_node, node_id, 3, "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);
|
||||||
|
|
||||||
|
vec![color, LayoutGroup::Row { widgets: size }, LayoutGroup::Row { widgets: hardness }, LayoutGroup::Row { widgets: flow }]
|
||||||
|
}
|
||||||
|
|
||||||
pub fn adjust_threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn adjust_threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
let thereshold_min = number_widget(document_node, node_id, 1, "Min Luminance", NumberInput::default().min(0.).max(100.).unit("%"), true);
|
let thereshold_min = number_widget(document_node, node_id, 1, "Min Luminance", NumberInput::default().min(0.).max(100.).unit("%"), true);
|
||||||
let thereshold_max = number_widget(document_node, node_id, 2, "Max Luminance", NumberInput::default().min(0.).max(100.).unit("%"), true);
|
let thereshold_max = number_widget(document_node, node_id, 2, "Max Luminance", NumberInput::default().min(0.).max(100.).unit("%"), true);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ pub use crate::messages::workspace::{WorkspaceMessage, WorkspaceMessageDiscrimin
|
|||||||
pub use crate::messages::broadcast::broadcast_event::{BroadcastEvent, BroadcastEventDiscriminant};
|
pub use crate::messages::broadcast::broadcast_event::{BroadcastEvent, BroadcastEventDiscriminant};
|
||||||
pub use crate::messages::message::{Message, MessageDiscriminant};
|
pub use crate::messages::message::{Message, MessageDiscriminant};
|
||||||
pub use crate::messages::tool::tool_messages::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant};
|
pub use crate::messages::tool::tool_messages::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant};
|
||||||
|
pub use crate::messages::tool::tool_messages::brush_tool::{BrushToolMessage, BrushToolMessageDiscriminant};
|
||||||
pub use crate::messages::tool::tool_messages::ellipse_tool::{EllipseToolMessage, EllipseToolMessageDiscriminant};
|
pub use crate::messages::tool::tool_messages::ellipse_tool::{EllipseToolMessage, EllipseToolMessageDiscriminant};
|
||||||
pub use crate::messages::tool::tool_messages::eyedropper_tool::{EyedropperToolMessage, EyedropperToolMessageDiscriminant};
|
pub use crate::messages::tool::tool_messages::eyedropper_tool::{EyedropperToolMessage, EyedropperToolMessageDiscriminant};
|
||||||
pub use crate::messages::tool::tool_messages::fill_tool::{FillToolMessage, FillToolMessageDiscriminant};
|
pub use crate::messages::tool::tool_messages::fill_tool::{FillToolMessage, FillToolMessageDiscriminant};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::messages::prelude::*;
|
|||||||
|
|
||||||
use bezier_rs::Subpath;
|
use bezier_rs::Subpath;
|
||||||
use document_legacy::{LayerId, Operation};
|
use document_legacy::{LayerId, Operation};
|
||||||
|
use graph_craft::document::NodeNetwork;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
use graphene_core::uuid::ManipulatorGroupId;
|
||||||
|
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
@@ -10,9 +11,12 @@ use std::collections::VecDeque;
|
|||||||
|
|
||||||
/// Create a new vector layer from a vector of [`bezier_rs::Subpath`].
|
/// Create a new vector layer from a vector of [`bezier_rs::Subpath`].
|
||||||
pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
|
||||||
|
|
||||||
let network = node_graph::new_vector_network(subpaths);
|
let network = node_graph::new_vector_network(subpaths);
|
||||||
|
new_custom_layer(network, layer_path, responses);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_custom_layer(network: NodeNetwork, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||||
|
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
Operation::AddNodeGraphFrame {
|
Operation::AddNodeGraphFrame {
|
||||||
path: layer_path.clone(),
|
path: layer_path.clone(),
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ pub enum ToolMessage {
|
|||||||
#[child]
|
#[child]
|
||||||
Text(TextToolMessage),
|
Text(TextToolMessage),
|
||||||
|
|
||||||
// #[remain::unsorted]
|
#[remain::unsorted]
|
||||||
// #[child]
|
#[child]
|
||||||
// Brush(BrushToolMessage),
|
Brush(BrushToolMessage),
|
||||||
// #[remain::unsorted]
|
// #[remain::unsorted]
|
||||||
// #[child]
|
// #[child]
|
||||||
// Heal(HealToolMessage),
|
// Heal(HealToolMessage),
|
||||||
@@ -119,6 +119,8 @@ pub enum ToolMessage {
|
|||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
ActivateToolShape,
|
ActivateToolShape,
|
||||||
|
|
||||||
|
#[remain::unsorted]
|
||||||
|
ActivateToolBrush,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
ActivateToolImaginate,
|
ActivateToolImaginate,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
|
|||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
ToolMessage::ActivateToolShape => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }.into()),
|
ToolMessage::ActivateToolShape => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }.into()),
|
||||||
|
|
||||||
|
#[remain::unsorted]
|
||||||
|
ToolMessage::ActivateToolBrush => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Brush }.into()),
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
ToolMessage::ActivateToolImaginate => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Imaginate }.into()),
|
ToolMessage::ActivateToolImaginate => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Imaginate }.into()),
|
||||||
|
|
||||||
@@ -276,6 +278,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
|
|||||||
ActivateToolEllipse,
|
ActivateToolEllipse,
|
||||||
ActivateToolShape,
|
ActivateToolShape,
|
||||||
|
|
||||||
|
ActivateToolBrush,
|
||||||
ActivateToolImaginate,
|
ActivateToolImaginate,
|
||||||
|
|
||||||
SelectRandomPrimaryColor,
|
SelectRandomPrimaryColor,
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||||
|
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
|
||||||
|
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||||
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
|
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||||
|
use crate::messages::prelude::*;
|
||||||
|
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::{HintData, HintGroup, HintInfo};
|
||||||
|
|
||||||
|
use document_legacy::LayerId;
|
||||||
|
use document_legacy::Operation;
|
||||||
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
||||||
|
use graph_craft::{concrete, Type, TypeDescriptor};
|
||||||
|
use graphene_core::vector::style::Stroke;
|
||||||
|
use graphene_core::Cow;
|
||||||
|
|
||||||
|
use glam::DVec2;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct BrushTool {
|
||||||
|
fsm_state: BrushToolFsmState,
|
||||||
|
data: BrushToolData,
|
||||||
|
options: BrushOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BrushOptions {
|
||||||
|
diameter: f64,
|
||||||
|
hardness: f64,
|
||||||
|
flow: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BrushOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
diameter: 40.,
|
||||||
|
hardness: 50.,
|
||||||
|
flow: 100.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
|
#[impl_message(Message, ToolMessage, Brush)]
|
||||||
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||||
|
pub enum BrushToolMessage {
|
||||||
|
// Standard messages
|
||||||
|
#[remain::unsorted]
|
||||||
|
Abort,
|
||||||
|
|
||||||
|
// Tool-specific messages
|
||||||
|
DragStart,
|
||||||
|
DragStop,
|
||||||
|
PointerMove,
|
||||||
|
UpdateOptions(BrushToolMessageOptionsUpdate),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||||
|
pub enum BrushToolMessageOptionsUpdate {
|
||||||
|
Diameter(f64),
|
||||||
|
Flow(f64),
|
||||||
|
Hardness(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
|
enum BrushToolFsmState {
|
||||||
|
#[default]
|
||||||
|
Ready,
|
||||||
|
Drawing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolMetadata for BrushTool {
|
||||||
|
fn icon_name(&self) -> String {
|
||||||
|
"RasterBrushTool".into()
|
||||||
|
}
|
||||||
|
fn tooltip(&self) -> String {
|
||||||
|
"Brush Tool".into()
|
||||||
|
}
|
||||||
|
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
|
||||||
|
ToolType::Brush
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PropertyHolder for BrushTool {
|
||||||
|
fn properties(&self) -> Layout {
|
||||||
|
let diameter = NumberInput::new(Some(self.options.diameter))
|
||||||
|
.label("Diameter")
|
||||||
|
.min(1.)
|
||||||
|
.unit(" px")
|
||||||
|
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Diameter(number_input.value.unwrap())).into())
|
||||||
|
.widget_holder();
|
||||||
|
let hardness = NumberInput::new(Some(self.options.hardness))
|
||||||
|
.label("Hardness")
|
||||||
|
.min(0.)
|
||||||
|
.max(100.)
|
||||||
|
.unit("%")
|
||||||
|
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Hardness(number_input.value.unwrap())).into())
|
||||||
|
.widget_holder();
|
||||||
|
let flow = NumberInput::new(Some(self.options.flow))
|
||||||
|
.label("Flow")
|
||||||
|
.min(1.)
|
||||||
|
.max(100.)
|
||||||
|
.unit("%")
|
||||||
|
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Flow(number_input.value.unwrap())).into())
|
||||||
|
.widget_holder();
|
||||||
|
|
||||||
|
let separator = Separator::new(SeparatorDirection::Horizontal, SeparatorType::Related).widget_holder();
|
||||||
|
|
||||||
|
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||||
|
widgets: vec![diameter, separator.clone(), hardness, separator, flow],
|
||||||
|
}]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool {
|
||||||
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
|
if let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message {
|
||||||
|
match action {
|
||||||
|
BrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter,
|
||||||
|
BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness,
|
||||||
|
BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow,
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actions(&self) -> ActionList {
|
||||||
|
use BrushToolFsmState::*;
|
||||||
|
|
||||||
|
match self.fsm_state {
|
||||||
|
Ready => actions!(BrushToolMessageDiscriminant;
|
||||||
|
DragStart,
|
||||||
|
DragStop,
|
||||||
|
Abort,
|
||||||
|
),
|
||||||
|
Drawing => actions!(BrushToolMessageDiscriminant;
|
||||||
|
DragStop,
|
||||||
|
PointerMove,
|
||||||
|
Abort,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolTransition for BrushTool {
|
||||||
|
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||||
|
EventToMessageMap {
|
||||||
|
document_dirty: None,
|
||||||
|
tool_abort: Some(BrushToolMessage::Abort.into()),
|
||||||
|
selection_changed: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
struct BrushToolData {
|
||||||
|
points: Vec<DVec2>,
|
||||||
|
diameter: f64,
|
||||||
|
hardness: f64,
|
||||||
|
flow: f64,
|
||||||
|
path: Option<Vec<LayerId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fsm for BrushToolFsmState {
|
||||||
|
type ToolData = BrushToolData;
|
||||||
|
type ToolOptions = BrushOptions;
|
||||||
|
|
||||||
|
fn transition(
|
||||||
|
self,
|
||||||
|
event: ToolMessage,
|
||||||
|
tool_data: &mut Self::ToolData,
|
||||||
|
ToolActionHandlerData {
|
||||||
|
document, global_tool_data, input, ..
|
||||||
|
}: &mut ToolActionHandlerData,
|
||||||
|
tool_options: &Self::ToolOptions,
|
||||||
|
responses: &mut VecDeque<Message>,
|
||||||
|
) -> Self {
|
||||||
|
use BrushToolFsmState::*;
|
||||||
|
use BrushToolMessage::*;
|
||||||
|
|
||||||
|
let transform = document.document_legacy.root.transform;
|
||||||
|
|
||||||
|
if let ToolMessage::Brush(event) = event {
|
||||||
|
match (self, event) {
|
||||||
|
(Ready, DragStart) => {
|
||||||
|
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||||
|
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||||
|
tool_data.path = Some(document.get_path_for_new_layer());
|
||||||
|
|
||||||
|
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||||
|
|
||||||
|
tool_data.points.push(pos);
|
||||||
|
|
||||||
|
tool_data.diameter = tool_options.diameter;
|
||||||
|
tool_data.hardness = tool_options.hardness;
|
||||||
|
tool_data.flow = tool_options.flow;
|
||||||
|
|
||||||
|
add_polyline(tool_data, global_tool_data, responses);
|
||||||
|
|
||||||
|
Drawing
|
||||||
|
}
|
||||||
|
(Drawing, PointerMove) => {
|
||||||
|
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||||
|
|
||||||
|
if tool_data.points.last() != Some(&pos) {
|
||||||
|
tool_data.points.push(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_polyline(tool_data, global_tool_data, responses);
|
||||||
|
|
||||||
|
Drawing
|
||||||
|
}
|
||||||
|
(Drawing, DragStop) | (Drawing, Abort) => {
|
||||||
|
if !tool_data.points.is_empty() {
|
||||||
|
responses.push_back(remove_preview(tool_data));
|
||||||
|
add_brush_render(tool_data, global_tool_data, responses);
|
||||||
|
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||||
|
} else {
|
||||||
|
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_data.path = None;
|
||||||
|
tool_data.points.clear();
|
||||||
|
|
||||||
|
Ready
|
||||||
|
}
|
||||||
|
_ => self,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
||||||
|
let hint_data = match self {
|
||||||
|
BrushToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polyline")])]),
|
||||||
|
BrushToolFsmState::Drawing => HintData(vec![]),
|
||||||
|
};
|
||||||
|
|
||||||
|
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_cursor(&self, responses: &mut VecDeque<Message>) {
|
||||||
|
responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_preview(data: &BrushToolData) -> Message {
|
||||||
|
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_polyline(data: &BrushToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
|
||||||
|
let layer_path = data.path.clone().unwrap();
|
||||||
|
let subpath = bezier_rs::Subpath::from_anchors(data.points.iter().copied(), false);
|
||||||
|
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||||
|
|
||||||
|
responses.add(GraphOperationMessage::StrokeSet {
|
||||||
|
layer: layer_path,
|
||||||
|
stroke: Stroke::new(tool_data.primary_color.apply_opacity(data.flow as f32 / 100.), data.diameter),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_brush_render(data: &BrushToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
|
||||||
|
let layer_path = data.path.clone().unwrap();
|
||||||
|
let brush_node = DocumentNode {
|
||||||
|
name: "Brush".to_string(),
|
||||||
|
inputs: vec![
|
||||||
|
NodeInput::ShortCircut(concrete!(())),
|
||||||
|
NodeInput::value(TaggedValue::VecDVec2(data.points.clone()), false),
|
||||||
|
// Diameter
|
||||||
|
NodeInput::value(TaggedValue::F64(data.diameter), false),
|
||||||
|
// Hardness
|
||||||
|
NodeInput::value(TaggedValue::F64(data.hardness), false),
|
||||||
|
// Flow
|
||||||
|
NodeInput::value(TaggedValue::F64(data.flow), false),
|
||||||
|
// Color
|
||||||
|
NodeInput::value(TaggedValue::Color(tool_data.primary_color), false),
|
||||||
|
],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved("graphene_std::brush::BrushNode".into()),
|
||||||
|
metadata: graph_craft::document::DocumentNodeMetadata { position: (8, 4).into() },
|
||||||
|
};
|
||||||
|
let mut network = NodeNetwork::value_network(brush_node);
|
||||||
|
network.push_output_node();
|
||||||
|
graph_modification_utils::new_custom_layer(network, layer_path.clone(), responses);
|
||||||
|
}
|
||||||
@@ -226,10 +226,4 @@ fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData, responses
|
|||||||
layer: layer_path.clone(),
|
layer: layer_path.clone(),
|
||||||
stroke: Stroke::new(tool_data.primary_color, data.weight),
|
stroke: Stroke::new(tool_data.primary_color, data.weight),
|
||||||
});
|
});
|
||||||
responses.add(GraphOperationMessage::TransformSet {
|
|
||||||
layer: layer_path,
|
|
||||||
transform: DAffine2::from_translation(position),
|
|
||||||
transform_in: TransformIn::Local,
|
|
||||||
skip_rerender: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod artboard_tool;
|
pub mod artboard_tool;
|
||||||
|
pub mod brush_tool;
|
||||||
pub mod ellipse_tool;
|
pub mod ellipse_tool;
|
||||||
pub mod eyedropper_tool;
|
pub mod eyedropper_tool;
|
||||||
pub mod fill_tool;
|
pub mod fill_tool;
|
||||||
|
|||||||
@@ -270,10 +270,4 @@ fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, s
|
|||||||
layer: layer_path.clone(),
|
layer: layer_path.clone(),
|
||||||
stroke: Stroke::new(global_tool_data.primary_color, tool_data.weight),
|
stroke: Stroke::new(global_tool_data.primary_color, tool_data.weight),
|
||||||
});
|
});
|
||||||
responses.add(GraphOperationMessage::TransformSet {
|
|
||||||
layer: layer_path,
|
|
||||||
transform: glam::DAffine2::from_translation(position),
|
|
||||||
transform_in: TransformIn::Local,
|
|
||||||
skip_rerender: false,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -410,12 +410,7 @@ fn list_tools_in_groups() -> Vec<Vec<ToolAvailability>> {
|
|||||||
// Raster tool group
|
// Raster tool group
|
||||||
ToolAvailability::Available(Box::<frame_tool::NodeGraphFrameTool>::default()),
|
ToolAvailability::Available(Box::<frame_tool::NodeGraphFrameTool>::default()),
|
||||||
ToolAvailability::Available(Box::<imaginate_tool::ImaginateTool>::default()),
|
ToolAvailability::Available(Box::<imaginate_tool::ImaginateTool>::default()),
|
||||||
ToolAvailability::ComingSoon(ToolEntry {
|
ToolAvailability::Available(Box::<brush_tool::BrushTool>::default()),
|
||||||
tool_type: ToolType::Brush,
|
|
||||||
icon_name: "RasterBrushTool".into(),
|
|
||||||
tooltip: "Coming Soon: Brush Tool (B)".into(),
|
|
||||||
tooltip_shortcut: None,
|
|
||||||
}),
|
|
||||||
ToolAvailability::ComingSoon(ToolEntry {
|
ToolAvailability::ComingSoon(ToolEntry {
|
||||||
tool_type: ToolType::Heal,
|
tool_type: ToolType::Heal,
|
||||||
icon_name: "RasterHealTool".into(),
|
icon_name: "RasterHealTool".into(),
|
||||||
@@ -472,7 +467,7 @@ pub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType {
|
|||||||
ToolMessage::Text(_) => ToolType::Text,
|
ToolMessage::Text(_) => ToolType::Text,
|
||||||
|
|
||||||
// Raster tool group
|
// Raster tool group
|
||||||
// ToolMessage::Brush(_) => ToolType::Brush,
|
ToolMessage::Brush(_) => ToolType::Brush,
|
||||||
// ToolMessage::Heal(_) => ToolType::Heal,
|
// ToolMessage::Heal(_) => ToolType::Heal,
|
||||||
// ToolMessage::Clone(_) => ToolType::Clone,
|
// ToolMessage::Clone(_) => ToolType::Clone,
|
||||||
// ToolMessage::Patch(_) => ToolType::Patch,
|
// ToolMessage::Patch(_) => ToolType::Patch,
|
||||||
@@ -509,7 +504,7 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis
|
|||||||
ToolType::Text => ToolMessageDiscriminant::ActivateToolText,
|
ToolType::Text => ToolMessageDiscriminant::ActivateToolText,
|
||||||
|
|
||||||
// Raster tool group
|
// Raster tool group
|
||||||
// ToolType::Brush => ToolMessageDiscriminant::ActivateToolBrush,
|
ToolType::Brush => ToolMessageDiscriminant::ActivateToolBrush,
|
||||||
// ToolType::Heal => ToolMessageDiscriminant::ActivateToolHeal,
|
// ToolType::Heal => ToolMessageDiscriminant::ActivateToolHeal,
|
||||||
// ToolType::Clone => ToolMessageDiscriminant::ActivateToolClone,
|
// ToolType::Clone => ToolMessageDiscriminant::ActivateToolClone,
|
||||||
// ToolType::Patch => ToolMessageDiscriminant::ActivateToolPatch,
|
// ToolType::Patch => ToolMessageDiscriminant::ActivateToolPatch,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use document_legacy::{LayerId, Operation};
|
|||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||||
use graph_craft::executor::Compiler;
|
use graph_craft::executor::Compiler;
|
||||||
|
use graph_craft::{concrete, Type, TypeDescriptor};
|
||||||
use graphene_core::raster::{Image, ImageFrame};
|
use graphene_core::raster::{Image, ImageFrame};
|
||||||
use graphene_core::vector::VectorData;
|
use graphene_core::vector::VectorData;
|
||||||
use interpreted_executor::executor::DynamicExecutor;
|
use interpreted_executor::executor::DynamicExecutor;
|
||||||
@@ -42,7 +43,11 @@ impl NodeGraphExecutor {
|
|||||||
use dyn_any::IntoDynAny;
|
use dyn_any::IntoDynAny;
|
||||||
use graph_craft::executor::Executor;
|
use graph_craft::executor::Executor;
|
||||||
|
|
||||||
self.executor.execute(image_frame.into_dyn()).map_err(|e| e.to_string())
|
match self.executor.input_type() {
|
||||||
|
Some(t) if t == concrete!(ImageFrame) => self.executor.execute(image_frame.into_dyn()).map_err(|e| e.to_string()),
|
||||||
|
Some(t) if t == concrete!(()) => self.executor.execute(().into_dyn()).map_err(|e| e.to_string()),
|
||||||
|
_ => Err("Invalid input type".to_string()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes an input for a node in the graph
|
/// Computes an input for a node in the graph
|
||||||
|
|||||||
@@ -159,6 +159,27 @@ impl<T: StaticTypeSized> StaticType for *mut [T] {
|
|||||||
impl<'a, T: StaticTypeSized> StaticType for &'a [T] {
|
impl<'a, T: StaticTypeSized> StaticType for &'a [T] {
|
||||||
type Static = &'static [<T as StaticTypeSized>::Static];
|
type Static = &'static [<T as StaticTypeSized>::Static];
|
||||||
}
|
}
|
||||||
|
macro_rules! impl_slice {
|
||||||
|
($($id:ident),*) => {
|
||||||
|
$(
|
||||||
|
impl<'a, T: StaticTypeSized> StaticType for $id<'a, T> {
|
||||||
|
type Static = $id<'static, <T as StaticTypeSized>::Static>;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod slice {
|
||||||
|
use super::*;
|
||||||
|
use core::slice::*;
|
||||||
|
impl_slice!(Iter, IterMut, Chunks, ChunksMut, RChunks, RChunksMut, Windows);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl<'a, T: StaticTypeSized> StaticType for Box<dyn Iterator<Item = T> + 'a + Send + Sync> {
|
||||||
|
type Static = Box<dyn Iterator<Item = T::Static> + Send + Sync>;
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> StaticType for &'a str {
|
impl<'a> StaticType for &'a str {
|
||||||
type Static = &'static str;
|
type Static = &'static str;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
std = ["dyn-any", "dyn-any/std", "alloc", "glam/std", "specta"]
|
std = ["dyn-any", "dyn-any/std", "alloc", "glam/std", "specta"]
|
||||||
default = ["async", "serde", "kurbo", "log", "std"]
|
default = ["async", "serde", "kurbo", "log", "std"]
|
||||||
log = ["dep:log"]
|
log = ["dep:log"]
|
||||||
serde = ["dep:serde", "glam/serde"]
|
serde = ["dep:serde", "glam/serde", "bezier-rs/serde"]
|
||||||
gpu = ["spirv-std", "bytemuck", "glam/bytemuck", "dyn-any", "glam/libm"]
|
gpu = ["spirv-std", "bytemuck", "glam/bytemuck", "dyn-any", "glam/libm"]
|
||||||
async = ["async-trait", "alloc"]
|
async = ["async-trait", "alloc"]
|
||||||
nightly = []
|
nightly = []
|
||||||
@@ -20,13 +20,18 @@ alloc = ["dyn-any", "bezier-rs", "once_cell"]
|
|||||||
type_id_logging = []
|
type_id_logging = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dyn-any = {path = "../../libraries/dyn-any", features = ["derive", "glam"], optional = true, default-features = false }
|
dyn-any = { path = "../../libraries/dyn-any", features = [
|
||||||
|
"derive",
|
||||||
|
"glam",
|
||||||
|
], optional = true, default-features = false }
|
||||||
|
|
||||||
spirv-std = { version = "0.5", features = ["glam"] , optional = true}
|
spirv-std = { version = "0.5", features = ["glam"], optional = true }
|
||||||
bytemuck = {version = "1.8", features = ["derive"], optional = true}
|
bytemuck = { version = "1.8", features = ["derive"], optional = true }
|
||||||
async-trait = {version = "0.1", optional = true}
|
async-trait = { version = "0.1", optional = true }
|
||||||
serde = {version = "1.0", features = ["derive"], optional = true, default-features = false }
|
serde = { version = "1.0", features = [
|
||||||
log = {version = "0.4", optional = true}
|
"derive",
|
||||||
|
], optional = true, default-features = false }
|
||||||
|
log = { version = "0.4", optional = true }
|
||||||
|
|
||||||
bezier-rs = { path = "../../libraries/bezier-rs", optional = true }
|
bezier-rs = { path = "../../libraries/bezier-rs", optional = true }
|
||||||
kurbo = { git = "https://github.com/linebender/kurbo.git", features = [
|
kurbo = { git = "https://github.com/linebender/kurbo.git", features = [
|
||||||
@@ -34,8 +39,10 @@ kurbo = { git = "https://github.com/linebender/kurbo.git", features = [
|
|||||||
], optional = true }
|
], optional = true }
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
spin = "0.9.2"
|
spin = "0.9.2"
|
||||||
glam = { version = "^0.22", default-features = false, features = ["scalar-math"]}
|
glam = { version = "^0.22", default-features = false, features = [
|
||||||
node-macro = {path = "../node-macro"}
|
"scalar-math",
|
||||||
|
] }
|
||||||
|
node-macro = { path = "../node-macro" }
|
||||||
specta.workspace = true
|
specta.workspace = true
|
||||||
specta.optional = true
|
specta.optional = true
|
||||||
once_cell = { version = "1.17.0", default-features = false, optional = true }
|
once_cell = { version = "1.17.0", default-features = false, optional = true }
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ where
|
|||||||
core::any::type_name::<Self::Output>()
|
core::any::type_name::<Self::Output>()
|
||||||
}
|
}
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
fn to_node_io(&self, parameters: Vec<(Type, Type)>) -> NodeIOTypes {
|
fn to_node_io(&self, parameters: Vec<Type>) -> NodeIOTypes {
|
||||||
NodeIOTypes {
|
NodeIOTypes {
|
||||||
input: concrete!(<Input as StaticType>::Static),
|
input: concrete!(<Input as StaticType>::Static),
|
||||||
output: concrete!(<Self::Output as StaticType>::Static),
|
output: concrete!(<Self::Output as StaticType>::Static),
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub struct AddParameterNode<Second> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(AddParameterNode)]
|
#[node_macro::node_fn(AddParameterNode)]
|
||||||
fn flat_map<U, T>(first: U, second: T) -> <U as Add<T>>::Output
|
fn add_parameter<U, T>(first: U, second: T) -> <U as Add<T>>::Output
|
||||||
where
|
where
|
||||||
U: Add<T>,
|
U: Add<T>,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -239,12 +239,11 @@ fn brighten_color_node(color: Color, brightness: f32) -> Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ForEachNode<Iter, MapNode> {
|
pub struct ForEachNode<MapNode> {
|
||||||
map_node: MapNode,
|
map_node: MapNode,
|
||||||
_iter: PhantomData<Iter>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(ForEachNode<_Iter>)]
|
#[node_macro::node_fn(ForEachNode)]
|
||||||
fn map_node<_Iter: Iterator, MapNode>(input: _Iter, map_node: &'any_input MapNode) -> ()
|
fn map_node<_Iter: Iterator, MapNode>(input: _Iter, map_node: &'any_input MapNode) -> ()
|
||||||
where
|
where
|
||||||
MapNode: for<'any_input> Node<'any_input, _Iter::Item, Output = ()> + 'input,
|
MapNode: for<'any_input> Node<'any_input, _Iter::Item, Output = ()> + 'input,
|
||||||
@@ -359,6 +358,15 @@ mod image {
|
|||||||
data: Vec::new(),
|
data: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new(width: u32, height: u32, color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data: vec![color; (width * height) as usize],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_slice(&self) -> ImageSlice {
|
pub fn as_slice(&self) -> ImageSlice {
|
||||||
ImageSlice {
|
ImageSlice {
|
||||||
width: self.width,
|
width: self.width,
|
||||||
@@ -366,6 +374,15 @@ mod image {
|
|||||||
data: self.data.as_slice(),
|
data: self.data.as_slice(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, x: u32, y: u32) -> Option<&mut Color> {
|
||||||
|
self.data.get_mut((y * self.width + x) as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, x: u32, y: u32) -> Option<&Color> {
|
||||||
|
self.data.get((y * self.width + x) as usize)
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate Image from some frontend image data (the canvas pixels as u8s in a flat array)
|
/// Generate Image from some frontend image data (the canvas pixels as u8s in a flat array)
|
||||||
pub fn from_image_data(image_data: &[u8], width: u32, height: u32) -> Self {
|
pub fn from_image_data(image_data: &[u8], width: u32, height: u32) -> Self {
|
||||||
let data = image_data.chunks_exact(4).map(|v| Color::from_rgba8(v[0], v[1], v[2], v[3])).collect();
|
let data = image_data.chunks_exact(4).map(|v| Color::from_rgba8(v[0], v[1], v[2], v[3])).collect();
|
||||||
@@ -451,6 +468,12 @@ mod image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<ImageFrame> for ImageFrame {
|
||||||
|
fn as_ref(&self) -> &ImageFrame {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Hash for ImageFrame {
|
impl Hash for ImageFrame {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.image.hash(state);
|
self.image.hash(state);
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ pub struct InvertRGBNode;
|
|||||||
|
|
||||||
#[node_macro::node_fn(InvertRGBNode)]
|
#[node_macro::node_fn(InvertRGBNode)]
|
||||||
fn invert_image(color: Color) -> Color {
|
fn invert_image(color: Color) -> Color {
|
||||||
color.map_rgb(|c| 1. - c)
|
color.map_rgb(|c| color.a() - c)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -339,43 +339,55 @@ pub struct BlendNode<BlendMode, Opacity> {
|
|||||||
opacity: Opacity,
|
opacity: Opacity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Opacity: dyn_any::StaticTypeSized, Blend: dyn_any::StaticTypeSized> StaticType for BlendNode<Blend, Opacity> {
|
||||||
|
type Static = BlendNode<Blend::Static, Opacity::Static>;
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(BlendNode)]
|
#[node_macro::node_fn(BlendNode)]
|
||||||
fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f64) -> Color {
|
fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f64) -> Color {
|
||||||
let (source_color, backdrop) = input;
|
let opacity = opacity / 100.;
|
||||||
let actual_opacity = 1. - (opacity / 100.) as f32;
|
|
||||||
return match blend_mode {
|
|
||||||
BlendMode::Normal => backdrop.blend_rgb(source_color, Color::blend_normal),
|
|
||||||
BlendMode::Multiply => backdrop.blend_rgb(source_color, Color::blend_multiply),
|
|
||||||
BlendMode::Darken => backdrop.blend_rgb(source_color, Color::blend_darken),
|
|
||||||
BlendMode::ColorBurn => backdrop.blend_rgb(source_color, Color::blend_color_burn),
|
|
||||||
BlendMode::LinearBurn => backdrop.blend_rgb(source_color, Color::blend_linear_burn),
|
|
||||||
BlendMode::DarkerColor => backdrop.blend_darker_color(source_color),
|
|
||||||
|
|
||||||
BlendMode::Screen => backdrop.blend_rgb(source_color, Color::blend_screen),
|
let (foreground, background) = input;
|
||||||
BlendMode::Lighten => backdrop.blend_rgb(source_color, Color::blend_lighten),
|
let foreground = foreground.to_linear_srgb();
|
||||||
BlendMode::ColorDodge => backdrop.blend_rgb(source_color, Color::blend_color_dodge),
|
let background = background.to_linear_srgb();
|
||||||
BlendMode::LinearDodge => backdrop.blend_rgb(source_color, Color::blend_linear_dodge),
|
|
||||||
BlendMode::LighterColor => backdrop.blend_lighter_color(source_color),
|
|
||||||
|
|
||||||
BlendMode::Overlay => source_color.blend_rgb(backdrop, Color::blend_hardlight),
|
let target_color = match blend_mode {
|
||||||
BlendMode::SoftLight => backdrop.blend_rgb(source_color, Color::blend_softlight),
|
BlendMode::Normal => background.blend_rgb(foreground, Color::blend_normal),
|
||||||
BlendMode::HardLight => backdrop.blend_rgb(source_color, Color::blend_hardlight),
|
BlendMode::Multiply => background.blend_rgb(foreground, Color::blend_multiply),
|
||||||
BlendMode::VividLight => backdrop.blend_rgb(source_color, Color::blend_vivid_light),
|
BlendMode::Darken => background.blend_rgb(foreground, Color::blend_darken),
|
||||||
BlendMode::LinearLight => backdrop.blend_rgb(source_color, Color::blend_linear_light),
|
BlendMode::ColorBurn => background.blend_rgb(foreground, Color::blend_color_burn),
|
||||||
BlendMode::PinLight => backdrop.blend_rgb(source_color, Color::blend_pin_light),
|
BlendMode::LinearBurn => background.blend_rgb(foreground, Color::blend_linear_burn),
|
||||||
BlendMode::HardMix => backdrop.blend_rgb(source_color, Color::blend_hard_mix),
|
BlendMode::DarkerColor => background.blend_darker_color(foreground),
|
||||||
|
|
||||||
BlendMode::Difference => backdrop.blend_rgb(source_color, Color::blend_exclusion),
|
BlendMode::Screen => background.blend_rgb(foreground, Color::blend_screen),
|
||||||
BlendMode::Exclusion => backdrop.blend_rgb(source_color, Color::blend_exclusion),
|
BlendMode::Lighten => background.blend_rgb(foreground, Color::blend_lighten),
|
||||||
BlendMode::Subtract => backdrop.blend_rgb(source_color, Color::blend_subtract),
|
BlendMode::ColorDodge => background.blend_rgb(foreground, Color::blend_color_dodge),
|
||||||
BlendMode::Divide => backdrop.blend_rgb(source_color, Color::blend_divide),
|
BlendMode::LinearDodge => background.blend_rgb(foreground, Color::blend_linear_dodge),
|
||||||
|
BlendMode::LighterColor => background.blend_lighter_color(foreground),
|
||||||
|
|
||||||
BlendMode::Hue => backdrop.blend_hue(source_color),
|
BlendMode::Overlay => foreground.blend_rgb(background, Color::blend_hardlight),
|
||||||
BlendMode::Saturation => backdrop.blend_saturation(source_color),
|
BlendMode::SoftLight => background.blend_rgb(foreground, Color::blend_softlight),
|
||||||
BlendMode::Color => backdrop.blend_color(source_color),
|
BlendMode::HardLight => background.blend_rgb(foreground, Color::blend_hardlight),
|
||||||
BlendMode::Luminosity => backdrop.blend_luminosity(source_color),
|
BlendMode::VividLight => background.blend_rgb(foreground, Color::blend_vivid_light),
|
||||||
}
|
BlendMode::LinearLight => background.blend_rgb(foreground, Color::blend_linear_light),
|
||||||
.lerp(backdrop, actual_opacity);
|
BlendMode::PinLight => background.blend_rgb(foreground, Color::blend_pin_light),
|
||||||
|
BlendMode::HardMix => background.blend_rgb(foreground, Color::blend_hard_mix),
|
||||||
|
|
||||||
|
BlendMode::Difference => background.blend_rgb(foreground, Color::blend_exclusion),
|
||||||
|
BlendMode::Exclusion => background.blend_rgb(foreground, Color::blend_exclusion),
|
||||||
|
BlendMode::Subtract => background.blend_rgb(foreground, Color::blend_subtract),
|
||||||
|
BlendMode::Divide => background.blend_rgb(foreground, Color::blend_divide),
|
||||||
|
|
||||||
|
BlendMode::Hue => background.blend_hue(foreground),
|
||||||
|
BlendMode::Saturation => background.blend_saturation(foreground),
|
||||||
|
BlendMode::Color => background.blend_color(foreground),
|
||||||
|
BlendMode::Luminosity => background.blend_luminosity(foreground),
|
||||||
|
};
|
||||||
|
|
||||||
|
let multiplied_target_color = target_color.to_associated_alpha(opacity as f32);
|
||||||
|
let blended = background.alpha_blend(multiplied_target_color);
|
||||||
|
|
||||||
|
blended.to_gamma_srgb()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ impl Color {
|
|||||||
Color { red, green, blue, alpha }
|
Color { red, green, blue, alpha }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return an opaque `Color` from given `f32` RGB channels.
|
||||||
|
pub fn from_unassociated_alpha(red: f32, green: f32, blue: f32, alpha: f32) -> Color {
|
||||||
|
Color::from_rgbaf32_unchecked(red * alpha, green * alpha, blue * alpha, alpha)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return an opaque SDR `Color` given RGB channels from `0` to `255`.
|
/// Return an opaque SDR `Color` given RGB channels from `0` to `255`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
@@ -602,14 +607,49 @@ impl Color {
|
|||||||
pub fn map_rgb<F: Fn(f32) -> f32>(&self, f: F) -> Self {
|
pub fn map_rgb<F: Fn(f32) -> f32>(&self, f: F) -> Self {
|
||||||
Self::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), self.a())
|
Self::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), self.a())
|
||||||
}
|
}
|
||||||
pub fn blend_rgb<F: Fn(f32, f32) -> f32>(&self, other: Color, f: F) -> Self {
|
|
||||||
Color {
|
pub fn apply_opacity(&self, opacity: f32) -> Self {
|
||||||
red: f(self.red, other.red).clamp(0., 1.),
|
Self::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), self.a() * opacity)
|
||||||
green: f(self.green, other.green).clamp(0., 1.),
|
}
|
||||||
blue: f(self.blue, other.blue).clamp(0., 1.),
|
|
||||||
|
pub fn to_associated_alpha(&self, alpha: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
red: self.red * alpha,
|
||||||
|
green: self.green * alpha,
|
||||||
|
blue: self.blue * alpha,
|
||||||
|
alpha: self.alpha * alpha,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_unassociated_alpha(&self) -> Self {
|
||||||
|
let unmultiply = 1. / self.alpha;
|
||||||
|
Self {
|
||||||
|
red: self.red * unmultiply,
|
||||||
|
green: self.green * unmultiply,
|
||||||
|
blue: self.blue * unmultiply,
|
||||||
alpha: self.alpha,
|
alpha: self.alpha,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn blend_rgb<F: Fn(f32, f32) -> f32>(&self, other: Color, f: F) -> Self {
|
||||||
|
let background = self.to_unassociated_alpha();
|
||||||
|
Color {
|
||||||
|
red: f(background.red, other.red).clamp(0., 1.),
|
||||||
|
green: f(background.green, other.green).clamp(0., 1.),
|
||||||
|
blue: f(background.blue, other.blue).clamp(0., 1.),
|
||||||
|
alpha: other.alpha,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alpha_blend(&self, other: Color) -> Self {
|
||||||
|
let inv_alpha = 1. - other.alpha;
|
||||||
|
Self {
|
||||||
|
red: self.red * inv_alpha + other.red,
|
||||||
|
green: self.green * inv_alpha + other.green,
|
||||||
|
blue: self.blue * inv_alpha + other.blue,
|
||||||
|
alpha: self.alpha * inv_alpha + other.alpha,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -6,6 +6,61 @@ use crate::raster::ImageFrame;
|
|||||||
use crate::vector::VectorData;
|
use crate::vector::VectorData;
|
||||||
use crate::Node;
|
use crate::Node;
|
||||||
|
|
||||||
|
pub trait Transform {
|
||||||
|
fn transform(&self) -> DAffine2;
|
||||||
|
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||||
|
pivot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TransformMut: Transform {
|
||||||
|
fn transform_mut(&mut self) -> &mut DAffine2;
|
||||||
|
fn translate(&mut self, offset: DVec2) {
|
||||||
|
*self.transform_mut() = DAffine2::from_translation(offset) * self.transform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transform for ImageFrame {
|
||||||
|
fn transform(&self) -> DAffine2 {
|
||||||
|
self.transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Transform for &ImageFrame {
|
||||||
|
fn transform(&self) -> DAffine2 {
|
||||||
|
self.transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TransformMut for ImageFrame {
|
||||||
|
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||||
|
&mut self.transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transform for VectorData {
|
||||||
|
fn transform(&self) -> DAffine2 {
|
||||||
|
self.transform
|
||||||
|
}
|
||||||
|
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||||
|
self.local_pivot(pivot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TransformMut for VectorData {
|
||||||
|
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||||
|
&mut self.transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transform for DAffine2 {
|
||||||
|
fn transform(&self) -> DAffine2 {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TransformMut for DAffine2 {
|
||||||
|
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct TransformNode<Translation, Rotation, Scale, Shear, Pivot> {
|
pub struct TransformNode<Translation, Rotation, Scale, Shear, Pivot> {
|
||||||
pub(crate) translate: Translation,
|
pub(crate) translate: Translation,
|
||||||
@@ -16,45 +71,12 @@ pub struct TransformNode<Translation, Rotation, Scale, Shear, Pivot> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(TransformNode)]
|
#[node_macro::node_fn(TransformNode)]
|
||||||
pub(crate) fn transform_vector_data(mut vector_data: VectorData, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2, pivot: DVec2) -> VectorData {
|
pub(crate) fn transform_vector_data<Data: TransformMut>(mut data: Data, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2, pivot: DVec2) -> Data {
|
||||||
let pivot = DAffine2::from_translation(vector_data.local_pivot(pivot));
|
let pivot = DAffine2::from_translation(data.local_pivot(pivot));
|
||||||
|
|
||||||
let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse();
|
let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse();
|
||||||
vector_data.transform = modification * vector_data.transform;
|
let data_transform = data.transform_mut();
|
||||||
|
*data_transform = modification * (*data_transform);
|
||||||
|
|
||||||
vector_data
|
data
|
||||||
}
|
|
||||||
|
|
||||||
impl<'input, Translation: 'input, Rotation: 'input, Scale: 'input, Shear: 'input, Pivot: 'input> Node<'input, ImageFrame> for TransformNode<Translation, Rotation, Scale, Shear, Pivot>
|
|
||||||
where
|
|
||||||
Translation: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
|
||||||
Rotation: for<'any_input> Node<'any_input, (), Output = f64>,
|
|
||||||
Scale: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
|
||||||
Shear: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
|
||||||
Pivot: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
|
||||||
{
|
|
||||||
type Output = ImageFrame;
|
|
||||||
#[inline]
|
|
||||||
fn eval<'node: 'input>(&'node self, mut image_frame: ImageFrame) -> Self::Output {
|
|
||||||
let translate = self.translate.eval(());
|
|
||||||
let rotate = self.rotate.eval(());
|
|
||||||
let scale = self.scale.eval(());
|
|
||||||
let shear = self.shear.eval(());
|
|
||||||
let pivot = self.pivot.eval(());
|
|
||||||
|
|
||||||
let pivot = DAffine2::from_translation(pivot);
|
|
||||||
let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse();
|
|
||||||
image_frame.transform = modification * image_frame.transform;
|
|
||||||
|
|
||||||
image_frame
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates a transform matrix that rotates around the center of the image
|
|
||||||
fn generate_transform(shear: DVec2, transform: &DAffine2, scale: DVec2, rotate: f64, translate: DVec2) -> DAffine2 {
|
|
||||||
let shear_matrix = DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
|
||||||
let pivot = transform.transform_point2(DVec2::splat(0.5));
|
|
||||||
let translate_to_center = DAffine2::from_translation(-pivot);
|
|
||||||
|
|
||||||
translate_to_center.inverse() * DAffine2::from_scale_angle_translation(scale, rotate, translate) * shear_matrix * translate_to_center
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ pub use std::borrow::Cow;
|
|||||||
pub struct NodeIOTypes {
|
pub struct NodeIOTypes {
|
||||||
pub input: Type,
|
pub input: Type,
|
||||||
pub output: Type,
|
pub output: Type,
|
||||||
pub parameters: Vec<(Type, Type)>,
|
pub parameters: Vec<Type>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeIOTypes {
|
impl NodeIOTypes {
|
||||||
pub fn new(input: Type, output: Type, parameters: Vec<(Type, Type)>) -> Self {
|
pub fn new(input: Type, output: Type, parameters: Vec<Type>) -> Self {
|
||||||
Self { input, output, parameters }
|
Self { input, output, parameters }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ty(&self) -> Type {
|
||||||
|
Type::Fn(Box::new(self.input.clone()), Box::new(self.output.clone()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@@ -34,6 +38,20 @@ macro_rules! generic {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! fn_type {
|
||||||
|
($input:ty, $output:ty) => {
|
||||||
|
Type::Fn(Box::new(concrete!($input)), Box::new(concrete!($output)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! value_fn {
|
||||||
|
($output:ty) => {
|
||||||
|
Type::Fn(Box::new(concrete!(())), Box::new(concrete!($output)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, specta::Type)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, specta::Type)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct NodeIdentifier {
|
pub struct NodeIdentifier {
|
||||||
@@ -72,16 +90,62 @@ impl PartialEq for TypeDescriptor {
|
|||||||
pub enum Type {
|
pub enum Type {
|
||||||
Generic(Cow<'static, str>),
|
Generic(Cow<'static, str>),
|
||||||
Concrete(TypeDescriptor),
|
Concrete(TypeDescriptor),
|
||||||
|
Fn(Box<Type>, Box<Type>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
pub fn is_generic(&self) -> bool {
|
||||||
|
matches!(self, Type::Generic(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_concrete(&self) -> bool {
|
||||||
|
matches!(self, Type::Concrete(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_fn(&self) -> bool {
|
||||||
|
matches!(self, Type::Fn(_, _))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_value(&self) -> bool {
|
||||||
|
matches!(self, Type::Fn(_, _) | Type::Concrete(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_unit(&self) -> bool {
|
||||||
|
matches!(self, Type::Fn(_, _) | Type::Concrete(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_generic_or_fn(&self) -> bool {
|
||||||
|
matches!(self, Type::Fn(_, _) | Type::Generic(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fn_input(&self) -> Option<&Type> {
|
||||||
|
match self {
|
||||||
|
Type::Fn(first, _) => Some(first),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fn_output(&self) -> Option<&Type> {
|
||||||
|
match self {
|
||||||
|
Type::Fn(_, second) => Some(second),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn function(input: &Type, output: &Type) -> Type {
|
||||||
|
Type::Fn(Box::new(input.clone()), Box::new(output.clone()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Debug for Type {
|
impl core::fmt::Debug for Type {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Generic(arg0) => f.write_fmt(format_args!("Generic({})", arg0)),
|
Self::Generic(arg0) => write!(f, "Generic({})", arg0),
|
||||||
#[cfg(feature = "type_id_logging")]
|
#[cfg(feature = "type_id_logging")]
|
||||||
Self::Concrete(arg0) => f.write_fmt(format_args!("Concrete({}, {:?}))", arg0.name, arg0.id)),
|
Self::Concrete(arg0) => write!(f, "Concrete({}, {:?})", arg0.name, arg0.id),
|
||||||
#[cfg(not(feature = "type_id_logging"))]
|
#[cfg(not(feature = "type_id_logging"))]
|
||||||
Self::Concrete(arg0) => f.write_fmt(format_args!("Concrete({})", arg0.name)),
|
Self::Concrete(arg0) => write!(f, "Concrete({})", arg0.name),
|
||||||
|
Self::Fn(arg0, arg1) => write!(f, "({:?} -> {:?})", arg0, arg1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,6 +155,7 @@ impl std::fmt::Display for Type {
|
|||||||
match self {
|
match self {
|
||||||
Type::Generic(name) => write!(f, "{}", name),
|
Type::Generic(name) => write!(f, "{}", name),
|
||||||
Type::Concrete(ty) => write!(f, "{}", ty.name),
|
Type::Concrete(ty) => write!(f, "{}", ty.name),
|
||||||
|
Type::Fn(input, output) => write!(f, "({} -> {})", input, output),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||||
|
|
||||||
use crate::Node;
|
use crate::Node;
|
||||||
|
|
||||||
@@ -15,6 +16,10 @@ impl<'i, const N: u32> Node<'i, ()> for IntNode<N> {
|
|||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ValueNode<T>(pub T);
|
pub struct ValueNode<T>(pub T);
|
||||||
|
|
||||||
|
impl<T: StaticTypeSized> StaticType for ValueNode<T> {
|
||||||
|
type Static = ValueNode<T::Static>;
|
||||||
|
}
|
||||||
|
|
||||||
impl<'i, T: 'i> Node<'i, ()> for ValueNode<T> {
|
impl<'i, T: 'i> Node<'i, ()> for ValueNode<T> {
|
||||||
type Output = &'i T;
|
type Output = &'i T;
|
||||||
fn eval<'s: 'i>(&'s self, _input: ()) -> Self::Output {
|
fn eval<'s: 'i>(&'s self, _input: ()) -> Self::Output {
|
||||||
@@ -43,6 +48,13 @@ impl<T: Clone + Copy> Copy for ValueNode<T> {}
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ClonedNode<T: Clone>(pub T);
|
pub struct ClonedNode<T: Clone>(pub T);
|
||||||
|
|
||||||
|
impl<T: Clone + StaticTypeSized> StaticType for ClonedNode<T>
|
||||||
|
where
|
||||||
|
T::Static: Clone,
|
||||||
|
{
|
||||||
|
type Static = ClonedNode<T::Static>;
|
||||||
|
}
|
||||||
|
|
||||||
impl<'i, T: Clone + 'i> Node<'i, ()> for ClonedNode<T> {
|
impl<'i, T: Clone + 'i> Node<'i, ()> for ClonedNode<T> {
|
||||||
type Output = T;
|
type Output = T;
|
||||||
fn eval<'s: 'i>(&'s self, _input: ()) -> Self::Output {
|
fn eval<'s: 'i>(&'s self, _input: ()) -> Self::Output {
|
||||||
|
|||||||
Generated
+38
-3
@@ -80,9 +80,11 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bezier-rs"
|
name = "bezier-rs"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -421,7 +423,6 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"nvtx",
|
"nvtx",
|
||||||
"rand_chacha",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"spirv-builder",
|
"spirv-builder",
|
||||||
@@ -435,6 +436,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
"bezier-rs",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
@@ -442,9 +444,9 @@ dependencies = [
|
|||||||
"graphene-core",
|
"graphene-core",
|
||||||
"log",
|
"log",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rand_chacha",
|
|
||||||
"serde",
|
"serde",
|
||||||
"specta",
|
"specta",
|
||||||
|
"xxhash-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -459,8 +461,10 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"rand_chacha",
|
||||||
"serde",
|
"serde",
|
||||||
"specta",
|
"specta",
|
||||||
|
"spin",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -620,6 +624,16 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa"
|
checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.17"
|
version = "0.4.17"
|
||||||
@@ -963,6 +977,12 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scratch"
|
name = "scratch"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -1061,6 +1081,15 @@ dependencies = [
|
|||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.9.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5d6e0250b93c8427a177b849d144a96d5acc57006149479403d7861ab721e34"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spirt"
|
name = "spirt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1404,3 +1433,9 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xxhash-rust"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ impl DocumentNode {
|
|||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, input)| matches!(input, NodeInput::Network(_)))
|
.filter(|(_, input)| matches!(input, NodeInput::Network(_) | NodeInput::ShortCircut(_)))
|
||||||
.nth(offset)
|
.nth(offset)
|
||||||
.unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}"));
|
.unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}"));
|
||||||
|
|
||||||
@@ -72,6 +72,7 @@ impl DocumentNode {
|
|||||||
NodeInput::ShortCircut(ty) => (ProtoNodeInput::ShortCircut(ty), ConstructionArgs::Nodes(vec![])),
|
NodeInput::ShortCircut(ty) => (ProtoNodeInput::ShortCircut(ty), ConstructionArgs::Nodes(vec![])),
|
||||||
};
|
};
|
||||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter");
|
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter");
|
||||||
|
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::ShortCircut(_))), "recieved non resolved parameter");
|
||||||
assert!(
|
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: {:#?}",
|
"recieved value as parameter. inupts: {:#?}, construction_args: {:#?}",
|
||||||
@@ -218,6 +219,12 @@ pub enum DocumentNodeImplementation {
|
|||||||
Unresolved(NodeIdentifier),
|
Unresolved(NodeIdentifier),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for DocumentNodeImplementation {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Unresolved(NodeIdentifier::new("graphene_cored::ops::IdNode"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DocumentNodeImplementation {
|
impl DocumentNodeImplementation {
|
||||||
pub fn get_network(&self) -> Option<&NodeNetwork> {
|
pub fn get_network(&self) -> Option<&NodeNetwork> {
|
||||||
if let DocumentNodeImplementation::Network(n) = self {
|
if let DocumentNodeImplementation::Network(n) = self {
|
||||||
@@ -260,6 +267,227 @@ pub struct NodeNetwork {
|
|||||||
pub previous_outputs: Option<Vec<NodeOutput>>,
|
pub previous_outputs: Option<Vec<NodeOutput>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Graph modification functions
|
||||||
|
impl NodeNetwork {
|
||||||
|
/// Get the original output nodes of this network, ignoring any preview node
|
||||||
|
pub fn original_outputs(&self) -> &Vec<NodeOutput> {
|
||||||
|
self.previous_outputs.as_ref().unwrap_or(&self.outputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input_types<'a>(&'a self) -> impl Iterator<Item = Type> + 'a {
|
||||||
|
self.inputs.iter().map(move |id| self.nodes[id].inputs.get(0).map(|i| i.ty().clone()).unwrap_or(concrete!(())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An empty graph
|
||||||
|
pub fn value_network(node: DocumentNode) -> Self {
|
||||||
|
Self {
|
||||||
|
inputs: vec![0],
|
||||||
|
outputs: vec![NodeOutput::new(0, 0)],
|
||||||
|
nodes: [(0, node)].into_iter().collect(),
|
||||||
|
disabled: vec![],
|
||||||
|
previous_outputs: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A graph with just an input node
|
||||||
|
pub fn new_network() -> Self {
|
||||||
|
Self {
|
||||||
|
inputs: vec![0],
|
||||||
|
outputs: vec![NodeOutput::new(0, 0)],
|
||||||
|
nodes: [(
|
||||||
|
0,
|
||||||
|
DocumentNode {
|
||||||
|
name: "Input Frame".into(),
|
||||||
|
inputs: vec![NodeInput::ShortCircut(concrete!(u32))],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||||
|
metadata: DocumentNodeMetadata { position: (8, 4).into() },
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a new node to the network after the output node and sets it as the new output
|
||||||
|
pub fn push_node(&mut self, mut node: DocumentNode) -> NodeId {
|
||||||
|
let id = self.nodes.len().try_into().expect("Too many nodes in network");
|
||||||
|
// Set the correct position for the new node
|
||||||
|
if let Some(pos) = self.nodes.get(&self.original_outputs()[0].node_id).map(|n| n.metadata.position) {
|
||||||
|
node.metadata.position = pos + IVec2::new(8, 0);
|
||||||
|
}
|
||||||
|
if self.outputs.is_empty() {
|
||||||
|
self.outputs.push(NodeOutput::new(id, 0));
|
||||||
|
}
|
||||||
|
let input = NodeInput::node(self.outputs[0].node_id, self.outputs[0].node_output_index);
|
||||||
|
if node.inputs.is_empty() {
|
||||||
|
node.inputs.push(input);
|
||||||
|
} else {
|
||||||
|
node.inputs[0] = input;
|
||||||
|
}
|
||||||
|
self.nodes.insert(id, node);
|
||||||
|
self.outputs = vec![NodeOutput::new(id, 0)];
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a output identity node to the network
|
||||||
|
pub fn push_output_node(&mut self) -> NodeId {
|
||||||
|
let node = DocumentNode {
|
||||||
|
name: "Output".into(),
|
||||||
|
inputs: vec![],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||||
|
metadata: DocumentNodeMetadata { position: (0, 0).into() },
|
||||||
|
};
|
||||||
|
self.push_node(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a Cache and a Clone node to the network
|
||||||
|
pub fn push_cache_node(&mut self, ty: Type) -> NodeId {
|
||||||
|
let node = DocumentNode {
|
||||||
|
name: "Cache".into(),
|
||||||
|
inputs: vec![],
|
||||||
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
|
inputs: vec![0],
|
||||||
|
outputs: vec![NodeOutput::new(1, 0)],
|
||||||
|
nodes: vec![
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
DocumentNode {
|
||||||
|
name: "CacheNode".to_string(),
|
||||||
|
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::Network(ty)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
|
||||||
|
metadata: Default::default(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
DocumentNode {
|
||||||
|
name: "CloneNode".to_string(),
|
||||||
|
inputs: vec![NodeInput::node(0, 0)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::CloneNode<_>")),
|
||||||
|
metadata: Default::default(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
metadata: DocumentNodeMetadata { position: (0, 0).into() },
|
||||||
|
};
|
||||||
|
self.push_node(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the nested network given by the path of node ids
|
||||||
|
pub fn nested_network(&self, nested_path: &[NodeId]) -> Option<&Self> {
|
||||||
|
let mut network = Some(self);
|
||||||
|
|
||||||
|
for segment in nested_path {
|
||||||
|
network = network.and_then(|network| network.nodes.get(segment)).and_then(|node| node.implementation.get_network());
|
||||||
|
}
|
||||||
|
network
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the mutable nested network given by the path of node ids
|
||||||
|
pub fn nested_network_mut(&mut self, nested_path: &[NodeId]) -> Option<&mut Self> {
|
||||||
|
let mut network = Some(self);
|
||||||
|
|
||||||
|
for segment in nested_path {
|
||||||
|
network = network.and_then(|network| network.nodes.get_mut(segment)).and_then(|node| node.implementation.get_network_mut());
|
||||||
|
}
|
||||||
|
network
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the specified node id is connected to the output
|
||||||
|
pub fn connected_to_output(&self, target_node_id: NodeId, ignore_imaginate: bool) -> bool {
|
||||||
|
// If the node is the output then return true
|
||||||
|
if self.outputs.iter().any(|&NodeOutput { node_id, .. }| node_id == target_node_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Get the outputs
|
||||||
|
let Some(mut stack) = self.outputs.iter().map(|&output| self.nodes.get(&output.node_id)).collect::<Option<Vec<_>>>() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let mut already_visited = HashSet::new();
|
||||||
|
already_visited.extend(self.outputs.iter().map(|output| output.node_id));
|
||||||
|
|
||||||
|
while let Some(node) = stack.pop() {
|
||||||
|
// Skip the imaginate node inputs
|
||||||
|
if ignore_imaginate && node.name == "Imaginate" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for input in &node.inputs {
|
||||||
|
if let &NodeInput::Node { node_id: ref_id, .. } = input {
|
||||||
|
// Skip if already viewed
|
||||||
|
if already_visited.contains(&ref_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the target node is used as input then return true
|
||||||
|
if ref_id == target_node_id {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Add the referenced node to the stack
|
||||||
|
let Some(ref_node) = self.nodes.get(&ref_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
already_visited.insert(ref_id);
|
||||||
|
stack.push(ref_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is the node being used directly as an output?
|
||||||
|
pub fn outputs_contain(&self, node_id: NodeId) -> bool {
|
||||||
|
self.outputs.iter().any(|output| output.node_id == node_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is the node being used directly as an original output?
|
||||||
|
pub fn original_outputs_contain(&self, node_id: NodeId) -> bool {
|
||||||
|
self.original_outputs().iter().any(|output| output.node_id == node_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is the node being used directly as a previous output?
|
||||||
|
pub fn previous_outputs_contain(&self, node_id: NodeId) -> Option<bool> {
|
||||||
|
self.previous_outputs.as_ref().map(|outputs| outputs.iter().any(|output| output.node_id == node_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A iterator of all nodes connected by primary inputs.
|
||||||
|
///
|
||||||
|
/// Used for the properties panel and tools.
|
||||||
|
pub fn primary_flow(&self) -> impl Iterator<Item = (&DocumentNode, u64)> {
|
||||||
|
struct FlowIter<'a> {
|
||||||
|
stack: Vec<NodeId>,
|
||||||
|
network: &'a NodeNetwork,
|
||||||
|
}
|
||||||
|
impl<'a> Iterator for FlowIter<'a> {
|
||||||
|
type Item = (&'a DocumentNode, NodeId);
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
let node_id = self.stack.pop()?;
|
||||||
|
if let Some(document_node) = self.network.nodes.get(&node_id) {
|
||||||
|
self.stack.extend(
|
||||||
|
document_node
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.take(1) // Only show the primary input
|
||||||
|
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
|
||||||
|
);
|
||||||
|
return Some((document_node, node_id));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FlowIter {
|
||||||
|
stack: self.outputs.iter().map(|output| output.node_id).collect(),
|
||||||
|
network: &self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Functions for compiling the network
|
||||||
impl NodeNetwork {
|
impl NodeNetwork {
|
||||||
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) {
|
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) {
|
||||||
self.inputs.iter_mut().for_each(|id| *id = f(*id));
|
self.inputs.iter_mut().for_each(|id| *id = f(*id));
|
||||||
@@ -405,6 +633,31 @@ impl NodeNetwork {
|
|||||||
self.nodes.insert(id, node);
|
self.nodes.insert(id, node);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// replace value inputs with value nodes
|
||||||
|
for input in &mut node.inputs {
|
||||||
|
let mut dummy_input = NodeInput::ShortCircut(concrete!(()));
|
||||||
|
std::mem::swap(&mut dummy_input, input);
|
||||||
|
if let NodeInput::Value { tagged_value, exposed } = dummy_input {
|
||||||
|
let value_node_id = gen_id();
|
||||||
|
let value_node_id = map_ids(id, value_node_id);
|
||||||
|
self.nodes.insert(
|
||||||
|
value_node_id,
|
||||||
|
DocumentNode {
|
||||||
|
name: "Value".into(),
|
||||||
|
inputs: vec![NodeInput::Value { tagged_value, exposed }],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
*input = NodeInput::Node {
|
||||||
|
node_id: value_node_id,
|
||||||
|
output_index: 0,
|
||||||
|
lambda: false,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
*input = dummy_input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match node.implementation {
|
match node.implementation {
|
||||||
DocumentNodeImplementation::Network(mut inner_network) => {
|
DocumentNodeImplementation::Network(mut inner_network) => {
|
||||||
@@ -423,20 +676,6 @@ impl NodeNetwork {
|
|||||||
let network_input = self.nodes.get_mut(network_input).unwrap();
|
let network_input = self.nodes.get_mut(network_input).unwrap();
|
||||||
network_input.populate_first_network_input(node_id, output_index, *offset, lambda);
|
network_input.populate_first_network_input(node_id, output_index, *offset, lambda);
|
||||||
}
|
}
|
||||||
NodeInput::Value { tagged_value, exposed } => {
|
|
||||||
let name = "Value".to_string();
|
|
||||||
let new_id = map_ids(id, gen_id());
|
|
||||||
let value_node = DocumentNode {
|
|
||||||
name,
|
|
||||||
inputs: vec![NodeInput::Value { tagged_value, exposed }],
|
|
||||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()),
|
|
||||||
metadata: DocumentNodeMetadata::default(),
|
|
||||||
};
|
|
||||||
assert!(!self.nodes.contains_key(&new_id));
|
|
||||||
self.nodes.insert(new_id, value_node);
|
|
||||||
let network_input = self.nodes.get_mut(network_input).unwrap();
|
|
||||||
network_input.populate_first_network_input(new_id, 0, *offset, false);
|
|
||||||
}
|
|
||||||
NodeInput::Network(_) => {
|
NodeInput::Network(_) => {
|
||||||
*network_offsets.get_mut(network_input).unwrap() += 1;
|
*network_offsets.get_mut(network_input).unwrap() += 1;
|
||||||
if let Some(index) = self.inputs.iter().position(|i| *i == id) {
|
if let Some(index) = self.inputs.iter().position(|i| *i == id) {
|
||||||
@@ -444,6 +683,7 @@ impl NodeNetwork {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeInput::ShortCircut(_) => (),
|
NodeInput::ShortCircut(_) => (),
|
||||||
|
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
|
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
|
||||||
@@ -461,7 +701,7 @@ impl NodeNetwork {
|
|||||||
self.flatten_with_fns(node_id, map_ids, gen_id);
|
self.flatten_with_fns(node_id, map_ids, gen_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentNodeImplementation::Unresolved(_) => (),
|
DocumentNodeImplementation::Unresolved(_) => {}
|
||||||
}
|
}
|
||||||
assert!(!self.nodes.contains_key(&id), "Trying to insert a node into the network caused an id conflict");
|
assert!(!self.nodes.contains_key(&id), "Trying to insert a node into the network caused an id conflict");
|
||||||
self.nodes.insert(id, node);
|
self.nodes.insert(id, node);
|
||||||
@@ -478,151 +718,6 @@ impl NodeNetwork {
|
|||||||
nodes: nodes.clone(),
|
nodes: nodes.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the original output nodes of this network, ignoring any preview node
|
|
||||||
pub fn original_outputs(&self) -> &Vec<NodeOutput> {
|
|
||||||
self.previous_outputs.as_ref().unwrap_or(&self.outputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A graph with just an input and output node
|
|
||||||
pub fn new_network(output_offset: i32, output_node_id: NodeId) -> Self {
|
|
||||||
Self {
|
|
||||||
inputs: vec![0],
|
|
||||||
outputs: vec![NodeOutput::new(1, 0)],
|
|
||||||
nodes: [
|
|
||||||
(
|
|
||||||
0,
|
|
||||||
DocumentNode {
|
|
||||||
name: "Input Frame".into(),
|
|
||||||
inputs: vec![NodeInput::Network(concrete!(u32))],
|
|
||||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
|
||||||
metadata: DocumentNodeMetadata { position: (8, 4).into() },
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
1,
|
|
||||||
DocumentNode {
|
|
||||||
name: "Output".into(),
|
|
||||||
inputs: vec![NodeInput::node(output_node_id, 0)],
|
|
||||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
|
||||||
metadata: DocumentNodeMetadata { position: (output_offset, 4).into() },
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the nested network given by the path of node ids
|
|
||||||
pub fn nested_network(&self, nested_path: &[NodeId]) -> Option<&Self> {
|
|
||||||
let mut network = Some(self);
|
|
||||||
|
|
||||||
for segment in nested_path {
|
|
||||||
network = network.and_then(|network| network.nodes.get(segment)).and_then(|node| node.implementation.get_network());
|
|
||||||
}
|
|
||||||
network
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the mutable nested network given by the path of node ids
|
|
||||||
pub fn nested_network_mut(&mut self, nested_path: &[NodeId]) -> Option<&mut Self> {
|
|
||||||
let mut network = Some(self);
|
|
||||||
|
|
||||||
for segment in nested_path {
|
|
||||||
network = network.and_then(|network| network.nodes.get_mut(segment)).and_then(|node| node.implementation.get_network_mut());
|
|
||||||
}
|
|
||||||
network
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the specified node id is connected to the output
|
|
||||||
pub fn connected_to_output(&self, target_node_id: NodeId, ignore_imaginate: bool) -> bool {
|
|
||||||
// If the node is the output then return true
|
|
||||||
if self.outputs.iter().any(|&NodeOutput { node_id, .. }| node_id == target_node_id) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Get the outputs
|
|
||||||
let Some(mut stack) = self.outputs.iter().map(|&output| self.nodes.get(&output.node_id)).collect::<Option<Vec<_>>>() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let mut already_visited = HashSet::new();
|
|
||||||
already_visited.extend(self.outputs.iter().map(|output| output.node_id));
|
|
||||||
|
|
||||||
while let Some(node) = stack.pop() {
|
|
||||||
// Skip the imaginate node inputs
|
|
||||||
if ignore_imaginate && node.name == "Imaginate" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for input in &node.inputs {
|
|
||||||
if let &NodeInput::Node { node_id: ref_id, .. } = input {
|
|
||||||
// Skip if already viewed
|
|
||||||
if already_visited.contains(&ref_id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// If the target node is used as input then return true
|
|
||||||
if ref_id == target_node_id {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Add the referenced node to the stack
|
|
||||||
let Some(ref_node) = self.nodes.get(&ref_id) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
already_visited.insert(ref_id);
|
|
||||||
stack.push(ref_node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the node being used directly as an output?
|
|
||||||
pub fn outputs_contain(&self, node_id: NodeId) -> bool {
|
|
||||||
self.outputs.iter().any(|output| output.node_id == node_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the node being used directly as an original output?
|
|
||||||
pub fn original_outputs_contain(&self, node_id: NodeId) -> bool {
|
|
||||||
self.original_outputs().iter().any(|output| output.node_id == node_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the node being used directly as a previous output?
|
|
||||||
pub fn previous_outputs_contain(&self, node_id: NodeId) -> Option<bool> {
|
|
||||||
self.previous_outputs.as_ref().map(|outputs| outputs.iter().any(|output| output.node_id == node_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A iterator of all nodes connected by primary inputs.
|
|
||||||
///
|
|
||||||
/// Used for the properties panel and tools.
|
|
||||||
pub fn primary_flow(&self) -> impl Iterator<Item = (&DocumentNode, u64)> {
|
|
||||||
struct FlowIter<'a> {
|
|
||||||
stack: Vec<NodeId>,
|
|
||||||
network: &'a NodeNetwork,
|
|
||||||
}
|
|
||||||
impl<'a> Iterator for FlowIter<'a> {
|
|
||||||
type Item = (&'a DocumentNode, NodeId);
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
loop {
|
|
||||||
let node_id = self.stack.pop()?;
|
|
||||||
if let Some(document_node) = self.network.nodes.get(&node_id) {
|
|
||||||
self.stack.extend(
|
|
||||||
document_node
|
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.take(1) // Only show the primary input
|
|
||||||
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
|
|
||||||
);
|
|
||||||
return Some((document_node, node_id));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FlowIter {
|
|
||||||
stack: self.outputs.iter().map(|output| output.node_id).collect(),
|
|
||||||
network: &self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ pub enum TaggedValue {
|
|||||||
Quantization(graphene_core::quantization::QuantizationChannels),
|
Quantization(graphene_core::quantization::QuantizationChannels),
|
||||||
OptionalColor(Option<graphene_core::raster::color::Color>),
|
OptionalColor(Option<graphene_core::raster::color::Color>),
|
||||||
ManipulatorGroupIds(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
ManipulatorGroupIds(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
||||||
|
VecDVec2(Vec<DVec2>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||||
@@ -104,6 +105,12 @@ impl Hash for TaggedValue {
|
|||||||
Self::Quantization(quantized_image) => quantized_image.hash(state),
|
Self::Quantization(quantized_image) => quantized_image.hash(state),
|
||||||
Self::OptionalColor(color) => color.hash(state),
|
Self::OptionalColor(color) => color.hash(state),
|
||||||
Self::ManipulatorGroupIds(mirror) => mirror.hash(state),
|
Self::ManipulatorGroupIds(mirror) => mirror.hash(state),
|
||||||
|
Self::VecDVec2(vec_dvec2) => {
|
||||||
|
vec_dvec2.len().hash(state);
|
||||||
|
for dvec2 in vec_dvec2 {
|
||||||
|
dvec2.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,6 +152,7 @@ impl<'a> TaggedValue {
|
|||||||
TaggedValue::Quantization(x) => Box::new(x),
|
TaggedValue::Quantization(x) => Box::new(x),
|
||||||
TaggedValue::OptionalColor(x) => Box::new(x),
|
TaggedValue::OptionalColor(x) => Box::new(x),
|
||||||
TaggedValue::ManipulatorGroupIds(x) => Box::new(x),
|
TaggedValue::ManipulatorGroupIds(x) => Box::new(x),
|
||||||
|
TaggedValue::VecDVec2(x) => Box::new(x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +193,7 @@ impl<'a> TaggedValue {
|
|||||||
TaggedValue::Quantization(_) => concrete!(graphene_core::quantization::QuantizationChannels),
|
TaggedValue::Quantization(_) => concrete!(graphene_core::quantization::QuantizationChannels),
|
||||||
TaggedValue::OptionalColor(_) => concrete!(Option<graphene_core::Color>),
|
TaggedValue::OptionalColor(_) => concrete!(Option<graphene_core::Color>),
|
||||||
TaggedValue::ManipulatorGroupIds(_) => concrete!(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
TaggedValue::ManipulatorGroupIds(_) => concrete!(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
||||||
|
TaggedValue::VecDVec2(_) => concrete!(Vec<DVec2>),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,6 +403,11 @@ impl TypingContext {
|
|||||||
self.constructor.get(&node_id).copied()
|
self.constructor.get(&node_id).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of a given node id if it exists
|
||||||
|
pub fn type_of(&self, node_id: NodeId) -> Option<&NodeIOTypes> {
|
||||||
|
self.inferred.get(&node_id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the inferred types for a given node id.
|
/// Returns the inferred types for a given node id.
|
||||||
pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<NodeIOTypes, String> {
|
pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<NodeIOTypes, String> {
|
||||||
let identifier = node.identifier.name.clone();
|
let identifier = node.identifier.name.clone();
|
||||||
@@ -416,7 +421,8 @@ impl TypingContext {
|
|||||||
// If the node has a value parameter we can infer the return type from it
|
// If the node has a value parameter we can infer the return type from it
|
||||||
ConstructionArgs::Value(ref v) => {
|
ConstructionArgs::Value(ref v) => {
|
||||||
assert!(matches!(node.input, ProtoNodeInput::None));
|
assert!(matches!(node.input, ProtoNodeInput::None));
|
||||||
let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![]);
|
// TODO: This should return a reference to the value
|
||||||
|
let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![v.ty()]);
|
||||||
self.inferred.insert(node_id, types.clone());
|
self.inferred.insert(node_id, types.clone());
|
||||||
return Ok(types);
|
return Ok(types);
|
||||||
}
|
}
|
||||||
@@ -427,9 +433,9 @@ impl TypingContext {
|
|||||||
self.inferred
|
self.inferred
|
||||||
.get(id)
|
.get(id)
|
||||||
.ok_or(format!("Inferring type of {node_id} depends on {id} which is not present in the typing context"))
|
.ok_or(format!("Inferring type of {node_id} depends on {id} which is not present in the typing context"))
|
||||||
.map(|node| (node.input.clone(), node.output.clone()))
|
.map(|node| node.ty())
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<(Type, Type)>, String>>()?,
|
.collect::<Result<Vec<Type>, String>>()?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the node input type from the proto node declaration
|
// Get the node input type from the proto node declaration
|
||||||
@@ -450,26 +456,27 @@ impl TypingContext {
|
|||||||
if matches!(input, Type::Generic(_)) {
|
if matches!(input, Type::Generic(_)) {
|
||||||
return Err(format!("Generic types are not supported as inputs yet {:?} occured in {:?}", &input, node.identifier));
|
return Err(format!("Generic types are not supported as inputs yet {:?} occured in {:?}", &input, node.identifier));
|
||||||
}
|
}
|
||||||
if parameters.iter().any(|p| matches!(p.1, Type::Generic(_))) {
|
if parameters.iter().any(|p| match p {
|
||||||
|
Type::Fn(_, b) if matches!(b.as_ref(), Type::Generic(_)) => true,
|
||||||
|
_ => false,
|
||||||
|
}) {
|
||||||
return Err(format!("Generic types are not supported in parameters: {:?} occured in {:?}", parameters, node.identifier));
|
return Err(format!("Generic types are not supported in parameters: {:?} occured in {:?}", parameters, node.identifier));
|
||||||
}
|
}
|
||||||
let covariant = |output, input| match (&output, &input) {
|
fn covariant(from: &Type, to: &Type) -> bool {
|
||||||
(Type::Concrete(t1), Type::Concrete(t2)) => t1 == t2,
|
match (from, to) {
|
||||||
(Type::Concrete(_), Type::Generic(_)) => true,
|
(Type::Concrete(t1), Type::Concrete(t2)) => t1 == t2,
|
||||||
// TODO: relax this requirement when allowing generic types as inputs
|
(Type::Fn(a1, b1), Type::Fn(a2, b2)) => covariant(a1, a2) && covariant(b1, b2),
|
||||||
(Type::Generic(_), _) => false,
|
// TODO: relax this requirement when allowing generic types as inputs
|
||||||
};
|
(Type::Generic(_), _) => false,
|
||||||
|
(_, Type::Generic(_)) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// List of all implementations that match the input and parameter types
|
// List of all implementations that match the input and parameter types
|
||||||
let valid_output_types = impls
|
let valid_output_types = impls
|
||||||
.keys()
|
.keys()
|
||||||
.filter(|node_io| {
|
.filter(|node_io| covariant(&input, &node_io.input) && parameters.iter().zip(node_io.parameters.iter()).all(|(p1, p2)| covariant(p1, p2) && covariant(p1, p2)))
|
||||||
covariant(input.clone(), node_io.input.clone())
|
|
||||||
&& parameters
|
|
||||||
.iter()
|
|
||||||
.zip(node_io.parameters.iter())
|
|
||||||
.all(|(p1, p2)| covariant(p1.0.clone(), p2.0.clone()) && covariant(p1.1.clone(), p2.1.clone()))
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Attempt to substitute generic types with concrete types and save the list of results
|
// Attempt to substitute generic types with concrete types and save the list of results
|
||||||
@@ -517,7 +524,7 @@ impl TypingContext {
|
|||||||
|
|
||||||
/// Returns a list of all generic types used in the node
|
/// Returns a list of all generic types used in the node
|
||||||
fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
|
fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
|
||||||
let inputs = [&types.input].into_iter().chain(types.parameters.iter().map(|(_, x)| x));
|
let inputs = [&types.input].into_iter().chain(types.parameters.iter().flat_map(|x| x.fn_output()));
|
||||||
let mut generics = inputs
|
let mut generics = inputs
|
||||||
.filter_map(|t| match t {
|
.filter_map(|t| match t {
|
||||||
Type::Generic(out) => Some(out.clone()),
|
Type::Generic(out) => Some(out.clone()),
|
||||||
@@ -532,15 +539,16 @@ fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a generic type can be substituted with a concrete type and returns the concrete type
|
/// Checks if a generic type can be substituted with a concrete type and returns the concrete type
|
||||||
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[(Type, Type)], generic: &str) -> Result<Type, String> {
|
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result<Type, String> {
|
||||||
let inputs = [(&types.input, input)]
|
let inputs = [(Some(&types.input), Some(input))]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(types.parameters.iter().map(|(_, x)| x).zip(parameters.iter().map(|(_, x)| x)));
|
.chain(types.parameters.iter().map(|x| x.fn_output()).zip(parameters.iter().map(|x| x.fn_output())));
|
||||||
let mut concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Type::Generic(input) if generic == input));
|
let concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Some(Type::Generic(input)) if generic == input));
|
||||||
let (_, out_ty) = concrete_inputs
|
let mut outputs = concrete_inputs.flat_map(|(_, out)| out);
|
||||||
|
let out_ty = outputs
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| format!("Generic output type {generic} is not dependent on input {input:?} or parameters {parameters:?}",))?;
|
.ok_or_else(|| format!("Generic output type {generic} is not dependent on input {input:?} or parameters {parameters:?}",))?;
|
||||||
if concrete_inputs.any(|(_, ty)| ty != out_ty) {
|
if outputs.any(|ty| ty != out_ty) {
|
||||||
return Err(format!("Generic output type {generic} is dependent on multiple inputs or parameters",));
|
return Err(format!("Generic output type {generic} is dependent on multiple inputs or parameters",));
|
||||||
}
|
}
|
||||||
Ok(out_ty.clone())
|
Ok(out_ty.clone())
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ impl<N: Clone, O: StaticType> Clone for DowncastNode<O, N> {
|
|||||||
impl<N: Copy, O: StaticType> Copy for DowncastNode<O, N> {}
|
impl<N: Copy, O: StaticType> Copy for DowncastNode<O, N> {}
|
||||||
|
|
||||||
#[node_macro::node_fn(DowncastNode<_O>)]
|
#[node_macro::node_fn(DowncastNode<_O>)]
|
||||||
fn downcast<N, _O: StaticType>(input: Any<'input>, node: &'input N) -> _O
|
fn downcast<N: 'input, _O: StaticType>(input: Any<'input>, node: &'input N) -> _O
|
||||||
where
|
where
|
||||||
N: for<'any_input> Node<'any_input, Any<'any_input>, Output = Any<'any_input>> + 'input,
|
N: for<'any_input> Node<'any_input, Any<'any_input>, Output = Any<'any_input>> + 'input,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graphene_core::raster::{Color, Image, ImageFrame};
|
||||||
|
use graphene_core::transform::TransformMut;
|
||||||
|
use graphene_core::vector::VectorData;
|
||||||
|
use graphene_core::Node;
|
||||||
|
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)]
|
||||||
|
pub struct ReduceNode<Initial, Lambda> {
|
||||||
|
pub initial: Initial,
|
||||||
|
pub lambda: Lambda,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_fn(ReduceNode)]
|
||||||
|
fn reduce<I: Iterator, Lambda, T>(iter: I, initial: T, lambda: &'any_input Lambda) -> T
|
||||||
|
where
|
||||||
|
Lambda: for<'a> Node<'a, (T, I::Item), Output = T>,
|
||||||
|
{
|
||||||
|
iter.fold(initial, |a, x| lambda.eval((a, x)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct IntoIterNode<T> {
|
||||||
|
_t: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_fn(IntoIterNode<_T>)]
|
||||||
|
fn into_iter<'i: 'input, _T: Send + Sync>(vec: &'i Vec<_T>) -> Box<dyn Iterator<Item = &'i _T> + Send + Sync + 'i> {
|
||||||
|
Box::new(vec.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct VectorPointsNode;
|
||||||
|
|
||||||
|
#[node_fn(VectorPointsNode)]
|
||||||
|
fn vector_points(vector: VectorData) -> Vec<DVec2> {
|
||||||
|
vector.subpaths.iter().flat_map(|subpath| subpath.manipulator_groups().iter().map(|group| group.anchor)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct BrushTextureNode<ColorNode, Hardness, Flow> {
|
||||||
|
pub color: ColorNode,
|
||||||
|
pub hardness: Hardness,
|
||||||
|
pub flow: Flow,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct EraseNode<Flow> {
|
||||||
|
flow: Flow,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_fn(EraseNode)]
|
||||||
|
fn erase(input: (Color, Color), flow: f64) -> Color {
|
||||||
|
let (input, brush) = input;
|
||||||
|
let alpha = input.a() * (1.0 - flow as f32 * brush.a());
|
||||||
|
Color::from_unassociated_alpha(input.r(), input.g(), input.b(), alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_fn(BrushTextureNode)]
|
||||||
|
fn brush_texture(diameter: f64, color: Color, hardness: f64, flow: f64) -> ImageFrame {
|
||||||
|
// Diameter
|
||||||
|
let radius = diameter / 2.;
|
||||||
|
// TODO: Remove the 4px padding after figuring out why the brush stamp gets randomly offset by 1px up/down/left/right when clicking with the Brush tool
|
||||||
|
let dimension = diameter.ceil() as u32 + 4;
|
||||||
|
let center = DVec2::splat(radius + (dimension as f64 - diameter) / 2.);
|
||||||
|
|
||||||
|
// Hardness
|
||||||
|
let hardness = hardness / 100.;
|
||||||
|
let feather_exponent = 1. / (1. - hardness);
|
||||||
|
|
||||||
|
// Flow
|
||||||
|
let flow = flow / 100.;
|
||||||
|
|
||||||
|
// Color
|
||||||
|
let color = color.apply_opacity(flow as f32);
|
||||||
|
|
||||||
|
// Initial transparent image
|
||||||
|
let mut image = Image::new(dimension, dimension, Color::TRANSPARENT);
|
||||||
|
|
||||||
|
for y in 0..dimension {
|
||||||
|
for x in 0..dimension {
|
||||||
|
let summation = MULTISAMPLE_GRID.iter().fold(0., |acc, (offset_x, offset_y)| {
|
||||||
|
let position = DVec2::new(x as f64 + offset_x, y as f64 + offset_y);
|
||||||
|
let distance = (position - center).length();
|
||||||
|
|
||||||
|
if distance < radius {
|
||||||
|
acc + (1. - (distance / radius).powf(feather_exponent)).clamp(0., 1.)
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let pixel_fill = summation / MULTISAMPLE_GRID.len() as f64;
|
||||||
|
|
||||||
|
let pixel = image.get_mut(x, y).unwrap();
|
||||||
|
*pixel = color.apply_opacity(pixel_fill as f32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageFrame {
|
||||||
|
image,
|
||||||
|
transform: DAffine2::from_scale_angle_translation(DVec2::splat(dimension as f64), 0., -DVec2::splat(radius)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct TranslateNode<Translatable> {
|
||||||
|
translatable: Translatable,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_fn(TranslateNode)]
|
||||||
|
fn translate_node<Data: TransformMut>(offset: DVec2, mut translatable: Data) -> Data {
|
||||||
|
translatable.translate(offset);
|
||||||
|
translatable
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::raster::*;
|
||||||
|
use glam::DAffine2;
|
||||||
|
use graphene_core::ops::{AddNode, CloneNode};
|
||||||
|
use graphene_core::raster::*;
|
||||||
|
use graphene_core::structural::Then;
|
||||||
|
use graphene_core::transform::{Transform, TransformMut};
|
||||||
|
use graphene_core::value::{ClonedNode, ValueNode};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_translate_node() {
|
||||||
|
let image = Image::new(10, 10, Color::TRANSPARENT);
|
||||||
|
let mut image = ImageFrame { image, transform: DAffine2::IDENTITY };
|
||||||
|
image.translate(DVec2::new(1.0, 2.0));
|
||||||
|
let translate_node = TranslateNode::new(ClonedNode::new(image));
|
||||||
|
let image = translate_node.eval(DVec2::new(1.0, 2.0));
|
||||||
|
assert_eq!(image.transform(), DAffine2::from_translation(DVec2::new(2.0, 4.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reduce() {
|
||||||
|
let reduce_node = ReduceNode::new(ClonedNode::new(0u32), ValueNode::new(AddNode));
|
||||||
|
let sum = reduce_node.eval(vec![1, 2, 3, 4, 5].into_iter());
|
||||||
|
assert_eq!(sum, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_brush_texture() {
|
||||||
|
let brush_texture_node = BrushTextureNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(100.), ClonedNode::new(100.));
|
||||||
|
let size = 20.;
|
||||||
|
let image = brush_texture_node.eval(size);
|
||||||
|
assert_eq!(image.image.width, size.ceil() as u32 + 4);
|
||||||
|
assert_eq!(image.image.height, size.ceil() as u32 + 4);
|
||||||
|
assert_eq!(image.transform, DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil() + 4.), 0., -DVec2::splat(size / 2.)));
|
||||||
|
// center pixel should be BLACK
|
||||||
|
assert_eq!(image.image.get(11, 11), Some(&Color::BLACK));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_brush() {
|
||||||
|
let brush_texture_node = BrushTextureNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(1.0), ClonedNode::new(1.0));
|
||||||
|
let image = brush_texture_node.eval(20.);
|
||||||
|
let trace = vec![DVec2::new(0.0, 0.0), DVec2::new(10.0, 0.0)];
|
||||||
|
let trace = ClonedNode::new(trace.into_iter());
|
||||||
|
let translate_node = TranslateNode::new(ClonedNode::new(image));
|
||||||
|
let frames = MapNode::new(ValueNode::new(translate_node));
|
||||||
|
let frames = trace.then(frames).eval(()).collect::<Vec<_>>();
|
||||||
|
assert_eq!(frames.len(), 2);
|
||||||
|
assert_eq!(frames[0].image.width, 24);
|
||||||
|
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 = ClonedNode::new(background_bounds.unwrap().to_transform());
|
||||||
|
let background_image = background_bounds.then(EmptyImageNode::new(ClonedNode::new(Color::TRANSPARENT)));
|
||||||
|
let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(BlendMode::Normal), ClonedNode::new(1.0));
|
||||||
|
let final_image = ReduceNode::new(background_image, ValueNode::new(BlendImageTupleNode::new(ValueNode::new(blend_node))));
|
||||||
|
let final_image = final_image.eval(frames.into_iter());
|
||||||
|
assert_eq!(final_image.image.height, 24);
|
||||||
|
assert_eq!(final_image.image.width, 34);
|
||||||
|
drop(final_image);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,3 +19,5 @@ pub mod executor;
|
|||||||
pub mod quantization;
|
pub mod quantization;
|
||||||
|
|
||||||
pub use graphene_core::*;
|
pub use graphene_core::*;
|
||||||
|
|
||||||
|
pub mod brush;
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use dyn_any::{DynAny, StaticType};
|
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||||
|
|
||||||
use glam::{BVec2, DAffine2, DVec2};
|
use glam::{BVec2, DAffine2, DVec2};
|
||||||
use graphene_core::raster::{Color, Image, ImageFrame};
|
use graphene_core::raster::{Color, Image, ImageFrame};
|
||||||
|
use graphene_core::transform::Transform;
|
||||||
|
use graphene_core::value::{ClonedNode, ValueNode};
|
||||||
use graphene_core::Node;
|
use graphene_core::Node;
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Debug, DynAny)]
|
#[derive(Debug, DynAny)]
|
||||||
@@ -139,6 +142,10 @@ pub struct MapImageFrameNode<MapFn> {
|
|||||||
map_fn: MapFn,
|
map_fn: MapFn,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<MapFn: dyn_any::StaticTypeSized> StaticType for MapImageFrameNode<MapFn> {
|
||||||
|
type Static = MapImageFrameNode<MapFn::Static>;
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(MapImageFrameNode)]
|
#[node_macro::node_fn(MapImageFrameNode)]
|
||||||
fn map_image<MapFn>(mut image_frame: ImageFrame, map_fn: &'any_input MapFn) -> ImageFrame
|
fn map_image<MapFn>(mut image_frame: ImageFrame, map_fn: &'any_input MapFn) -> ImageFrame
|
||||||
where
|
where
|
||||||
@@ -151,12 +158,37 @@ where
|
|||||||
image_frame
|
image_frame
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, DynAny)]
|
||||||
struct AxisAlignedBbox {
|
pub struct AxisAlignedBbox {
|
||||||
start: DVec2,
|
start: DVec2,
|
||||||
end: DVec2,
|
end: DVec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AxisAlignedBbox {
|
||||||
|
pub fn size(&self) -> DVec2 {
|
||||||
|
self.end - self.start
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_transform(&self) -> DAffine2 {
|
||||||
|
DAffine2::from_translation(self.start) * DAffine2::from_scale(self.size())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, point: DVec2) -> bool {
|
||||||
|
point.x >= self.start.x && point.x <= self.end.x && point.y >= self.start.y && point.y <= self.end.y
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intersects(&self, other: &AxisAlignedBbox) -> bool {
|
||||||
|
other.start.x <= self.end.x && other.end.x >= self.start.x && other.start.y <= self.end.y && other.end.y >= self.start.y
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn union(&self, other: &AxisAlignedBbox) -> AxisAlignedBbox {
|
||||||
|
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)]
|
||||||
struct Bbox {
|
struct Bbox {
|
||||||
top_left: DVec2,
|
top_left: DVec2,
|
||||||
@@ -228,18 +260,42 @@ fn mask_image(mut image: ImageFrame, mask: ImageFrame) -> ImageFrame {
|
|||||||
image
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct BlendImageTupleNode<MapFn> {
|
||||||
|
map_fn: MapFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MapFn: StaticTypeSized> StaticType for BlendImageTupleNode<MapFn> {
|
||||||
|
type Static = BlendImageTupleNode<MapFn::Static>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node_fn(BlendImageTupleNode)]
|
||||||
|
fn blend_image_tuple<MapFn>(images: (ImageFrame, ImageFrame), map_fn: &'any_input MapFn) -> ImageFrame
|
||||||
|
where
|
||||||
|
MapFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color> + 'input + Clone,
|
||||||
|
{
|
||||||
|
let (mut background, foreground) = images;
|
||||||
|
let node = BlendImageNode::new(ClonedNode::new(background), ValueNode::new(map_fn.clone()));
|
||||||
|
node.eval(foreground)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct BlendImageNode<Background, MapFn> {
|
pub struct BlendImageNode<Background, MapFn> {
|
||||||
background: Background,
|
background: Background,
|
||||||
map_fn: MapFn,
|
map_fn: MapFn,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Background: StaticTypeSized, MapFn: StaticTypeSized> StaticType for BlendImageNode<Background, MapFn> {
|
||||||
|
type Static = BlendImageNode<Background::Static, MapFn::Static>;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Implement proper blending
|
// TODO: Implement proper blending
|
||||||
#[node_macro::node_fn(BlendImageNode)]
|
#[node_macro::node_fn(BlendImageNode)]
|
||||||
fn blend_image<MapFn>(foreground: ImageFrame, mut background: ImageFrame, map_fn: &'any_input MapFn) -> ImageFrame
|
fn blend_image<MapFn, Frame: AsRef<ImageFrame>>(foreground: Frame, mut background: ImageFrame, map_fn: &'any_input MapFn) -> ImageFrame
|
||||||
where
|
where
|
||||||
MapFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color> + 'input,
|
MapFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color> + 'input,
|
||||||
{
|
{
|
||||||
|
let foreground = foreground.as_ref();
|
||||||
let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64);
|
let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64);
|
||||||
let background_size = DVec2::new(background.image.width as f64, background.image.height as f64);
|
let background_size = DVec2::new(background.image.width as f64, background.image.height as f64);
|
||||||
|
|
||||||
@@ -271,6 +327,38 @@ where
|
|||||||
background
|
background
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct MergeBoundingBoxNode<Data> {
|
||||||
|
_data: PhantomData<Data>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node_fn(MergeBoundingBoxNode<_Data>)]
|
||||||
|
fn merge_bounding_box_node<_Data: Transform>(input: (Option<AxisAlignedBbox>, _Data)) -> Option<AxisAlignedBbox> {
|
||||||
|
let (initial_aabb, data) = input;
|
||||||
|
|
||||||
|
let snd_aabb = compute_transformed_bounding_box(data.transform()).axis_aligned_bbox();
|
||||||
|
|
||||||
|
if let Some(fst_aabb) = initial_aabb {
|
||||||
|
Some(fst_aabb.union(&snd_aabb))
|
||||||
|
} else {
|
||||||
|
Some(snd_aabb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct EmptyImageNode<FillColor> {
|
||||||
|
pub color: FillColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node_fn(EmptyImageNode)]
|
||||||
|
fn empty_image(transform: DAffine2, color: Color) -> ImageFrame {
|
||||||
|
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
|
||||||
|
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
|
||||||
|
|
||||||
|
let image = Image::new(width, height, color);
|
||||||
|
ImageFrame { image, transform }
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ImaginateNode<E> {
|
pub struct ImaginateNode<E> {
|
||||||
cached: E,
|
cached: E,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use graph_craft::document::value::UpcastNode;
|
|||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graph_craft::executor::Executor;
|
use graph_craft::executor::Executor;
|
||||||
use graph_craft::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, TypingContext};
|
use graph_craft::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, TypingContext};
|
||||||
|
use graph_craft::{Type, TypeDescriptor};
|
||||||
use graphene_std::any::{Any, TypeErasedPinned, TypeErasedPinnedRef};
|
use graphene_std::any::{Any, TypeErasedPinned, TypeErasedPinnedRef};
|
||||||
|
|
||||||
use crate::node_registry;
|
use crate::node_registry;
|
||||||
@@ -48,6 +49,14 @@ impl DynamicExecutor {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn input_type(&self) -> Option<Type> {
|
||||||
|
self.typing_context.type_of(self.output).map(|node_io| node_io.input.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_type(&self) -> Option<Type> {
|
||||||
|
self.typing_context.type_of(self.output).map(|node_io| node_io.output.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Executor for DynamicExecutor {
|
impl Executor for DynamicExecutor {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use graphene_core::raster::*;
|
|||||||
use graphene_core::structural::Then;
|
use graphene_core::structural::Then;
|
||||||
use graphene_core::value::{ClonedNode, ForgetNode, ValueNode};
|
use graphene_core::value::{ClonedNode, ForgetNode, ValueNode};
|
||||||
use graphene_core::{Node, NodeIO, NodeIOTypes};
|
use graphene_core::{Node, NodeIO, NodeIOTypes};
|
||||||
|
use graphene_std::brush::*;
|
||||||
|
use graphene_std::raster::*;
|
||||||
|
|
||||||
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DowncastBothRefNode, DynAnyInRefNode, DynAnyNode, DynAnyRefNode, IntoTypeErasedNode, TypeErasedPinnedRef};
|
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DowncastBothRefNode, DynAnyInRefNode, DynAnyNode, DynAnyRefNode, IntoTypeErasedNode, TypeErasedPinnedRef};
|
||||||
|
|
||||||
@@ -17,8 +19,9 @@ use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor};
|
|||||||
|
|
||||||
use graph_craft::proto::NodeConstructor;
|
use graph_craft::proto::NodeConstructor;
|
||||||
|
|
||||||
use graphene_core::{concrete, generic};
|
use graphene_core::{concrete, fn_type, generic, value_fn};
|
||||||
use graphene_std::memo::{CacheNode, LetNode};
|
use graphene_std::memo::{CacheNode, LetNode};
|
||||||
|
use graphene_std::raster::{BlendImageTupleNode, MapImageFrameNode};
|
||||||
|
|
||||||
use crate::executor::NodeContainer;
|
use crate::executor::NodeContainer;
|
||||||
|
|
||||||
@@ -55,7 +58,7 @@ macro_rules! register_node {
|
|||||||
let node = <$path>::new($(
|
let node = <$path>::new($(
|
||||||
graphene_std::any::input_node::<$type>(_node)
|
graphene_std::any::input_node::<$type>(_node)
|
||||||
),*);
|
),*);
|
||||||
let params = vec![$((concrete!(()), concrete!($type))),*];
|
let params = vec![$(value_fn!($type)),*];
|
||||||
let mut node_io = <$path as NodeIO<'_, $input>>::to_node_io(&node, params);
|
let mut node_io = <$path as NodeIO<'_, $input>>::to_node_io(&node, params);
|
||||||
node_io.input = concrete!(<$input as StaticType>::Static);
|
node_io.input = concrete!(<$input as StaticType>::Static);
|
||||||
node_io
|
node_io
|
||||||
@@ -75,7 +78,7 @@ macro_rules! raster_node {
|
|||||||
Box::pin(any)
|
Box::pin(any)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
let params = vec![$((concrete!(()), concrete!($type))),*];
|
let params = vec![$(value_fn!($type)),*];
|
||||||
NodeIOTypes::new(concrete!(Color), concrete!(Color), params)
|
NodeIOTypes::new(concrete!(Color), concrete!(Color), params)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -88,7 +91,7 @@ macro_rules! raster_node {
|
|||||||
Box::pin(any)
|
Box::pin(any)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
let params = vec![$((concrete!(()), concrete!($type))),*];
|
let params = vec![$(value_fn!($type)),*];
|
||||||
NodeIOTypes::new(concrete!(Image), concrete!(Image), params)
|
NodeIOTypes::new(concrete!(Image), concrete!(Image), params)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -101,7 +104,7 @@ macro_rules! raster_node {
|
|||||||
Box::pin(any)
|
Box::pin(any)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
let params = vec![$((concrete!(()), concrete!($type))),*];
|
let params = vec![$(value_fn!($type)),*];
|
||||||
NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), params)
|
NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), params)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -137,6 +140,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
register_node!(graphene_core::ops::SomeNode, input: ImageFrame, params: []),
|
register_node!(graphene_core::ops::SomeNode, input: ImageFrame, params: []),
|
||||||
register_node!(graphene_std::raster::DownresNode, input: ImageFrame, params: []),
|
register_node!(graphene_std::raster::DownresNode, input: ImageFrame, params: []),
|
||||||
register_node!(graphene_std::raster::MaskImageNode<_>, input: ImageFrame, params: [ImageFrame]),
|
register_node!(graphene_std::raster::MaskImageNode<_>, input: ImageFrame, params: [ImageFrame]),
|
||||||
|
register_node!(graphene_std::raster::EmptyImageNode<_>, input: DAffine2, params: [Color]),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image, params: [String]),
|
register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image, params: [String]),
|
||||||
vec![(
|
vec![(
|
||||||
@@ -145,30 +149,100 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
let node = ComposeTypeErased::new(args[0], args[1]);
|
let node = ComposeTypeErased::new(args[0], args[1]);
|
||||||
node.into_type_erased()
|
node.into_type_erased()
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(generic!(T), generic!(U), vec![(generic!(T), generic!(V)), (generic!(V), generic!(U))]),
|
NodeIOTypes::new(
|
||||||
|
generic!(T),
|
||||||
|
generic!(U),
|
||||||
|
vec![Type::Fn(Box::new(generic!(T)), Box::new(generic!(V))), Type::Fn(Box::new(generic!(V)), Box::new(generic!(U)))],
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
//register_node!(graphene_std::brush::ReduceNode<_, _>, input: core::slice::Iter<ImageFrame>, params: [ImageFrame, &ValueNode<BlendImageTupleNode<ValueNode<BlendNode<ClonedNode<BlendMode>, ClonedNode<f64>>>>>]),
|
||||||
|
//register_node!(graphene_std::brush::ReduceNode<_, _>, input: core::slice::Iter<ImageFrame>, params: [AxisAlignedBbox, &MergeBoundingBoxNode]),
|
||||||
|
register_node!(graphene_std::brush::IntoIterNode<_>, input: &Vec<DVec2>, params: []),
|
||||||
|
vec![(
|
||||||
|
NodeIdentifier::new("graphene_std::brush::BrushNode"),
|
||||||
|
|args| {
|
||||||
|
use graphene_std::brush::*;
|
||||||
|
|
||||||
|
let trace: DowncastBothNode<(), Vec<DVec2>> = DowncastBothNode::new(args[0]);
|
||||||
|
let diameter: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1]);
|
||||||
|
let hardness: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]);
|
||||||
|
let flow: DowncastBothNode<(), f64> = DowncastBothNode::new(args[3]);
|
||||||
|
let color: DowncastBothNode<(), Color> = DowncastBothNode::new(args[4]);
|
||||||
|
|
||||||
|
let stamp = BrushTextureNode::new(color, ClonedNode::new(hardness.eval(())), ClonedNode::new(flow.eval(())));
|
||||||
|
let stamp = stamp.eval(diameter.eval(()));
|
||||||
|
|
||||||
|
let frames = TranslateNode::new(ClonedNode::new(stamp));
|
||||||
|
let frames = MapNode::new(ValueNode::new(frames));
|
||||||
|
let frames = frames.eval(trace.eval(()).into_iter()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
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 = ClonedNode::new(background_bounds.unwrap().to_transform());
|
||||||
|
|
||||||
|
let background_image = background_bounds.then(EmptyImageNode::new(ClonedNode::new(Color::TRANSPARENT)));
|
||||||
|
|
||||||
|
let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(BlendMode::Normal), ClonedNode::new(100.));
|
||||||
|
|
||||||
|
let final_image = ReduceNode::new(background_image, ValueNode::new(BlendImageTupleNode::new(ValueNode::new(blend_node))));
|
||||||
|
let final_image = final_image.eval(frames.into_iter());
|
||||||
|
let final_image = ClonedNode::new(final_image);
|
||||||
|
|
||||||
|
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(final_image));
|
||||||
|
Box::pin(any)
|
||||||
|
},
|
||||||
|
NodeIOTypes::new(
|
||||||
|
concrete!(()),
|
||||||
|
concrete!(ImageFrame),
|
||||||
|
vec![value_fn!(Vec<DVec2>), value_fn!(f64), value_fn!(f64), value_fn!(f64), value_fn!(Color)],
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
vec![(
|
||||||
|
NodeIdentifier::new("graphene_std::brush::ReduceNode<_, _>"),
|
||||||
|
|args| {
|
||||||
|
let acc: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]);
|
||||||
|
let image = acc.eval(());
|
||||||
|
let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(BlendMode::Normal), ClonedNode::new(1.0));
|
||||||
|
let _ = &blend_node as &dyn for<'i> Node<'i, (Color, Color), Output = Color>;
|
||||||
|
let node = ReduceNode::new(ClonedNode::new(image), ValueNode::new(BlendImageTupleNode::new(ValueNode::new(blend_node))));
|
||||||
|
//let _ = &node as &dyn for<'i> Node<'i, core::slice::Iter<ImageFrame>, Output = ImageFrame>;
|
||||||
|
let any: DynAnyNode<Box<dyn Iterator<Item = ImageFrame> + Sync + Send>, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(node));
|
||||||
|
Box::pin(any)
|
||||||
|
},
|
||||||
|
NodeIOTypes::new(concrete!(Box<dyn Iterator<Item = &ImageFrame> + Sync + Send>), concrete!(ImageFrame), vec![value_fn!(ImageFrame)]),
|
||||||
)],
|
)],
|
||||||
// Filters
|
// Filters
|
||||||
raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]),
|
raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]),
|
||||||
raster_node!(graphene_core::raster::LevelsNode<_, _, _, _, _>, params: [f64, f64, f64, f64, f64]),
|
raster_node!(graphene_core::raster::LevelsNode<_, _, _, _, _>, params: [f64, f64, f64, f64, f64]),
|
||||||
vec![(
|
vec![
|
||||||
NodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"),
|
(
|
||||||
|args| {
|
NodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"),
|
||||||
use graphene_core::Node;
|
|args| {
|
||||||
let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]);
|
let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]);
|
||||||
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]);
|
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]);
|
||||||
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]);
|
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]);
|
||||||
let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(blend_mode.eval(())), ClonedNode::new(opacity.eval(())));
|
let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(blend_mode.eval(())), ClonedNode::new(opacity.eval(())));
|
||||||
let node = graphene_std::raster::BlendImageNode::new(image, ValueNode::new(blend_node));
|
let node = graphene_std::raster::BlendImageNode::new(image, ValueNode::new(blend_node));
|
||||||
let _ = &node as &dyn for<'i> Node<'i, ImageFrame, Output = ImageFrame>;
|
let _ = &node as &dyn for<'i> Node<'i, ImageFrame, Output = ImageFrame>;
|
||||||
let any: DynAnyNode<ImageFrame, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
|
let any: DynAnyNode<ImageFrame, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
|
||||||
any.into_type_erased()
|
any.into_type_erased()
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(
|
NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![value_fn!(ImageFrame), value_fn!(BlendMode), value_fn!(f64)]),
|
||||||
concrete!(ImageFrame),
|
|
||||||
concrete!(ImageFrame),
|
|
||||||
vec![(concrete!(()), concrete!(ImageFrame)), (concrete!(()), concrete!(BlendMode)), (concrete!(()), concrete!(f64))],
|
|
||||||
),
|
),
|
||||||
)],
|
(
|
||||||
|
NodeIdentifier::new("graphene_core::raster::EraseNode<_, _>"),
|
||||||
|
|args| {
|
||||||
|
let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]);
|
||||||
|
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1]);
|
||||||
|
let blend_node = graphene_std::brush::EraseNode::new(ClonedNode::new(opacity.eval(())));
|
||||||
|
let node = graphene_std::raster::BlendImageNode::new(image, ValueNode::new(blend_node));
|
||||||
|
let _ = &node as &dyn for<'i> Node<'i, ImageFrame, Output = ImageFrame>;
|
||||||
|
let any: DynAnyNode<ImageFrame, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
|
||||||
|
any.into_type_erased()
|
||||||
|
},
|
||||||
|
NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![value_fn!(ImageFrame), value_fn!(f64)]),
|
||||||
|
),
|
||||||
|
],
|
||||||
raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]),
|
raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]),
|
||||||
raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]),
|
raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]),
|
||||||
raster_node!(graphene_core::raster::InvertRGBNode, params: []),
|
raster_node!(graphene_core::raster::InvertRGBNode, params: []),
|
||||||
@@ -196,7 +270,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
let any: DynAnyInRefNode<ImageFrame, _, _> = graphene_std::any::DynAnyInRefNode::new(node);
|
let any: DynAnyInRefNode<ImageFrame, _, _> = graphene_std::any::DynAnyInRefNode::new(node);
|
||||||
any.into_type_erased()
|
any.into_type_erased()
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![(concrete!(()), concrete!(ImageFrame))]),
|
NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![value_fn!(ImageFrame)]),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"),
|
NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"),
|
||||||
@@ -206,7 +280,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
let any: DynAnyInRefNode<ImageFrame, _, _> = graphene_std::any::DynAnyInRefNode::new(node);
|
let any: DynAnyInRefNode<ImageFrame, _, _> = graphene_std::any::DynAnyInRefNode::new(node);
|
||||||
any.into_type_erased()
|
any.into_type_erased()
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![(concrete!(()), concrete!(VectorData))]),
|
NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![value_fn!(VectorData)]),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"),
|
NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"),
|
||||||
@@ -240,24 +314,24 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
concrete!(ImageFrame),
|
concrete!(ImageFrame),
|
||||||
concrete!(ImageFrame),
|
concrete!(ImageFrame),
|
||||||
vec![
|
vec![
|
||||||
(concrete!(()), concrete!(f64)),
|
value_fn!(f64),
|
||||||
(concrete!(()), concrete!(Option<DVec2>)),
|
value_fn!(Option<DVec2>),
|
||||||
(concrete!(()), concrete!(f64)),
|
value_fn!(f64),
|
||||||
(concrete!(()), concrete!(ImaginateSamplingMethod)),
|
value_fn!(ImaginateSamplingMethod),
|
||||||
(concrete!(()), concrete!(f64)),
|
value_fn!(f64),
|
||||||
(concrete!(()), concrete!(String)),
|
value_fn!(String),
|
||||||
(concrete!(()), concrete!(String)),
|
value_fn!(String),
|
||||||
(concrete!(()), concrete!(bool)),
|
value_fn!(bool),
|
||||||
(concrete!(()), concrete!(f64)),
|
value_fn!(f64),
|
||||||
(concrete!(()), concrete!(Option<Vec<u64>>)),
|
value_fn!(Option<Vec<u64>>),
|
||||||
(concrete!(()), concrete!(bool)),
|
value_fn!(bool),
|
||||||
(concrete!(()), concrete!(f64)),
|
value_fn!(f64),
|
||||||
(concrete!(()), concrete!(ImaginateMaskStartingFill)),
|
value_fn!(ImaginateMaskStartingFill),
|
||||||
(concrete!(()), concrete!(bool)),
|
value_fn!(bool),
|
||||||
(concrete!(()), concrete!(bool)),
|
value_fn!(bool),
|
||||||
(concrete!(()), concrete!(Option<std::sync::Arc<Image>>)),
|
value_fn!(Option<std::sync::Arc<Image>>),
|
||||||
(concrete!(()), concrete!(f64)),
|
value_fn!(f64),
|
||||||
(concrete!(()), concrete!(ImaginateStatus)),
|
value_fn!(ImaginateStatus),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -296,7 +370,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
let node: DynAnyNode<&Image, _, _> = DynAnyNode::new(ValueNode::new(new_image));
|
let node: DynAnyNode<&Image, _, _> = DynAnyNode::new(ValueNode::new(new_image));
|
||||||
node.into_type_erased()
|
node.into_type_erased()
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(concrete!(Image), concrete!(Image), vec![(concrete!(()), concrete!(u32)), (concrete!(()), concrete!(f64))]),
|
NodeIOTypes::new(concrete!(Image), concrete!(Image), vec![value_fn!(u32), value_fn!(f64)]),
|
||||||
),
|
),
|
||||||
//register_node!(graphene_std::memo::CacheNode<_>, input: Image, params: []),
|
//register_node!(graphene_std::memo::CacheNode<_>, input: Image, params: []),
|
||||||
(
|
(
|
||||||
@@ -307,7 +381,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
let any = DynAnyRefNode::new(node);
|
let any = DynAnyRefNode::new(node);
|
||||||
any.into_type_erased()
|
any.into_type_erased()
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(concrete!(()), concrete!(&Image), vec![(concrete!(()), concrete!(Image))]),
|
NodeIOTypes::new(concrete!(()), concrete!(&Image), vec![value_fn!(Image)]),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier::new("graphene_std::memo::CacheNode"),
|
NodeIdentifier::new("graphene_std::memo::CacheNode"),
|
||||||
@@ -317,7 +391,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
let any = DynAnyRefNode::new(node);
|
let any = DynAnyRefNode::new(node);
|
||||||
any.into_type_erased()
|
any.into_type_erased()
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(concrete!(()), concrete!(&ImageFrame), vec![(concrete!(()), concrete!(ImageFrame))]),
|
NodeIOTypes::new(concrete!(()), concrete!(&ImageFrame), vec![value_fn!(ImageFrame)]),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier::new("graphene_std::memo::CacheNode"),
|
NodeIdentifier::new("graphene_std::memo::CacheNode"),
|
||||||
@@ -327,7 +401,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
let any = DynAnyRefNode::new(node);
|
let any = DynAnyRefNode::new(node);
|
||||||
any.into_type_erased()
|
any.into_type_erased()
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(concrete!(ImageFrame), concrete!(&ImageFrame), vec![(concrete!(ImageFrame), concrete!(ImageFrame))]),
|
NodeIOTypes::new(concrete!(ImageFrame), concrete!(&ImageFrame), vec![fn_type!(ImageFrame, ImageFrame)]),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier::new("graphene_std::memo::CacheNode"),
|
NodeIdentifier::new("graphene_std::memo::CacheNode"),
|
||||||
@@ -337,7 +411,17 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
let any = DynAnyRefNode::new(node);
|
let any = DynAnyRefNode::new(node);
|
||||||
any.into_type_erased()
|
any.into_type_erased()
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(concrete!(()), concrete!(&QuantizationChannels), vec![(concrete!(()), concrete!(QuantizationChannels))]),
|
NodeIOTypes::new(concrete!(()), concrete!(&QuantizationChannels), vec![value_fn!(QuantizationChannels)]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NodeIdentifier::new("graphene_std::memo::CacheNode"),
|
||||||
|
|args| {
|
||||||
|
let input: DowncastBothNode<(), Vec<DVec2>> = DowncastBothNode::new(args[0]);
|
||||||
|
let node: CacheNode<Vec<DVec2>, _> = graphene_std::memo::CacheNode::new(input);
|
||||||
|
let any = DynAnyRefNode::new(node);
|
||||||
|
any.into_type_erased()
|
||||||
|
},
|
||||||
|
NodeIOTypes::new(concrete!(()), concrete!(&Vec<DVec2>), vec![value_fn!(Vec<DVec2>)]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]),
|
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]),
|
||||||
@@ -357,6 +441,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>,
|
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>,
|
||||||
params: [Vec<graphene_core::uuid::ManipulatorGroupId>]
|
params: [Vec<graphene_core::uuid::ManipulatorGroupId>]
|
||||||
),
|
),
|
||||||
|
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
|
||||||
];
|
];
|
||||||
let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
||||||
for (id, c, types) in node_types.into_iter().flatten() {
|
for (id, c, types) in node_types.into_iter().flatten() {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use proc_macro::TokenStream;
|
|||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use quote::{format_ident, ToTokens};
|
use quote::{format_ident, ToTokens};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse_macro_input, punctuated::Punctuated, token::Comma, FnArg, GenericParam, Ident, ItemFn, Lifetime, Pat, PatIdent, PathArguments, PredicateType, ReturnType, Token, TraitBound, Type, TypeParam,
|
parse_macro_input, punctuated::Punctuated, token::Comma, FnArg, GenericParam, Ident, ItemFn, Lifetime, Pat, PatIdent, PatType, PathArguments, PredicateType, ReturnType, Token, TraitBound, Type,
|
||||||
TypeParamBound, WhereClause, WherePredicate,
|
TypeParam, TypeParamBound, WhereClause, WherePredicate,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
@@ -46,6 +46,20 @@ pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
panic!("Expected ident as primary input.");
|
panic!("Expected ident as primary input.");
|
||||||
};
|
};
|
||||||
let primary_input_ty = &primary_input.ty;
|
let primary_input_ty = &primary_input.ty;
|
||||||
|
let aux_type_generics = type_generics
|
||||||
|
.iter()
|
||||||
|
.filter(|gen| {
|
||||||
|
if let GenericParam::Type(ty) = gen {
|
||||||
|
!function.sig.inputs.iter().take(1).any(|param_ty| match param_ty {
|
||||||
|
FnArg::Typed(pat_ty) => pat_ty.ty.to_token_stream().to_string() == ty.ident.to_string(),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let body = function.block;
|
let body = function.block;
|
||||||
|
|
||||||
@@ -101,6 +115,7 @@ pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let generics = type_generics.into_iter().chain(node_generics.iter().cloned()).collect::<Punctuated<_, Comma>>();
|
let generics = type_generics.into_iter().chain(node_generics.iter().cloned()).collect::<Punctuated<_, Comma>>();
|
||||||
|
let new_fn_generics = aux_type_generics.into_iter().chain(node_generics.iter().cloned()).collect::<Punctuated<_, Comma>>();
|
||||||
// Bindings for all of the above generics to a node with an input of `()` and an output of the type in the function
|
// Bindings for all of the above generics to a node with an input of `()` and an output of the type in the function
|
||||||
let extra_where_clause = parameter_inputs
|
let extra_where_clause = parameter_inputs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -122,7 +137,7 @@ pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
where_clause.predicates.extend(extra_where_clause);
|
where_clause.predicates.extend(extra_where_clause.clone());
|
||||||
|
|
||||||
quote::quote! {
|
quote::quote! {
|
||||||
|
|
||||||
@@ -140,7 +155,8 @@ pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <#(#args),*> #node_name<#(#args),*>
|
impl <'input, #new_fn_generics> #node_name<#(#args),*>
|
||||||
|
where #(#extra_where_clause),*
|
||||||
{
|
{
|
||||||
pub const fn new(#(#parameter_idents: #struct_generics_iter),*) -> Self{
|
pub const fn new(#(#parameter_idents: #struct_generics_iter),*) -> Self{
|
||||||
Self{
|
Self{
|
||||||
|
|||||||
Reference in New Issue
Block a user