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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 842 additions and 243 deletions

View file

@ -130,7 +130,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..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<DocumentNodeDefinition> {
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<DocumentNodeDefinition> {
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<DocumentNodeDefinition> {
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<DocumentNodeDefinition> {
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<DocumentNodeDefinition> {
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<DocumentNodeDefinition> {
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<DocumentNodeDefinition> {
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<DocumentNodeDefinition> {
},
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<DocumentNodeDefinition> {
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<DocumentNodeDefinition> {
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<DocumentNodeDefinition> {
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<DocumentNodeDefinition> {
},
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);

View file

@ -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::<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::<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::<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),
@ -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<LayoutGroup> {
let color = color_widget(document_node, node_id, 1, "Color", ColorButton::default(), true);
vec![color]
}

View file

@ -232,13 +232,25 @@ impl ArtboardGroup {
}
#[node_macro::node(category(""))]
async fn layer<F: 'n + Copy + Send>(
#[implementations((), Footprint)] footprint: F,
#[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] stack: impl Node<F, Output = GraphicGroup>,
#[implementations(() -> GraphicElement, Footprint -> GraphicElement)] graphic_element: impl Node<F, Output = GraphicElement>,
async fn layer<F: 'n + Send + Copy>(
#[implementations(
(),
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>,
) -> 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<F: 'n + Copy + Send>(
#[node_macro::node(category("Debug"))]
async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
#[implementations((), (), (), (), Footprint)] footprint: F,
#[implementations(
() -> VectorData,
() -> ImageFrame<Color>,
(),
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> TextureFrame,
Footprint -> VectorData,
() -> VectorData,
() -> ImageFrame<Color>,
() -> TextureFrame,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
Footprint -> GraphicGroup,
Footprint -> TextureFrame,
Footprint -> TextureFrame,
)]
data: impl Node<F, Output = Data>,
) -> GraphicElement {
@ -273,15 +292,22 @@ async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
#[node_macro::node(category("General"))]
async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
#[implementations((), (), (), (), Footprint)] footprint: F,
#[implementations(
(),
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
() -> ImageFrame<Color>,
() -> GraphicGroup,
() -> TextureFrame,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
Footprint -> GraphicGroup,
Footprint -> TextureFrame,
)]
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(""))]
async fn to_artboard<F: 'n + Copy + Send + ApplyTransform>(
#[implementations((), Footprint)] mut footprint: F,
#[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] contents: impl Node<F, Output = GraphicGroup>,
async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroup> + 'n>(
#[implementations(
(),
(),
(),
(),
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,
location: IVec2,
dimensions: IVec2,
@ -303,7 +346,7 @@ async fn to_artboard<F: 'n + Copy + Send + ApplyTransform>(
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<F: 'n + Copy + Send + ApplyTransform>(
clip,
}
}
#[node_macro::node(category(""))]
async fn append_artboard<F: 'n + Copy + Send>(
#[implementations((), Footprint)] footprint: F,
#[implementations(() -> ArtboardGroup, Footprint -> ArtboardGroup)] artboards: impl Node<F, Output = ArtboardGroup>,
#[implementations(() -> Artboard, Footprint -> Artboard)] artboard: impl Node<F, Output = Artboard>,
async fn append_artboard<F: 'n + Send + Copy>(
#[implementations(
(),
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>,
) -> ArtboardGroup {
let artboard = artboard.eval(footprint).await;

View file

@ -1,8 +1,11 @@
use crate::vector::VectorData;
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"))]
fn log_to_console<T: core::fmt::Debug>(
_: (),
#[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"))]

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::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<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, 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,
) -> <U as Add<T>>::Output {
augend + addend
}
@ -23,8 +28,8 @@ fn add<U: Add<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))]
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, 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,
) -> <U as Sub<T>>::Output {
minuend - subtrahend
}
@ -33,9 +38,9 @@ fn subtract<U: Sub<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))]
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.)]
#[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,
) -> <U as Mul<T>>::Output {
multiplier * multiplicand
@ -45,9 +50,9 @@ fn multiply<U: Mul<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))]
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.)]
#[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,
) -> <U as Div<T>>::Output {
numerator / denominator
@ -57,9 +62,9 @@ fn divide<U: Div<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))]
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.)]
#[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,
) -> <U as Rem<T>>::Output {
numerator % modulus
@ -69,7 +74,7 @@ fn modulo<U: Rem<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))]
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.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
power: T,
@ -119,52 +124,63 @@ fn logarithm<U: num_traits::float::Float>(
// Sine
#[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()
}
// Cosine
#[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()
}
// Tangent
#[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()
}
// Random
#[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 result = rng.gen::<f64>();
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<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
value.round()
}
// Floor
#[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()
}
// Ceiling
#[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()
}
// Absolute Value
#[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()
}
@ -190,8 +206,8 @@ fn max<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32,
#[node_macro::node(category("Math: Logic"))]
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, &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<Color>) -> Option<Color> {
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<usize> {
// Some
#[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)
}
// Unwrap
#[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()
}
// Clone
#[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()
}

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 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<Color> {
}
#[node_macro::node(category("Style"))]
async fn blend_mode<T: SetBlendMode>(
footprint: Footprint,
#[implementations(Footprint -> crate::vector::VectorData, Footprint -> crate::GraphicGroup, Footprint -> ImageFrame<Color>)] value: impl Node<Footprint, Output = T>,
async fn blend_mode<F: 'n + Send, T: SetBlendMode>(
#[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
() -> ImageFrame<Color>,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
)]
value: impl Node<F, Output = T>,
blend_mode: BlendMode,
) -> T {
let mut value = value.eval(footprint).await;
@ -319,9 +333,23 @@ async fn blend_mode<T: SetBlendMode>(
}
#[node_macro::node(category("Style"))]
async fn opacity<T: MultiplyAlpha>(
footprint: Footprint,
#[implementations(Footprint -> crate::vector::VectorData, Footprint -> crate::GraphicGroup, Footprint -> ImageFrame<Color>)] value: impl Node<Footprint, Output = T>,
async fn opacity<F: 'n + Send, T: MultiplyAlpha>(
#[implementations(
(),
(),
(),
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,
) -> T {
let mut value = value.eval(footprint).await;

View file

@ -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<BlendMode> for vello::peniko::Mix {
}
}
#[node_macro::node(category("Raster: Adjustment"))] // Unique to Graphite
async fn luminance<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>,
#[node_macro::node(category("Raster: Adjustment"))]
async fn luminance<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>,
luminance_calc: LuminanceCalculation,
) -> T {
let mut input = input.eval(footprint).await;
@ -289,9 +318,23 @@ async fn luminance<T: Adjust<Color>>(
}
#[node_macro::node(category("Raster"))]
async fn extract_channel<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>,
async fn extract_channel<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>,
channel: RedGreenBlueAlpha,
) -> T {
let mut input = input.eval(footprint).await;
@ -308,7 +351,24 @@ async fn extract_channel<T: Adjust<Color>>(
}
#[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;
input.adjust(|color| {
if color.a() == 0. {
@ -319,11 +379,29 @@ async fn make_opaque<T: Adjust<Color>>(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<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
async fn levels<F: 'n + Send, T: Adjust<Color>>(
#[implementations(
(),
(),
(),
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(50.)] midtones: Percentage,
#[default(100.)] highlights: Percentage,
@ -376,13 +454,32 @@ async fn levels<T: Adjust<Color>>(
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
#[node_macro::node(name("Black & White"), category("Raster: Adjustment"))]
async fn black_and_white<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
#[default(000000ff)] tint: Color,
async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
#[implementations(
(),
(),
(),
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.)]
#[range((-200., 300.))]
reds: Percentage,
@ -443,10 +540,27 @@ async fn black_and_white<T: Adjust<Color>>(
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<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>,
async fn hue_saturation<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>,
hue_shift: Angle,
saturation_shift: SignedPercentage,
lightness_shift: SignedPercentage,
@ -471,8 +585,27 @@ async fn hue_saturation<T: Adjust<Color>>(
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<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;
input.adjust(|color| {
let color = color.to_gamma_srgb();
@ -484,10 +617,26 @@ async fn invert<T: Adjust<Color>>(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<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
async fn threshold<F: 'n + Send, T: Adjust<Color>>(
#[implementations(
(),
(),
(),
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(100.)] max_luminance: Percentage,
luminance_calc: LuminanceCalculation,
@ -571,8 +720,14 @@ impl Blend<Color> for GradientStops {
}
#[node_macro::node(category("Raster"))]
async fn blend<F: 'n + Copy + Send, T: Blend<Color> + Send>(
#[implementations((), (), (), Footprint)] footprint: F,
async fn blend<F: 'n + Send + Copy, T: Blend<Color> + Send>(
#[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> 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))
}
// 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<F: 'n + Copy + Send, T: Adjust<Color>>(
#[implementations((), (), (), Footprint)] footprint: F,
async fn gradient_map<F: 'n + Send, T: Adjust<Color>>(
#[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
@ -715,12 +879,31 @@ async fn gradient_map<F: 'n + Copy + Send, T: Adjust<Color>>(
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
#[node_macro::node(category("Raster: Adjustment"))]
async fn vibrance<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
#[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
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<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
#[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
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<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
#[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
mode: RelativeAbsolute,
#[name("(Reds) Cyan")] r_c: 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.
#[node_macro::node(category("Raster: Adjustment"))]
async fn posterize<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>,
async fn posterize<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>,
#[default(4)]
#[min(2.)]
levels: u32,
@ -1313,11 +1550,30 @@ async fn posterize<T: Adjust<Color>>(
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<T: Adjust<Color>>(
footprint: Footprint,
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>,
async fn exposure<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>,
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<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};
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")]
#[node_macro::node(category("Raster: Adjustment"))] // Unique to Graphite
async fn color_overlay<F: 'n + Copy + Send, T: Adjust<Color>>(
#[implementations((), (), (), Footprint)] footprint: F,
#[node_macro::node(category("Raster: Adjustment"))]
async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
#[implementations(
(),
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
@ -1395,7 +1657,7 @@ async fn color_overlay<F: 'n + Copy + Send, T: Adjust<Color>>(
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
#[default(000000ff)] color: Color,
#[default(Color::BLACK)] color: Color,
blend_mode: BlendMode,
#[default(100.)] opacity: Percentage,
) -> T {

View file

@ -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<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
}
@ -217,15 +211,21 @@ impl ApplyTransform for () {
}
#[node_macro::node(category(""))]
async fn transform<I: Into<Footprint> + ApplyTransform + 'n + Clone + Send + Sync, T: TransformMut + 'n>(
#[implementations(Footprint, Footprint, Footprint, (), (), ())] mut input: I,
async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Sync, T: 'n + TransformMut>(
#[implementations(
(),
(),
(),
Footprint,
)]
mut input: I,
#[implementations(
Footprint -> VectorData,
Footprint -> GraphicGroup,
Footprint -> ImageFrame<crate::Color>,
() -> VectorData,
() -> GraphicGroup,
() -> ImageFrame<crate::Color>,
() -> ImageFrame<Color>,
Footprint -> VectorData,
Footprint -> GraphicGroup,
Footprint -> ImageFrame<Color>,
)]
transform_target: impl Node<I, Output = T>,
translate: DVec2,
@ -251,7 +251,7 @@ async fn transform<I: Into<Footprint> + ApplyTransform + 'n + Clone + Send + Syn
#[node_macro::node(category("Debug"))]
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,
) -> Data {
let data_transform = data.transform_mut();

View file

@ -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<F: 'n + Send>(#[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<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 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<T: CornerRadius>(
_: (),
fn rectangle<F: 'n + Send, T: CornerRadius>(
#[implementations((), Footprint)] _footprint: F,
_primary: (),
#[default(100)] width: f64,
#[default(100)] height: f64,
@ -69,8 +69,8 @@ fn rectangle<T: CornerRadius>(
}
#[node_macro::node(category("Vector: Shape"))]
fn regular_polygon(
_: (),
fn regular_polygon<F: 'n + Send>(
#[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<F: 'n + Send>(
#[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<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))
}
#[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));
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<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
#[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);
vector_data.colinear_manipulators = colinear_manipulators
.iter()

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 {
pub const fn new(color: Option<Color>, weight: f64) -> Self {
Self {

View file

@ -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<F: 'n + Send + Sync + Clone>(
#[implementations((), Footprint)] input: F,
#[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node<F, Output = VectorData>,
#[implementations(
(),
Footprint,
)]
input: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
modification: Box<VectorModification>,
) -> VectorData {
let mut vector_data = vector_data.eval(input).await;

View file

@ -27,9 +27,20 @@ impl VectorIterMut for VectorData {
}
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn assign_colors<T: VectorIterMut>(
footprint: Footprint,
#[implementations(Footprint -> GraphicGroup, Footprint -> VectorData)] vector_group: impl Node<Footprint, Output = T>,
async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
#[implementations(
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
Footprint -> GraphicGroup,
Footprint -> VectorData,
)]
vector_group: impl Node<F, Output = T>,
#[default(true)] fill: bool,
stroke: bool,
gradient: GradientStops,
@ -70,10 +81,35 @@ async fn assign_colors<T: VectorIterMut>(
}
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn fill<T: Into<Fill> + 'n + Send>(
footprint: Footprint,
vector_data: impl Node<Footprint, Output = VectorData>,
#[implementations(Fill, Color, Option<Color>, crate::vector::style::Gradient)] fill: T, // TODO: Set the default to black
async fn fill<F: 'n + Send, T: Into<Fill> + 'n + Send>(
#[implementations(
(),
(),
(),
(),
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_gradient: Gradient,
) -> VectorData {
@ -84,10 +120,27 @@ async fn fill<T: Into<Fill> + 'n + Send>(
}
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn stroke(
footprint: Footprint,
vector_data: impl Node<Footprint, Output = VectorData>,
color: Option<Color>, // TODO: Set the default to black
async fn stroke<F: 'n + Send, T: Into<Option<Color>> + 'n + Send>(
#[implementations(
(),
(),
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,
dash_lengths: Vec<f64>,
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<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 angle = angle.to_radians();
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.style.set_stroke_transform(DAffine2::IDENTITY);
result
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn circular_repeat(
footprint: Footprint,
instance: impl Node<Footprint, Output = VectorData>,
async fn circular_repeat<F: 'n + Send>(
#[implementations(
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
instance: impl Node<F, Output = VectorData>,
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<F: 'n + Copy + Send>(
#[implementations((), Footprint)] footprint: F,
#[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node<F, Output = VectorData>,
async fn bounding_box<F: 'n + Send>(
#[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 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<Footprint, Output = VectorData>) -> VectorData {
// Grab what we need from original data.
async fn solidify_stroke<F: 'n + Send>(
#[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 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<I: GraphicElementRendered + Default + ConcatElement + TransformMut + Send>(
footprint: Footprint,
points: impl Node<Footprint, Output = VectorData>,
async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + Default + ConcatElement + TransformMut + Send>(
#[implementations(
(),
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
() -> VectorData,
Footprint -> VectorData,
)]
points: impl Node<F, Output = VectorData>,
#[expose]
#[implementations(Footprint -> VectorData, Footprint -> GraphicGroup)]
instance: impl Node<Footprint, Output = I>,
#[implementations(
() -> VectorData,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> GraphicGroup,
)]
instance: impl Node<F, Output = I>,
#[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: GraphicElementRendered + Default + ConcatElement + Tr
) -> 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<I: GraphicElementRendered + Default + ConcatElement + Tr
}
#[node_macro::node(category(""))]
async fn sample_points(
footprint: Footprint,
vector_data: impl Node<Footprint, Output = VectorData>,
async fn sample_points<F: 'n + Send + Copy>(
#[implementations(
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
spacing: f64,
start_offset: f64,
stop_offset: f64,
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 {
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<F: 'n + Copy + Send>(
#[implementations((), Footprint)] footprint: F,
#[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node<F, Output = VectorData>,
async fn poisson_disk_points<F: 'n + Send>(
#[implementations(
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
#[default(10.)]
#[min(0.01)]
separation_disk_diameter: f64,
@ -417,8 +556,19 @@ async fn poisson_disk_points<F: 'n + Copy + Send>(
result
}
#[node_macro::node(name("Lengths of Segments of Subpaths"), category(""))]
async fn lengths_of_segments_of_subpaths(footprint: Footprint, vector_data: impl Node<Footprint, Output = VectorData>) -> Vec<f64> {
#[node_macro::node(category(""))]
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;
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<Footprint, Output = VectorData>,
#[expose] target: impl Node<Footprint, Output = VectorData>,
async fn morph<F: 'n + Send + Copy>(
#[implementations(
(),
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.))]
#[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]

View file

@ -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<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 {
Type::Generic(_) => None,
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::<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::<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::<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,
};
Some(ty)

View file

@ -4,8 +4,16 @@ use graphene_core::Color;
#[node_macro::node(category("Raster"))]
async fn image_color_palette<F: 'n + Send>(
#[implementations((), Footprint)] footprint: F,
#[implementations(() -> ImageFrame<Color>, Footprint -> ImageFrame<Color>)] image: impl Node<F, Output = ImageFrame<Color>>,
#[implementations(
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> ImageFrame<Color>,
Footprint -> ImageFrame<Color>,
)]
image: impl Node<F, Output = ImageFrame<Color>>,
#[min(1.)]
#[max(28.)]
max_size: u32,

View file

@ -436,8 +436,8 @@ pub struct ImageFrameNode<P, Transform> {
_p: PhantomData<P>,
}
#[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<Color> {
) -> ImageFrame<Color> {
let viewport_bounds = footprint.viewport_bounds_in_local_space();
let mut size = viewport_bounds.size();

View file

@ -11,9 +11,17 @@ use path_bool::PathBooleanOperation;
use std::ops::{Div, Mul};
#[node_macro::node(category(""))]
async fn boolean_operation<F: 'n + Copy + Send>(
#[implementations((), Footprint)] footprint: F,
#[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] group_of_paths: impl Node<F, Output = GraphicGroup>,
async fn boolean_operation<F: 'n + Send>(
#[implementations(
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
Footprint -> GraphicGroup,
)]
group_of_paths: impl Node<F, Output = GraphicGroup>,
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<Subpath<PointId>> {
}
type Path = Vec<path_bool::PathSegment>;
fn boolean_union(a: Path, b: Path) -> Vec<Path> {
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> {
path_bool(a, b, PathBooleanOperation::Difference)
}
fn boolean_intersect(a: Path, b: Path) -> Vec<Path> {
path_bool(a, b, PathBooleanOperation::Intersection)
}

View file

@ -133,7 +133,12 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
#[cfg(target_arch = "wasm32")]
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,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
) -> ImageFrame<Color> {