mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
Improve type compatibility and clean up new node macro usages (#2002)
* Improve type compatibility * More
This commit is contained in:
parent
33df58eda9
commit
3ddc052538
17 changed files with 842 additions and 243 deletions
|
@ -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);
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"))]
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue