Improve type compatibility and clean up new node macro usages (#2002)

* Improve type compatibility

* More
This commit is contained in:
Keavon Chambers
2024-09-22 01:44:18 -07:00
committed by GitHub
parent 33df58eda9
commit 3ddc052538
17 changed files with 842 additions and 243 deletions
@@ -130,7 +130,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Footprint)), manual_composition: Some(generic!(T)),
inputs: vec![ inputs: vec![
NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(1), 0),
NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(2), 0),
@@ -215,7 +215,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ 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 // 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 { DocumentNode {
manual_composition: Some(concrete!(Footprint)), manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToArtboardNode"),
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(TaggedValue), 1), NodeInput::network(concrete!(TaggedValue), 1),
NodeInput::value(TaggedValue::String(String::from("Artboard")), false), NodeInput::value(TaggedValue::String(String::from("Artboard")), false),
@@ -224,7 +225,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(TaggedValue), 4), NodeInput::network(concrete!(TaggedValue), 4),
NodeInput::network(concrete!(TaggedValue), 5), NodeInput::network(concrete!(TaggedValue), 5),
], ],
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToArtboardNode"),
..Default::default() ..Default::default()
}, },
// The monitor node is used to display a thumbnail in the UI. // The monitor node is used to display a thumbnail in the UI.
@@ -876,7 +876,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
manual_composition: Some(concrete!(Footprint)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
@@ -885,7 +885,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
manual_composition: Some(concrete!(Footprint)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
@@ -894,7 +894,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
manual_composition: Some(concrete!(Footprint)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
@@ -903,7 +903,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
manual_composition: Some(concrete!(Footprint)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
] ]
@@ -1880,6 +1880,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
properties: &node_properties::node_no_properties, properties: &node_properties::node_no_properties,
}, },
DocumentNodeDefinition { 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", identifier: "Brightness/Contrast",
category: "Raster: Adjustment", category: "Raster: Adjustment",
node_template: NodeTemplate { node_template: NodeTemplate {
@@ -1901,6 +1904,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
properties: &node_properties::brightness_contrast_properties, 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 { DocumentNodeDefinition {
identifier: "Curves", identifier: "Curves",
category: "Raster: Adjustment", category: "Raster: Adjustment",
@@ -2264,7 +2270,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(graphene_core::vector::VectorData), 0)], 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)), manual_composition: Some(concrete!(Footprint)),
..Default::default() ..Default::default()
}, },
@@ -2275,7 +2281,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(f64), 2), // From the document node's parameters 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!(f64), 3), // From the document node's parameters
NodeInput::network(concrete!(bool), 4), // 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")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_nodes::SamplePointsNode")),
manual_composition: Some(concrete!(Footprint)), manual_composition: Some(concrete!(Footprint)),
@@ -2309,7 +2315,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [ node_metadata: [
DocumentNodeMetadata { DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata { persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Lengths of Segments of Subpaths".to_string(), display_name: "Subpath Segment Lengths".to_string(),
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()
@@ -2440,27 +2446,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
properties: &node_properties::index_properties, 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<LayoutGroup> + Sync); type PropertiesLayout = &'static (dyn Fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup> + Sync);
@@ -132,7 +132,7 @@ pub(crate) fn property_from_type(
x if x == TypeId::of::<u32>() => 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::<u32>() => 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::<u64>() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)), true).into(), x if x == TypeId::of::<u64>() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)), true).into(),
x if x == TypeId::of::<String>() => text_widget(document_node, node_id, index, name, true).into(), x if x == TypeId::of::<String>() => text_widget(document_node, node_id, index, name, true).into(),
x if x == TypeId::of::<Color>() => color_widget(document_node, node_id, index, name, ColorButton::default(), true), x if x == TypeId::of::<Color>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true),
x if x == TypeId::of::<Option<Color>>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(true), true), x if x == TypeId::of::<Option<Color>>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(true), true),
x if x == TypeId::of::<DVec2>() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist), x if x == TypeId::of::<DVec2>() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist),
x if x == TypeId::of::<UVec2>() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", Some(0.), add_blank_assist), x if x == TypeId::of::<UVec2>() => 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] vec![location, dimensions, background, clip_row]
} }
pub(crate) fn color_fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color = color_widget(document_node, node_id, 1, "Color", ColorButton::default(), true);
vec![color]
}
+79 -23
View File
@@ -232,13 +232,25 @@ impl ArtboardGroup {
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn layer<F: 'n + Copy + Send>( async fn layer<F: 'n + Send + Copy>(
#[implementations((), Footprint)] footprint: F, #[implementations(
#[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] stack: impl Node<F, Output = GraphicGroup>, (),
#[implementations(() -> GraphicElement, Footprint -> GraphicElement)] graphic_element: impl Node<F, Output = GraphicElement>, Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
Footprint -> GraphicGroup,
)]
stack: impl Node<F, Output = GraphicGroup>,
#[implementations(
() -> GraphicElement,
Footprint -> GraphicElement,
)]
element: impl Node<F, Output = GraphicElement>,
node_path: Vec<NodeId>, node_path: Vec<NodeId>,
) -> GraphicGroup { ) -> GraphicGroup {
let mut element = graphic_element.eval(footprint).await; let mut element = element.eval(footprint).await;
let mut stack = stack.eval(footprint).await; let mut stack = stack.eval(footprint).await;
if stack.transform.matrix2.determinant() != 0. { if stack.transform.matrix2.determinant() != 0. {
*element.transform_mut() = stack.transform.inverse() * element.transform(); *element.transform_mut() = stack.transform.inverse() * element.transform();
@@ -255,16 +267,23 @@ async fn layer<F: 'n + Copy + Send>(
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>( async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
#[implementations((), (), (), (), Footprint)] footprint: F,
#[implementations( #[implementations(
() -> VectorData, (),
() -> ImageFrame<Color>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup, () -> GraphicGroup,
() -> TextureFrame, () -> VectorData,
Footprint -> VectorData, () -> ImageFrame<Color>,
() -> TextureFrame,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrame<Color>,
Footprint -> GraphicGroup, Footprint -> TextureFrame,
Footprint -> TextureFrame,
)] )]
data: impl Node<F, Output = Data>, data: impl Node<F, Output = Data>,
) -> GraphicElement { ) -> GraphicElement {
@@ -273,15 +292,22 @@ async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>( async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
#[implementations((), (), (), (), Footprint)] footprint: F,
#[implementations( #[implementations(
(),
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData, () -> VectorData,
() -> ImageFrame<Color>, () -> ImageFrame<Color>,
() -> GraphicGroup,
() -> TextureFrame, () -> TextureFrame,
Footprint -> GraphicGroup,
Footprint -> VectorData, Footprint -> VectorData,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrame<Color>,
Footprint -> GraphicGroup,
Footprint -> TextureFrame, Footprint -> TextureFrame,
)] )]
element: impl Node<F, Output = Data>, element: impl Node<F, Output = Data>,
@@ -290,9 +316,26 @@ async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn to_artboard<F: 'n + Copy + Send + ApplyTransform>( async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroup> + 'n>(
#[implementations((), Footprint)] mut footprint: F, #[implementations(
#[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] contents: impl Node<F, Output = GraphicGroup>, (),
(),
(),
(),
Footprint,
)]
mut footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
() -> ImageFrame<Color>,
() -> TextureFrame,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
Footprint -> TextureFrame,
)]
contents: impl Node<F, Output = Data>,
label: String, label: String,
location: IVec2, location: IVec2,
dimensions: IVec2, dimensions: IVec2,
@@ -303,7 +346,7 @@ async fn to_artboard<F: 'n + Copy + Send + ApplyTransform>(
let graphic_group = contents.eval(footprint).await; let graphic_group = contents.eval(footprint).await;
Artboard { Artboard {
graphic_group, graphic_group: graphic_group.into(),
label, label,
location: location.min(location + dimensions), location: location.min(location + dimensions),
dimensions: dimensions.abs(), dimensions: dimensions.abs(),
@@ -311,11 +354,24 @@ async fn to_artboard<F: 'n + Copy + Send + ApplyTransform>(
clip, clip,
} }
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn append_artboard<F: 'n + Copy + Send>( async fn append_artboard<F: 'n + Send + Copy>(
#[implementations((), Footprint)] footprint: F, #[implementations(
#[implementations(() -> ArtboardGroup, Footprint -> ArtboardGroup)] artboards: impl Node<F, Output = ArtboardGroup>, (),
#[implementations(() -> Artboard, Footprint -> Artboard)] artboard: impl Node<F, Output = Artboard>, Footprint,
)]
footprint: F,
#[implementations(
() -> ArtboardGroup,
Footprint -> ArtboardGroup,
)]
artboards: impl Node<F, Output = ArtboardGroup>,
#[implementations(
() -> Artboard,
Footprint -> Artboard,
)]
artboard: impl Node<F, Output = Artboard>,
node_path: Vec<NodeId>, node_path: Vec<NodeId>,
) -> ArtboardGroup { ) -> ArtboardGroup {
let artboard = artboard.eval(footprint).await; let artboard = artboard.eval(footprint).await;
+4 -1
View File
@@ -1,8 +1,11 @@
use crate::vector::VectorData;
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn log_to_console<T: core::fmt::Debug>( fn log_to_console<T: core::fmt::Debug>(
_: (), _: (),
#[default("Not connected to value yet")] #[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, value: T,
) -> T { ) -> T {
#[cfg(not(target_arch = "spirv"))] #[cfg(not(target_arch = "spirv"))]
+47 -32
View File
@@ -1,8 +1,13 @@
use crate::raster::adjustments::RedGreenBlue;
use crate::raster::BlendMode;
use crate::raster::ImageFrame;
use crate::registry::types::Percentage; use crate::registry::types::Percentage;
use crate::Node; use crate::vector::style::GradientStops;
use crate::{Color, Node};
use core::marker::PhantomData; use core::marker::PhantomData;
use core::ops::{Add, Div, Mul, Rem, Sub}; use core::ops::{Add, Div, Mul, Rem, Sub};
use glam::DVec2;
use num_traits::Pow; use num_traits::Pow;
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
@@ -13,8 +18,8 @@ use spirv_std::num_traits::float::Float;
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn add<U: Add<T>, T>( fn add<U: Add<T>, 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, DVec2, f64, 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, DVec2, f64)] addend: T,
) -> <U as Add<T>>::Output { ) -> <U as Add<T>>::Output {
augend + addend augend + addend
} }
@@ -23,8 +28,8 @@ fn add<U: Add<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn subtract<U: Sub<T>, T>( fn subtract<U: Sub<T>, 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, DVec2, f64, 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, DVec2, f64)] subtrahend: T,
) -> <U as Sub<T>>::Output { ) -> <U as Sub<T>>::Output {
minuend - subtrahend minuend - subtrahend
} }
@@ -33,9 +38,9 @@ fn subtract<U: Sub<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn multiply<U: Mul<T>, T>( fn multiply<U: Mul<T>, 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.)] #[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, multiplicand: T,
) -> <U as Mul<T>>::Output { ) -> <U as Mul<T>>::Output {
multiplier * multiplicand multiplier * multiplicand
@@ -45,9 +50,9 @@ fn multiply<U: Mul<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn divide<U: Div<T>, T>( fn divide<U: Div<T>, 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.)] #[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, denominator: T,
) -> <U as Div<T>>::Output { ) -> <U as Div<T>>::Output {
numerator / denominator numerator / denominator
@@ -57,9 +62,9 @@ fn divide<U: Div<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn modulo<U: Rem<T>, T>( fn modulo<U: Rem<T>, 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.)] #[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, modulus: T,
) -> <U as Rem<T>>::Output { ) -> <U as Rem<T>>::Output {
numerator % modulus numerator % modulus
@@ -69,7 +74,7 @@ fn modulo<U: Rem<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn exponent<U: Pow<T>, T>( fn exponent<U: Pow<T>, 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.)] #[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)]
power: T, power: T,
@@ -119,52 +124,63 @@ fn logarithm<U: num_traits::float::Float>(
// Sine // Sine
#[node_macro::node(category("Math: Trig"))] #[node_macro::node(category("Math: Trig"))]
fn sine(_: (), theta: f64) -> f64 { fn sine<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta: U) -> U {
theta.sin() theta.sin()
} }
// Cosine // Cosine
#[node_macro::node(category("Math: Trig"))] #[node_macro::node(category("Math: Trig"))]
fn cosine(_: (), theta: f64) -> f64 { fn cosine<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta: U) -> U {
theta.cos() theta.cos()
} }
// Tangent // Tangent
#[node_macro::node(category("Math: Trig"))] #[node_macro::node(category("Math: Trig"))]
fn tangent(_: (), theta: f64) -> f64 { fn tangent<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta: U) -> U {
theta.tan() theta.tan()
} }
// Random // Random
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn random(_: (), _primary: (), seed: u64, min: f64, #[default(1.)] max: f64) -> f64 { fn random<U: num_traits::float::Float>(
_: (),
_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 mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let result = rng.gen::<f64>(); let result = rng.gen::<f64>();
let (min, max) = if min < max { (min, max) } else { (max, min) }; 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 result * (max - min) + min
} }
// Round // Round
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn round(_: (), value: f64) -> f64 { fn round<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
value.round() value.round()
} }
// Floor // Floor
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn floor(_: (), value: f64) -> f64 { fn floor<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
value.floor() value.floor()
} }
// Ceiling // Ceiling
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn ceiling(_: (), value: f64) -> f64 { fn ceiling<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
value.ceil() value.ceil()
} }
// Absolute Value // Absolute Value
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn absolute_value(_: (), value: f64) -> f64 { fn absolute_value<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
value.abs() value.abs()
} }
@@ -190,8 +206,8 @@ fn max<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32,
#[node_macro::node(category("Math: Logic"))] #[node_macro::node(category("Math: Logic"))]
fn equals<U: core::cmp::PartialEq<T>, T>( fn equals<U: core::cmp::PartialEq<T>, T>(
_: (), _: (),
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)]
#[min(100.)] #[min(100.)]
#[max(200.)] #[max(200.)]
other_value: U, other_value: U,
@@ -243,32 +259,31 @@ fn percentage_value(_: (), _primary: (), percentage: Percentage) -> f64 {
// Vector2 Value // Vector2 Value
#[node_macro::node(category("Value"))] #[node_macro::node(category("Value"))]
fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> glam::DVec2 { fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> DVec2 {
glam::DVec2::new(x, y) DVec2::new(x, y)
} }
// TODO: Make it possible to give Color::BLACK instead of 000000ff as the default
// Color Value // Color Value
#[node_macro::node(category("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<Color>) -> Option<Color> {
color color
} }
// Gradient Value // Gradient Value
#[node_macro::node(category("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 gradient
} }
// Color Channel Value // Color Channel Value
#[node_macro::node(category("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 color_channel
} }
// Blend Mode Value // Blend Mode Value
#[node_macro::node(category("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 blend_mode
} }
@@ -281,19 +296,19 @@ fn size_of(_: (), ty: crate::Type) -> Option<usize> {
// Some // Some
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn some<T>(_: (), #[implementations(f64, f32, u32, u64, String, crate::Color)] input: T) -> Option<T> { fn some<T>(_: (), #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option<T> {
Some(input) Some(input)
} }
// Unwrap // Unwrap
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn unwrap<T: Default>(_: (), #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>, Option<crate::Color>)] input: Option<T>) -> T { fn unwrap<T: Default>(_: (), #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>, Option<Color>)] input: Option<T>) -> T {
input.unwrap_or_default() input.unwrap_or_default()
} }
// Clone // Clone
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&crate::raster::ImageFrame<crate::Color>)] value: &'i T) -> T { fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrame<Color>)] value: &'i T) -> T {
value.clone() value.clone()
} }
+40 -12
View File
@@ -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 crate::{registry::types::Percentage, transform::Footprint};
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use core::fmt::Debug;
use glam::DVec2; use glam::DVec2;
pub use self::color::{Color, Luma, SRGBA8};
#[cfg(target_arch = "spirv")] #[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float; use spirv_std::num_traits::float::Float;
@@ -291,12 +291,12 @@ trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode); 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) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode; 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) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode; self.alpha_blending.blend_mode = blend_mode;
} }
@@ -308,9 +308,23 @@ impl SetBlendMode for ImageFrame<Color> {
} }
#[node_macro::node(category("Style"))] #[node_macro::node(category("Style"))]
async fn blend_mode<T: SetBlendMode>( async fn blend_mode<F: 'n + Send, T: SetBlendMode>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> crate::vector::VectorData, Footprint -> crate::GraphicGroup, Footprint -> ImageFrame<Color>)] value: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
() -> ImageFrame<Color>,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
)]
value: impl Node<F, Output = T>,
blend_mode: BlendMode, blend_mode: BlendMode,
) -> T { ) -> T {
let mut value = value.eval(footprint).await; let mut value = value.eval(footprint).await;
@@ -319,9 +333,23 @@ async fn blend_mode<T: SetBlendMode>(
} }
#[node_macro::node(category("Style"))] #[node_macro::node(category("Style"))]
async fn opacity<T: MultiplyAlpha>( async fn opacity<F: 'n + Send, T: MultiplyAlpha>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> crate::vector::VectorData, Footprint -> crate::GraphicGroup, Footprint -> ImageFrame<Color>)] value: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
() -> ImageFrame<Color>,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
)]
value: impl Node<F, Output = T>,
#[default(100.)] factor: Percentage, #[default(100.)] factor: Percentage,
) -> T { ) -> T {
let mut value = value.eval(footprint).await; let mut value = value.eval(footprint).await;
+314 -52
View File
@@ -19,6 +19,21 @@ use core::fmt::Debug;
#[cfg(target_arch = "spirv")] #[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float; 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 = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))] #[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, Hash)] #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, Hash)]
@@ -268,10 +283,24 @@ impl From<BlendMode> for vello::peniko::Mix {
} }
} }
#[node_macro::node(category("Raster: Adjustment"))] // Unique to Graphite #[node_macro::node(category("Raster: Adjustment"))]
async fn luminance<T: Adjust<Color>>( async fn luminance<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
luminance_calc: LuminanceCalculation, luminance_calc: LuminanceCalculation,
) -> T { ) -> T {
let mut input = input.eval(footprint).await; let mut input = input.eval(footprint).await;
@@ -289,9 +318,23 @@ async fn luminance<T: Adjust<Color>>(
} }
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"))]
async fn extract_channel<T: Adjust<Color>>( async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
channel: RedGreenBlueAlpha, channel: RedGreenBlueAlpha,
) -> T { ) -> T {
let mut input = input.eval(footprint).await; let mut input = input.eval(footprint).await;
@@ -308,7 +351,24 @@ async fn extract_channel<T: Adjust<Color>>(
} }
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"))]
async fn make_opaque<T: Adjust<Color>>(footprint: Footprint, #[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>) -> T { async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
#[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
) -> T {
let mut input = input.eval(footprint).await; let mut input = input.eval(footprint).await;
input.adjust(|color| { input.adjust(|color| {
if color.a() == 0. { if color.a() == 0. {
@@ -319,11 +379,29 @@ async fn make_opaque<T: Adjust<Color>>(footprint: Footprint, #[implementations(F
input 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"))] #[node_macro::node(category("Raster: Adjustment"))]
async fn levels<T: Adjust<Color>>( async fn levels<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
#[default(0.)] shadows: Percentage, #[default(0.)] shadows: Percentage,
#[default(50.)] midtones: Percentage, #[default(50.)] midtones: Percentage,
#[default(100.)] highlights: Percentage, #[default(100.)] highlights: Percentage,
@@ -376,13 +454,32 @@ async fn levels<T: Adjust<Color>>(
input input
} }
// From <https://stackoverflow.com/a/55233732/775283> // 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 // Works the same for gamma and linear color
#[node_macro::node(name("Black & White"), category("Raster: Adjustment"))] #[node_macro::node(name("Black & White"), category("Raster: Adjustment"))]
async fn black_and_white<T: Adjust<Color>>( async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>, (),
#[default(000000ff)] tint: Color, (),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
#[default(Color::BLACK)] tint: Color,
#[default(40.)] #[default(40.)]
#[range((-200., 300.))] #[range((-200., 300.))]
reds: Percentage, reds: Percentage,
@@ -443,10 +540,27 @@ async fn black_and_white<T: Adjust<Color>>(
input 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"))] #[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))]
async fn hue_saturation<T: Adjust<Color>>( async fn hue_saturation<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
hue_shift: Angle, hue_shift: Angle,
saturation_shift: SignedPercentage, saturation_shift: SignedPercentage,
lightness_shift: SignedPercentage, lightness_shift: SignedPercentage,
@@ -471,8 +585,27 @@ async fn hue_saturation<T: Adjust<Color>>(
input 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"))] #[node_macro::node(category("Raster: Adjustment"))]
async fn invert<T: Adjust<Color>>(footprint: Footprint, #[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>) -> T { async fn invert<F: 'n + Send, T: Adjust<Color>>(
#[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
) -> T {
let mut input = input.eval(footprint).await; let mut input = input.eval(footprint).await;
input.adjust(|color| { input.adjust(|color| {
let color = color.to_gamma_srgb(); let color = color.to_gamma_srgb();
@@ -484,10 +617,26 @@ async fn invert<T: Adjust<Color>>(footprint: Footprint, #[implementations(Footpr
input 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"))] #[node_macro::node(category("Raster: Adjustment"))]
async fn threshold<T: Adjust<Color>>( async fn threshold<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
#[default(50.)] min_luminance: Percentage, #[default(50.)] min_luminance: Percentage,
#[default(100.)] max_luminance: Percentage, #[default(100.)] max_luminance: Percentage,
luminance_calc: LuminanceCalculation, luminance_calc: LuminanceCalculation,
@@ -571,8 +720,14 @@ impl Blend<Color> for GradientStops {
} }
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"))]
async fn blend<F: 'n + Copy + Send, T: Blend<Color> + Send>( async fn blend<F: 'n + Send + Copy, T: Blend<Color> + Send>(
#[implementations((), (), (), Footprint)] footprint: F, #[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrame<Color>,
@@ -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)) 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"))] #[node_macro::node(category("Raster: Adjustment"))]
async fn gradient_map<F: 'n + Copy + Send, T: Adjust<Color>>( async fn gradient_map<F: 'n + Send, T: Adjust<Color>>(
#[implementations((), (), (), Footprint)] footprint: F, #[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrame<Color>,
@@ -715,12 +879,31 @@ async fn gradient_map<F: 'n + Copy + Send, T: Adjust<Color>>(
input input
} }
// Based on <https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters> // 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 // The results of this implementation are very close to correct, but not quite perfect
#[node_macro::node(category("Raster: Adjustment"))] #[node_macro::node(category("Raster: Adjustment"))]
async fn vibrance<T: Adjust<Color>>( async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
vibrance: SignedPercentage, vibrance: SignedPercentage,
) -> T { ) -> T {
let mut input = image.eval(footprint).await; 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"))] #[node_macro::node(category("Raster: Adjustment"))]
async fn channel_mixer<T: Adjust<Color>>( async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
monochrome: bool, monochrome: bool,
#[default(40.)] #[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"))] #[node_macro::node(category("Raster: Adjustment"))]
async fn selective_color<T: Adjust<Color>>( async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
mode: RelativeAbsolute, mode: RelativeAbsolute,
#[name("(Reds) Cyan")] r_c: f64, #[name("(Reds) Cyan")] r_c: f64,
#[name("(Reds) Magenta")] r_m: f64, #[name("(Reds) Magenta")] r_m: f64,
@@ -1288,12 +1507,30 @@ impl<P: Pixel> MultiplyAlpha for ImageFrame<P> {
} }
} }
// 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. // This algorithm produces fully accurate output in relation to the industry standard.
#[node_macro::node(category("Raster: Adjustment"))] #[node_macro::node(category("Raster: Adjustment"))]
async fn posterize<T: Adjust<Color>>( async fn posterize<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
#[default(4)] #[default(4)]
#[min(2.)] #[min(2.)]
levels: u32, levels: u32,
@@ -1313,11 +1550,30 @@ async fn posterize<T: Adjust<Color>>(
input 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"))] #[node_macro::node(category("Raster: Adjustment"))]
async fn exposure<T: Adjust<Color>>( async fn exposure<F: 'n + Send, T: Adjust<Color>>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>, (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
exposure: f64, exposure: f64,
offset: f64, offset: f64,
#[default(1.)] #[default(1.)]
@@ -1343,7 +1599,7 @@ const WINDOW_SIZE: usize = 1024;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
fn generate_curves<C: Channel + super::Linear>(_: (), curve: Curve, #[implementations(f32)] _target_format: C) -> ValueMapperNode<C> { fn generate_curves<C: Channel + super::Linear>(_: (), curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
use bezier_rs::{Bezier, TValue}; use bezier_rs::{Bezier, TValue};
let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle]; let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle];
@@ -1383,9 +1639,15 @@ fn generate_curves<C: Channel + super::Linear>(_: (), curve: Curve, #[implementa
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[node_macro::node(category("Raster: Adjustment"))] // Unique to Graphite #[node_macro::node(category("Raster: Adjustment"))]
async fn color_overlay<F: 'n + Copy + Send, T: Adjust<Color>>( async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
#[implementations((), (), (), Footprint)] footprint: F, #[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrame<Color>,
@@ -1395,7 +1657,7 @@ async fn color_overlay<F: 'n + Copy + Send, T: Adjust<Color>>(
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
#[default(000000ff)] color: Color, #[default(Color::BLACK)] color: Color,
blend_mode: BlendMode, blend_mode: BlendMode,
#[default(100.)] opacity: Percentage, #[default(100.)] opacity: Percentage,
) -> T { ) -> T {
+18 -18
View File
@@ -1,15 +1,9 @@
use glam::DAffine2;
use glam::DVec2;
use crate::raster::bbox::AxisAlignedBbox; use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::ImageFrame; use crate::raster::{ImageFrame, Pixel};
use crate::raster::Pixel;
use crate::vector::VectorData; use crate::vector::VectorData;
use crate::Artboard; use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup};
use crate::ArtboardGroup;
use crate::GraphicElement; use glam::{DAffine2, DVec2};
use crate::GraphicGroup;
pub trait Transform { pub trait Transform {
fn transform(&self) -> DAffine2; fn transform(&self) -> DAffine2;
@@ -182,7 +176,7 @@ impl From<()> for Footprint {
} }
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn cull<T>(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame<crate::Color>, ArtboardGroup)] data: T) -> T { fn cull<T>(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame<Color>, ArtboardGroup)] data: T) -> T {
data data
} }
@@ -217,15 +211,21 @@ impl ApplyTransform for () {
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn transform<I: Into<Footprint> + ApplyTransform + 'n + Clone + Send + Sync, T: TransformMut + 'n>( async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Sync, T: 'n + TransformMut>(
#[implementations(Footprint, Footprint, Footprint, (), (), ())] mut input: I, #[implementations(
(),
(),
(),
Footprint,
)]
mut input: I,
#[implementations( #[implementations(
Footprint -> VectorData,
Footprint -> GraphicGroup,
Footprint -> ImageFrame<crate::Color>,
() -> VectorData, () -> VectorData,
() -> GraphicGroup, () -> GraphicGroup,
() -> ImageFrame<crate::Color>, () -> ImageFrame<Color>,
Footprint -> VectorData,
Footprint -> GraphicGroup,
Footprint -> ImageFrame<Color>,
)] )]
transform_target: impl Node<I, Output = T>, transform_target: impl Node<I, Output = T>,
translate: DVec2, translate: DVec2,
@@ -251,7 +251,7 @@ async fn transform<I: Into<Footprint> + ApplyTransform + 'n + Clone + Send + Syn
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn replace_transform<Data: TransformMut, TransformInput: Transform>( fn replace_transform<Data: TransformMut, TransformInput: Transform>(
_: (), _: (),
#[implementations(VectorData, ImageFrame<crate::Color>, GraphicGroup)] mut data: Data, #[implementations(VectorData, ImageFrame<Color>, GraphicGroup)] mut data: Data,
#[implementations(DAffine2)] transform: TransformInput, #[implementations(DAffine2)] transform: TransformInput,
) -> Data { ) -> Data {
let data_transform = data.transform_mut(); let data_transform = data.transform_mut();
+12 -12
View File
@@ -1,4 +1,5 @@
use super::HandleId; use super::HandleId;
use crate::transform::Footprint;
use crate::vector::{PointId, VectorData}; use crate::vector::{PointId, VectorData};
use bezier_rs::Subpath; use bezier_rs::Subpath;
@@ -35,13 +36,12 @@ impl CornerRadius for [f64; 4] {
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn circle(_: (), _primary: (), #[default(50.)] radius: f64) -> VectorData { fn circle<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorData {
let radius: f64 = radius;
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))) super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn ellipse(_: (), _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorData { fn ellipse<F: 'n + Send>(#[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 radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius; let corner1 = -radius;
let corner2 = 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"))] #[node_macro::node(category("Vector: Shape"))]
fn rectangle<T: CornerRadius>( fn rectangle<F: 'n + Send, T: CornerRadius>(
_: (), #[implementations((), Footprint)] _footprint: F,
_primary: (), _primary: (),
#[default(100)] width: f64, #[default(100)] width: f64,
#[default(100)] height: f64, #[default(100)] height: f64,
@@ -69,8 +69,8 @@ fn rectangle<T: CornerRadius>(
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn regular_polygon( fn regular_polygon<F: 'n + Send>(
_: (), #[implementations((), Footprint)] _footprint: F,
_primary: (), _primary: (),
#[default(6)] #[default(6)]
#[min(3.)] #[min(3.)]
@@ -83,8 +83,8 @@ fn regular_polygon(
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn star( fn star<F: 'n + Send>(
_: (), #[implementations((), Footprint)] _footprint: F,
_primary: (), _primary: (),
#[default(5)] #[default(5)]
#[min(2.)] #[min(2.)]
@@ -100,12 +100,12 @@ fn star(
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn line(_: (), _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorData { fn line<F: 'n + Send>(#[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)) super::VectorData::from_subpath(Subpath::new_line(start, end))
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn spline(_: (), _primary: (), points: Vec<DVec2>) -> VectorData { fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec<DVec2>) -> VectorData {
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(points)); let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(points));
for pair in spline.segment_domain.ids().windows(2) { for pair in spline.segment_domain.ids().windows(2) {
spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]); spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]);
@@ -116,7 +116,7 @@ fn spline(_: (), _primary: (), points: Vec<DVec2>) -> 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 // 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(""))] #[node_macro::node(category(""))]
fn path(_: (), path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData { fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
let mut vector_data = super::VectorData::from_subpaths(path_data, false); let mut vector_data = super::VectorData::from_subpaths(path_data, false);
vector_data.colinear_manipulators = colinear_manipulators vector_data.colinear_manipulators = colinear_manipulators
.iter() .iter()
+11
View File
@@ -482,6 +482,17 @@ impl core::hash::Hash for Stroke {
} }
} }
impl From<Color> for Stroke {
fn from(color: Color) -> Self {
Self::new(Some(color), 1.)
}
}
impl From<Option<Color>> for Stroke {
fn from(color: Option<Color>) -> Self {
Self::new(color, 1.)
}
}
impl Stroke { impl Stroke {
pub const fn new(color: Option<Color>, weight: f64) -> Self { pub const fn new(color: Option<Color>, weight: f64) -> Self {
Self { Self {
@@ -425,8 +425,16 @@ use crate::transform::Footprint;
/// A node that applies a procedural modification to some [`VectorData`]. /// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn path_modify<F: 'n + Send + Sync + Clone>( async fn path_modify<F: 'n + Send + Sync + Clone>(
#[implementations((), Footprint)] input: F, #[implementations(
#[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node<F, Output = VectorData>, (),
Footprint,
)]
input: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
modification: Box<VectorModification>, modification: Box<VectorModification>,
) -> VectorData { ) -> VectorData {
let mut vector_data = vector_data.eval(input).await; let mut vector_data = vector_data.eval(input).await;
+207 -44
View File
@@ -27,9 +27,20 @@ impl VectorIterMut for VectorData {
} }
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn assign_colors<T: VectorIterMut>( async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
footprint: Footprint, #[implementations(
#[implementations(Footprint -> GraphicGroup, Footprint -> VectorData)] vector_group: impl Node<Footprint, Output = T>, (),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
Footprint -> GraphicGroup,
Footprint -> VectorData,
)]
vector_group: impl Node<F, Output = T>,
#[default(true)] fill: bool, #[default(true)] fill: bool,
stroke: bool, stroke: bool,
gradient: GradientStops, gradient: GradientStops,
@@ -70,10 +81,35 @@ async fn assign_colors<T: VectorIterMut>(
} }
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn fill<T: Into<Fill> + 'n + Send>( async fn fill<F: 'n + Send, T: Into<Fill> + 'n + Send>(
footprint: Footprint, #[implementations(
vector_data: impl Node<Footprint, Output = VectorData>, (),
#[implementations(Fill, Color, Option<Color>, crate::vector::style::Gradient)] fill: T, // TODO: Set the default to black (),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
() -> VectorData,
() -> VectorData,
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
#[implementations(
Fill,
Option<Color>,
Color,
Gradient,
Fill,
Option<Color>,
Color,
Gradient,
)]
#[default(Color::BLACK)]
fill: T,
_backup_color: Option<Color>, _backup_color: Option<Color>,
_backup_gradient: Gradient, _backup_gradient: Gradient,
) -> VectorData { ) -> VectorData {
@@ -84,10 +120,27 @@ async fn fill<T: Into<Fill> + 'n + Send>(
} }
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn stroke( async fn stroke<F: 'n + Send, T: Into<Option<Color>> + 'n + Send>(
footprint: Footprint, #[implementations(
vector_data: impl Node<Footprint, Output = VectorData>, (),
color: Option<Color>, // TODO: Set the default to black (),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
#[implementations(
Option<Color>,
Color,
Option<Color>,
Color,
)]
#[default(Color::BLACK)]
color: T,
#[default(5.)] weight: f64, #[default(5.)] weight: f64,
dash_lengths: Vec<f64>, dash_lengths: Vec<f64>,
dash_offset: f64, dash_offset: f64,
@@ -97,7 +150,7 @@ async fn stroke(
) -> VectorData { ) -> VectorData {
let mut vector_data = vector_data.eval(footprint).await; let mut vector_data = vector_data.eval(footprint).await;
vector_data.style.set_stroke(Stroke { vector_data.style.set_stroke(Stroke {
color, color: color.into(),
weight, weight,
dash_lengths, dash_lengths,
dash_offset, dash_offset,
@@ -110,7 +163,23 @@ async fn stroke(
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn repeat(footprint: Footprint, instance: impl Node<Footprint, Output = VectorData>, #[default(100., 100.)] direction: DVec2, angle: Angle, #[default(4)] instances: IntegerCount) -> VectorData { async fn repeat<F: 'n + Send>(
#[implementations(
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
instance: impl Node<F, Output = VectorData>,
#[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 instance = instance.eval(footprint).await;
let angle = angle.to_radians(); let angle = angle.to_radians();
let instances = instances.max(1); let instances = instances.max(1);
@@ -137,13 +206,23 @@ async fn repeat(footprint: Footprint, instance: impl Node<Footprint, Output = Ve
result.concat(&instance, transform); result.concat(&instance, transform);
} }
result.style.set_stroke_transform(DAffine2::IDENTITY);
result result
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn circular_repeat( async fn circular_repeat<F: 'n + Send>(
footprint: Footprint, #[implementations(
instance: impl Node<Footprint, Output = VectorData>, (),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
instance: impl Node<F, Output = VectorData>,
angle_offset: Angle, angle_offset: Angle,
#[default(5)] radius: Length, #[default(5)] radius: Length,
#[default(5)] instances: IntegerCount, #[default(5)] instances: IntegerCount,
@@ -171,24 +250,48 @@ async fn circular_repeat(
result.concat(&instance, transform); result.concat(&instance, transform);
} }
result.style.set_stroke_transform(DAffine2::IDENTITY);
result result
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn bounding_box<F: 'n + Copy + Send>( async fn bounding_box<F: 'n + Send>(
#[implementations((), Footprint)] footprint: F, #[implementations(
#[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node<F, Output = VectorData>, (),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
) -> VectorData { ) -> VectorData {
let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.eval(footprint).await;
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap(); 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))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn solidify_stroke(footprint: Footprint, vector_data: impl Node<Footprint, Output = VectorData>) -> VectorData { async fn solidify_stroke<F: 'n + Send>(
// Grab what we need from original data. #[implementations(
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
) -> VectorData {
let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.eval(footprint).await;
let VectorData { transform, style, .. } = &vector_data; let VectorData { transform, style, .. } = &vector_data;
let subpaths = vector_data.stroke_bezier_paths(); let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty(); let mut result = VectorData::empty();
@@ -249,12 +352,27 @@ impl ConcatElement for GraphicGroup {
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + TransformMut + Send>( async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + Default + ConcatElement + TransformMut + Send>(
footprint: Footprint, #[implementations(
points: impl Node<Footprint, Output = VectorData>, (),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
() -> VectorData,
Footprint -> VectorData,
)]
points: impl Node<F, Output = VectorData>,
#[expose] #[expose]
#[implementations(Footprint -> VectorData, Footprint -> GraphicGroup)] #[implementations(
instance: impl Node<Footprint, Output = I>, () -> VectorData,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> GraphicGroup,
)]
instance: impl Node<F, Output = I>,
#[default(1)] random_scale_min: f64, #[default(1)] random_scale_min: f64,
#[default(1)] random_scale_max: f64, #[default(1)] random_scale_max: f64,
random_scale_bias: f64, random_scale_bias: f64,
@@ -264,6 +382,7 @@ async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + Tr
) -> I { ) -> I {
let points = points.eval(footprint).await; let points = points.eval(footprint).await;
let instance = instance.eval(footprint).await; let instance = instance.eval(footprint).await;
let random_scale_difference = random_scale_max - random_scale_min; let random_scale_difference = random_scale_max - random_scale_min;
let points_list = points.point_domain.positions(); let points_list = points.point_domain.positions();
@@ -311,17 +430,29 @@ async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + Tr
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn sample_points( async fn sample_points<F: 'n + Send + Copy>(
footprint: Footprint, #[implementations(
vector_data: impl Node<Footprint, Output = VectorData>, (),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
spacing: f64, spacing: f64,
start_offset: f64, start_offset: f64,
stop_offset: f64, stop_offset: f64,
adaptive_spacing: bool, adaptive_spacing: bool,
lengths_of_segments_of_subpaths: impl Node<Footprint, Output = Vec<f64>>, #[implementations(
() -> Vec<f64>,
Footprint -> Vec<f64>,
)]
subpath_segment_lengths: impl Node<F, Output = Vec<f64>>,
) -> VectorData { ) -> VectorData {
let vector_data = vector_data.eval(footprint).await; 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(); let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
@@ -329,11 +460,11 @@ async fn sample_points(
result.transform = vector_data.transform; result.transform = vector_data.transform;
while let Some((index, (segment, _, _, mut last_end))) = bezier.next() { 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() { while let Some((index, (segment, _, _, end))) = bezier.peek().is_some_and(|(_, (_, _, start, _))| *start == last_end).then(|| bezier.next()).flatten() {
last_end = end; 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(); 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))] #[node_macro::node(category(""), path(graphene_core::vector))]
async fn poisson_disk_points<F: 'n + Copy + Send>( async fn poisson_disk_points<F: 'n + Send>(
#[implementations((), Footprint)] footprint: F, #[implementations(
#[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node<F, Output = VectorData>, (),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
#[default(10.)] #[default(10.)]
#[min(0.01)] #[min(0.01)]
separation_disk_diameter: f64, separation_disk_diameter: f64,
@@ -417,8 +556,19 @@ async fn poisson_disk_points<F: 'n + Copy + Send>(
result result
} }
#[node_macro::node(name("Lengths of Segments of Subpaths"), category(""))] #[node_macro::node(category(""))]
async fn lengths_of_segments_of_subpaths(footprint: Footprint, vector_data: impl Node<Footprint, Output = VectorData>) -> Vec<f64> { async fn subpath_segment_lengths<F: 'n + Send>(
#[implementations(
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
) -> Vec<f64> {
let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.eval(footprint).await;
vector_data vector_data
@@ -453,10 +603,23 @@ fn splines_from_points(_: (), mut vector_data: VectorData) -> VectorData {
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn morph( async fn morph<F: 'n + Send + Copy>(
footprint: Footprint, #[implementations(
source: impl Node<Footprint, Output = VectorData>, (),
#[expose] target: impl Node<Footprint, Output = VectorData>, Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
source: impl Node<F, Output = VectorData>,
#[expose]
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
target: impl Node<F, Output = VectorData>,
#[range((0., 1.))] #[range((0., 1.))]
#[default(0.5)] #[default(0.5)]
time: Fraction, time: Fraction,
@@ -734,7 +897,7 @@ mod test {
#[tokio::test] #[tokio::test]
async fn lengths() { async fn lengths() {
let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); 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.]); assert_eq!(lengths, vec![100.]);
} }
#[test] #[test]
+51 -1
View File
@@ -9,6 +9,7 @@ use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::{BlendMode, LuminanceCalculation}; use graphene_core::raster::{BlendMode, LuminanceCalculation};
use graphene_core::renderer::RenderMetadata; use graphene_core::renderer::RenderMetadata;
use graphene_core::uuid::NodeId; use graphene_core::uuid::NodeId;
use graphene_core::vector::style::Fill;
use graphene_core::{Color, MemoHash, Node, Type}; use graphene_core::{Color, MemoHash, Node, Type};
pub use glam::{DAffine2, DVec2, IVec2, UVec2}; pub use glam::{DAffine2, DVec2, IVec2, UVec2};
@@ -196,6 +197,52 @@ impl TaggedValue {
} }
pub fn from_primitive_string(string: &str, ty: &Type) -> Option<Self> { pub fn from_primitive_string(string: &str, ty: &Type) -> Option<Self> {
fn to_dvec2(input: &str) -> Option<DVec2> {
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<Color> {
// 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 { match ty {
Type::Generic(_) => None, Type::Generic(_) => None,
Type::Concrete(concrete_type) => { Type::Concrete(concrete_type) => {
@@ -209,8 +256,11 @@ impl TaggedValue {
x if x == TypeId::of::<f64>() => FromStr::from_str(string).map(TaggedValue::F64).ok()?, x if x == TypeId::of::<f64>() => FromStr::from_str(string).map(TaggedValue::F64).ok()?,
x if x == TypeId::of::<u64>() => FromStr::from_str(string).map(TaggedValue::U64).ok()?, x if x == TypeId::of::<u64>() => FromStr::from_str(string).map(TaggedValue::U64).ok()?,
x if x == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?, x if x == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?,
x if x == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
x if x == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?, x if x == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
x if x == TypeId::of::<Color>() => Color::from_rgba_str(string).map(TaggedValue::Color)?, x if x == TypeId::of::<Color>() => to_color(string).map(TaggedValue::Color)?,
x if x == TypeId::of::<Option<Color>>() => to_color(string).map(|color| TaggedValue::OptionalColor(Some(color)))?,
x if x == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
_ => return None, _ => return None,
}; };
Some(ty) Some(ty)
+10 -2
View File
@@ -4,8 +4,16 @@ use graphene_core::Color;
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"))]
async fn image_color_palette<F: 'n + Send>( async fn image_color_palette<F: 'n + Send>(
#[implementations((), Footprint)] footprint: F, #[implementations(
#[implementations(() -> ImageFrame<Color>, Footprint -> ImageFrame<Color>)] image: impl Node<F, Output = ImageFrame<Color>>, (),
Footprint,
)]
footprint: F,
#[implementations(
() -> ImageFrame<Color>,
Footprint -> ImageFrame<Color>,
)]
image: impl Node<F, Output = ImageFrame<Color>>,
#[min(1.)] #[min(1.)]
#[max(28.)] #[max(28.)]
max_size: u32, max_size: u32,
+3 -3
View File
@@ -436,8 +436,8 @@ pub struct ImageFrameNode<P, Transform> {
_p: PhantomData<P>, _p: PhantomData<P>,
} }
#[node_macro::old_node_fn(ImageFrameNode<_P>)] #[node_macro::old_node_fn(ImageFrameNode<_P>)]
fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> graphene_core::raster::ImageFrame<_P> { fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> ImageFrame<_P> {
graphene_core::raster::ImageFrame { ImageFrame {
image, image,
transform, transform,
alpha_blending: AlphaBlending::default(), alpha_blending: AlphaBlending::default(),
@@ -463,7 +463,7 @@ fn noise_pattern(
cellular_distance_function: CellularDistanceFunction, cellular_distance_function: CellularDistanceFunction,
cellular_return_type: CellularReturnType, cellular_return_type: CellularReturnType,
cellular_jitter: f64, cellular_jitter: f64,
) -> graphene_core::raster::ImageFrame<Color> { ) -> ImageFrame<Color> {
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let viewport_bounds = footprint.viewport_bounds_in_local_space();
let mut size = viewport_bounds.size(); let mut size = viewport_bounds.size();
+13 -3
View File
@@ -11,9 +11,17 @@ use path_bool::PathBooleanOperation;
use std::ops::{Div, Mul}; use std::ops::{Div, Mul};
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn boolean_operation<F: 'n + Copy + Send>( async fn boolean_operation<F: 'n + Send>(
#[implementations((), Footprint)] footprint: F, #[implementations(
#[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] group_of_paths: impl Node<F, Output = GraphicGroup>, (),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
Footprint -> GraphicGroup,
)]
group_of_paths: impl Node<F, Output = GraphicGroup>,
operation: BooleanOperation, operation: BooleanOperation,
) -> VectorData { ) -> VectorData {
let group_of_paths = group_of_paths.eval(footprint).await; let group_of_paths = group_of_paths.eval(footprint).await;
@@ -303,6 +311,7 @@ pub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<PointId>> {
} }
type Path = Vec<path_bool::PathSegment>; type Path = Vec<path_bool::PathSegment>;
fn boolean_union(a: Path, b: Path) -> Vec<Path> { fn boolean_union(a: Path, b: Path) -> Vec<Path> {
path_bool(a, b, PathBooleanOperation::Union) path_bool(a, b, PathBooleanOperation::Union)
} }
@@ -323,6 +332,7 @@ fn path_bool(a: Path, b: Path, op: PathBooleanOperation) -> Vec<Path> {
fn boolean_subtract(a: Path, b: Path) -> Vec<Path> { fn boolean_subtract(a: Path, b: Path) -> Vec<Path> {
path_bool(a, b, PathBooleanOperation::Difference) path_bool(a, b, PathBooleanOperation::Difference)
} }
fn boolean_intersect(a: Path, b: Path) -> Vec<Path> { fn boolean_intersect(a: Path, b: Path) -> Vec<Path> {
path_bool(a, b, PathBooleanOperation::Intersection) path_bool(a, b, PathBooleanOperation::Intersection)
} }
+6 -1
View File
@@ -133,7 +133,12 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>( async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>(
_: (), _: (),
#[implementations(Footprint -> VectorData, Footprint -> ImageFrame<Color>, Footprint -> GraphicGroup)] data: impl Node<Footprint, Output = T>, #[implementations(
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
Footprint -> GraphicGroup,
)]
data: impl Node<Footprint, Output = T>,
footprint: Footprint, footprint: Footprint,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>, surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
) -> ImageFrame<Color> { ) -> ImageFrame<Color> {