diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 08b56025..70c14d9f 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -130,7 +130,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(generic!(T)), inputs: vec![ NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0), @@ -215,7 +215,8 @@ fn static_nodes() -> Vec { nodes: [ // Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias DocumentNode { - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(generic!(T)), + implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToArtboardNode"), inputs: vec![ NodeInput::network(concrete!(TaggedValue), 1), NodeInput::value(TaggedValue::String(String::from("Artboard")), false), @@ -224,7 +225,6 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(TaggedValue), 4), NodeInput::network(concrete!(TaggedValue), 5), ], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToArtboardNode"), ..Default::default() }, // The monitor node is used to display a thumbnail in the UI. @@ -876,7 +876,7 @@ fn static_nodes() -> Vec { NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), ], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { @@ -885,7 +885,7 @@ fn static_nodes() -> Vec { NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), ], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { @@ -894,7 +894,7 @@ fn static_nodes() -> Vec { NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), ], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { @@ -903,7 +903,7 @@ fn static_nodes() -> Vec { NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), ], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(generic!(T)), ..Default::default() }, ] @@ -1880,6 +1880,9 @@ fn static_nodes() -> Vec { properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { + // Aims for interoperable compatibility with: + // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27brit%27%20%3D%20Brightness/Contrast + // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Padding-,Brightness%20and%20Contrast,-Key%20is%20%27brit identifier: "Brightness/Contrast", category: "Raster: Adjustment", node_template: NodeTemplate { @@ -1901,6 +1904,9 @@ fn static_nodes() -> Vec { }, properties: &node_properties::brightness_contrast_properties, }, + // Aims for interoperable compatibility with: + // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure + // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Max%20input%20range-,Curves,-Curves%20settings%20files DocumentNodeDefinition { identifier: "Curves", category: "Raster: Adjustment", @@ -2264,7 +2270,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::network(concrete!(graphene_core::vector::VectorData), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_nodes::LengthsOfSegmentsOfSubpathsNode")), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode")), manual_composition: Some(concrete!(Footprint)), ..Default::default() }, @@ -2275,7 +2281,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(f64), 2), // From the document node's parameters NodeInput::network(concrete!(f64), 3), // From the document node's parameters NodeInput::network(concrete!(bool), 4), // From the document node's parameters - NodeInput::node(NodeId(0), 0), // From output 0 of Lengths of Segments of Subpaths + NodeInput::node(NodeId(0), 0), // From output 0 of SubpathSegmentLengthsNode ], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_nodes::SamplePointsNode")), manual_composition: Some(concrete!(Footprint)), @@ -2309,7 +2315,7 @@ fn static_nodes() -> Vec { node_metadata: [ DocumentNodeMetadata { persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Lengths of Segments of Subpaths".to_string(), + display_name: "Subpath Segment Lengths".to_string(), ..Default::default() }, ..Default::default() @@ -2440,27 +2446,6 @@ fn static_nodes() -> Vec { }, properties: &node_properties::index_properties, }, - // Applies the given color to each pixel of an image but maintains the alpha value - DocumentNodeDefinition { - identifier: "Color Fill", - category: "Raster", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::raster::adjustments::ColorFillNode"), - inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), - NodeInput::value(TaggedValue::Color(Color::BLACK), false), - ], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string(), "Color".to_string()], - output_names: vec!["Image".to_string()], - ..Default::default() - }, - }, - properties: &node_properties::color_fill_properties, - }, ]; type PropertiesLayout = &'static (dyn Fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec + Sync); diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index d47a2ee9..222999ec 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -132,7 +132,7 @@ pub(crate) fn property_from_type( x if x == TypeId::of::() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)).max(max(f64::from(u32::MAX))), true).into(), x if x == TypeId::of::() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)), true).into(), x if x == TypeId::of::() => text_widget(document_node, node_id, index, name, true).into(), - x if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorButton::default(), true), + x if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true), x if x == TypeId::of::>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(true), true), x if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist), x if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", Some(0.), add_blank_assist), @@ -2590,8 +2590,3 @@ pub(crate) fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, vec![location, dimensions, background, clip_row] } - -pub(crate) fn color_fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let color = color_widget(document_node, node_id, 1, "Color", ColorButton::default(), true); - vec![color] -} diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 2304d71a..f4d54668 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -232,13 +232,25 @@ impl ArtboardGroup { } #[node_macro::node(category(""))] -async fn layer( - #[implementations((), Footprint)] footprint: F, - #[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] stack: impl Node, - #[implementations(() -> GraphicElement, Footprint -> GraphicElement)] graphic_element: impl Node, +async fn layer( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> GraphicGroup, + Footprint -> GraphicGroup, + )] + stack: impl Node, + #[implementations( + () -> GraphicElement, + Footprint -> GraphicElement, + )] + element: impl Node, node_path: Vec, ) -> GraphicGroup { - let mut element = graphic_element.eval(footprint).await; + let mut element = element.eval(footprint).await; let mut stack = stack.eval(footprint).await; if stack.transform.matrix2.determinant() != 0. { *element.transform_mut() = stack.transform.inverse() * element.transform(); @@ -255,16 +267,23 @@ async fn layer( #[node_macro::node(category("Debug"))] async fn to_element + 'n>( - #[implementations((), (), (), (), Footprint)] footprint: F, #[implementations( - () -> VectorData, - () -> ImageFrame, + (), + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( () -> GraphicGroup, - () -> TextureFrame, - Footprint -> VectorData, + () -> VectorData, + () -> ImageFrame, + () -> TextureFrame, + Footprint -> GraphicGroup, + Footprint -> VectorData, Footprint -> ImageFrame, - Footprint -> GraphicGroup, - Footprint -> TextureFrame, + Footprint -> TextureFrame, )] data: impl Node, ) -> GraphicElement { @@ -273,15 +292,22 @@ async fn to_element + 'n>( #[node_macro::node(category("General"))] async fn to_group + 'n>( - #[implementations((), (), (), (), Footprint)] footprint: F, #[implementations( + (), + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> GraphicGroup, () -> VectorData, () -> ImageFrame, - () -> GraphicGroup, () -> TextureFrame, + Footprint -> GraphicGroup, Footprint -> VectorData, Footprint -> ImageFrame, - Footprint -> GraphicGroup, Footprint -> TextureFrame, )] element: impl Node, @@ -290,9 +316,26 @@ async fn to_group + 'n>( } #[node_macro::node(category(""))] -async fn to_artboard( - #[implementations((), Footprint)] mut footprint: F, - #[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] contents: impl Node, +async fn to_artboard + 'n>( + #[implementations( + (), + (), + (), + (), + Footprint, + )] + mut footprint: F, + #[implementations( + () -> GraphicGroup, + () -> VectorData, + () -> ImageFrame, + () -> TextureFrame, + Footprint -> GraphicGroup, + Footprint -> VectorData, + Footprint -> ImageFrame, + Footprint -> TextureFrame, + )] + contents: impl Node, label: String, location: IVec2, dimensions: IVec2, @@ -303,7 +346,7 @@ async fn to_artboard( let graphic_group = contents.eval(footprint).await; Artboard { - graphic_group, + graphic_group: graphic_group.into(), label, location: location.min(location + dimensions), dimensions: dimensions.abs(), @@ -311,11 +354,24 @@ async fn to_artboard( clip, } } + #[node_macro::node(category(""))] -async fn append_artboard( - #[implementations((), Footprint)] footprint: F, - #[implementations(() -> ArtboardGroup, Footprint -> ArtboardGroup)] artboards: impl Node, - #[implementations(() -> Artboard, Footprint -> Artboard)] artboard: impl Node, +async fn append_artboard( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> ArtboardGroup, + Footprint -> ArtboardGroup, + )] + artboards: impl Node, + #[implementations( + () -> Artboard, + Footprint -> Artboard, + )] + artboard: impl Node, node_path: Vec, ) -> ArtboardGroup { let artboard = artboard.eval(footprint).await; diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 3913bdcf..cfa2e1eb 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -1,8 +1,11 @@ +use crate::vector::VectorData; +use glam::{DAffine2, DVec2}; + #[node_macro::node(category("Debug"))] fn log_to_console( _: (), #[default("Not connected to value yet")] - #[implementations(String, bool, f64, f64, u32, u64, glam::DVec2, crate::vector::VectorData, glam::DAffine2)] + #[implementations(String, bool, f64, f64, u32, u64, DVec2, VectorData, DAffine2)] value: T, ) -> T { #[cfg(not(target_arch = "spirv"))] diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index e0902f4e..2f02b244 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -1,8 +1,13 @@ +use crate::raster::adjustments::RedGreenBlue; +use crate::raster::BlendMode; +use crate::raster::ImageFrame; use crate::registry::types::Percentage; -use crate::Node; +use crate::vector::style::GradientStops; +use crate::{Color, Node}; use core::marker::PhantomData; use core::ops::{Add, Div, Mul, Rem, Sub}; +use glam::DVec2; use num_traits::Pow; use rand::{Rng, SeedableRng}; @@ -13,8 +18,8 @@ use spirv_std::num_traits::float::Float; #[node_macro::node(category("Math: Arithmetic"))] fn add, T>( _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, glam::DVec2)] augend: U, - #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, glam::DVec2)] addend: T, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] augend: U, + #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] addend: T, ) -> >::Output { augend + addend } @@ -23,8 +28,8 @@ fn add, T>( #[node_macro::node(category("Math: Arithmetic"))] fn subtract, T>( _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, glam::DVec2)] minuend: U, - #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, glam::DVec2)] subtrahend: T, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U, + #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T, ) -> >::Output { minuend - subtrahend } @@ -33,9 +38,9 @@ fn subtract, T>( #[node_macro::node(category("Math: Arithmetic"))] fn multiply, T>( _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, glam::DVec2, f64)] multiplier: U, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U, #[default(1.)] - #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, glam::DVec2, glam::DVec2)] + #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] multiplicand: T, ) -> >::Output { multiplier * multiplicand @@ -45,9 +50,9 @@ fn multiply, T>( #[node_macro::node(category("Math: Arithmetic"))] fn divide, T>( _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, glam::DVec2, glam::DVec2)] numerator: U, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, #[default(1.)] - #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, glam::DVec2, f64)] + #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] denominator: T, ) -> >::Output { numerator / denominator @@ -57,9 +62,9 @@ fn divide, T>( #[node_macro::node(category("Math: Arithmetic"))] fn modulo, T>( _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] numerator: U, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, #[default(2.)] - #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)] + #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] modulus: T, ) -> >::Output { numerator % modulus @@ -69,7 +74,7 @@ fn modulo, T>( #[node_macro::node(category("Math: Arithmetic"))] fn exponent, T>( _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, )] base: U, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U, #[default(2.)] #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)] power: T, @@ -119,52 +124,63 @@ fn logarithm( // Sine #[node_macro::node(category("Math: Trig"))] -fn sine(_: (), theta: f64) -> f64 { +fn sine(_: (), #[implementations(f64, f32)] theta: U) -> U { theta.sin() } // Cosine #[node_macro::node(category("Math: Trig"))] -fn cosine(_: (), theta: f64) -> f64 { +fn cosine(_: (), #[implementations(f64, f32)] theta: U) -> U { theta.cos() } // Tangent #[node_macro::node(category("Math: Trig"))] -fn tangent(_: (), theta: f64) -> f64 { +fn tangent(_: (), #[implementations(f64, f32)] theta: U) -> U { theta.tan() } // Random #[node_macro::node(category("Math: Numeric"))] -fn random(_: (), _primary: (), seed: u64, min: f64, #[default(1.)] max: f64) -> f64 { +fn random( + _: (), + _primary: (), + seed: u64, + #[implementations(f64, f32)] + #[default(0.)] + min: U, + #[implementations(f64, f32)] + #[default(1.)] + max: U, +) -> f64 { let mut rng = rand::rngs::StdRng::seed_from_u64(seed); let result = rng.gen::(); let (min, max) = if min < max { (min, max) } else { (max, min) }; + let (min, max) = (min.to_f64().unwrap(), max.to_f64().unwrap()); result * (max - min) + min } // Round #[node_macro::node(category("Math: Numeric"))] -fn round(_: (), value: f64) -> f64 { +fn round(_: (), #[implementations(f64, f32)] value: U) -> U { value.round() } // Floor #[node_macro::node(category("Math: Numeric"))] -fn floor(_: (), value: f64) -> f64 { +fn floor(_: (), #[implementations(f64, f32)] value: U) -> U { value.floor() } // Ceiling #[node_macro::node(category("Math: Numeric"))] -fn ceiling(_: (), value: f64) -> f64 { +fn ceiling(_: (), #[implementations(f64, f32)] value: U) -> U { value.ceil() } // Absolute Value #[node_macro::node(category("Math: Numeric"))] -fn absolute_value(_: (), value: f64) -> f64 { +fn absolute_value(_: (), #[implementations(f64, f32)] value: U) -> U { value.abs() } @@ -190,8 +206,8 @@ fn max(_: (), #[implementations(f64, &f64, f32, &f32, #[node_macro::node(category("Math: Logic"))] fn equals, T>( _: (), - #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, - #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] + #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T, + #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] #[min(100.)] #[max(200.)] other_value: U, @@ -243,32 +259,31 @@ fn percentage_value(_: (), _primary: (), percentage: Percentage) -> f64 { // Vector2 Value #[node_macro::node(category("Value"))] -fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> glam::DVec2 { - glam::DVec2::new(x, y) +fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> DVec2 { + DVec2::new(x, y) } -// TODO: Make it possible to give Color::BLACK instead of 000000ff as the default // Color Value #[node_macro::node(category("Value"))] -fn color_value(_: (), _primary: (), #[default(000000ff)] color: crate::Color) -> crate::Color { +fn color_value(_: (), _primary: (), #[default(Color::BLACK)] color: Option) -> Option { color } // Gradient Value #[node_macro::node(category("Value"))] -fn gradient_value(_: (), _primary: (), gradient: crate::vector::style::GradientStops) -> crate::vector::style::GradientStops { +fn gradient_value(_: (), _primary: (), gradient: GradientStops) -> GradientStops { gradient } // Color Channel Value #[node_macro::node(category("Value"))] -fn color_channel_value(_: (), _primary: (), color_channel: crate::raster::adjustments::RedGreenBlue) -> crate::raster::adjustments::RedGreenBlue { +fn color_channel_value(_: (), _primary: (), color_channel: RedGreenBlue) -> RedGreenBlue { color_channel } // Blend Mode Value #[node_macro::node(category("Value"))] -fn blend_mode_value(_: (), _primary: (), blend_mode: crate::raster::BlendMode) -> crate::raster::BlendMode { +fn blend_mode_value(_: (), _primary: (), blend_mode: BlendMode) -> BlendMode { blend_mode } @@ -281,19 +296,19 @@ fn size_of(_: (), ty: crate::Type) -> Option { // Some #[node_macro::node(category("Debug"))] -fn some(_: (), #[implementations(f64, f32, u32, u64, String, crate::Color)] input: T) -> Option { +fn some(_: (), #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option { Some(input) } // Unwrap #[node_macro::node(category("Debug"))] -fn unwrap(_: (), #[implementations(Option, Option, Option, Option, Option, Option)] input: Option) -> T { +fn unwrap(_: (), #[implementations(Option, Option, Option, Option, Option, Option)] input: Option) -> T { input.unwrap_or_default() } // Clone #[node_macro::node(category("Debug"))] -fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&crate::raster::ImageFrame)] value: &'i T) -> T { +fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrame)] value: &'i T) -> T { value.clone() } diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 4ba6adfd..ff8cd2e3 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -1,12 +1,12 @@ -use core::fmt::Debug; - +pub use self::color::{Color, Luma, SRGBA8}; +use crate::vector::VectorData; +use crate::GraphicGroup; use crate::{registry::types::Percentage, transform::Footprint}; use bytemuck::{Pod, Zeroable}; +use core::fmt::Debug; use glam::DVec2; -pub use self::color::{Color, Luma, SRGBA8}; - #[cfg(target_arch = "spirv")] use spirv_std::num_traits::float::Float; @@ -291,12 +291,12 @@ trait SetBlendMode { fn set_blend_mode(&mut self, blend_mode: BlendMode); } -impl SetBlendMode for crate::vector::VectorData { +impl SetBlendMode for VectorData { fn set_blend_mode(&mut self, blend_mode: BlendMode) { self.alpha_blending.blend_mode = blend_mode; } } -impl SetBlendMode for crate::GraphicGroup { +impl SetBlendMode for GraphicGroup { fn set_blend_mode(&mut self, blend_mode: BlendMode) { self.alpha_blending.blend_mode = blend_mode; } @@ -308,9 +308,23 @@ impl SetBlendMode for ImageFrame { } #[node_macro::node(category("Style"))] -async fn blend_mode( - footprint: Footprint, - #[implementations(Footprint -> crate::vector::VectorData, Footprint -> crate::GraphicGroup, Footprint -> ImageFrame)] value: impl Node, +async fn blend_mode( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> GraphicGroup, + () -> VectorData, + () -> ImageFrame, + Footprint -> GraphicGroup, + Footprint -> VectorData, + Footprint -> ImageFrame, + )] + value: impl Node, blend_mode: BlendMode, ) -> T { let mut value = value.eval(footprint).await; @@ -319,9 +333,23 @@ async fn blend_mode( } #[node_macro::node(category("Style"))] -async fn opacity( - footprint: Footprint, - #[implementations(Footprint -> crate::vector::VectorData, Footprint -> crate::GraphicGroup, Footprint -> ImageFrame)] value: impl Node, +async fn opacity( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> GraphicGroup, + () -> VectorData, + () -> ImageFrame, + Footprint -> GraphicGroup, + Footprint -> VectorData, + Footprint -> ImageFrame, + )] + value: impl Node, #[default(100.)] factor: Percentage, ) -> T { let mut value = value.eval(footprint).await; diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 00eefba4..ff785e38 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -19,6 +19,21 @@ use core::fmt::Debug; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::float::Float; +// TODO: Implement the following: +// Color Balance +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27blnc%27%20%3D%20Color%20Balance +// +// Photo Filter +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27phfl%27%20%3D%20Photo%20Filter +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=of%20the%20file.-,Photo%20Filter,-Key%20is%20%27phfl +// +// Color Lookup +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27clrL%27%20%3D%20Color%20Lookup +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Color%20Lookup%20(Photoshop%20CS6 + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "std", derive(specta::Type))] #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, Hash)] @@ -268,10 +283,24 @@ impl From for vello::peniko::Mix { } } -#[node_macro::node(category("Raster: Adjustment"))] // Unique to Graphite -async fn luminance>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] input: impl Node, +#[node_macro::node(category("Raster: Adjustment"))] +async fn luminance>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + input: impl Node, luminance_calc: LuminanceCalculation, ) -> T { let mut input = input.eval(footprint).await; @@ -289,9 +318,23 @@ async fn luminance>( } #[node_macro::node(category("Raster"))] -async fn extract_channel>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] input: impl Node, +async fn extract_channel>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + input: impl Node, channel: RedGreenBlueAlpha, ) -> T { let mut input = input.eval(footprint).await; @@ -308,7 +351,24 @@ async fn extract_channel>( } #[node_macro::node(category("Raster"))] -async fn make_opaque>(footprint: Footprint, #[implementations(Footprint -> Color, Footprint -> ImageFrame)] input: impl Node) -> T { +async fn make_opaque>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + input: impl Node, +) -> T { let mut input = input.eval(footprint).await; input.adjust(|color| { if color.a() == 0. { @@ -319,11 +379,29 @@ async fn make_opaque>(footprint: Footprint, #[implementations(F input } -// From https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Brightness/Contrast-,%27levl%27%20%3D%20Levels,-%27curv%27%20%3D%20Curves +// +// Algorithm from: +// https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels #[node_macro::node(category("Raster: Adjustment"))] -async fn levels>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] image: impl Node, +async fn levels>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + image: impl Node, #[default(0.)] shadows: Percentage, #[default(50.)] midtones: Percentage, #[default(100.)] highlights: Percentage, @@ -376,13 +454,32 @@ async fn levels>( input } -// From +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27blwh%27%20%3D%20Black%20and%20White +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Black%20White%20(Photoshop%20CS3) +// +// Algorithm from: +// https://stackoverflow.com/a/55233732/775283 // Works the same for gamma and linear color #[node_macro::node(name("Black & White"), category("Raster: Adjustment"))] -async fn black_and_white>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] image: impl Node, - #[default(000000ff)] tint: Color, +async fn black_and_white>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + image: impl Node, + #[default(Color::BLACK)] tint: Color, #[default(40.)] #[range((-200., 300.))] reds: Percentage, @@ -443,10 +540,27 @@ async fn black_and_white>( input } +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0 +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings #[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))] -async fn hue_saturation>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] input: impl Node, +async fn hue_saturation>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + input: impl Node, hue_shift: Angle, saturation_shift: SignedPercentage, lightness_shift: SignedPercentage, @@ -471,8 +585,27 @@ async fn hue_saturation>( input } +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize #[node_macro::node(category("Raster: Adjustment"))] -async fn invert>(footprint: Footprint, #[implementations(Footprint -> Color, Footprint -> ImageFrame)] input: impl Node) -> T { +async fn invert>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + input: impl Node, +) -> T { let mut input = input.eval(footprint).await; input.adjust(|color| { let color = color.to_gamma_srgb(); @@ -484,10 +617,26 @@ async fn invert>(footprint: Footprint, #[implementations(Footpr input } +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient #[node_macro::node(category("Raster: Adjustment"))] -async fn threshold>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] image: impl Node, +async fn threshold>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + image: impl Node, #[default(50.)] min_luminance: Percentage, #[default(100.)] max_luminance: Percentage, luminance_calc: LuminanceCalculation, @@ -571,8 +720,14 @@ impl Blend for GradientStops { } #[node_macro::node(category("Raster"))] -async fn blend + Send>( - #[implementations((), (), (), Footprint)] footprint: F, +async fn blend + Send>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, #[implementations( () -> Color, () -> ImageFrame, @@ -689,9 +844,18 @@ pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, background.alpha_blend(target_color.to_associated_alpha(opacity as f32)) } +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0) #[node_macro::node(category("Raster: Adjustment"))] -async fn gradient_map>( - #[implementations((), (), (), Footprint)] footprint: F, +async fn gradient_map>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, #[implementations( () -> Color, () -> ImageFrame, @@ -715,12 +879,31 @@ async fn gradient_map>( input } -// Based on +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27-,vibA%27%20%3D%20Vibrance,-%27hue%20%27%20%3D%20Old +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Vibrance%20(Photoshop%20CS3) +// +// Algorithm based on: +// https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters // The results of this implementation are very close to correct, but not quite perfect #[node_macro::node(category("Raster: Adjustment"))] -async fn vibrance>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] image: impl Node, +async fn vibrance>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + image: impl Node, vibrance: SignedPercentage, ) -> T { let mut input = image.eval(footprint).await; @@ -1000,10 +1183,27 @@ impl DomainWarpType { } } +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr #[node_macro::node(category("Raster: Adjustment"))] -async fn channel_mixer>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] image: impl Node, +async fn channel_mixer>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + image: impl Node, monochrome: bool, #[default(40.)] @@ -1141,11 +1341,30 @@ impl core::fmt::Display for SelectiveColorChoice { } } -// Based on https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27selc%27%20%3D%20Selective%20color +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=from%20%2D100...100.%20.-,Selective%20Color,-Selective%20Color%20settings +// +// Algorithm based on: +// https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html #[node_macro::node(category("Raster: Adjustment"))] -async fn selective_color>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] image: impl Node, +async fn selective_color>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + image: impl Node, mode: RelativeAbsolute, #[name("(Reds) Cyan")] r_c: f64, #[name("(Reds) Magenta")] r_m: f64, @@ -1288,12 +1507,30 @@ impl MultiplyAlpha for ImageFrame

{ } } -// Based on https://www.axiomx.com/posterize.htm +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=nvrt%27%20%3D%20Invert-,%27post%27%20%3D%20Posterize,-%27thrs%27%20%3D%20Threshold +// +// Algorithm based on: +// https://www.axiomx.com/posterize.htm // This algorithm produces fully accurate output in relation to the industry standard. #[node_macro::node(category("Raster: Adjustment"))] -async fn posterize>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] input: impl Node, +async fn posterize>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + input: impl Node, #[default(4)] #[min(2.)] levels: u32, @@ -1313,11 +1550,30 @@ async fn posterize>( input } -// Based on https://geraldbakker.nl/psnumbers/exposure.html +// Aims for interoperable compatibility with: +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=curv%27%20%3D%20Curves-,%27expA%27%20%3D%20Exposure,-%27vibA%27%20%3D%20Vibrance +// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Flag%20(%20%3D%20128%20)-,Exposure,-Key%20is%20%27expA +// +// Algorithm based on: +// https://geraldbakker.nl/psnumbers/exposure.html #[node_macro::node(category("Raster: Adjustment"))] -async fn exposure>( - footprint: Footprint, - #[implementations(Footprint -> Color, Footprint -> ImageFrame)] input: impl Node, +async fn exposure>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> Color, + () -> ImageFrame, + () -> GradientStops, + Footprint -> Color, + Footprint -> ImageFrame, + Footprint -> GradientStops, + )] + input: impl Node, exposure: f64, offset: f64, #[default(1.)] @@ -1343,7 +1599,7 @@ const WINDOW_SIZE: usize = 1024; #[cfg(feature = "alloc")] #[node_macro::node(category(""))] -fn generate_curves(_: (), curve: Curve, #[implementations(f32)] _target_format: C) -> ValueMapperNode { +fn generate_curves(_: (), curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode { use bezier_rs::{Bezier, TValue}; let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle]; @@ -1383,9 +1639,15 @@ fn generate_curves(_: (), curve: Curve, #[implementa } #[cfg(feature = "alloc")] -#[node_macro::node(category("Raster: Adjustment"))] // Unique to Graphite -async fn color_overlay>( - #[implementations((), (), (), Footprint)] footprint: F, +#[node_macro::node(category("Raster: Adjustment"))] +async fn color_overlay>( + #[implementations( + (), + (), + (), + Footprint, + )] + footprint: F, #[implementations( () -> Color, () -> ImageFrame, @@ -1395,7 +1657,7 @@ async fn color_overlay>( Footprint -> GradientStops, )] image: impl Node, - #[default(000000ff)] color: Color, + #[default(Color::BLACK)] color: Color, blend_mode: BlendMode, #[default(100.)] opacity: Percentage, ) -> T { diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 2c73da0b..11fc220e 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -1,15 +1,9 @@ -use glam::DAffine2; - -use glam::DVec2; - use crate::raster::bbox::AxisAlignedBbox; -use crate::raster::ImageFrame; -use crate::raster::Pixel; +use crate::raster::{ImageFrame, Pixel}; use crate::vector::VectorData; -use crate::Artboard; -use crate::ArtboardGroup; -use crate::GraphicElement; -use crate::GraphicGroup; +use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup}; + +use glam::{DAffine2, DVec2}; pub trait Transform { fn transform(&self) -> DAffine2; @@ -182,7 +176,7 @@ impl From<()> for Footprint { } #[node_macro::node(category("Debug"))] -fn cull(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame, ArtboardGroup)] data: T) -> T { +fn cull(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame, ArtboardGroup)] data: T) -> T { data } @@ -217,15 +211,21 @@ impl ApplyTransform for () { } #[node_macro::node(category(""))] -async fn transform + ApplyTransform + 'n + Clone + Send + Sync, T: TransformMut + 'n>( - #[implementations(Footprint, Footprint, Footprint, (), (), ())] mut input: I, +async fn transform + 'n + ApplyTransform + Clone + Send + Sync, T: 'n + TransformMut>( + #[implementations( + (), + (), + (), + Footprint, + )] + mut input: I, #[implementations( - Footprint -> VectorData, - Footprint -> GraphicGroup, - Footprint -> ImageFrame, () -> VectorData, () -> GraphicGroup, - () -> ImageFrame, + () -> ImageFrame, + Footprint -> VectorData, + Footprint -> GraphicGroup, + Footprint -> ImageFrame, )] transform_target: impl Node, translate: DVec2, @@ -251,7 +251,7 @@ async fn transform + ApplyTransform + 'n + Clone + Send + Syn #[node_macro::node(category("Debug"))] fn replace_transform( _: (), - #[implementations(VectorData, ImageFrame, GraphicGroup)] mut data: Data, + #[implementations(VectorData, ImageFrame, GraphicGroup)] mut data: Data, #[implementations(DAffine2)] transform: TransformInput, ) -> Data { let data_transform = data.transform_mut(); diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index 4f939207..93cd9e58 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -1,4 +1,5 @@ use super::HandleId; +use crate::transform::Footprint; use crate::vector::{PointId, VectorData}; use bezier_rs::Subpath; @@ -35,13 +36,12 @@ impl CornerRadius for [f64; 4] { } #[node_macro::node(category("Vector: Shape"))] -fn circle(_: (), _primary: (), #[default(50.)] radius: f64) -> VectorData { - let radius: f64 = radius; +fn circle(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorData { super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))) } #[node_macro::node(category("Vector: Shape"))] -fn ellipse(_: (), _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorData { +fn ellipse(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorData { let radius = DVec2::new(radius_x, radius_y); let corner1 = -radius; let corner2 = radius; @@ -56,8 +56,8 @@ fn ellipse(_: (), _primary: (), #[default(50)] radius_x: f64, #[default(25)] rad } #[node_macro::node(category("Vector: Shape"))] -fn rectangle( - _: (), +fn rectangle( + #[implementations((), Footprint)] _footprint: F, _primary: (), #[default(100)] width: f64, #[default(100)] height: f64, @@ -69,8 +69,8 @@ fn rectangle( } #[node_macro::node(category("Vector: Shape"))] -fn regular_polygon( - _: (), +fn regular_polygon( + #[implementations((), Footprint)] _footprint: F, _primary: (), #[default(6)] #[min(3.)] @@ -83,8 +83,8 @@ fn regular_polygon( } #[node_macro::node(category("Vector: Shape"))] -fn star( - _: (), +fn star( + #[implementations((), Footprint)] _footprint: F, _primary: (), #[default(5)] #[min(2.)] @@ -100,12 +100,12 @@ fn star( } #[node_macro::node(category("Vector: Shape"))] -fn line(_: (), _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorData { +fn line(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorData { super::VectorData::from_subpath(Subpath::new_line(start, end)) } #[node_macro::node(category("Vector: Shape"))] -fn spline(_: (), _primary: (), points: Vec) -> VectorData { +fn spline(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec) -> VectorData { let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(points)); for pair in spline.segment_domain.ids().windows(2) { spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]); @@ -116,7 +116,7 @@ fn spline(_: (), _primary: (), points: Vec) -> VectorData { // TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node #[node_macro::node(category(""))] -fn path(_: (), path_data: Vec>, colinear_manipulators: Vec) -> super::VectorData { +fn path(#[implementations((), Footprint)] _footprint: F, path_data: Vec>, colinear_manipulators: Vec) -> super::VectorData { let mut vector_data = super::VectorData::from_subpaths(path_data, false); vector_data.colinear_manipulators = colinear_manipulators .iter() diff --git a/node-graph/gcore/src/vector/style.rs b/node-graph/gcore/src/vector/style.rs index 349ca542..cc8b14f3 100644 --- a/node-graph/gcore/src/vector/style.rs +++ b/node-graph/gcore/src/vector/style.rs @@ -482,6 +482,17 @@ impl core::hash::Hash for Stroke { } } +impl From for Stroke { + fn from(color: Color) -> Self { + Self::new(Some(color), 1.) + } +} +impl From> for Stroke { + fn from(color: Option) -> Self { + Self::new(color, 1.) + } +} + impl Stroke { pub const fn new(color: Option, weight: f64) -> Self { Self { diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 6d1f8eab..f4ad5525 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -425,8 +425,16 @@ use crate::transform::Footprint; /// A node that applies a procedural modification to some [`VectorData`]. #[node_macro::node(category(""))] async fn path_modify( - #[implementations((), Footprint)] input: F, - #[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node, + #[implementations( + (), + Footprint, + )] + input: F, + #[implementations( + () -> VectorData, + Footprint -> VectorData, + )] + vector_data: impl Node, modification: Box, ) -> VectorData { let mut vector_data = vector_data.eval(input).await; diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index e917b811..31a68019 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -27,9 +27,20 @@ impl VectorIterMut for VectorData { } #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] -async fn assign_colors( - footprint: Footprint, - #[implementations(Footprint -> GraphicGroup, Footprint -> VectorData)] vector_group: impl Node, +async fn assign_colors( + #[implementations( + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> GraphicGroup, + () -> VectorData, + Footprint -> GraphicGroup, + Footprint -> VectorData, + )] + vector_group: impl Node, #[default(true)] fill: bool, stroke: bool, gradient: GradientStops, @@ -70,10 +81,35 @@ async fn assign_colors( } #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] -async fn fill + 'n + Send>( - footprint: Footprint, - vector_data: impl Node, - #[implementations(Fill, Color, Option, crate::vector::style::Gradient)] fill: T, // TODO: Set the default to black +async fn fill + 'n + Send>( + #[implementations( + (), + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + () -> VectorData, + () -> VectorData, + () -> VectorData, + Footprint -> VectorData, + )] + vector_data: impl Node, + #[implementations( + Fill, + Option, + Color, + Gradient, + Fill, + Option, + Color, + Gradient, + )] + #[default(Color::BLACK)] + fill: T, _backup_color: Option, _backup_gradient: Gradient, ) -> VectorData { @@ -84,10 +120,27 @@ async fn fill + 'n + Send>( } #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] -async fn stroke( - footprint: Footprint, - vector_data: impl Node, - color: Option, // TODO: Set the default to black +async fn stroke> + 'n + Send>( + #[implementations( + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + () -> VectorData, + Footprint -> VectorData, + )] + vector_data: impl Node, + #[implementations( + Option, + Color, + Option, + Color, + )] + #[default(Color::BLACK)] + color: T, #[default(5.)] weight: f64, dash_lengths: Vec, dash_offset: f64, @@ -97,7 +150,7 @@ async fn stroke( ) -> VectorData { let mut vector_data = vector_data.eval(footprint).await; vector_data.style.set_stroke(Stroke { - color, + color: color.into(), weight, dash_lengths, dash_offset, @@ -110,7 +163,23 @@ async fn stroke( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn repeat(footprint: Footprint, instance: impl Node, #[default(100., 100.)] direction: DVec2, angle: Angle, #[default(4)] instances: IntegerCount) -> VectorData { +async fn repeat( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + Footprint -> VectorData, + )] + instance: impl Node, + #[default(100., 100.)] + // TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed. + direction: DVec2, + angle: Angle, + #[default(4)] instances: IntegerCount, +) -> VectorData { let instance = instance.eval(footprint).await; let angle = angle.to_radians(); let instances = instances.max(1); @@ -137,13 +206,23 @@ async fn repeat(footprint: Footprint, instance: impl Node, +async fn circular_repeat( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + Footprint -> VectorData, + )] + instance: impl Node, angle_offset: Angle, #[default(5)] radius: Length, #[default(5)] instances: IntegerCount, @@ -171,24 +250,48 @@ async fn circular_repeat( result.concat(&instance, transform); } + result.style.set_stroke_transform(DAffine2::IDENTITY); + result } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn bounding_box( - #[implementations((), Footprint)] footprint: F, - #[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node, +async fn bounding_box( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + Footprint -> VectorData, + )] + vector_data: impl Node, ) -> VectorData { let vector_data = vector_data.eval(footprint).await; let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap(); - VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1])) + let mut result = VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1])); + result.style = vector_data.style.clone(); + result.style.set_stroke_transform(DAffine2::IDENTITY); + result } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn solidify_stroke(footprint: Footprint, vector_data: impl Node) -> VectorData { - // Grab what we need from original data. +async fn solidify_stroke( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + Footprint -> VectorData, + )] + vector_data: impl Node, +) -> VectorData { let vector_data = vector_data.eval(footprint).await; + let VectorData { transform, style, .. } = &vector_data; let subpaths = vector_data.stroke_bezier_paths(); let mut result = VectorData::empty(); @@ -249,12 +352,27 @@ impl ConcatElement for GraphicGroup { } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn copy_to_points( - footprint: Footprint, - points: impl Node, +async fn copy_to_points( + #[implementations( + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + () -> VectorData, + Footprint -> VectorData, + )] + points: impl Node, #[expose] - #[implementations(Footprint -> VectorData, Footprint -> GraphicGroup)] - instance: impl Node, + #[implementations( + () -> VectorData, + () -> GraphicGroup, + Footprint -> VectorData, + Footprint -> GraphicGroup, + )] + instance: impl Node, #[default(1)] random_scale_min: f64, #[default(1)] random_scale_max: f64, random_scale_bias: f64, @@ -264,6 +382,7 @@ async fn copy_to_points I { let points = points.eval(footprint).await; let instance = instance.eval(footprint).await; + let random_scale_difference = random_scale_max - random_scale_min; let points_list = points.point_domain.positions(); @@ -311,17 +430,29 @@ async fn copy_to_points, +async fn sample_points( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + Footprint -> VectorData, + )] + vector_data: impl Node, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, - lengths_of_segments_of_subpaths: impl Node>, + #[implementations( + () -> Vec, + Footprint -> Vec, + )] + subpath_segment_lengths: impl Node>, ) -> VectorData { let vector_data = vector_data.eval(footprint).await; - let lengths_of_segments_of_subpaths = lengths_of_segments_of_subpaths.eval(footprint).await; + let subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await; let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable(); @@ -329,11 +460,11 @@ async fn sample_points( result.transform = vector_data.transform; while let Some((index, (segment, _, _, mut last_end))) = bezier.next() { - let mut lengths = vec![(segment, lengths_of_segments_of_subpaths.get(index).copied().unwrap_or_default())]; + let mut lengths = vec![(segment, subpath_segment_lengths.get(index).copied().unwrap_or_default())]; while let Some((index, (segment, _, _, end))) = bezier.peek().is_some_and(|(_, (_, _, start, _))| *start == last_end).then(|| bezier.next()).flatten() { last_end = end; - lengths.push((segment, lengths_of_segments_of_subpaths.get(index).copied().unwrap_or_default())); + lengths.push((segment, subpath_segment_lengths.get(index).copied().unwrap_or_default())); } let total_length: f64 = lengths.iter().map(|(_, len)| *len).sum(); @@ -385,9 +516,17 @@ async fn sample_points( } #[node_macro::node(category(""), path(graphene_core::vector))] -async fn poisson_disk_points( - #[implementations((), Footprint)] footprint: F, - #[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node, +async fn poisson_disk_points( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + Footprint -> VectorData, + )] + vector_data: impl Node, #[default(10.)] #[min(0.01)] separation_disk_diameter: f64, @@ -417,8 +556,19 @@ async fn poisson_disk_points( result } -#[node_macro::node(name("Lengths of Segments of Subpaths"), category(""))] -async fn lengths_of_segments_of_subpaths(footprint: Footprint, vector_data: impl Node) -> Vec { +#[node_macro::node(category(""))] +async fn subpath_segment_lengths( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + Footprint -> VectorData, + )] + vector_data: impl Node, +) -> Vec { let vector_data = vector_data.eval(footprint).await; vector_data @@ -453,10 +603,23 @@ fn splines_from_points(_: (), mut vector_data: VectorData) -> VectorData { } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn morph( - footprint: Footprint, - source: impl Node, - #[expose] target: impl Node, +async fn morph( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + Footprint -> VectorData, + )] + source: impl Node, + #[expose] + #[implementations( + () -> VectorData, + Footprint -> VectorData, + )] + target: impl Node, #[range((0., 1.))] #[default(0.5)] time: Fraction, @@ -734,7 +897,7 @@ mod test { #[tokio::test] async fn lengths() { let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); - let lengths = lengths_of_segments_of_subpaths(Footprint::default(), &vector_node(subpath)).await; + let lengths = subpath_segment_lengths(Footprint::default(), &vector_node(subpath)).await; assert_eq!(lengths, vec![100.]); } #[test] diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index a19005f1..48e83532 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -9,6 +9,7 @@ use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::{BlendMode, LuminanceCalculation}; use graphene_core::renderer::RenderMetadata; use graphene_core::uuid::NodeId; +use graphene_core::vector::style::Fill; use graphene_core::{Color, MemoHash, Node, Type}; pub use glam::{DAffine2, DVec2, IVec2, UVec2}; @@ -196,6 +197,52 @@ impl TaggedValue { } pub fn from_primitive_string(string: &str, ty: &Type) -> Option { + fn to_dvec2(input: &str) -> Option { + let mut split = input.split(','); + let x = split.next()?.trim().parse().ok()?; + let y = split.next()?.trim().parse().ok()?; + Some(DVec2::new(x, y)) + } + + fn to_color(input: &str) -> Option { + // String syntax (e.g. "000000ff") + if input.starts_with('"') && input.ends_with('"') { + let color = input.trim().trim_matches('"').trim().trim_start_matches('#'); + match color.len() { + 6 => return Color::from_rgb_str(color), + 8 => return Color::from_rgba_str(color), + _ => { + log::error!("Invalid default value color string: {}", input); + return None; + } + } + } + + // Color constant syntax (e.g. Color::BLACK) + let mut choices = input.split("::"); + let (first, second) = (choices.next()?.trim(), choices.next()?.trim()); + if first == "Color" { + return Some(match second { + "BLACK" => Color::BLACK, + "WHITE" => Color::WHITE, + "RED" => Color::RED, + "GREEN" => Color::GREEN, + "BLUE" => Color::BLUE, + "YELLOW" => Color::YELLOW, + "CYAN" => Color::CYAN, + "MAGENTA" => Color::MAGENTA, + "TRANSPARENT" => Color::TRANSPARENT, + _ => { + log::error!("Invalid default value color constant: {}", input); + return None; + } + }); + } + + log::error!("Invalid default value color: {}", input); + None + } + match ty { Type::Generic(_) => None, Type::Concrete(concrete_type) => { @@ -209,8 +256,11 @@ impl TaggedValue { x if x == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::F64).ok()?, x if x == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::U64).ok()?, x if x == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::U32).ok()?, + x if x == TypeId::of::() => to_dvec2(string).map(TaggedValue::DVec2)?, x if x == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?, - x if x == TypeId::of::() => Color::from_rgba_str(string).map(TaggedValue::Color)?, + x if x == TypeId::of::() => to_color(string).map(TaggedValue::Color)?, + x if x == TypeId::of::>() => to_color(string).map(|color| TaggedValue::OptionalColor(Some(color)))?, + x if x == TypeId::of::() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?, _ => return None, }; Some(ty) diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index 5218e780..da78668e 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -4,8 +4,16 @@ use graphene_core::Color; #[node_macro::node(category("Raster"))] async fn image_color_palette( - #[implementations((), Footprint)] footprint: F, - #[implementations(() -> ImageFrame, Footprint -> ImageFrame)] image: impl Node>, + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> ImageFrame, + Footprint -> ImageFrame, + )] + image: impl Node>, #[min(1.)] #[max(28.)] max_size: u32, diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 56d21cec..5787dd66 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -436,8 +436,8 @@ pub struct ImageFrameNode { _p: PhantomData

, } #[node_macro::old_node_fn(ImageFrameNode<_P>)] -fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> graphene_core::raster::ImageFrame<_P> { - graphene_core::raster::ImageFrame { +fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> ImageFrame<_P> { + ImageFrame { image, transform, alpha_blending: AlphaBlending::default(), @@ -463,7 +463,7 @@ fn noise_pattern( cellular_distance_function: CellularDistanceFunction, cellular_return_type: CellularReturnType, cellular_jitter: f64, -) -> graphene_core::raster::ImageFrame { +) -> ImageFrame { let viewport_bounds = footprint.viewport_bounds_in_local_space(); let mut size = viewport_bounds.size(); diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index fcb42539..1f7aa58e 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -11,9 +11,17 @@ use path_bool::PathBooleanOperation; use std::ops::{Div, Mul}; #[node_macro::node(category(""))] -async fn boolean_operation( - #[implementations((), Footprint)] footprint: F, - #[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] group_of_paths: impl Node, +async fn boolean_operation( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> GraphicGroup, + Footprint -> GraphicGroup, + )] + group_of_paths: impl Node, operation: BooleanOperation, ) -> VectorData { let group_of_paths = group_of_paths.eval(footprint).await; @@ -303,6 +311,7 @@ pub fn convert_usvg_path(path: &usvg::Path) -> Vec> { } type Path = Vec; + fn boolean_union(a: Path, b: Path) -> Vec { path_bool(a, b, PathBooleanOperation::Union) } @@ -323,6 +332,7 @@ fn path_bool(a: Path, b: Path, op: PathBooleanOperation) -> Vec { fn boolean_subtract(a: Path, b: Path) -> Vec { path_bool(a, b, PathBooleanOperation::Difference) } + fn boolean_intersect(a: Path, b: Path) -> Vec { path_bool(a, b, PathBooleanOperation::Intersection) } diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 084975a9..caebb8b8 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -133,7 +133,12 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen #[cfg(target_arch = "wasm32")] async fn rasterize( _: (), - #[implementations(Footprint -> VectorData, Footprint -> ImageFrame, Footprint -> GraphicGroup)] data: impl Node, + #[implementations( + Footprint -> VectorData, + Footprint -> ImageFrame, + Footprint -> GraphicGroup, + )] + data: impl Node, footprint: Footprint, surface_handle: Arc>, ) -> ImageFrame {