Refactor the node macro and simply most of the node implementations (#1942)

* Add support structure for new node macro to gcore

* Fix compile issues and code generation

* Implement new node_fn macro

* Implement property translation

* Fix NodeIO type generation

* Start translating math nodes

* Move node implementation to outer scope to allow usage of local imports

* Add expose attribute to allow controlling the parameter exposure

* Add rust analyzer support for #[implementations] attribute

* Migrate logic nodes

* Handle where clause properly

* Implement argument ident pattern preservation

* Implement adjustment layer mapping

* Fix node registry types

* Fix module paths

* Improve demo artwork comptibility

* Improve macro error reporting

* Fix handling of impl node implementations

* Fix nodeio type computation

* Fix opacity node and graph type resolution

* Fix loading of demo artworks

* Fix eslint

* Fix typo in macro test

* Remove node definitions for Adjustment Nodes

* Fix type alias property generation and make adjustments footprint aware

* Convert vector nodes

* Implement path overrides

* Fix stroke node

* Fix painted dreams

* Implement experimental type level specialization

* Fix poisson disk sampling -> all demo artworks should work again

* Port text node + make node macro more robust by implementing lifetime substitution

* Fix vector node tests

* Fix red dress demo + ci

* Fix clippy warnings

* Code review

* Fix primary input issues

* Improve math nodes and audit others

* Set no_properties when no automatic properties are derived

* Port vector generator nodes (could not derive all definitions yet)

* Various QA changes and add min/max/mode_range to number parameters

* Add min and max for f64 and u32

* Convert gpu nodes and clean up unused nodes

* Partially port transform node

* Allow implementations on call arg

* Port path modify node

* Start porting graphic element nodes

* Transform nodes in graphic_element.rs

* Port brush node

* Port nodes in wasm_executior

* Rename node macro

* Fix formatting

* Fix Mandelbrot node

* Formatting

* Fix Load Image and Load Resource nodes, add scope input to node macro

* Remove unnecessary underscores

* Begin attemping to make nodes resolution-aware

* Infer a generic manual compositon type on generic call arg

* Various fixes and work towards merging

* Final changes for merge!

* Fix tests, probably

* More free line removals!

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2024-09-20 12:50:30 +02:00
committed by GitHub
parent ca0d102296
commit e352c7fa71
92 changed files with 4255 additions and 7275 deletions

View File

@@ -10,15 +10,12 @@ The graph that is presented to users in the editor is known as the document grap
```rs
pub struct DocumentNode {
pub name: String,
pub inputs: Vec<NodeInput>,
pub manual_composition: Option<Type>,
pub has_primary_output: bool,
pub implementation: DocumentNodeImplementation,
pub metadata: DocumentNodeMetadata,
pub skip_deduplication: bool,
pub hash: u64,
pub path: Option<Vec<NodeId>>,
pub visible: bool,
pub original_location: OriginalLocation,
}
```
(Explanatory comments omitted; the actual definition is currently found in [`node-graph/graph-craft/src/document.rs`](https://github.com/GraphiteEditor/Graphite/blob/master/node-graph/graph-craft/src/document.rs))
@@ -29,7 +26,7 @@ Each `DocumentNode` is of a particular type, for example the "Opacity" node type
DocumentNodeDefinition {
name: "Opacity",
category: "Image Adjustments",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::OpacityNode<_>"),
implementation: DocumentNodeImplementation::proto("graphene_core::raster::OpacityNode"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Factor", TaggedValue::F32(100.), false),
@@ -40,7 +37,6 @@ DocumentNodeDefinition {
},
```
The identifier here must be the same as that of the proto-node which will be discussed soon (usually the path to the node implementation).
> [!NOTE]
@@ -100,28 +96,21 @@ fn test_opacity_node() {
The `graphene_core::value::CopiedNode` is a node that, when evaluated, copies `10_f32` and returns it.
## Creating a new proto node
## Creating a new node
Instead of manually implementing the `Node` trait with complex generics, one can use the `node_fn` macro, which can be applied to a function like `opacity_node` with an attribute of the name of the node:
Instead of manually implementing the `Node` trait with complex generics, one can use the `node` macro, which can be applied to a function like `opacity`. This will generate the struct, implementation, node_registry entry, doucment node definition and properties panel entries:
```rs
#[derive(Debug, Clone, Copy)]
pub struct OpacityNode<O> {
opacity_multiplier: O,
}
#[node_macro::node_fn(OpacityNode)]
fn opacity_node(color: Color, opacity_multiplier: f64) -> Color {
#[node_macro::node(category("Raster: Adjustments"))]
fn opacity(_input: (), #[default(424242)] color: Color,#[min(0.1)] opacity_multiplier: f64) -> Color {
let opacity_multiplier = opacity_multiplier as f32 / 100.;
Color::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), color.a() * opacity_multiplier)
}
```
## Alternative macros
## Additional Macro Options
`#[node_macro::node_fn(NodeName)]` generates an implementation of the `Node` trait for NodeName with the specific input types, and also generates a `fn new` that can be used to construct the node struct. If multiple implementations for different types are needed, then it is necessary to avoid creating this `new` function twice, so you can use `#[node_macro::node_impl(NodeName)]`.
If you need to manually implement the `Node` trait without using the macro, but wish to have an automatically generated `fn new`, you can use `#[node_macro::node_new(NodeName)]`, which can be applied to a function.
The macro invocation can be extended with additional attributes. The currently supported attributes are (`name`, `path`, `skip_impl`, `category`). When using generics the `#[implementations()]` attribute can be used to automatically populate the node_registry for you. You can also use the `default`, `expose`, `min`, `max` and `range_mode` attributes to influence how the properties are generated.
## Executing a document `NodeNetwork`
@@ -136,7 +125,7 @@ The definition for the constructor of a node that applies the opacity transforma
```rs
(
// Matches against the string defined in the document node.
ProtoNodeIdentifier::new("graphene_core::raster::OpacityNode<_>"),
ProtoNodeIdentifier::new("graphene_core::raster::OpacityNode"),
// This function is run when converting the `ProtoNode` struct into the desired struct.
|args| {
Box::pin(async move {
@@ -179,6 +168,6 @@ We need a utility to easily view a graph as the various steps are applied. We al
## Conclusion
Currently defining nodes is a very laborious and error prone process, spanning many files and concepts. It is necessary to simplify this if we want contributors to be able to write their own nodes.
While we simplify the writing of nodes using the macro by hiding some of the details, creating nodes involves many files and concepts. We will work to continue making this system easier to use.
Any contributions you might have would be greatly appreciated. If any parts of this guide are outdated or difficult to understand, please feel free to ask for help in the Graphite Discord. We are very happy to answer any questions :)

View File

@@ -13,6 +13,7 @@ gpu-compiler-bin-wrapper = { path = "../gpu-compiler/gpu-compiler-bin-wrapper" }
# Workspace dependencies
graphene-core = { workspace = true }
dyn-any = { workspace = true }
anyhow = { workspace = true }
serde_json = { workspace = true }
reqwest = { workspace = true, features = ["blocking", "json", "rustls-tls"] }

View File

@@ -18,6 +18,7 @@ pub async fn compile(networks: Vec<ProtoNetwork>, inputs: Vec<Type>, outputs: Ve
// TODO: should we add the entry point as a field?
/// A compiled shader with type annotations.
#[derive(dyn_any::DynAny)]
pub struct Shader {
pub spirv_binary: Vec<u32>,
pub input_types: Vec<Type>,

View File

@@ -16,6 +16,7 @@ type_id_logging = []
wasm = ["web-sys"]
wgpu = ["dep:wgpu"]
vello = ["dep:vello", "bezier-rs/kurbo", "wgpu"]
dealloc_nodes = ["reflections"]
std = [
"dyn-any",
"dyn-any/std",
@@ -25,6 +26,11 @@ std = [
"num-traits/std",
"rustybuzz",
"image",
"reflections",
]
reflections = [
"alloc",
"ctor",
]
serde = [
"dep:serde",
@@ -54,6 +60,7 @@ half = { version = "2.4.1", default-features = false, features = ["bytemuck"] }
dyn-any = { workspace = true, optional = true }
spirv-std = { workspace = true, optional = true }
serde = { workspace = true, optional = true, features = ["derive"] }
ctor = { workspace = true, optional = true }
log = { workspace = true, optional = true }
rand_chacha = { workspace = true, optional = true }
bezier-rs = { workspace = true, optional = true }

View File

@@ -1,6 +1,6 @@
use core::marker::PhantomData;
use crate::{Node, NodeMut};
use crate::Node;
pub struct FnNode<T: Fn(I) -> O, I, O>(T, PhantomData<(I, O)>);
impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode<T, I, O> {
@@ -15,31 +15,3 @@ impl<T: Fn(I) -> O, I, O> FnNode<T, I, O> {
FnNode(f, PhantomData)
}
}
pub struct FnMutNode<T: FnMut(I) -> O, I, O>(T, PhantomData<(I, O)>);
impl<'i, T: FnMut(I) -> O + 'i, O: 'i, I: 'i> NodeMut<'i, I> for FnMutNode<T, I, O> {
type MutOutput = O;
fn eval_mut(&'i mut self, input: I) -> Self::MutOutput {
self.0(input)
}
}
impl<'i, T: FnMut(I) -> O + 'i, I: 'i, O: 'i> FnMutNode<T, I, O> {
pub fn new(f: T) -> Self {
FnMutNode(f, PhantomData)
}
}
pub struct FnNodeWithState<'i, T: Fn(I, &'i State) -> O, I, O, State: 'i>(T, State, PhantomData<(&'i O, I)>);
impl<'i, I: 'i, O: 'i, State, T: Fn(I, &'i State) -> O + 'i> Node<'i, I> for FnNodeWithState<'i, T, I, O, State> {
type Output = O;
fn eval(&'i self, input: I) -> Self::Output {
(self.0)(input, &self.1)
}
}
impl<'i, I, O, State, T: Fn(I, &'i State) -> O> FnNodeWithState<'i, T, I, O, State> {
pub fn new(f: T, state: State) -> Self {
FnNodeWithState(f, state, PhantomData)
}
}

View File

@@ -1,12 +1,11 @@
use crate::application_io::TextureFrame;
use crate::raster::{BlendMode, ImageFrame};
use crate::transform::{Footprint, Transform, TransformMut};
use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut};
use crate::uuid::NodeId;
use crate::vector::VectorData;
use crate::{Color, Node};
use crate::Color;
use dyn_any::{DynAny, StaticType};
use node_macro::node_fn;
use dyn_any::DynAny;
use core::ops::{Deref, DerefMut};
use glam::{DAffine2, IVec2};
@@ -227,27 +226,20 @@ impl ArtboardGroup {
Default::default()
}
fn add_artboard(&mut self, artboard: Artboard, node_id: Option<NodeId>) {
fn append_artboard(&mut self, artboard: Artboard, node_id: Option<NodeId>) {
self.artboards.push((artboard, node_id));
}
}
pub struct ConstructLayerNode<Stack, GraphicElement, NodePath> {
stack: Stack,
graphic_element: GraphicElement,
node_path: NodePath,
}
#[node_fn(ConstructLayerNode)]
async fn construct_layer<Data: Into<GraphicElement> + Send>(
footprint: crate::transform::Footprint,
mut stack: impl Node<crate::transform::Footprint, Output = GraphicGroup>,
graphic_element: impl Node<crate::transform::Footprint, Output = Data>,
#[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>,
node_path: Vec<NodeId>,
) -> GraphicGroup {
let graphic_element = self.graphic_element.eval(footprint).await;
let mut stack = self.stack.eval(footprint).await;
let mut element: GraphicElement = graphic_element.into();
let mut element = graphic_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();
} else {
@@ -261,41 +253,54 @@ async fn construct_layer<Data: Into<GraphicElement> + Send>(
stack
}
pub struct ToGraphicElementNode {}
#[node_fn(ToGraphicElementNode)]
fn to_graphic_element<Data: Into<GraphicElement>>(data: Data) -> GraphicElement {
data.into()
#[node_macro::node(category("Debug"))]
async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
#[implementations((), (), (), (), Footprint)] footprint: F,
#[implementations(
((), VectorData),
((), ImageFrame<Color>),
((), GraphicGroup),
((), TextureFrame),
(Footprint, VectorData),
(Footprint, ImageFrame<Color>),
(Footprint, GraphicGroup),
(Footprint, TextureFrame),
)]
data: impl Node<F, Output = Data>,
) -> GraphicElement {
data.eval(footprint).await.into()
}
pub struct ToGraphicGroupNode {}
#[node_fn(ToGraphicGroupNode)]
fn to_graphic_group<Data: Into<GraphicGroup>>(data: Data) -> GraphicGroup {
data.into()
#[node_macro::node(category("General"))]
async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
#[implementations((), (), (), (), Footprint)] footprint: F,
#[implementations(
((), VectorData),
((), ImageFrame<Color>),
((), GraphicGroup),
((), TextureFrame),
(Footprint, VectorData),
(Footprint, ImageFrame<Color>),
(Footprint, GraphicGroup),
(Footprint, TextureFrame),
)]
element: impl Node<F, Output = Data>,
) -> GraphicGroup {
element.eval(footprint).await.into()
}
pub struct ConstructArtboardNode<Contents, Label, Location, Dimensions, Background, Clip> {
contents: Contents,
label: Label,
location: Location,
dimensions: Dimensions,
background: Background,
clip: Clip,
}
#[node_fn(ConstructArtboardNode)]
async fn construct_artboard(
mut footprint: Footprint,
contents: impl Node<Footprint, Output = GraphicGroup>,
#[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>,
label: String,
location: IVec2,
dimensions: IVec2,
background: Color,
clip: bool,
) -> Artboard {
footprint.transform *= DAffine2::from_translation(location.as_dvec2());
let graphic_group = self.contents.eval(footprint).await;
footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2()));
let graphic_group = contents.eval(footprint).await;
Artboard {
graphic_group,
@@ -306,25 +311,19 @@ async fn construct_artboard(
clip,
}
}
pub struct AddArtboardNode<ArtboardGroup, Artboard, NodePath> {
artboards: ArtboardGroup,
artboard: Artboard,
node_path: NodePath,
}
#[node_fn(AddArtboardNode)]
async fn add_artboard<Data: Into<Artboard> + Send>(
footprint: Footprint,
artboards: impl Node<Footprint, Output = ArtboardGroup>,
artboard: impl Node<Footprint, Output = Data>,
#[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>,
node_path: Vec<NodeId>,
) -> ArtboardGroup {
let artboard = self.artboard.eval(footprint).await;
let mut artboards = self.artboards.eval(footprint).await;
let artboard = artboard.eval(footprint).await;
let mut artboards = artboards.eval(footprint).await;
// Get the penultimate element of the node path, or None if the path is too short
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
artboards.add_artboard(artboard.into(), encapsulating_node_id);
artboards.append_artboard(artboard, encapsulating_node_id);
artboards
}
@@ -369,6 +368,7 @@ trait ToGraphicElement: Into<GraphicElement> {}
impl ToGraphicElement for VectorData {}
impl ToGraphicElement for ImageFrame<Color> {}
impl ToGraphicElement for TextureFrame {}
impl<T> From<T> for GraphicGroup
where

View File

@@ -12,7 +12,7 @@ use crate::Raster;
use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup};
use bezier_rs::Subpath;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use base64::Engine;
use glam::{DAffine2, DVec2};

View File

@@ -2,12 +2,17 @@
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use core::future::Future;
#[cfg_attr(feature = "log", macro_use)]
#[cfg(feature = "log")]
extern crate log;
pub use crate as graphene_core;
#[cfg(feature = "reflections")]
pub use ctor;
pub mod consts;
pub mod generic;
pub mod logic;
@@ -24,7 +29,6 @@ pub mod gpu;
#[cfg(feature = "alloc")]
pub mod memo;
pub mod storage;
pub mod raster;
#[cfg(feature = "alloc")]
@@ -40,7 +44,8 @@ pub mod vector;
#[cfg(feature = "alloc")]
pub mod application_io;
pub mod quantization;
#[cfg(feature = "reflections")]
pub mod registry;
use core::any::TypeId;
pub use memo::MemoHash;
@@ -68,32 +73,6 @@ pub trait Node<'i, Input: 'i>: 'i {
}
}
pub trait NodeMut<'i, Input: 'i>: 'i {
type MutOutput: 'i;
fn eval_mut(&'i mut self, input: Input) -> Self::MutOutput;
}
pub trait NodeOnce<'i, Input>
where
Input: 'i,
{
type OnceOutput: 'i;
fn eval_once(self, input: Input) -> Self::OnceOutput;
}
impl<'i, T: Node<'i, I>, I: 'i> NodeOnce<'i, I> for &'i T {
type OnceOutput = T::Output;
fn eval_once(self, input: I) -> Self::OnceOutput {
(self).eval(input)
}
}
impl<'i, T: Node<'i, I> + ?Sized, I: 'i> NodeMut<'i, I> for &'i T {
type MutOutput = T::Output;
fn eval_mut(&'i mut self, input: I) -> Self::MutOutput {
(*self).eval(input)
}
}
#[cfg(feature = "alloc")]
mod types;
#[cfg(feature = "alloc")]
@@ -124,6 +103,19 @@ where
parameters,
}
}
#[cfg(feature = "alloc")]
fn to_async_node_io(&self, parameters: Vec<Type>) -> NodeIOTypes
where
<Self::Output as Future>::Output: StaticTypeSized,
Self::Output: Future,
{
NodeIOTypes {
input: concrete!(<Input as StaticTypeSized>::Static),
// TODO return actual future type
output: concrete!(<<Self::Output as Future>::Output as StaticTypeSized>::Static),
parameters,
}
}
}
impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N

View File

@@ -1,45 +1,12 @@
use crate::Node;
pub struct LogToConsoleNode;
#[node_macro::node_fn(LogToConsoleNode)]
fn log_to_console<T: core::fmt::Debug>(value: T) -> T {
#[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)]
value: T,
) -> T {
#[cfg(not(target_arch = "spirv"))]
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
debug!("{value:#?}");
value
}
pub struct LogicOrNode<Second> {
second: Second,
}
#[node_macro::node_fn(LogicOrNode)]
fn logic_or(first: bool, second: bool) -> bool {
first || second
}
pub struct LogicAndNode<Second> {
second: Second,
}
#[node_macro::node_fn(LogicAndNode)]
fn logic_and(first: bool, second: bool) -> bool {
first && second
}
pub struct LogicXorNode<Second> {
second: Second,
}
#[node_macro::node_fn(LogicXorNode)]
fn logic_xor(first: bool, second: bool) -> bool {
first ^ second
}
pub struct LogicNotNode;
#[node_macro::node_fn(LogicNotNode)]
fn logic_not(first: bool) -> bool {
!first
}

View File

@@ -1,324 +1,308 @@
use crate::registry::types::Percentage;
use crate::Node;
use core::marker::PhantomData;
use core::ops::{Add, Div, Mul, Rem, Sub};
use num_traits::Pow;
use rand::{Rng, SeedableRng};
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float;
// Add Pair
// TODO: Delete this redundant (two-argument version of the) add node. It's only used in tests.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct AddPairNode;
impl<'i, L: Add<R, Output = O> + 'i, R: 'i, O: 'i> Node<'i, (L, R)> for AddPairNode {
type Output = <L as Add<R>>::Output;
fn eval(&'i self, input: (L, R)) -> Self::Output {
input.0 + input.1
}
}
impl AddPairNode {
pub const fn new() -> Self {
Self
}
}
// Add
pub struct AddNode<Second> {
second: Second,
}
#[node_macro::node_fn(AddNode)]
fn add_parameter<U, T>(first: U, second: T) -> <U as Add<T>>::Output
where
U: Add<T>,
{
first + second
#[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,
) -> <U as Add<T>>::Output {
augend + addend
}
// Subtract
pub struct SubtractNode<Second> {
second: Second,
}
#[node_macro::node_fn(SubtractNode)]
fn sub<U, T>(first: U, second: T) -> <U as Sub<T>>::Output
where
U: Sub<T>,
{
first - second
}
// Divide
pub struct DivideNode<Second> {
second: Second,
}
#[node_macro::node_fn(DivideNode)]
fn div<U, T>(first: U, second: T) -> <U as Div<T>>::Output
where
U: Div<T>,
{
first / second
#[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,
) -> <U as Sub<T>>::Output {
minuend - subtrahend
}
// Multiply
pub struct MultiplyNode<Second> {
second: Second,
#[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,
#[default(1.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, glam::DVec2, glam::DVec2)]
multiplicand: T,
) -> <U as Mul<T>>::Output {
multiplier * multiplicand
}
#[node_macro::node_fn(MultiplyNode)]
fn mul<U, T>(first: U, second: T) -> <U as Mul<T>>::Output
where
U: Mul<T>,
{
first * second
// Divide
#[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,
#[default(1.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, glam::DVec2, f64)]
denominator: T,
) -> <U as Div<T>>::Output {
numerator / denominator
}
// Modulo
#[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,
#[default(2.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
modulus: T,
) -> <U as Rem<T>>::Output {
numerator % modulus
}
// Exponent
pub struct ExponentNode<Second> {
second: Second,
}
#[node_macro::node_fn(ExponentNode)]
fn exp<U, T>(first: U, second: T) -> <U as Pow<T>>::Output
where
U: Pow<T>,
{
first.pow(second)
#[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,
#[default(2.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
power: T,
) -> <U as num_traits::Pow<T>>::Output {
base.pow(power)
}
// Floor
pub struct FloorNode;
#[node_macro::node_fn(FloorNode)]
fn floor(input: f64) -> f64 {
input.floor()
}
// Ceil
pub struct CeilingNode;
#[node_macro::node_fn(CeilingNode)]
fn ceil(input: f64) -> f64 {
input.ceil()
}
// Round
pub struct RoundNode;
#[node_macro::node_fn(RoundNode)]
fn round(input: f64) -> f64 {
input.round()
}
// Absolute Value
pub struct AbsoluteValue;
#[node_macro::node_fn(AbsoluteValue)]
fn abs(input: f64) -> f64 {
input.abs()
}
// Log
pub struct LogarithmNode<Second> {
second: Second,
}
#[node_macro::node_fn(LogarithmNode)]
fn ln<U: num_traits::float::Float>(first: U, second: U) -> U {
first.log(second)
}
// Natural Log
pub struct NaturalLogarithmNode;
#[node_macro::node_fn(NaturalLogarithmNode)]
fn ln(input: f64) -> f64 {
input.ln()
}
// Sine
pub struct SineNode;
#[node_macro::node_fn(SineNode)]
fn ln(input: f64) -> f64 {
input.sin()
}
// Cosine
pub struct CosineNode;
#[node_macro::node_fn(CosineNode)]
fn ln(input: f64) -> f64 {
input.cos()
}
// Tangent
pub struct TangentNode;
#[node_macro::node_fn(TangentNode)]
fn ln(input: f64) -> f64 {
input.tan()
}
// Min
pub struct MinimumNode<Second> {
second: Second,
}
#[node_macro::node_fn(MinimumNode)]
fn min<T: core::cmp::PartialOrd>(first: T, second: T) -> T {
match first < second {
true => first,
false => second,
// Root
#[node_macro::node(category("Math: Arithmetic"))]
fn root<U: num_traits::float::Float>(
_: (),
#[default(2.)]
#[implementations(f64, f32)]
radicand: U,
#[default(2.)]
#[implementations(f64, f32)]
degree: U,
) -> U {
if degree == U::from(2.).unwrap() {
radicand.sqrt()
} else if degree == U::from(3.).unwrap() {
radicand.cbrt()
} else {
radicand.powf(U::from(1.).unwrap() / degree)
}
}
// Maxi
pub struct MaximumNode<Second> {
second: Second,
// Logarithm
#[node_macro::node(category("Math: Arithmetic"))]
fn logarithm<U: num_traits::float::Float>(
_: (),
#[implementations(f64, f32)] value: U,
#[default(2.)]
#[implementations(f64, f32)]
base: U,
) -> U {
if base == U::from(2.).unwrap() {
value.log2()
} else if base == U::from(10.).unwrap() {
value.log10()
} else if base - U::from(std::f64::consts::E).unwrap() < U::epsilon() * U::from(1e6).unwrap() {
value.ln()
} else {
value.log(base)
}
}
#[node_macro::node_fn(MaximumNode)]
fn max<T: core::cmp::PartialOrd>(first: T, second: T) -> T {
match first > second {
true => first,
false => second,
// Sine
#[node_macro::node(category("Math: Trig"))]
fn sine(_: (), theta: f64) -> f64 {
theta.sin()
}
// Cosine
#[node_macro::node(category("Math: Trig"))]
fn cosine(_: (), theta: f64) -> f64 {
theta.cos()
}
// Tangent
#[node_macro::node(category("Math: Trig"))]
fn tangent(_: (), theta: f64) -> f64 {
theta.tan()
}
// Random
#[node_macro::node(category("Math: Numeric"))]
fn random(_: (), _primary: (), seed: u64, min: f64, #[default(1.)] max: f64) -> 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) };
result * (max - min) + min
}
// Round
#[node_macro::node(category("Math: Numeric"))]
fn round(_: (), value: f64) -> f64 {
value.round()
}
// Floor
#[node_macro::node(category("Math: Numeric"))]
fn floor(_: (), value: f64) -> f64 {
value.floor()
}
// Ceiling
#[node_macro::node(category("Math: Numeric"))]
fn ceiling(_: (), value: f64) -> f64 {
value.ceil()
}
// Absolute Value
#[node_macro::node(category("Math: Numeric"))]
fn absolute_value(_: (), value: f64) -> f64 {
value.abs()
}
// Min
#[node_macro::node(category("Math: Numeric"))]
fn min<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
match value < other_value {
true => value,
false => other_value,
}
}
// Max
#[node_macro::node(category("Math: Numeric"))]
fn max<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
match value > other_value {
true => value,
false => other_value,
}
}
// Equals
pub struct EqualsNode<Second> {
second: Second,
}
#[node_macro::node_fn(EqualsNode)]
fn eq<T: core::cmp::PartialEq>(first: T, second: T) -> bool {
first == second
#[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)]
#[min(100.)]
#[max(200.)]
other_value: U,
) -> bool {
other_value == value
}
// Modulo
pub struct ModuloNode<Second> {
second: Second,
}
#[node_macro::node_fn(ModuloNode)]
fn modulo<U, T>(first: U, second: T) -> <U as Rem<T>>::Output
where
U: Rem<T>,
{
first % second
// Logical Or
#[node_macro::node(category("Math: Logic"))]
fn logical_or(_: (), value: bool, other_value: bool) -> bool {
value || other_value
}
pub struct ConstructVector2<X, Y> {
x: X,
y: Y,
// Logical And
#[node_macro::node(category("Math: Logic"))]
fn logical_and(_: (), value: bool, other_value: bool) -> bool {
value && other_value
}
#[node_macro::node_fn(ConstructVector2)]
fn construct_vector2(_primary: (), x: f64, y: f64) -> glam::DVec2 {
// Logical Xor
#[node_macro::node(category("Math: Logic"))]
fn logical_xor(_: (), value: bool, other_value: bool) -> bool {
value ^ other_value
}
// Logical Not
#[node_macro::node(category("Math: Logic"))]
fn logical_not(_: (), input: bool) -> bool {
!input
}
// Bool Value
#[node_macro::node(category("Value"))]
fn bool_value(_: (), _primary: (), #[name("Bool")] bool_value: bool) -> bool {
bool_value
}
// Number Value
#[node_macro::node(category("Value"))]
fn number_value(_: (), _primary: (), number: f64) -> f64 {
number
}
// Percentage Value
#[node_macro::node(category("Value"))]
fn percentage_value(_: (), _primary: (), percentage: Percentage) -> f64 {
percentage
}
// Vector2 Value
#[node_macro::node(category("Value"))]
fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> glam::DVec2 {
glam::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 {
color
}
// Gradient Value
#[node_macro::node(category("Value"))]
fn gradient_value(_: (), _primary: (), gradient: crate::vector::style::GradientStops) -> crate::vector::style::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 {
color_channel
}
// Blend Mode Value
#[node_macro::node(category("Value"))]
fn blend_mode_value(_: (), _primary: (), blend_mode: crate::raster::BlendMode) -> crate::raster::BlendMode {
blend_mode
}
// Size Of
#[cfg(feature = "std")]
pub struct SizeOfNode;
#[cfg(feature = "std")]
#[node_macro::node_fn(SizeOfNode)]
fn flat_map(ty: crate::Type) -> Option<usize> {
#[node_macro::node(category("Debug"))]
fn size_of(_: (), ty: crate::Type) -> Option<usize> {
ty.size()
}
// Some
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SomeNode;
#[node_macro::node_fn(SomeNode)]
fn some<T>(input: T) -> Option<T> {
#[node_macro::node(category("Debug"))]
fn some<T>(_: (), #[implementations(f64, f32, u32, u64, String, crate::Color)] input: T) -> Option<T> {
Some(input)
}
// Unwrap
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct UnwrapNode;
#[node_macro::node_fn(UnwrapNode)]
fn some<T: Default>(input: Option<T>) -> T {
#[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 {
input.unwrap_or_default()
}
// Clone
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct CloneNode<O>(PhantomData<O>);
impl<'i, 'n: 'i, O: Clone + 'i> Node<'i, &'n O> for CloneNode<O> {
type Output = O;
fn eval(&'i self, input: &'i O) -> Self::Output {
input.clone()
}
}
impl<O> CloneNode<O> {
pub const fn new() -> Self {
Self(PhantomData)
}
}
// First of Pair
/// Return the first element of a 2-tuple
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct FirstOfPairNode;
impl<'i, L: 'i, R: 'i> Node<'i, (L, R)> for FirstOfPairNode {
type Output = L;
fn eval(&'i self, input: (L, R)) -> Self::Output {
input.0
}
}
impl FirstOfPairNode {
pub fn new() -> Self {
Self
}
}
// Second of Pair
/// Return the second element of a 2-tuple
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SecondOfPairNode;
impl<'i, L: 'i, R: 'i> Node<'i, (L, R)> for SecondOfPairNode {
type Output = R;
fn eval(&'i self, input: (L, R)) -> Self::Output {
input.1
}
}
impl SecondOfPairNode {
pub fn new() -> Self {
Self
}
}
// Swap Pair
/// Return a new 2-tuple with the elements reversed
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SwapPairNode;
impl<'i, L: 'i, R: 'i> Node<'i, (L, R)> for SwapPairNode {
type Output = (R, L);
fn eval(&'i self, input: (L, R)) -> Self::Output {
(input.1, input.0)
}
}
impl SwapPairNode {
pub fn new() -> Self {
Self
}
}
// Make Pair
/// Return a 2-tuple with two duplicates of the input argument
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct MakePairNode;
impl<'i, O: Clone + 'i> Node<'i, O> for MakePairNode {
type Output = (O, O);
fn eval(&'i self, input: O) -> Self::Output {
(input.clone(), input)
}
}
impl MakePairNode {
pub fn new() -> Self {
Self
}
#[node_macro::node(category("Debug"))]
fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&crate::raster::ImageFrame<crate::Color>)] value: &'i T) -> T {
value.clone()
}
// Identity
/// Return the input argument unchanged
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct IdentityNode;
impl<'i, O: 'i> Node<'i, O> for IdentityNode {
type Output = O;
fn eval(&'i self, input: O) -> Self::Output {
input
}
}
impl IdentityNode {
pub fn new() -> Self {
Self
}
// TODO: Rename to "Passthrough"
/// The identity function returns the input argument unchanged.
#[node_macro::node(skip_impl)]
fn identity<'i, T: 'i>(value: T) -> T {
value
}
// Type
@@ -332,6 +316,14 @@ where
fn eval(&'i self, input: I) -> Self::Output {
self.0.eval(input)
}
fn reset(&self) {
self.0.reset();
}
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
self.0.serialize()
}
}
impl<'i, N: for<'a> Node<'a, I>, I: 'i> TypeNode<N, I, <N as Node<'i, I>>::Output> {
pub fn new(node: N) -> Self {
@@ -345,62 +337,15 @@ impl<'i, N: for<'a> Node<'a, I> + Clone, I: 'i> Clone for TypeNode<N, I, <N as N
}
impl<'i, N: for<'a> Node<'a, I> + Copy, I: 'i> Copy for TypeNode<N, I, <N as Node<'i, I>>::Output> {}
// Map Option
pub struct MapOptionNode<I, Mn> {
node: Mn,
_i: PhantomData<I>,
}
#[node_macro::node_fn(MapOptionNode<_I>)]
fn map_option_node<_I, N>(input: Option<_I>, node: &'input N) -> Option<<N as Node<'input, _I>>::Output>
where
N: for<'a> Node<'a, _I>,
{
input.map(|x| node.eval(x))
}
// Map Result
pub struct MapResultNode<I, E, Mn> {
node: Mn,
_i: PhantomData<I>,
_e: PhantomData<E>,
}
#[node_macro::node_fn(MapResultNode<_I, _E>)]
fn map_result_node<_I, _E, N>(input: Result<_I, _E>, node: &'input N) -> Result<<N as Node<'input, _I>>::Output, _E>
where
N: for<'a> Node<'a, _I>,
{
input.map(|x| node.eval(x))
}
// Flat Map Result
pub struct FlatMapResultNode<I, O, E, Mn> {
node: Mn,
_i: PhantomData<I>,
_o: PhantomData<O>,
_e: PhantomData<E>,
}
#[node_macro::node_fn(FlatMapResultNode<_I, _O, _E>)]
fn flat_map_node<_I, _O, _E, N>(input: Result<_I, _E>, node: &'input N) -> Result<_O, _E>
where
N: for<'a> Node<'a, _I, Output = Result<_O, _E>>,
{
match input.map(|x| node.eval(x)) {
Ok(Ok(x)) => Ok(x),
Ok(Err(e)) => Err(e),
Err(e) => Err(e),
}
}
// Into
pub struct IntoNode<I, O> {
_i: PhantomData<I>,
pub struct IntoNode<O> {
_o: PhantomData<O>,
}
#[cfg(feature = "alloc")]
#[node_macro::node_fn(IntoNode<_I, _O>)]
async fn into<_I, _O>(input: _I) -> _O
#[node_macro::old_node_fn(IntoNode<_O>)]
async fn into<I, _O>(input: I) -> _O
where
_I: Into<_O> + Sync + Send,
I: Into<_O> + Sync + Send,
{
input.into()
}
@@ -410,86 +355,14 @@ mod test {
use super::*;
use crate::{generic::*, structural::*, value::*};
#[test]
pub fn duplicate_node() {
let value = ValueNode(4u32);
let pair = ComposeNode::new(value, MakePairNode::new());
assert_eq!(pair.eval(()), (&4, &4));
}
#[test]
pub fn identity_node() {
let value = ValueNode(4u32).then(IdentityNode::new());
assert_eq!(value.eval(()), &4);
}
#[test]
pub fn clone_node() {
let cloned = ValueNode(4u32).then(CloneNode::new());
assert_eq!(cloned.eval(()), 4);
let type_erased = &CloneNode::new() as &dyn for<'a> Node<'a, &'a u32, Output = u32>;
assert_eq!(type_erased.eval(&4), 4);
let type_erased = &cloned as &dyn for<'a> Node<'a, (), Output = u32>;
assert_eq!(type_erased.eval(()), 4);
}
#[test]
pub fn first_node() {
let first_of_pair = ValueNode((4u32, "a")).then(CloneNode::new()).then(FirstOfPairNode::new());
assert_eq!(first_of_pair.eval(()), 4);
}
#[test]
pub fn second_node() {
let second_of_pair = ValueNode((4u32, "a")).then(CloneNode::new()).then(SecondOfPairNode::new());
assert_eq!(second_of_pair.eval(()), "a");
}
#[test]
pub fn object_safe() {
let second_of_pair = ValueNode((4u32, "a")).then(CloneNode::new()).then(SecondOfPairNode::new());
let foo = &second_of_pair as &dyn Node<(), Output = &str>;
assert_eq!(foo.eval(()), "a");
}
#[test]
pub fn map_result() {
let value: ClonedNode<Result<&u32, ()>> = ClonedNode(Ok(&4u32));
assert_eq!(value.eval(()), Ok(&4u32));
// let type_erased_clone = clone as &dyn for<'a> Node<'a, &'a u32, Output = u32>;
let map_result = MapResultNode::new(ValueNode::new(FnNode::new(|x: &u32| *x)));
// let type_erased = &map_result as &dyn for<'a> Node<'a, Result<&'a u32, ()>, Output = Result<u32, ()>>;
assert_eq!(map_result.eval(Ok(&4u32)), Ok(4u32));
let fst = value.then(map_result);
// let type_erased = &fst as &dyn for<'a> Node<'a, (), Output = Result<u32, ()>>;
assert_eq!(fst.eval(()), Ok(4u32));
}
#[test]
pub fn flat_map_result() {
let fst = ValueNode(Ok(&4u32)).then(CloneNode::new());
let fn_node: FnNode<_, &u32, Result<&u32, _>> = FnNode::new(|_| Err(8u32));
assert_eq!(fn_node.eval(&4u32), Err(8u32));
let flat_map = FlatMapResultNode::new(ValueNode::new(fn_node));
let fst = fst.then(flat_map);
assert_eq!(fst.eval(()), Err(8u32));
}
#[test]
pub fn add_node() {
let a = ValueNode(42u32);
let b = ValueNode(6u32);
let cons_a = ConsNode::new(a);
let tuple = b.then(cons_a);
let sum = tuple.then(AddPairNode::new());
assert_eq!(sum.eval(()), 48);
}
#[test]
pub fn foo() {
fn int(_: (), state: &u32) -> u32 {
*state
}
fn swap(input: (u32, u32)) -> (u32, u32) {
(input.1, input.0)
}
let fnn = FnNode::new(&swap);
let fns = FnNodeWithState::new(int, 42u32);
let fnn = FnNode::new(|(a, b)| (b, a));
assert_eq!(fnn.eval((1u32, 2u32)), (2, 1));
let result: u32 = fns.eval(());
assert_eq!(result, 42);
}
}

View File

@@ -1,209 +0,0 @@
use crate::raster::{Color, Pixel};
use crate::Node;
use bytemuck::{Pod, Zeroable};
use dyn_any::{DynAny, StaticType};
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::Float;
#[derive(Clone, Copy, DynAny, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C, align(16))]
pub struct Quantization {
pub a: f32,
pub b: f32,
pub bits: u32,
_padding: u32,
}
impl core::fmt::Debug for Quantization {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Quantization").field("a", &self.a).field("b", &self.b()).field("bits", &self.bits()).finish()
}
}
impl Quantization {
pub fn new(a: f32, b: f32, bits: u32) -> Self {
Self { a, b, bits, _padding: 0 }
}
pub fn a(&self) -> f32 {
self.a
}
pub fn b(&self) -> f32 {
self.b
}
pub fn bits(&self) -> u32 {
self.bits
}
}
impl core::hash::Hash for Quantization {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.bits().hash(state);
self.a().to_bits().hash(state);
self.b().to_bits().hash(state);
}
}
impl Default for Quantization {
fn default() -> Self {
Self::new(1., 0., 8)
}
}
pub type QuantizationChannels = [Quantization; 4];
#[repr(transparent)]
#[derive(DynAny, Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
pub struct PackedPixel(pub u32);
impl Pixel for PackedPixel {}
/*
#[inline(always)]
fn quantize(value: f32, offset: u32, quantization: Quantization) -> u32 {
let a = quantization.a();
let bits = quantization.bits();
let b = quantization.b();
let value = (((a * value) * ((1 << bits) - 1) as f32) as i32 + b) as u32;
value.checked_shl(32 - bits - offset).unwrap_or(0)
}*/
#[inline(always)]
fn quantize(value: f32, offset: u32, quantization: Quantization) -> u32 {
let a = quantization.a();
let b = quantization.b();
let bits = quantization.bits();
// Calculate the quantized value
// Scale the value by 'a' and the maximum quantization range
let scaled_value = ((a * value) + b) * ((1 << bits) - 1) as f32;
// Round the scaled value to the nearest integer
let rounded_value = scaled_value.clamp(0., (1 << bits) as f32 - 1.) as u32;
// Shift the quantized value to the appropriate position based on the offset
rounded_value.checked_shl(32 - bits - offset).unwrap()
}
/*
#[inline(always)]
fn decode(value: u32, offset: u32, quantization: Quantization) -> f32 {
let a = quantization.a();
let bits = quantization.bits();
let b = quantization.b();
let value = (value << offset) >> (31 - bits);
let value = value as i32 - b;
(value as f32 / ((1 << bits) - 1) as f32) / a
}*/
#[inline(always)]
fn decode(value: u32, offset: u32, quantization: Quantization) -> f32 {
let a = quantization.a();
let bits = quantization.bits();
let b = quantization.b();
// Shift the value to the appropriate position based on the offset
let shifted_value = value.checked_shr(32 - bits - offset).unwrap();
// Unpack the quantized value
let unpacked_value = shifted_value & ((1 << bits) - 1); // Mask out the unnecessary bits
let normalized_value = unpacked_value as f32 / ((1 << bits) - 1) as f32; // Normalize the value based on the quantization range
let decoded_value = normalized_value - b;
decoded_value / a
}
pub struct QuantizeNode<Quantization> {
quantization: Quantization,
}
#[node_macro::node_fn(QuantizeNode)]
fn quantize_fn<'a>(color: Color, quantization: [Quantization; 4]) -> PackedPixel {
let quant = quantization;
quantize_color(color, quant)
}
pub fn quantize_color(color: Color, quant: [Quantization; 4]) -> PackedPixel {
let mut offset = 0;
let r = quantize(color.r(), offset, quant[0]);
offset += quant[0].bits();
let g = quantize(color.g(), offset, quant[1]);
offset += quant[1].bits();
let b = quantize(color.b(), offset, quant[2]);
offset += quant[2].bits();
let a = quantize(color.a(), offset, quant[3]);
PackedPixel(r | g | b | a)
}
pub struct DeQuantizeNode<Quantization> {
quantization: Quantization,
}
#[node_macro::node_fn(DeQuantizeNode)]
fn dequantize_fn<'a>(color: PackedPixel, quantization: [Quantization; 4]) -> Color {
let quant = quantization;
dequantize_color(color, quant)
}
pub fn dequantize_color(color: PackedPixel, quant: [Quantization; 4]) -> Color {
let mut offset = 0;
let mut r = decode(color.0, offset, quant[0]);
offset += quant[0].bits();
let mut g = decode(color.0, offset, quant[1]);
offset += quant[1].bits();
let mut b = decode(color.0, offset, quant[2]);
offset += quant[2].bits();
let mut a = decode(color.0, offset, quant[3]);
if a.is_nan() {
a = 0.;
}
if r.is_nan() {
r = 0.;
}
if g.is_nan() {
g = 0.;
}
if b.is_nan() {
b = 0.;
}
Color::from_rgbaf32_unchecked(r, g, b, a)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn quantize() {
let quant = Quantization::new(1., 0., 8);
let color = Color::from_rgbaf32_unchecked(0.5, 0.5, 0.5, 0.5);
let quantized = quantize_color(color, [quant; 4]);
assert_eq!(quantized.0, 0x7f7f7f7f);
let _dequantized = dequantize_color(quantized, [quant; 4]);
// assert_eq!(color, dequantized);
}
#[test]
fn quantize_black() {
let quant = Quantization::new(1., 0., 8);
let color = Color::from_rgbaf32_unchecked(0., 0., 0., 1.);
let quantized = quantize_color(color, [quant; 4]);
assert_eq!(quantized.0, 0xff);
let dequantized = dequantize_color(quantized, [quant; 4]);
assert_eq!(color, dequantized);
}
#[test]
fn test_getters() {
let quant = Quantization::new(1., 3., 8);
assert_eq!(quant.a(), 1.);
assert_eq!(quant.b(), 3.);
assert_eq!(quant.bits(), 8);
}
}

View File

@@ -1,6 +1,6 @@
use core::{fmt::Debug, marker::PhantomData};
use core::fmt::Debug;
use crate::Node;
use crate::{registry::types::Percentage, transform::Footprint};
use bytemuck::{Pod, Zeroable};
use glam::DVec2;
@@ -282,422 +282,50 @@ impl<'i, T: BitmapMut + Bitmap> BitmapMut for &'i mut T {
}
}
#[derive(Debug, Default)]
pub struct MapNode<MapFn> {
map_fn: MapFn,
}
#[node_macro::node_fn(MapNode)]
fn map_node<_Iter: Iterator, MapFnNode>(input: _Iter, map_fn: &'input MapFnNode) -> MapFnIterator<'input, _Iter, MapFnNode>
where
MapFnNode: for<'any_input> Node<'any_input, _Iter::Item>,
{
MapFnIterator::new(input, map_fn)
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct MapFnIterator<'i, Iter, MapFn> {
iter: Iter,
map_fn: &'i MapFn,
}
impl<'i, Iter: Debug, MapFn> Debug for MapFnIterator<'i, Iter, MapFn> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MapFnIterator").field("iter", &self.iter).field("map_fn", &"MapFn").finish()
}
}
impl<'i, Iter: Clone, MapFn> Clone for MapFnIterator<'i, Iter, MapFn> {
fn clone(&self) -> Self {
Self {
iter: self.iter.clone(),
map_fn: self.map_fn,
}
}
}
impl<'i, Iter: Copy, MapFn> Copy for MapFnIterator<'i, Iter, MapFn> {}
impl<'i, Iter, MapFn> MapFnIterator<'i, Iter, MapFn> {
pub fn new(iter: Iter, map_fn: &'i MapFn) -> Self {
Self { iter, map_fn }
}
}
impl<'i, I: Iterator + 'i, F> Iterator for MapFnIterator<'i, I, F>
where
F: Node<'i, I::Item> + 'i,
Self: 'i,
{
type Item = F::Output;
#[inline]
fn next(&mut self) -> Option<F::Output> {
self.iter.next().map(|x| self.map_fn.eval(x))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
#[derive(Debug, Clone, Copy)]
pub struct WeightedAvgNode {}
#[node_macro::node_fn(WeightedAvgNode)]
fn weighted_avg_node<_Iter: Iterator<Item = (Color, f32)>>(input: _Iter) -> Color
where
_Iter: Clone,
{
let total_weight: f32 = input.clone().map(|(_, weight)| weight).sum();
let total_r: f32 = input.clone().map(|(color, weight)| color.r() * weight).sum();
let total_g: f32 = input.clone().map(|(color, weight)| color.g() * weight).sum();
let total_b: f32 = input.clone().map(|(color, weight)| color.b() * weight).sum();
let total_a: f32 = input.map(|(color, weight)| color.a() * weight).sum();
Color::from_rgbaf32_unchecked(total_r / total_weight, total_g / total_weight, total_b / total_weight, total_a / total_weight)
}
#[derive(Debug)]
pub struct GaussianNode<Sigma> {
sigma: Sigma,
}
#[node_macro::node_fn(GaussianNode)]
fn gaussian_node(input: f32, sigma: f64) -> f32 {
let sigma = sigma as f32;
(1.0 / (2.0 * core::f32::consts::PI * sigma * sigma).sqrt()) * (-input * input / (2.0 * sigma * sigma)).exp()
}
#[derive(Debug, Clone, Copy)]
pub struct DistanceNode;
#[node_macro::node_fn(DistanceNode)]
fn distance_node(input: (i32, i32)) -> f32 {
let (x, y) = input;
((x * x + y * y) as f32).sqrt()
}
#[derive(Debug, Clone, Copy)]
pub struct ImageIndexIterNode<P> {
_p: core::marker::PhantomData<P>,
}
#[node_macro::node_fn(ImageIndexIterNode<_P>)]
fn image_index_iter_node<_P>(input: ImageSlice<'input, _P>) -> core::ops::Range<u32> {
0..(input.width * input.height)
}
#[derive(Debug)]
pub struct WindowNode<P, Radius: for<'i> Node<'i, (), Output = u32>, Image: for<'i> Node<'i, (), Output = ImageSlice<'i, P>>> {
radius: Radius,
image: Image,
_pixel: core::marker::PhantomData<P>,
}
impl<'input, P: 'input, S0: 'input, S1: 'input> Node<'input, u32> for WindowNode<P, S0, S1>
where
S0: for<'any_input> Node<'any_input, (), Output = u32>,
S1: for<'any_input> Node<'any_input, (), Output = ImageSlice<'any_input, P>>,
{
type Output = ImageWindowIterator<'input, P>;
#[inline]
fn eval(&'input self, input: u32) -> Self::Output {
let radius = self.radius.eval(());
let image = self.image.eval(());
{
let iter = ImageWindowIterator::new(image, radius, input);
iter
}
}
}
impl<P, S0, S1> WindowNode<P, S0, S1>
where
S0: for<'any_input> Node<'any_input, (), Output = u32>,
S1: for<'any_input> Node<'any_input, (), Output = ImageSlice<'any_input, P>>,
{
pub const fn new(radius: S0, image: S1) -> Self {
Self {
radius,
image,
_pixel: core::marker::PhantomData,
}
}
}
/*
#[node_macro::node_fn(WindowNode)]
fn window_node(input: u32, radius: u32, image: ImageSlice<'input>) -> ImageWindowIterator<'input> {
let iter = ImageWindowIterator::new(image, radius, input);
iter
}*/
#[derive(Debug, Clone, Copy)]
pub struct ImageWindowIterator<'a, P> {
image: ImageSlice<'a, P>,
radius: u32,
index: u32,
x: u32,
y: u32,
}
impl<'a, P> ImageWindowIterator<'a, P> {
fn new(image: ImageSlice<'a, P>, radius: u32, index: u32) -> Self {
let start_x = index as i32 % image.width as i32;
let start_y = index as i32 / image.width as i32;
let min_x = (start_x - radius as i32).max(0) as u32;
let min_y = (start_y - radius as i32).max(0) as u32;
Self {
image,
radius,
index,
x: min_x,
y: min_y,
}
}
}
#[cfg(not(target_arch = "spirv"))]
impl<'a, P: Copy> Iterator for ImageWindowIterator<'a, P> {
type Item = (P, (i32, i32));
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let start_x = self.index as i32 % self.image.width as i32;
let start_y = self.index as i32 / self.image.width as i32;
let radius = self.radius as i32;
let min_x = (start_x - radius).max(0) as u32;
let max_x = (start_x + radius).min(self.image.width as i32 - 1) as u32;
let max_y = (start_y + radius).min(self.image.height as i32 - 1) as u32;
if self.y > max_y {
return None;
}
#[cfg(target_arch = "spirv")]
let value = None;
#[cfg(not(target_arch = "spirv"))]
let value = Some((self.image.data[(self.x + self.y * self.image.width) as usize], (self.x as i32 - start_x, self.y as i32 - start_y)));
self.x += 1;
if self.x > max_x {
self.x = min_x;
self.y += 1;
}
value
}
}
#[derive(Debug)]
pub struct MapSecondNode<First, Second, MapFn> {
map_fn: MapFn,
_first: PhantomData<First>,
_second: PhantomData<Second>,
}
#[node_macro::node_fn(MapSecondNode< _First, _Second>)]
fn map_snd_node<MapFn, _First, _Second>(input: (_First, _Second), map_fn: &'input MapFn) -> (_First, <MapFn as Node<'input, _Second>>::Output)
where
MapFn: for<'any_input> Node<'any_input, _Second>,
{
let (a, b) = input;
(a, map_fn.eval(b))
}
#[derive(Debug)]
pub struct BrightenColorNode<Brightness> {
brightness: Brightness,
}
#[node_macro::node_fn(BrightenColorNode)]
fn brighten_color_node(color: Color, brightness: f32) -> Color {
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
Color::from_rgbaf32_unchecked(per_channel(color.r()), per_channel(color.g()), per_channel(color.b()), color.a())
}
#[derive(Debug)]
pub struct ForEachNode<MapNode> {
map_node: MapNode,
}
#[node_macro::node_fn(ForEachNode)]
fn map_node<_Iter: Iterator, MapNode>(input: _Iter, map_node: &'input MapNode) -> ()
where
MapNode: for<'any_input> Node<'any_input, _Iter::Item, Output = ()> + 'input,
{
input.for_each(|x| map_node.eval(x));
}
#[cfg(target_arch = "spirv")]
const NOTHING: () = ();
use dyn_any::{StaticType, StaticTypeSized};
#[derive(Clone, Debug, PartialEq, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ImageSlice<'a, Pixel> {
pub width: u32,
pub height: u32,
#[cfg(not(target_arch = "spirv"))]
pub data: &'a [Pixel],
#[cfg(target_arch = "spirv")]
pub data: &'a (),
#[cfg(target_arch = "spirv")]
pub _marker: PhantomData<Pixel>,
}
unsafe impl<P: StaticTypeSized> StaticType for ImageSlice<'_, P> {
type Static = ImageSlice<'static, P::Static>;
}
#[allow(clippy::derivable_impls)]
impl<'a, P> Default for ImageSlice<'a, P> {
#[cfg(not(target_arch = "spirv"))]
fn default() -> Self {
Self {
width: Default::default(),
height: Default::default(),
data: Default::default(),
}
}
#[cfg(target_arch = "spirv")]
fn default() -> Self {
Self {
width: Default::default(),
height: Default::default(),
data: &NOTHING,
_marker: PhantomData,
}
}
}
#[cfg(not(target_arch = "spirv"))]
impl<P: Copy + Debug + Pixel> Bitmap for ImageSlice<'_, P> {
type Pixel = P;
fn get_pixel(&self, x: u32, y: u32) -> Option<P> {
self.data.get((x + y * self.width) as usize).copied()
}
fn width(&self) -> u32 {
self.width
}
fn height(&self) -> u32 {
self.height
}
}
impl<P> ImageSlice<'_, P> {
#[cfg(not(target_arch = "spirv"))]
pub const fn empty() -> Self {
Self { width: 0, height: 0, data: &[] }
}
}
#[cfg(not(target_arch = "spirv"))]
impl<'a, P: 'a> IntoIterator for ImageSlice<'a, P> {
type Item = &'a P;
type IntoIter = core::slice::Iter<'a, P>;
fn into_iter(self) -> Self::IntoIter {
self.data.iter()
}
}
#[cfg(not(target_arch = "spirv"))]
impl<'a, P: 'a> IntoIterator for &'a ImageSlice<'a, P> {
type Item = &'a P;
type IntoIter = core::slice::Iter<'a, P>;
fn into_iter(self) -> Self::IntoIter {
self.data.iter()
}
}
#[derive(Debug)]
pub struct ImageDimensionsNode<P> {
_p: PhantomData<P>,
}
#[node_macro::node_fn(ImageDimensionsNode<_P>)]
fn dimensions_node<_P>(input: ImageSlice<'input, _P>) -> (u32, u32) {
(input.width, input.height)
}
#[cfg(feature = "alloc")]
pub use self::image::{CollectNode, Image, ImageFrame, ImageRefNode, MapImageSliceNode};
pub use self::image::{Image, ImageFrame};
#[cfg(feature = "alloc")]
pub(crate) mod image;
#[cfg(test)]
mod test {
use super::*;
use crate::{ops::CloneNode, structural::Then, value::ValueNode, Node};
#[ignore]
#[test]
fn map_node() {
// let array = &mut [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()];
// LuminanceNode.eval(Color::from_rgbf32_unchecked(1., 0., 0.));
/*let map = ForEachNode(MutWrapper(LuminanceNode));
(&map).eval(array.iter_mut());
assert_eq!(array[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());*/
}
#[test]
fn window_node() {
use alloc::vec;
let radius = ValueNode::new(1u32).then(CloneNode::new());
let image = ValueNode::<_>::new(Image {
width: 5,
height: 5,
data: vec![Color::from_rgbf32_unchecked(1., 0., 0.); 25],
base64_string: None,
});
let image = image.then(ImageRefNode::new());
let window = WindowNode::new(radius, image);
let vec = window.eval(0);
assert_eq!(vec.count(), 4);
let vec = window.eval(5);
assert_eq!(vec.count(), 6);
let vec = window.eval(12);
assert_eq!(vec.count(), 9);
}
// TODO: I can't be bothered to fix this test rn
// #[test]
// fn blur_node() {
// use alloc::vec;
// let radius = ValueNode::new(1u32).then(CloneNode::new());
// let sigma = ValueNode::new(3f64).then(CloneNode::new());
// let radius = ValueNode::new(1u32).then(CloneNode::new());
// let image = ValueNode::<_>::new(Image {
// width: 5,
// height: 5,
// data: vec![Color::from_rgbf32_unchecked(1., 0., 0.); 25],
// });
// let image = image.then(ImageRefNode::new());
// let window = WindowNode::new(radius, image);
// let window: TypeNode<_, u32, ImageWindowIterator<'_>> = TypeNode::new(window);
// let distance = ValueNode::new(DistanceNode::new());
// let pos_to_dist = MapSecondNode::new(distance);
// let type_erased = &window as &dyn for<'a> Node<'a, u32, Output = ImageWindowIterator<'a>>;
// type_erased.eval(0);
// let map_pos_to_dist = MapNode::new(ValueNode::new(pos_to_dist));
// let type_erased = &map_pos_to_dist as &dyn for<'a> Node<'a, u32, Output = ImageWindowIterator<'a>>;
// type_erased.eval(0);
// let distance = window.then(map_pos_to_dist);
// let map_gaussian = MapSecondNode::new(ValueNode(GaussianNode::new(sigma)));
// let map_gaussian: TypeNode<_, (_, f32), (_, f32)> = TypeNode::new(map_gaussian);
// let map_gaussian = ValueNode(map_gaussian);
// let map_gaussian: TypeNode<_, (), &_> = TypeNode::new(map_gaussian);
// let map_distances = MapNode::new(map_gaussian);
// let map_distances: TypeNode<_, _, MapFnIterator<'_, '_, _, _>> = TypeNode::new(map_distances);
// let gaussian_iter = distance.then(map_distances);
// let avg = gaussian_iter.then(WeightedAvgNode::new());
// let avg: TypeNode<_, u32, Color> = TypeNode::new(avg);
// let blur_iter = MapNode::new(ValueNode::new(avg));
// let blur = image.then(ImageIndexIterNode).then(blur_iter);
// let blur: TypeNode<_, (), MapFnIterator<_, _>> = TypeNode::new(blur);
// let collect = CollectNode::new();
// let vec = collect.eval(0..10);
// assert_eq!(vec.len(), 10);
// let _ = blur.eval(());
// let vec = blur.then(collect);
// let _image = vec.eval(());
// }
trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode);
}
impl SetBlendMode for crate::vector::VectorData {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode;
}
}
impl SetBlendMode for crate::GraphicGroup {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode;
}
}
impl SetBlendMode for ImageFrame<Color> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode;
}
}
#[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>,
blend_mode: BlendMode,
) -> T {
let mut value = value.eval(footprint).await;
value.set_blend_mode(blend_mode);
value
}
#[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>,
#[default(100.)] factor: Percentage,
) -> T {
let mut value = value.eval(footprint).await;
let opacity_multiplier = factor / 100.;
value.multiply_alpha(opacity_multiplier);
value
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]

View File

@@ -26,8 +26,8 @@ pub struct GenerateBrightnessContrastLegacyMapperNode<Brightness, Contrast> {
contrast: Contrast,
}
#[node_macro::node_fn(GenerateBrightnessContrastLegacyMapperNode)]
fn brightness_contrast_legacy_node(_primary: (), brightness: f64, contrast: f64) -> BrightnessContrastLegacyMapperNode {
#[node_macro::old_node_fn(GenerateBrightnessContrastLegacyMapperNode)]
fn brightness_contrast_legacy(_primary: (), brightness: f64, contrast: f64) -> BrightnessContrastLegacyMapperNode {
let brightness = brightness as f32 / 255.;
let contrast = contrast as f32 / 100.;
@@ -67,8 +67,8 @@ pub struct GenerateBrightnessContrastMapperNode<Brightness, Contrast> {
// TODO: Replace this node implementation with one that reuses the more generalized Curves adjustment node.
// TODO: It will be necessary to ensure the tests below are faithfully translated in a way that ensures identical results.
#[node_macro::node_fn(GenerateBrightnessContrastMapperNode)]
fn brightness_contrast_node(_primary: (), brightness: f64, contrast: f64) -> BrightnessContrastMapperNode {
#[node_macro::old_node_fn(GenerateBrightnessContrastMapperNode)]
fn brightness_contrast(_primary: (), brightness: f64, contrast: f64) -> BrightnessContrastMapperNode {
// Brightness LUT
let brightness_is_negative = brightness < 0.;
let brightness = brightness.abs() as f32 / 100.;

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use crate::raster::Image;
use crate::raster::ImageFrame;
@@ -100,12 +100,18 @@ pub struct BrushPlan {
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, DynAny, Default)]
#[derive(Debug, DynAny)]
pub struct BrushCache {
inner: Arc<Mutex<BrushCacheImpl>>,
proto: bool,
}
impl Default for BrushCache {
fn default() -> Self {
Self::new_proto()
}
}
// A bit of a cursed implementation to work around the current node system.
// The original object is a 'prototype' that when cloned gives you a independent
// new object. Any further clones however are all the same underlying cache object.

View File

@@ -1,7 +1,7 @@
use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};
use super::{Alpha, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
#[cfg(feature = "serde")]
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float;

View File

@@ -1,7 +1,7 @@
use super::{Channel, Linear, LuminanceMut};
use crate::Node;
use dyn_any::{DynAny, StaticType};
use dyn_any::{DynAny, StaticType, StaticTypeSized};
use core::ops::{Add, Mul, Sub};
@@ -176,6 +176,10 @@ pub struct ValueMapperNode<C> {
lut: Vec<C>,
}
unsafe impl<C: StaticTypeSized> StaticType for ValueMapperNode<C> {
type Static = ValueMapperNode<C::Static>;
}
impl<C> ValueMapperNode<C> {
pub const fn new(lut: Vec<C>) -> Self {
Self { lut }

View File

@@ -1,6 +1,6 @@
use super::discrete_srgb::float_to_srgb_u8;
use super::{Color, ImageSlice};
use crate::{AlphaBlending, Node};
use super::Color;
use crate::AlphaBlending;
use alloc::vec::Vec;
use core::hash::{Hash, Hasher};
use dyn_any::StaticType;
@@ -65,7 +65,7 @@ impl<P: Pixel + Debug> Debug for Image<P> {
}
}
unsafe impl<P: StaticTypeSized + Pixel> StaticType for Image<P>
unsafe impl<P: dyn_any::StaticTypeSized + Pixel> StaticType for Image<P>
where
P::Static: Pixel,
{
@@ -126,14 +126,6 @@ impl<P: Pixel> Image<P> {
base64_string: None,
}
}
pub fn as_slice(&self) -> ImageSlice<P> {
ImageSlice {
width: self.width,
height: self.height,
data: self.data.as_slice(),
}
}
}
impl Image<Color> {
@@ -224,42 +216,6 @@ impl<P: Pixel> IntoIterator for Image<P> {
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ImageRefNode<P> {
_p: PhantomData<P>,
}
#[node_macro::node_fn(ImageRefNode<_P>)]
fn image_ref_node<_P: Pixel>(image: &'input Image<_P>) -> ImageSlice<'input, _P> {
image.as_slice()
}
#[derive(Debug, Clone)]
pub struct CollectNode {}
#[node_macro::node_fn(CollectNode)]
fn collect_node<_Iter>(input: _Iter) -> Vec<_Iter::Item>
where
_Iter: Iterator,
{
input.collect()
}
#[derive(Debug)]
pub struct MapImageSliceNode<Data> {
data: Data,
}
#[node_macro::node_fn(MapImageSliceNode)]
fn map_node<P: Pixel>(input: (u32, u32), data: Vec<P>) -> Image<P> {
Image {
width: input.0,
height: input.1,
data,
base64_string: None,
}
}
#[derive(Clone, Debug, PartialEq, Default, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ImageFrame<P: Pixel> {
@@ -314,7 +270,7 @@ impl<P: Copy + Pixel> BitmapMut for ImageFrame<P> {
}
}
unsafe impl<P: StaticTypeSized + Pixel> StaticType for ImageFrame<P>
unsafe impl<P: dyn_any::StaticTypeSized + Pixel> StaticType for ImageFrame<P>
where
P::Static: Pixel,
{

View File

@@ -0,0 +1,298 @@
use std::collections::HashMap;
use std::ops::Deref;
use std::pin::Pin;
use std::sync::{LazyLock, Mutex};
use dyn_any::DynAny;
use crate::transform::Footprint;
use crate::NodeIO;
use crate::NodeIOTypes;
pub mod types {
/// 0% - 100%
pub type Percentage = f64;
/// -180° - 180°
pub type Angle = f64;
/// -100% - 100%
pub type SignedPercentage = f64;
/// Non negative integer, px unit
pub type PixelLength = f64;
/// Non negative
pub type Length = f64;
/// 0.- 1.
pub type Fraction = f64;
pub type IntegerCount = u32;
/// Int input with randomization button
pub type SeedValue = u32;
/// Non Negative integer vec with px unit
pub type Resolution = glam::UVec2;
}
#[derive(Clone)]
pub struct NodeMetadata {
pub display_name: &'static str,
pub category: Option<&'static str>,
pub fields: Vec<FieldMetadata>,
}
#[derive(Clone, Debug)]
pub struct FieldMetadata {
pub name: &'static str,
pub exposed: bool,
pub value_source: ValueSource,
pub number_min: Option<f64>,
pub number_max: Option<f64>,
pub number_mode_range: Option<(f64, f64)>,
}
#[derive(Clone, Debug)]
pub enum ValueSource {
None,
Default(&'static str),
Scope(&'static str),
}
type NodeRegistry = LazyLock<Mutex<HashMap<String, Vec<(NodeConstructor, NodeIOTypes)>>>>;
pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new()));
pub static NODE_METADATA: LazyLock<Mutex<HashMap<String, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
#[cfg(not(target_arch = "wasm32"))]
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n + Send>>;
#[cfg(target_arch = "wasm32")]
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;
pub type LocalFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;
#[cfg(not(target_arch = "wasm32"))]
pub type Any<'n> = Box<dyn DynAny<'n> + 'n + Send>;
#[cfg(target_arch = "wasm32")]
pub type Any<'n> = Box<dyn DynAny<'n> + 'n>;
pub type FutureAny<'n> = DynFuture<'n, Any<'n>>;
// TODO: is this safe? This is assumed to be send+sync.
#[cfg(not(target_arch = "wasm32"))]
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n + Send + Sync;
#[cfg(target_arch = "wasm32")]
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n;
pub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>;
pub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>;
pub type TypeErasedBox<'n> = Box<TypeErasedNode<'n>>;
pub type TypeErasedPinned<'n> = Pin<Box<TypeErasedNode<'n>>>;
pub type SharedNodeContainer = std::sync::Arc<NodeContainer>;
pub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>;
#[derive(Clone)]
pub struct NodeContainer {
#[cfg(feature = "dealloc_nodes")]
pub node: *const TypeErasedNode<'static>,
#[cfg(not(feature = "dealloc_nodes"))]
pub node: TypeErasedRef<'static>,
}
impl Deref for NodeContainer {
type Target = TypeErasedNode<'static>;
#[cfg(feature = "dealloc_nodes")]
fn deref(&self) -> &Self::Target {
unsafe { &*(self.node) }
#[cfg(not(feature = "dealloc_nodes"))]
self.node
}
#[cfg(not(feature = "dealloc_nodes"))]
fn deref(&self) -> &Self::Target {
self.node
}
}
/// # Safety
/// Marks NodeContainer as Sync. This dissallows the use of threadlocal storage for nodes as this would invalidate references to them.
// TODO: implement this on a higher level wrapper to avoid missuse
#[cfg(feature = "dealloc_nodes")]
unsafe impl Send for NodeContainer {}
#[cfg(feature = "dealloc_nodes")]
unsafe impl Sync for NodeContainer {}
#[cfg(feature = "dealloc_nodes")]
impl Drop for NodeContainer {
fn drop(&mut self) {
unsafe { self.dealloc_unchecked() }
}
}
impl core::fmt::Debug for NodeContainer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodeContainer").finish()
}
}
impl NodeContainer {
pub fn new(node: TypeErasedBox<'static>) -> SharedNodeContainer {
let node = Box::leak(node);
Self { node }.into()
}
#[cfg(feature = "dealloc_nodes")]
unsafe fn dealloc_unchecked(&mut self) {
std::mem::drop(Box::from_raw(self.node as *mut TypeErasedNode));
}
}
use crate::Node;
use crate::WasmNotSend;
use dyn_any::StaticType;
use std::marker::PhantomData;
/// Boxes the input and downcasts the output.
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
#[derive(Clone)]
pub struct DowncastBothNode<I, O> {
node: SharedNodeContainer,
_i: PhantomData<I>,
_o: PhantomData<O>,
}
impl<'input, O: 'input + StaticType + WasmNotSend, I: 'input + StaticType + WasmNotSend> Node<'input, I> for DowncastBothNode<I, O> {
type Output = DynFuture<'input, O>;
#[inline]
fn eval(&'input self, input: I) -> Self::Output {
{
let node_name = self.node.node_name();
let input = Box::new(input);
let future = self.node.eval(input);
Box::pin(async move {
let out = dyn_any::downcast(future.await).unwrap_or_else(|e| panic!("DowncastBothNode Input {e} in: \n{node_name}"));
*out
})
}
}
fn reset(&self) {
self.node.reset();
}
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
self.node.serialize()
}
}
impl<I, O> DowncastBothNode<I, O> {
pub const fn new(node: SharedNodeContainer) -> Self {
Self {
node,
_i: core::marker::PhantomData,
_o: core::marker::PhantomData,
}
}
}
pub struct FutureWrapperNode<Node> {
node: Node,
}
impl<'i, T: 'i + WasmNotSend, N> Node<'i, T> for FutureWrapperNode<N>
where
N: Node<'i, T, Output: WasmNotSend> + WasmNotSend,
{
type Output = DynFuture<'i, N::Output>;
#[inline(always)]
fn eval(&'i self, input: T) -> Self::Output {
let result = self.node.eval(input);
Box::pin(async move { result })
}
#[inline(always)]
fn reset(&self) {
self.node.reset();
}
#[inline(always)]
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
self.node.serialize()
}
}
impl<N> FutureWrapperNode<N> {
pub const fn new(node: N) -> Self {
Self { node }
}
}
pub struct DynAnyNode<I, O, Node> {
node: Node,
_i: PhantomData<I>,
_o: PhantomData<O>,
}
impl<'input, _I: 'input + StaticType + WasmNotSend, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N>
where
N: Node<'input, _I, Output = DynFuture<'input, _O>>,
{
type Output = FutureAny<'input>;
#[inline]
fn eval(&'input self, input: Any<'input>) -> Self::Output {
let node_name = core::any::type_name::<N>();
let output = |input| {
let result = self.node.eval(input);
async move { Box::new(result.await) as Any<'input> }
};
match dyn_any::downcast(input) {
Ok(input) => Box::pin(output(*input)),
// If the input type of the node is `()` and we supply an invalid type, we can still call the
// node and just ignore the input and call it with the unit type instead.
Err(_) if core::any::TypeId::of::<_I::Static>() == core::any::TypeId::of::<()>() => {
assert_eq!(std::mem::size_of::<_I>(), 0);
// Rust can't know, that `_I` and `()` are the same size, so we have to use a `transmute_copy()` here
Box::pin(output(unsafe { std::mem::transmute_copy(&()) }))
}
// If the Node expects a footprint but we provide (). In this case construct the default Footprint and pass that
// This is pretty hacky pls fix
Err(_) if core::any::TypeId::of::<_I::Static>() == core::any::TypeId::of::<Footprint>() => {
assert_eq!(std::mem::size_of::<_I>(), std::mem::size_of::<Footprint>());
assert_eq!(std::mem::align_of::<_I>(), std::mem::align_of::<Footprint>());
// Rust can't know, that `_I` and `Footprint` are the same size, so we have to use a `transmute_copy()` here
Box::pin(output(unsafe { std::mem::transmute_copy(&Footprint::default()) }))
}
Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, node_name),
}
}
fn reset(&self) {
self.node.reset();
}
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
self.node.serialize()
}
}
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> DynAnyNode<_I, _O, N>
where
N: Node<'input, _I, Output = DynFuture<'input, _O>>,
{
pub const fn new(node: N) -> Self {
Self {
node,
_i: core::marker::PhantomData,
_o: core::marker::PhantomData,
}
}
}
pub struct PanicNode<I: WasmNotSend, O: WasmNotSend>(PhantomData<I>, PhantomData<O>);
impl<'i, I: 'i + WasmNotSend, O: 'i + WasmNotSend> Node<'i, I> for PanicNode<I, O> {
type Output = O;
fn eval(&'i self, _: I) -> Self::Output {
unimplemented!("This node should never be evaluated")
}
}
impl<I: WasmNotSend, O: WasmNotSend> PanicNode<I, O> {
pub const fn new() -> Self {
Self(PhantomData, PhantomData)
}
}
impl<I: WasmNotSend, O: WasmNotSend> Default for PanicNode<I, O> {
fn default() -> Self {
Self::new()
}
}
// TODO: Evaluate safety
unsafe impl<I: WasmNotSend, O: WasmNotSend> Sync for PanicNode<I, O> {}

View File

@@ -1,146 +0,0 @@
use crate::Node;
use core::ops::{Deref, DerefMut, Index, IndexMut};
pub struct SetNode<Storage> {
storage: Storage,
}
impl<'input, T: 'input, I: 'input, A: 'input + 'input, S0: 'input> Node<'input, (T, I)> for SetNode<S0>
where
A: DerefMut,
A::Target: IndexMut<I, Output = T>,
S0: for<'any_input> Node<'input, (), Output = A>,
{
type Output = ();
#[inline]
fn eval(&'input self, input: (T, I)) -> Self::Output {
let mut storage = self.storage.eval(());
let (value, index) = input;
*storage.deref_mut().index_mut(index).deref_mut() = value;
}
}
impl<'input, S0: 'input> SetNode<S0> {
pub const fn new(storage: S0) -> Self {
Self { storage }
}
}
pub struct ExtractXNode {}
#[node_macro::node_fn(ExtractXNode)]
fn extract_x_node(input: glam::UVec3) -> usize {
input.x as usize
}
pub struct SetOwnedNode<Storage> {
storage: core::cell::RefCell<Storage>,
}
impl<Storage> SetOwnedNode<Storage> {
pub fn new(storage: Storage) -> Self {
Self {
storage: core::cell::RefCell::new(storage),
}
}
}
impl<'input, I: 'input, T: 'input, Storage, A: ?Sized> Node<'input, (T, I)> for SetOwnedNode<Storage>
where
Storage: DerefMut<Target = A> + 'input,
A: IndexMut<I, Output = T> + 'input,
{
type Output = ();
fn eval(&'input self, input: (T, I)) -> Self::Output {
let (value, index) = input;
*self.storage.borrow_mut().index_mut(index) = value;
}
}
pub struct GetNode<Storage> {
storage: Storage,
}
impl<Storage> GetNode<Storage> {
pub fn new(storage: Storage) -> Self {
Self { storage }
}
}
impl<'input, I: 'input, T: 'input, Storage, SNode, A: ?Sized> Node<'input, I> for GetNode<SNode>
where
SNode: Node<'input, (), Output = Storage>,
Storage: Deref<Target = A> + 'input,
A: Index<I, Output = T> + 'input,
T: Clone,
{
type Output = T;
fn eval(&'input self, index: I) -> Self::Output {
let storage = self.storage.eval(());
storage.deref().index(index).clone()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::value::{CopiedNode, OnceCellNode};
use crate::Node;
#[test]
fn get_node_array() {
let storage = [1, 2, 3];
let node = GetNode::new(CopiedNode::new(&storage));
assert_eq!((&node as &dyn Node<'_, usize, Output = i32>).eval(1), 2);
}
#[test]
fn get_node_vec() {
let storage = vec![1, 2, 3];
let node = GetNode::new(CopiedNode::new(&storage));
assert_eq!(node.eval(1), 2);
}
#[test]
fn get_node_slice() {
let storage: &[i32] = &[1, 2, 3];
let node = GetNode::new(CopiedNode::new(storage));
let _ = &node as &dyn Node<'_, usize, Output = i32>;
assert_eq!(node.eval(1), 2);
}
#[test]
fn set_node_slice() {
let mut backing_storage = [1, 2, 3];
let storage: &mut [i32] = &mut backing_storage;
let storage_node = OnceCellNode::new(storage);
let node = SetNode::new(storage_node);
node.eval((4, 1));
assert_eq!(backing_storage, [1, 4, 3]);
}
#[test]
fn set_owned_node_array() {
let mut storage = [1, 2, 3];
let node = SetOwnedNode::new(&mut storage);
node.eval((4, 1));
assert_eq!(storage, [1, 4, 3]);
}
#[test]
fn set_owned_node_vec() {
let mut storage = vec![1, 2, 3];
let node = SetOwnedNode::new(&mut storage);
node.eval((4, 1));
assert_eq!(storage, [1, 4, 3]);
}
#[test]
fn set_owned_node_slice() {
let mut backing_storage = [1, 2, 3];
let storage: &mut [i32] = &mut backing_storage;
let node = SetOwnedNode::new(storage);
let node = &node as &dyn Node<'_, (i32, usize), Output = ()>;
node.eval((4, 1));
assert_eq!(backing_storage, [1, 4, 3]);
}
}

View File

@@ -1,6 +1,6 @@
use core::marker::PhantomData;
use crate::{Node, NodeMut};
use crate::Node;
/// This is how we can generically define composition of two nodes.
/// This is done generically as shown: <https://files.keavon.com/-/SurprisedGaseousAnhinga/capture.png>
@@ -42,18 +42,6 @@ where
second.eval(arg)
}
}
impl<'i, 'f: 'i, 's: 'i, Input: 'i, First, Second> NodeMut<'i, Input> for ComposeNode<First, Second, Input>
where
First: Node<'i, Input>,
Second: NodeMut<'i, <First as Node<'i, Input>>::Output> + 'i,
{
type MutOutput = <Second as NodeMut<'i, <First as Node<'i, Input>>::Output>>::MutOutput;
fn eval_mut(&'i mut self, input: Input) -> Self::MutOutput {
let arg = self.first.eval(input);
let second = &mut self.second;
second.eval_mut(arg)
}
}
impl<'i, First, Second, Input: 'i> ComposeNode<First, Second, Input> {
pub const fn new(first: First, second: Second) -> Self {
@@ -141,38 +129,6 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode<I, Root> {
}
}
pub struct ApplyNode<O, N> {
pub node: N,
_o: PhantomData<O>,
}
/*
#[node_macro::node_fn(ApplyNode)]
fn apply<In, N>(input: In, node: &'any_input N) -> ()
where
// TODO: try to allows this to return output other than ()
N: for<'any_input> Node<'any_input, In, Output = ()>,
{
node.eval(input)
}
*/
impl<'input, In: 'input, N: 'input, S0: 'input, O: 'input> Node<'input, In> for ApplyNode<O, S0>
where
N: Node<'input, In, Output = O>,
S0: Node<'input, (), Output = &'input N>,
{
type Output = <N as Node<'input, In>>::Output;
#[inline]
fn eval(&'input self, input: In) -> Self::Output {
let node = self.node.eval(());
node.eval(input)
}
}
impl<'input, S0: 'input, O: 'static> ApplyNode<O, S0> {
pub const fn new(node: S0) -> Self {
Self { node, _o: PhantomData }
}
}
#[cfg(test)]
mod test {
use super::*;
@@ -198,16 +154,4 @@ mod test {
assert_eq!(compose.eval(()), &5);
}
#[test]
#[allow(clippy::unit_cmp)]
fn test_apply() {
let mut array = [1, 2, 3];
let slice = &mut array;
let set_node = crate::storage::SetOwnedNode::new(slice);
let apply = ApplyNode::new(ValueNode::new(set_node));
assert_eq!(apply.eval((1, 2)), ());
}
}

View File

@@ -1,21 +1,5 @@
mod font_cache;
mod to_path;
use crate::application_io::EditorApi;
pub use font_cache::*;
use node_macro::node_fn;
pub use to_path::*;
use crate::Node;
pub struct TextGeneratorNode<Text, FontName, Size> {
text: Text,
font_name: FontName,
font_size: Size,
}
#[node_fn(TextGeneratorNode)]
fn generate_text<'a: 'input, T: 'a>(editor: &'a EditorApi<T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None), false)
}

View File

@@ -1,4 +1,4 @@
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use std::collections::HashMap;

View File

@@ -1,4 +1,3 @@
use dyn_any::StaticType;
use glam::DAffine2;
use glam::DVec2;
@@ -8,9 +7,9 @@ use crate::raster::ImageFrame;
use crate::raster::Pixel;
use crate::vector::VectorData;
use crate::Artboard;
use crate::ArtboardGroup;
use crate::GraphicElement;
use crate::GraphicGroup;
use crate::Node;
pub trait Transform {
fn transform(&self) -> DAffine2;
@@ -176,15 +175,15 @@ impl Footprint {
}
}
#[derive(Debug, Clone, Copy)]
pub struct CullNode<VectorData> {
pub(crate) vector_data: VectorData,
impl From<()> for Footprint {
fn from(_: ()) -> Self {
Footprint::default()
}
}
#[node_macro::node_fn(CullNode)]
fn cull_vector_data<T>(footprint: Footprint, vector_data: T) -> T {
// TODO: Implement culling
vector_data
#[node_macro::node(category("Debug"))]
fn cull<T>(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame<crate::Color>, ArtboardGroup)] data: T) -> T {
data
}
impl core::hash::Hash for Footprint {
@@ -205,20 +204,30 @@ impl TransformMut for Footprint {
}
}
#[derive(Debug, Clone, Copy)]
pub struct TransformNode<TransformTarget, Translation, Rotation, Scale, Shear, Pivot> {
pub(crate) transform_target: TransformTarget,
pub(crate) translate: Translation,
pub(crate) rotate: Rotation,
pub(crate) scale: Scale,
pub(crate) shear: Shear,
pub(crate) _pivot: Pivot,
pub trait ApplyTransform {
fn apply_transform(&mut self, modification: &DAffine2);
}
impl<T: TransformMut> ApplyTransform for T {
fn apply_transform(&mut self, &modification: &DAffine2) {
*self.transform_mut() = self.transform() * modification
}
}
impl ApplyTransform for () {
fn apply_transform(&mut self, &_modification: &DAffine2) {}
}
#[node_macro::node_fn(TransformNode)]
pub(crate) async fn transform_vector_data<T: TransformMut>(
mut footprint: Footprint,
transform_target: impl Node<Footprint, Output = T>,
#[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,
#[implementations(
(Footprint, VectorData),
(Footprint, GraphicGroup),
(Footprint, ImageFrame<crate::Color>),
((), VectorData),
((), GraphicGroup),
((), ImageFrame<crate::Color>),
)]
transform_target: impl Node<I, Output = T>,
translate: DVec2,
rotate: f64,
scale: DVec2,
@@ -226,24 +235,25 @@ pub(crate) async fn transform_vector_data<T: TransformMut>(
_pivot: DVec2,
) -> T {
let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
let footprint = input.clone().into();
if !footprint.ignore_modifications {
*footprint.transform_mut() = footprint.transform() * modification;
input.apply_transform(&modification);
}
let mut data = self.transform_target.eval(footprint).await;
let mut data = transform_target.eval(input).await;
let data_transform = data.transform_mut();
*data_transform = modification * (*data_transform);
data
}
#[derive(Debug, Clone, Copy)]
pub struct SetTransformNode<TransformInput> {
pub(crate) transform: TransformInput,
}
#[node_macro::node_fn(SetTransformNode)]
pub(crate) fn set_transform<Data: TransformMut, TransformInput: Transform>(mut data: Data, transform: TransformInput) -> Data {
#[node_macro::node(category("Debug"))]
fn replace_transform<Data: TransformMut, TransformInput: Transform>(
_: (),
#[implementations(VectorData, ImageFrame<crate::Color>, GraphicGroup)] mut data: Data,
#[implementations(DAffine2)] transform: TransformInput,
) -> Data {
let data_transform = data.transform_mut();
*data_transform = transform.transform();
data

View File

@@ -6,7 +6,69 @@ use dyn_any::StaticType;
#[cfg(feature = "std")]
pub use std::borrow::Cow;
#[derive(Clone, PartialEq, Eq, Hash)]
#[macro_export]
macro_rules! concrete {
($type:ty) => {
$crate::Type::Concrete($crate::TypeDescriptor {
id: Some(core::any::TypeId::of::<$type>()),
name: $crate::Cow::Borrowed(core::any::type_name::<$type>()),
alias: None,
size: core::mem::size_of::<$type>(),
align: core::mem::align_of::<$type>(),
})
};
($type:ty, $name:ty) => {
$crate::Type::Concrete($crate::TypeDescriptor {
id: Some(core::any::TypeId::of::<$type>()),
name: $crate::Cow::Borrowed(core::any::type_name::<$type>()),
alias: Some($crate::Cow::Borrowed(stringify!($name))),
size: core::mem::size_of::<$type>(),
align: core::mem::align_of::<$type>(),
})
};
}
#[macro_export]
macro_rules! concrete_with_name {
($type:ty, $name:expr) => {
$crate::Type::Concrete($crate::TypeDescriptor {
id: Some(core::any::TypeId::of::<$type>()),
name: $crate::Cow::Borrowed($name),
alias: None,
size: core::mem::size_of::<$type>(),
align: core::mem::align_of::<$type>(),
})
};
}
#[macro_export]
macro_rules! generic {
($type:ty) => {{
$crate::Type::Generic($crate::Cow::Borrowed(stringify!($type)))
}};
}
#[macro_export]
macro_rules! future {
($type:ty) => {{
$crate::Type::Future(Box::new(concrete!($type)))
}};
}
#[macro_export]
macro_rules! fn_type {
($type:ty) => {
$crate::Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type)))
};
($in_type:ty, $type:ty, alias: $outname:ty) => {
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type, $outname)))
};
($in_type:ty, $type:ty) => {
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type)))
};
}
#[derive(Clone, PartialEq, Eq, Hash, Default)]
pub struct NodeIOTypes {
pub input: Type,
pub output: Type,
@@ -14,10 +76,32 @@ pub struct NodeIOTypes {
}
impl NodeIOTypes {
pub fn new(input: Type, output: Type, parameters: Vec<Type>) -> Self {
pub const fn new(input: Type, output: Type, parameters: Vec<Type>) -> Self {
Self { input, output, parameters }
}
pub const fn empty() -> Self {
let tds1 = TypeDescriptor {
id: None,
name: Cow::Borrowed("()"),
alias: None,
size: 0,
align: 0,
};
let tds2 = TypeDescriptor {
id: None,
name: Cow::Borrowed("()"),
alias: None,
size: 0,
align: 0,
};
Self {
input: Type::Concrete(tds1),
output: Type::Concrete(tds2),
parameters: Vec::new(),
}
}
pub fn ty(&self) -> Type {
Type::Fn(Box::new(self.input.clone()), Box::new(self.output.clone()))
}
@@ -33,52 +117,16 @@ impl core::fmt::Debug for NodeIOTypes {
}
}
#[macro_export]
macro_rules! concrete {
($type:ty) => {
$crate::Type::Concrete($crate::TypeDescriptor {
id: Some(core::any::TypeId::of::<$type>()),
name: $crate::Cow::Borrowed(core::any::type_name::<$type>()),
size: core::mem::size_of::<$type>(),
align: core::mem::align_of::<$type>(),
})
};
}
#[macro_export]
macro_rules! concrete_with_name {
($type:ty, $name:expr) => {
$crate::Type::Concrete($crate::TypeDescriptor {
id: Some(core::any::TypeId::of::<$type>()),
name: $crate::Cow::Borrowed($name),
size: core::mem::size_of::<$type>(),
align: core::mem::align_of::<$type>(),
})
};
}
#[macro_export]
macro_rules! generic {
($type:ty) => {{
$crate::Type::Generic($crate::Cow::Borrowed(stringify!($type)))
}};
}
#[macro_export]
macro_rules! fn_type {
($type:ty) => {
$crate::Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type)))
};
($in_type:ty, $type:ty) => {
$crate::Type::Fn(Box::new(concrete!(($in_type))), Box::new(concrete!($type)))
};
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProtoNodeIdentifier {
pub name: Cow<'static, str>,
}
impl From<String> for ProtoNodeIdentifier {
fn from(value: String) -> Self {
Self { name: Cow::Owned(value) }
}
}
fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> {
use serde::Deserialize;
@@ -101,6 +149,8 @@ pub struct TypeDescriptor {
#[serde(deserialize_with = "migrate_type_descriptor_names")]
pub name: Cow<'static, str>,
#[serde(default)]
pub alias: Option<Cow<'static, str>>,
#[serde(default)]
pub size: usize,
#[serde(default)]
pub align: usize,
@@ -199,6 +249,7 @@ impl Type {
Self::Concrete(TypeDescriptor {
id: Some(TypeId::of::<T::Static>()),
name: Cow::Borrowed(core::any::type_name::<T::Static>()),
alias: None,
size: core::mem::size_of::<T>(),
align: core::mem::align_of::<T>(),
})

View File

@@ -1,7 +1,5 @@
pub use uuid_generation::*;
use dyn_any::DynAny;
use dyn_any::StaticType;
pub use uuid_generation::*;
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct Uuid(

View File

@@ -2,7 +2,7 @@ use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::BlendMode;
use crate::Color;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use glam::DVec2;
use std::hash::{Hash, Hasher};

View File

@@ -1,52 +1,9 @@
use super::HandleId;
use crate::vector::{PointId, VectorData};
use crate::Node;
use bezier_rs::Subpath;
use glam::DVec2;
#[derive(Debug, Clone, Copy)]
pub struct CircleGenerator<Radius> {
radius: Radius,
}
#[node_macro::node_fn(CircleGenerator)]
fn circle_generator(_input: (), radius: f64) -> VectorData {
let radius: f64 = radius;
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))
}
#[derive(Debug, Clone, Copy)]
pub struct EllipseGenerator<RadiusX, RadiusY> {
radius_x: RadiusX,
radius_y: RadiusY,
}
#[node_macro::node_fn(EllipseGenerator)]
fn ellipse_generator(_input: (), radius_x: f64, radius_y: f64) -> VectorData {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius;
let corner2 = radius;
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
ellipse
.colinear_manipulators
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
}
ellipse
}
#[derive(Debug, Clone, Copy)]
pub struct RectangleGenerator<SizeX, SizeY, IsIndividual, CornerRadius, Clamped> {
size_x: SizeX,
size_y: SizeY,
is_individual: IsIndividual,
corner_radius: CornerRadius,
clamped: Clamped,
}
trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData;
}
@@ -77,59 +34,79 @@ impl CornerRadius for [f64; 4] {
}
}
#[node_macro::node_fn(RectangleGenerator)]
fn square_generator<T: CornerRadius>(_input: (), size_x: f64, size_y: f64, is_individual: bool, corner_radius: T, clamped: bool) -> VectorData {
corner_radius.generate(DVec2::new(size_x, size_y), clamped)
#[node_macro::node(category("Vector: Shape"))]
fn circle(_: (), _primary: (), #[default(50.)] radius: f64) -> VectorData {
let radius: f64 = radius;
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))
}
#[derive(Debug, Clone, Copy)]
pub struct RegularPolygonGenerator<Points, Radius> {
points: Points,
radius: Radius,
#[node_macro::node(category("Vector: Shape"))]
fn ellipse(_: (), _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;
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
ellipse
.colinear_manipulators
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
}
ellipse
}
#[node_macro::node_fn(RegularPolygonGenerator)]
fn regular_polygon_generator(_input: (), points: u32, radius: f64) -> VectorData {
let points = points.into();
#[node_macro::node(category("Vector: Shape"))]
fn rectangle<T: CornerRadius>(
_: (),
_primary: (),
#[default(100)] width: f64,
#[default(100)] height: f64,
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
#[implementations(f64, [f64; 4])] corner_radius: T,
#[default(true)] clamped: bool,
) -> VectorData {
corner_radius.generate(DVec2::new(width, height), clamped)
}
#[node_macro::node(category("Vector: Shape"))]
fn regular_polygon(
_: (),
_primary: (),
#[default(6)]
#[min(3.)]
sides: u32,
#[default(50)] radius: f64,
) -> VectorData {
let points = sides.into();
let radius: f64 = radius * 2.;
super::VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))
}
#[derive(Debug, Clone, Copy)]
pub struct StarGenerator<Points, Radius, InnerRadius> {
points: Points,
radius: Radius,
inner_radius: InnerRadius,
}
#[node_macro::node_fn(StarGenerator)]
fn star_generator(_input: (), points: u32, radius: f64, inner_radius: f64) -> VectorData {
let points = points.into();
#[node_macro::node(category("Vector: Shape"))]
fn star(
_: (),
_primary: (),
#[default(5)]
#[min(2.)]
sides: u32,
#[default(50)] radius: f64,
#[default(25)] inner_radius: f64,
) -> VectorData {
let points = sides.into();
let diameter: f64 = radius * 2.;
let inner_diameter = inner_radius * 2.;
super::VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))
}
#[derive(Debug, Clone, Copy)]
pub struct LineGenerator<Pos1, Pos2> {
pos_1: Pos1,
pos_2: Pos2,
#[node_macro::node(category("Vector: Shape"))]
fn line(_: (), _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorData {
super::VectorData::from_subpath(Subpath::new_line(start, end))
}
#[node_macro::node_fn(LineGenerator)]
fn line_generator(_input: (), pos_1: DVec2, pos_2: DVec2) -> VectorData {
super::VectorData::from_subpath(Subpath::new_line(pos_1, pos_2))
}
#[derive(Debug, Clone, Copy)]
pub struct SplineGenerator<Positions> {
positions: Positions,
}
#[node_macro::node_fn(SplineGenerator)]
fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(positions));
#[node_macro::node(category("Vector: Shape"))]
fn spline(_: (), _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])]);
}
@@ -137,13 +114,9 @@ fn spline_generator(_input: (), positions: 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
#[derive(Debug, Clone)]
pub struct PathGenerator<ColinearManipulators> {
colinear_manipulators: ColinearManipulators,
}
#[node_macro::node_fn(PathGenerator)]
fn generate_path(path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
#[node_macro::node(category(""))]
fn path(_: (), 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()
@@ -151,27 +124,3 @@ fn generate_path(path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<Po
.collect();
vector_data
}
// #[derive(Debug, Clone, Copy)]
// pub struct BlitSubpath<P> {
// path_data: P,
// }
// #[node_macro::node_fn(BlitSubpath)]
// fn blit_subpath(base_image: Image, path_data: VectorData) -> Image {
// // TODO: Get forma to compile
// use forma::prelude::*;
// let composition = Composition::new();
// let mut renderer = cpu::Renderer::new();
// let mut path_builder = PathBuilder::new();
// for path_segment in path_data.bezier_iter() {
// let points = path_segment.internal.get_points().collect::<Vec<_>>();
// match points.len() {
// 2 => path_builder.line_to(points[1].into()),
// 3 => path_builder.quad_to(points[1].into(), points[2].into()),
// 4 => path_builder.cubic_to(points[1].into(), points[2].into(), points[3].into()),
// }
// }
// base_image
// }

View File

@@ -1,4 +1,4 @@
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
/// Represents different ways of calculating the centroid.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]

View File

@@ -4,7 +4,7 @@ use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT};
use crate::renderer::format_transform_matrix;
use crate::Color;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use std::fmt::{self, Display, Write};

View File

@@ -7,7 +7,7 @@ use super::style::{PathStyle, Stroke};
use crate::{AlphaBlending, Color};
use bezier_rs::ManipulatorGroup;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use core::borrow::Borrow;
use glam::{DAffine2, DVec2};

View File

@@ -1,6 +1,6 @@
use super::HandleId;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use std::collections::HashMap;

View File

@@ -1,9 +1,8 @@
use super::*;
use crate::uuid::generate_uuid;
use crate::Node;
use bezier_rs::BezierHandles;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use core::hash::BuildHasher;
use std::collections::{HashMap, HashSet};
@@ -422,14 +421,15 @@ impl core::hash::Hash for VectorModification {
}
}
use crate::transform::Footprint;
/// A node that applies a procedural modification to some [`VectorData`].
#[derive(Debug, Clone, Copy)]
pub struct PathModify<VectorModificationNode> {
modification: VectorModificationNode,
}
#[node_macro::node_fn(PathModify)]
fn path_modify(mut vector_data: VectorData, modification: VectorModification) -> 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>,
modification: VectorModification,
) -> VectorData {
let mut vector_data = vector_data.eval(input).await;
modification.apply(&mut vector_data);
vector_data
}

View File

@@ -1,153 +1,101 @@
use super::misc::CentroidType;
use super::style::{Fill, GradientStops, Stroke};
use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentId, StrokeId, VectorData};
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, Transform, TransformMut};
use crate::{Color, GraphicGroup, Node};
use crate::{Color, GraphicGroup};
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
use glam::{DAffine2, DVec2};
use rand::{Rng, SeedableRng};
#[derive(Debug, Clone, Copy)]
pub struct AssignColorsNode<Fill, Stroke, Gradient, Reverse, Randomize, Seed, RepeatEvery> {
fill: Fill,
stroke: Stroke,
gradient: Gradient,
reverse: Reverse,
randomize: Randomize,
seed: Seed,
repeat_every: RepeatEvery,
trait VectorIterMut {
fn vector_iter_mut(&mut self) -> impl ExactSizeIterator<Item = &mut VectorData>;
}
#[node_macro::node_fn(AssignColorsNode)]
fn assign_colors_node(group: GraphicGroup, fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, seed: u32, repeat_every: u32) -> GraphicGroup {
let mut group = group;
let vector_data_list: Vec<_> = group.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).collect();
let list = (vector_data_list.len(), vector_data_list.into_iter());
assign_colors(
list,
AlignColorsOptions {
fill,
stroke,
gradient,
reverse,
randomize,
seed,
repeat_every,
},
);
group
impl VectorIterMut for GraphicGroup {
fn vector_iter_mut(&mut self) -> impl ExactSizeIterator<Item = &mut VectorData> {
self.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).collect::<Vec<_>>().into_iter()
}
}
#[node_macro::node_impl(AssignColorsNode)]
fn assign_colors_node(vector_data: VectorData, fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, seed: u32, repeat_every: u32) -> GraphicGroup {
let mut vector_data_list: Vec<_> = vector_data
.region_bezier_paths()
.map(|(_, subpath)| {
let mut vector = VectorData::from_subpath(subpath);
vector.style = vector_data.style.clone();
crate::GraphicElement::VectorData(Box::new(vector))
})
.collect();
let list = (vector_data_list.len(), vector_data_list.iter_mut().map(|element| element.as_vector_data_mut().unwrap()));
assign_colors(
list,
AlignColorsOptions {
fill,
stroke,
gradient,
reverse,
randomize,
seed,
repeat_every,
},
);
let mut group = GraphicGroup::new(vector_data_list);
group.transform = vector_data.transform;
group.alpha_blending = vector_data.alpha_blending;
group
impl VectorIterMut for VectorData {
fn vector_iter_mut(&mut self) -> impl ExactSizeIterator<Item = &mut VectorData> {
std::iter::once(self)
}
}
struct AlignColorsOptions {
fill: bool,
#[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>,
#[default(true)] fill: bool,
stroke: bool,
gradient: GradientStops,
reverse: bool,
randomize: bool,
seed: u32,
seed: SeedValue,
repeat_every: u32,
}
) -> T {
let mut input = vector_group.eval(footprint).await;
let vector_data = input.vector_iter_mut();
let length = vector_data.len();
let gradient = if reverse { gradient.reversed() } else { gradient };
fn assign_colors<'a>((length, vector_data): (usize, impl Iterator<Item = &'a mut VectorData>), options: AlignColorsOptions) {
let gradient = if options.reverse { options.gradient.reversed() } else { options.gradient };
let mut rng = rand::rngs::StdRng::seed_from_u64(options.seed as u64);
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
for (i, vector_data) in vector_data.enumerate() {
let factor = match options.randomize {
let factor = match randomize {
true => rng.gen::<f64>(),
false => match options.repeat_every {
false => match repeat_every {
0 => i as f64 / (length - 1) as f64,
1 => 0.,
_ => i as f64 % options.repeat_every as f64 / (options.repeat_every - 1) as f64,
_ => i as f64 % repeat_every as f64 / (repeat_every - 1) as f64,
},
};
let color = gradient.evalute(factor);
if options.fill {
if fill {
vector_data.style.set_fill(Fill::Solid(color));
}
if options.stroke {
if stroke {
if let Some(stroke) = vector_data.style.stroke().and_then(|stroke| stroke.with_color(&Some(color))) {
vector_data.style.set_stroke(stroke);
}
}
}
input
}
#[derive(Debug, Clone, Copy)]
pub struct SetFillNode<Fill> {
fill: Fill,
}
#[node_macro::node_fn(SetFillNode)]
fn set_vector_data_fill<T: Into<Fill>>(mut vector_data: VectorData, fill: T) -> VectorData {
#[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
_backup_color: Option<Color>,
_backup_gradient: Gradient,
) -> VectorData {
let mut vector_data = vector_data.eval(footprint).await;
vector_data.style.set_fill(fill.into());
vector_data
}
#[derive(Debug, Clone, Copy)]
pub struct SetStrokeNode<Color, Weight, DashLengths, DashOffset, LineCap, LineJoin, MiterLimit> {
color: Color,
weight: Weight,
dash_lengths: DashLengths,
dash_offset: DashOffset,
line_cap: LineCap,
line_join: LineJoin,
miter_limit: MiterLimit,
}
#[node_macro::node_fn(SetStrokeNode)]
fn set_vector_data_stroke(
mut vector_data: VectorData,
color: Option<Color>,
weight: f64,
#[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
#[default(5.)] weight: f64,
dash_lengths: Vec<f64>,
dash_offset: f64,
line_cap: super::style::LineCap,
line_join: super::style::LineJoin,
miter_limit: f64,
line_cap: crate::vector::style::LineCap,
line_join: crate::vector::style::LineJoin,
#[default(4.)] miter_limit: f64,
) -> VectorData {
let mut vector_data = vector_data.eval(footprint).await;
vector_data.style.set_stroke(Stroke {
color,
weight,
@@ -161,28 +109,22 @@ fn set_vector_data_stroke(
vector_data
}
#[derive(Debug, Clone, Copy)]
pub struct RepeatNode<Direction, Angle, Instances> {
direction: Direction,
angle: Angle,
instances: Instances,
}
#[node_macro::node_fn(RepeatNode)]
fn repeat_vector_data(vector_data: VectorData, direction: DVec2, angle: f64, instances: u32) -> VectorData {
#[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 {
let instance = instance.eval(footprint).await;
let angle = angle.to_radians();
let instances = instances.max(1);
let total = (instances - 1) as f64;
if instances == 1 {
return vector_data;
return instance;
}
// Repeat the vector data
let mut result = VectorData::empty();
let Some(bounding_box) = vector_data.bounding_box_with_transform(vector_data.transform) else {
return vector_data;
let Some(bounding_box) = instance.bounding_box_with_transform(instance.transform) else {
return instance;
};
let center = (bounding_box[0] + bounding_box[1]) / 2.;
@@ -192,31 +134,31 @@ fn repeat_vector_data(vector_data: VectorData, direction: DVec2, angle: f64, ins
let transform = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
result.concat(&vector_data, transform);
result.concat(&instance, transform);
}
result
}
#[derive(Debug, Clone, Copy)]
pub struct CircularRepeatNode<AngleOffset, Radius, Instances> {
angle_offset: AngleOffset,
radius: Radius,
instances: Instances,
}
#[node_macro::node_fn(CircularRepeatNode)]
fn circular_repeat_vector_data(vector_data: VectorData, angle_offset: f64, radius: f64, instances: u32) -> VectorData {
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn circular_repeat(
footprint: Footprint,
instance: impl Node<Footprint, Output = VectorData>,
angle_offset: Angle,
#[default(5)] radius: Length,
#[default(5)] instances: IntegerCount,
) -> VectorData {
let instance = instance.eval(footprint).await;
let instances = instances.max(1);
if instances == 1 {
return vector_data;
return instance;
}
let mut result = VectorData::empty();
let Some(bounding_box) = vector_data.bounding_box_with_transform(vector_data.transform) else {
return vector_data;
let Some(bounding_box) = instance.bounding_box_with_transform(instance.transform) else {
return instance;
};
let center = (bounding_box[0] + bounding_box[1]) / 2.;
@@ -226,27 +168,27 @@ fn circular_repeat_vector_data(vector_data: VectorData, angle_offset: f64, radiu
let angle = (std::f64::consts::TAU / instances as f64) * i as f64 + angle_offset.to_radians();
let rotation = DAffine2::from_angle(angle);
let transform = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
result.concat(&vector_data, transform);
result.concat(&instance, transform);
}
result
}
#[derive(Debug, Clone, Copy)]
pub struct BoundingBoxNode;
#[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>,
) -> VectorData {
let vector_data = vector_data.eval(footprint).await;
#[node_macro::node_fn(BoundingBoxNode)]
fn generate_bounding_box(vector_data: VectorData) -> VectorData {
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]))
}
#[derive(Debug, Clone, Copy)]
pub struct SolidifyStrokeNode;
#[node_macro::node_fn(SolidifyStrokeNode)]
fn solidify_stroke(vector_data: VectorData) -> VectorData {
#[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.
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();
@@ -306,33 +248,22 @@ impl ConcatElement for GraphicGroup {
}
}
#[derive(Debug, Clone, Copy)]
pub struct CopyToPoints<Points, Instance, RandomScaleMin, RandomScaleMax, RandomScaleBias, RandomScaleSeed, RandomRotation, RandomRotationSeed> {
points: Points,
instance: Instance,
random_scale_min: RandomScaleMin,
random_scale_max: RandomScaleMax,
random_scale_bias: RandomScaleBias,
random_scale_seed: RandomScaleSeed,
random_rotation: RandomRotation,
random_rotation_seed: RandomRotationSeed,
}
#[allow(clippy::too_many_arguments)]
#[node_macro::node_fn(CopyToPoints)]
#[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>,
#[expose]
#[implementations((Footprint, VectorData), (Footprint, GraphicGroup))]
instance: impl Node<Footprint, Output = I>,
random_scale_min: f64,
random_scale_max: f64,
#[default(1)] random_scale_min: f64,
#[default(1)] random_scale_max: f64,
random_scale_bias: f64,
random_scale_seed: u32,
random_rotation: f64,
random_rotation_seed: u32,
random_scale_seed: SeedValue,
random_rotation: Angle,
random_rotation_seed: SeedValue,
) -> I {
let points = self.points.eval(footprint).await;
let instance = self.instance.eval(footprint).await;
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();
@@ -340,8 +271,8 @@ async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + Tr
let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY).unwrap_or_default();
let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]);
let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed as u64);
let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed as u64);
let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into());
let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into());
let do_scale = random_scale_difference.abs() > 1e-6;
let do_rotation = random_rotation.abs() > 1e-6;
@@ -379,28 +310,18 @@ async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + Tr
result
}
#[derive(Debug, Clone, Copy)]
pub struct SamplePoints<VectorData, Spacing, StartOffset, StopOffset, AdaptiveSpacing, LengthsOfSegmentsOfSubpaths> {
vector_data: VectorData,
spacing: Spacing,
start_offset: StartOffset,
stop_offset: StopOffset,
adaptive_spacing: AdaptiveSpacing,
lengths_of_segments_of_subpaths: LengthsOfSegmentsOfSubpaths,
}
#[node_macro::node_fn(SamplePoints)]
#[node_macro::node(category(""))]
async fn sample_points(
footprint: Footprint,
mut vector_data: impl Node<Footprint, Output = VectorData>,
vector_data: impl Node<Footprint, Output = VectorData>,
spacing: f64,
start_offset: f64,
stop_offset: f64,
adaptive_spacing: bool,
lengths_of_segments_of_subpaths: impl Node<Footprint, Output = Vec<f64>>,
) -> VectorData {
let vector_data = self.vector_data.eval(footprint).await;
let lengths_of_segments_of_subpaths = self.lengths_of_segments_of_subpaths.eval(footprint).await;
let vector_data = vector_data.eval(footprint).await;
let lengths_of_segments_of_subpaths = lengths_of_segments_of_subpaths.eval(footprint).await;
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
@@ -463,16 +384,24 @@ async fn sample_points(
result
}
#[derive(Debug, Clone, Copy)]
pub struct PoissonDiskPoints<SeparationDiskDiameter, Seed> {
separation_disk_diameter: SeparationDiskDiameter,
seed: Seed,
}
#[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>,
#[default(10.)]
#[min(0.01)]
separation_disk_diameter: f64,
seed: SeedValue,
) -> VectorData {
let vector_data = vector_data.eval(footprint).await;
#[node_macro::node_fn(PoissonDiskPoints)]
fn poisson_disk_points(vector_data: VectorData, separation_disk_diameter: f64, seed: u32) -> VectorData {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed as u64);
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let mut result = VectorData::empty();
if separation_disk_diameter <= 0.01 {
return result;
}
for mut subpath in vector_data.stroke_bezier_paths() {
if subpath.manipulator_groups().len() < 3 {
continue;
@@ -488,22 +417,18 @@ fn poisson_disk_points(vector_data: VectorData, separation_disk_diameter: f64, s
result
}
#[derive(Debug, Clone, Copy)]
pub struct LengthsOfSegmentsOfSubpaths;
#[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> {
let vector_data = vector_data.eval(footprint).await;
#[node_macro::node_fn(LengthsOfSegmentsOfSubpaths)]
fn lengths_of_segments_of_subpaths(vector_data: VectorData) -> Vec<f64> {
vector_data
.segment_bezier_iter()
.map(|(_id, bezier, _, _)| bezier.apply_transformation(|point| vector_data.transform.transform_point2(point)).length(None))
.collect()
}
#[derive(Debug, Clone, Copy)]
pub struct SplinesFromPointsNode;
#[node_macro::node_fn(SplinesFromPointsNode)]
fn splines_from_points(mut vector_data: VectorData) -> VectorData {
#[node_macro::node(name("Splines from Points"), category(""), path(graphene_core::vector))]
fn splines_from_points(_: (), mut vector_data: VectorData) -> VectorData {
let points = &vector_data.point_domain;
vector_data.segment_domain.clear();
@@ -527,17 +452,18 @@ fn splines_from_points(mut vector_data: VectorData) -> VectorData {
vector_data
}
pub struct MorphNode<Source, Target, StartIndex, Time> {
source: Source,
target: Target,
start_index: StartIndex,
time: Time,
}
#[node_macro::node_fn(MorphNode)]
async fn morph(footprint: Footprint, source: impl Node<Footprint, Output = VectorData>, target: impl Node<Footprint, Output = VectorData>, start_index: u32, time: f64) -> VectorData {
let source = self.source.eval(footprint).await;
let target = self.target.eval(footprint).await;
#[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>,
#[range((0., 1.))]
#[default(0.5)]
time: Fraction,
#[min(0.)] start_index: IntegerCount,
) -> VectorData {
let source = source.eval(footprint).await;
let target = target.eval(footprint).await;
let mut result = VectorData::empty();
// Lerp styles
@@ -617,14 +543,9 @@ async fn morph(footprint: Footprint, source: impl Node<Footprint, Output = Vecto
result
}
#[derive(Debug, Clone, Copy)]
pub struct AreaNode<VectorData> {
vector_data: VectorData,
}
#[node_macro::node_fn(AreaNode)]
async fn area_node(empty: (), vector_data: impl Node<Footprint, Output = VectorData>) -> f64 {
let vector_data = self.vector_data.eval(Footprint::default()).await;
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorData>) -> f64 {
let vector_data = vector_data.eval(Footprint::default()).await;
let mut area = 0.;
let scale = vector_data.transform.decompose_scale();
@@ -634,15 +555,9 @@ async fn area_node(empty: (), vector_data: impl Node<Footprint, Output = VectorD
area * scale[0] * scale[1]
}
#[derive(Debug, Clone, Copy)]
pub struct CentroidNode<VectorData, CentroidType> {
vector_data: VectorData,
centroid_type: CentroidType,
}
#[node_macro::node_fn(CentroidNode)]
async fn centroid_node(empty: (), vector_data: impl Node<Footprint, Output = VectorData>, centroid_type: CentroidType) -> DVec2 {
let vector_data = self.vector_data.eval(Footprint::default()).await;
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorData>, centroid_type: CentroidType) -> DVec2 {
let vector_data = vector_data.eval(Footprint::default()).await;
if centroid_type == CentroidType::Area {
let mut area = 0.;
@@ -689,65 +604,50 @@ async fn centroid_node(empty: (), vector_data: impl Node<Footprint, Output = Vec
#[cfg(test)]
mod test {
use super::*;
use crate::transform::CullNode;
use crate::value::ClonedNode;
use crate::Node;
use bezier_rs::Bezier;
use std::pin::Pin;
#[derive(Clone)]
pub struct FutureWrapperNode<Node: Clone>(Node);
pub struct FutureWrapperNode<T: Clone>(T);
impl<'i, T: 'i, N: Node<'i, T> + Clone> Node<'i, T> for FutureWrapperNode<N>
where
N: Node<'i, T, Output: Send>,
{
type Output = Pin<Box<dyn core::future::Future<Output = N::Output> + 'i + Send>>;
fn eval(&'i self, input: T) -> Self::Output {
let result = self.0.eval(input);
Box::pin(async move { result })
impl<'i, T: 'i + Clone + Send> Node<'i, Footprint> for FutureWrapperNode<T> {
type Output = Pin<Box<dyn core::future::Future<Output = T> + 'i + Send>>;
fn eval(&'i self, _input: Footprint) -> Self::Output {
let value = self.0.clone();
Box::pin(async move { value })
}
}
#[test]
fn repeat() {
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorData> {
FutureWrapperNode(VectorData::from_subpath(data))
}
#[tokio::test]
async fn repeat() {
let direction = DVec2::X * 1.5;
let instances = 3;
let repeated = RepeatNode {
direction: ClonedNode::new(direction),
angle: ClonedNode::new(0.),
instances: ClonedNode::new(instances),
}
.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)));
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
assert_eq!(repeated.region_bezier_paths().count(), 3);
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
}
}
#[test]
fn repeat_transform_position() {
#[tokio::test]
async fn repeat_transform_position() {
let direction = DVec2::new(12., 10.);
let instances = 8;
let repeated = RepeatNode {
direction: ClonedNode::new(direction),
angle: ClonedNode::new(0.),
instances: ClonedNode::new(instances),
}
.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)));
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
assert_eq!(repeated.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
}
}
#[test]
fn circle_repeat() {
let repeated = CircularRepeatNode {
angle_offset: ClonedNode::new(45.),
radius: ClonedNode::new(4.),
instances: ClonedNode::new(8),
}
.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)));
#[tokio::test]
async fn circle_repeat() {
let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
assert_eq!(repeated.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
let expected_angle = (index as f64 + 1.) * 45.;
@@ -756,9 +656,12 @@ mod test {
assert!((actual_angle - expected_angle).abs() % 360. < 1e-5);
}
}
#[test]
fn bounding_box() {
let bounding_box = BoundingBoxNode.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)));
#[tokio::test]
async fn bounding_box() {
let bounding_box = BoundingBoxNode {
vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)),
};
let bounding_box = bounding_box.eval(Footprint::default()).await;
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);
@@ -766,7 +669,11 @@ mod test {
// test a VectorData with non-zero rotation
let mut square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE));
square.transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4);
let bounding_box = BoundingBoxNode.eval(square);
let bounding_box = BoundingBoxNode {
vector_data: FutureWrapperNode(square),
}
.eval(Footprint::default())
.await;
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
let sqrt2 = core::f64::consts::SQRT_2;
@@ -775,20 +682,10 @@ mod test {
}
#[tokio::test]
async fn copy_to_points() {
let points = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.));
let expected_points = points.point_domain.positions().to_vec();
let bounding_box = CopyToPoints {
points: CullNode::new(FutureWrapperNode(ClonedNode(points))),
instance: CullNode::new(FutureWrapperNode(ClonedNode(VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))))),
random_scale_min: FutureWrapperNode(ClonedNode(1.)),
random_scale_max: FutureWrapperNode(ClonedNode(1.)),
random_scale_bias: FutureWrapperNode(ClonedNode(0.)),
random_scale_seed: FutureWrapperNode(ClonedNode(0)),
random_rotation: FutureWrapperNode(ClonedNode(0.)),
random_rotation_seed: FutureWrapperNode(ClonedNode(0)),
}
.eval(Footprint::default())
.await;
let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.);
let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE);
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
let bounding_box = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await;
assert_eq!(bounding_box.region_bezier_paths().count(), expected_points.len());
for (index, (_, subpath)) in bounding_box.region_bezier_paths().enumerate() {
let offset = expected_points[index];
@@ -800,17 +697,8 @@ mod test {
}
#[tokio::test]
async fn sample_points() {
let path = VectorData::from_subpath(Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)));
let sample_points = SamplePoints {
vector_data: CullNode::new(FutureWrapperNode(ClonedNode(path))),
spacing: FutureWrapperNode(ClonedNode(30.)),
start_offset: FutureWrapperNode(ClonedNode(0.)),
stop_offset: FutureWrapperNode(ClonedNode(0.)),
adaptive_spacing: FutureWrapperNode(ClonedNode(false)),
lengths_of_segments_of_subpaths: CullNode::new(FutureWrapperNode(ClonedNode(vec![100.]))),
}
.eval(Footprint::default())
.await;
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 30., 0., 0., false, &FutureWrapperNode(vec![100.])).await;
assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@@ -818,29 +706,22 @@ mod test {
}
#[tokio::test]
async fn adaptive_spacing() {
let path = VectorData::from_subpath(Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)));
let sample_points = SamplePoints {
vector_data: CullNode::new(FutureWrapperNode(ClonedNode(path))),
spacing: FutureWrapperNode(ClonedNode(18.)),
start_offset: FutureWrapperNode(ClonedNode(45.)),
stop_offset: FutureWrapperNode(ClonedNode(10.)),
adaptive_spacing: FutureWrapperNode(ClonedNode(true)),
lengths_of_segments_of_subpaths: CullNode::new(FutureWrapperNode(ClonedNode(vec![100.]))),
}
.eval(Footprint::default())
.await;
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 18., 45., 10., true, &FutureWrapperNode(vec![100.])).await;
assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
}
}
#[test]
fn poisson() {
let sample_points = PoissonDiskPoints {
separation_disk_diameter: ClonedNode(10. * std::f64::consts::SQRT_2),
seed: ClonedNode(0),
}
.eval(VectorData::from_subpath(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)));
#[tokio::test]
async fn poisson() {
let sample_points = super::poisson_disk_points(
Footprint::default(),
&vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
10. * std::f64::consts::SQRT_2,
0,
)
.await;
assert!(
(20..=40).contains(&sample_points.point_domain.positions().len()),
"actual len {}",
@@ -850,31 +731,24 @@ mod test {
assert!(point.length() < 50. + 1., "Expected point in circle {point}")
}
}
#[test]
fn lengths() {
let subpath = VectorData::from_subpath(Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)));
let lengths = LengthsOfSegmentsOfSubpaths.eval(subpath);
#[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;
assert_eq!(lengths, vec![100.]);
}
#[test]
fn spline() {
let subpath = VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.));
let spline = SplinesFromPointsNode.eval(subpath);
let spline = splines_from_points((), subpath);
assert_eq!(spline.stroke_bezier_paths().count(), 1);
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
}
#[tokio::test]
async fn morph() {
let source = VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.));
let target = VectorData::from_subpath(Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO));
let sample_points = MorphNode {
source: CullNode::new(FutureWrapperNode(ClonedNode(source))),
target: CullNode::new(FutureWrapperNode(ClonedNode(target))),
time: FutureWrapperNode(ClonedNode(0.5)),
start_index: FutureWrapperNode(ClonedNode(0)),
}
.eval(Footprint::default())
.await;
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
let sample_points = super::morph(Footprint::default(), &vector_node(source), &vector_node(target), 0.5, 0).await;
assert_eq!(
&sample_points.point_domain.positions()[..4],
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]

View File

@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
[features]
default = ["dealloc_nodes"]
serde = ["dep:serde", "graphene-core/serde", "glam/serde", "bezier-rs/serde"]
dealloc_nodes = []
dealloc_nodes = ["graphene-core/dealloc_nodes"]
wgpu = []
tokio = ["dep:tokio"]
wayland = []

View File

@@ -1,7 +1,7 @@
use crate::document::value::TaggedValue;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use graphene_core::memo::MemoHashGuard;
pub use graphene_core::uuid::generate_uuid;
pub use graphene_core::uuid::NodeId;
@@ -275,6 +275,12 @@ impl DocumentNode {
let DocumentNodeImplementation::ProtoNode(fqn) = self.implementation else {
unreachable!("tried to resolve not flattened node on resolved node {self:?}");
};
// TODO replace with proper generics removal
let identifier = match fqn.name.clone().split_once('<') {
Some((path, _generics)) => ProtoNodeIdentifier { name: Cow::Owned(path.to_string()) },
_ => ProtoNodeIdentifier { name: fqn.name },
};
let (input, mut args) = if let Some(ty) = self.manual_composition {
(ProtoNodeInput::ManualComposition(ty), ConstructionArgs::Nodes(vec![]))
} else {
@@ -314,7 +320,7 @@ impl DocumentNode {
}));
}
ProtoNode {
identifier: fqn,
identifier,
input,
construction_args: args,
original_location: self.original_location,

View File

@@ -15,6 +15,7 @@ pub use glam::{DAffine2, DVec2, IVec2, UVec2};
use std::fmt::Display;
use std::hash::Hash;
use std::marker::PhantomData;
use std::str::FromStr;
pub use std::sync::Arc;
/// Macro to generate the tagged value enum.
@@ -82,32 +83,31 @@ macro_rules! tagged_value {
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
}
}
pub fn from_type(input: &Type) -> Self {
pub fn from_type(input: &Type) -> Option<Self> {
match input {
Type::Generic(_) => {
log::warn!("Generic type should be resolved");
TaggedValue::None
None
}
Type::Concrete(concrete_type) => {
let Some(internal_id) = concrete_type.id else {
return TaggedValue::None;
};
let internal_id = concrete_type.id?;
use std::any::TypeId;
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
match internal_id {
Some(match internal_id {
x if x == TypeId::of::<()>() => TaggedValue::None,
$( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )*
_ => TaggedValue::None,
}
_ => return None,
})
}
Type::Fn(_, output) => TaggedValue::from_type(output),
Type::Future(_) => {
log::warn!("Future type not used");
TaggedValue::None
None
}
}
}
pub fn from_type_or_none(input: &Type) -> Self {
Self::from_type(input).unwrap_or(TaggedValue::None)
}
}
};
}
@@ -160,7 +160,6 @@ tagged_value! {
GradientType(graphene_core::vector::style::GradientType),
#[serde(alias = "GradientPositions")] // TODO: Eventually remove this alias (probably starting late 2024)
GradientStops(graphene_core::vector::style::GradientStops),
Quantization(graphene_core::quantization::QuantizationChannels),
OptionalColor(Option<graphene_core::raster::color::Color>),
#[serde(alias = "ManipulatorGroupIds")] // TODO: Eventually remove this alias (probably starting late 2024)
PointIds(Vec<graphene_core::vector::PointId>),
@@ -196,6 +195,31 @@ impl TaggedValue {
}
}
pub fn from_primitive_string(string: &str, ty: &Type) -> Option<Self> {
match ty {
Type::Generic(_) => None,
Type::Concrete(concrete_type) => {
let internal_id = concrete_type.id?;
use std::any::TypeId;
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
let ty = match internal_id {
x if x == TypeId::of::<()>() => TaggedValue::None,
x if x == TypeId::of::<String>() => TaggedValue::String(string.into()),
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::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
x if x == TypeId::of::<Color>() => Color::from_rgba_str(string).map(TaggedValue::Color)?,
_ => return None,
};
Some(ty)
}
Type::Fn(_, output) => TaggedValue::from_primitive_string(string, output),
Type::Future(_) => None,
}
}
pub fn to_u32(&self) -> u32 {
match self {
TaggedValue::U32(x) => *x,

View File

@@ -1,4 +1,4 @@
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use graphene_core::Color;
use std::borrow::Cow;
use std::fmt::Debug;

View File

@@ -1,7 +1,7 @@
use crate::document::{value, InlineRust};
use crate::document::{NodeId, OriginalLocation};
use dyn_any::DynAny;
pub use graphene_core::registry::*;
use graphene_core::*;
use rustc_hash::FxHashMap;
@@ -10,88 +10,6 @@ use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::hash::Hash;
use std::ops::Deref;
use std::pin::Pin;
#[cfg(not(target_arch = "wasm32"))]
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n + Send>>;
#[cfg(target_arch = "wasm32")]
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;
pub type LocalFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;
#[cfg(not(target_arch = "wasm32"))]
pub type Any<'n> = Box<dyn DynAny<'n> + 'n + Send>;
#[cfg(target_arch = "wasm32")]
pub type Any<'n> = Box<dyn DynAny<'n> + 'n>;
pub type FutureAny<'n> = DynFuture<'n, Any<'n>>;
// TODO: is this safe? This is assumed to be send+sync.
#[cfg(not(target_arch = "wasm32"))]
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n + Send + Sync;
#[cfg(target_arch = "wasm32")]
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n;
pub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>;
pub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>;
pub type TypeErasedBox<'n> = Box<TypeErasedNode<'n>>;
pub type TypeErasedPinned<'n> = Pin<Box<TypeErasedNode<'n>>>;
pub type SharedNodeContainer = std::sync::Arc<NodeContainer>;
pub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>;
#[derive(Clone)]
pub struct NodeContainer {
#[cfg(feature = "dealloc_nodes")]
pub node: *const TypeErasedNode<'static>,
#[cfg(not(feature = "dealloc_nodes"))]
pub node: TypeErasedRef<'static>,
}
impl Deref for NodeContainer {
type Target = TypeErasedNode<'static>;
#[cfg(feature = "dealloc_nodes")]
fn deref(&self) -> &Self::Target {
unsafe { &*(self.node) }
#[cfg(not(feature = "dealloc_nodes"))]
self.node
}
#[cfg(not(feature = "dealloc_nodes"))]
fn deref(&self) -> &Self::Target {
self.node
}
}
/// #Safety
/// Marks NodeContainer as Sync. This disallows the use of threadlocal storage for nodes as this would invalidate references to them.
// TODO: implement this on a higher level wrapper to avoid misuse
#[cfg(feature = "dealloc_nodes")]
unsafe impl Send for NodeContainer {}
#[cfg(feature = "dealloc_nodes")]
unsafe impl Sync for NodeContainer {}
#[cfg(feature = "dealloc_nodes")]
impl Drop for NodeContainer {
fn drop(&mut self) {
unsafe { self.dealloc_unchecked() }
}
}
impl core::fmt::Debug for NodeContainer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodeContainer").finish()
}
}
impl NodeContainer {
pub fn new(node: TypeErasedBox<'static>) -> SharedNodeContainer {
let node = Box::leak(node);
Self { node }.into()
}
#[cfg(feature = "dealloc_nodes")]
unsafe fn dealloc_unchecked(&mut self) {
std::mem::drop(Box::from_raw(self.node as *mut TypeErasedNode));
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Default, PartialEq, Clone, Hash, Eq)]
@@ -487,7 +405,7 @@ impl ProtoNetwork {
self.nodes.push((
compose_node_id,
ProtoNode {
identifier: ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"),
identifier: ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode"),
construction_args: ConstructionArgs::Nodes(vec![(input_node_id, false), (node_id, true)]),
input,
original_location: OriginalLocation { path, ..Default::default() },
@@ -689,7 +607,7 @@ impl core::fmt::Debug for GraphError {
pub type GraphErrors = Vec<GraphError>;
/// The `TypingContext` is used to store the types of the nodes indexed by their stable node id.
#[derive(Default, Clone)]
#[derive(Default, Clone, dyn_any::DynAny)]
pub struct TypingContext {
lookup: Cow<'static, HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>>,
inferred: HashMap<NodeId, NodeIOTypes>,
@@ -855,6 +773,7 @@ impl TypingContext {
Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { parameters, error_inputs })])
}
[(org_nio, output)] => {
// TODO: Fix unsoundness caused by generic parameters not getting cleaned up
let node_io = NodeIOTypes::new(input, (*output).clone(), parameters);
// Save the inferred type
@@ -862,6 +781,25 @@ impl TypingContext {
self.constructor.insert(node_id, impls[org_nio]);
Ok(node_io)
}
// If two types are available and one of them accepts () an input, always choose that one
[first, second] => {
if first.0.input != second.0.input {
for (org_nio, output) in [first, second] {
if org_nio.input != concrete!(()) {
continue;
}
let node_io = NodeIOTypes::new(input, (*output).clone(), parameters);
// Save the inferred type
self.inferred.insert(node_id, node_io.clone());
self.constructor.insert(node_id, impls[org_nio]);
return Ok(node_io);
}
}
let parameters = [&input].into_iter().chain(&parameters).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let valid = valid_output_types.into_iter().cloned().collect();
Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { parameters, valid })])
}
_ => {
let parameters = [&input].into_iter().chain(&parameters).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
@@ -977,9 +915,9 @@ mod test {
NodeId(12083027370457564588),
NodeId(10127202135369428481),
NodeId(3781642984881236270),
NodeId(9447822059040146367),
NodeId(15916837829094140504),
NodeId(1758919868423328454)
NodeId(12160249450476233602),
NodeId(17962581471057044127),
NodeId(7906594012485169109)
]
);
}

View File

@@ -12,7 +12,6 @@ wgpu = ["wgpu-executor", "gpu", "graphene-std/wgpu"]
wayland = ["graphene-std/wayland"]
profiling = ["wgpu-executor/profiling"]
passthrough = ["wgpu-executor/passthrough"]
quantization = ["graphene-std/quantization"]
gpu = [
"interpreted-executor/gpu",
"graphene-std/gpu",

View File

@@ -111,7 +111,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
DocumentNode {
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
},
DocumentNode {
@@ -120,7 +120,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))), 0),
NodeInput::node(NodeId(1), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _, _, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode")),
..Default::default()
},
]

View File

@@ -15,7 +15,6 @@ gpu = [
"gpu-executor",
]
wgpu = ["gpu", "dep:wgpu", "graph-craft/wgpu"]
quantization = ["autoquant"]
wasm = ["wasm-bindgen", "web-sys", "js-sys"]
imaginate = ["image/png", "base64", "js-sys", "web-sys", "wasm-bindgen-futures"]
image-compare = ["dep:image-compare"]
@@ -25,7 +24,7 @@ wayland = ["graph-craft/wayland"]
[dependencies]
# Local dependencies
dyn-any = { path = "../../libraries/dyn-any", features = ["derive"] }
dyn-any = { path = "../../libraries/dyn-any", features = ["derive", "reqwest"] }
graph-craft = { path = "../graph-craft", features = ["serde"] }
wgpu-executor = { path = "../wgpu-executor" }
graphene-core = { path = "../gcore", default-features = false, features = [
@@ -85,9 +84,6 @@ web-sys = { workspace = true, optional = true, features = [
"HtmlImageElement",
"ImageBitmapRenderingContext",
] }
autoquant = { git = "https://github.com/truedoctor/autoquant", optional = true, features = [
"fitting",
] }
# Optional dependencies
image-compare = { version = "0.4.1", optional = true }

View File

@@ -6,141 +6,7 @@ pub use graphene_core::{generic, ops, Node};
use dyn_any::StaticType;
use std::marker::PhantomData;
pub struct DynAnyNode<I, O, Node> {
node: Node,
_i: PhantomData<I>,
_o: PhantomData<O>,
}
impl<'input, _I: 'input + StaticType + WasmNotSend, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N>
where
N: Node<'input, _I, Output = DynFuture<'input, _O>>,
{
type Output = FutureAny<'input>;
#[inline]
fn eval(&'input self, input: Any<'input>) -> Self::Output {
let node_name = core::any::type_name::<N>();
let output = |input| {
let result = self.node.eval(input);
async move { Box::new(result.await) as Any<'input> }
};
match dyn_any::downcast(input) {
Ok(input) => Box::pin(output(*input)),
// If the input type of the node is `()` and we supply an invalid type, we can still call the
// node and just ignore the input and call it with the unit type instead.
Err(_) if core::any::TypeId::of::<_I::Static>() == core::any::TypeId::of::<()>() => {
assert_eq!(std::mem::size_of::<_I>(), 0);
// Rust can't know, that `_I` and `()` are the same size, so we have to use a `transmute_copy()` here
Box::pin(output(unsafe { std::mem::transmute_copy(&()) }))
}
Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, node_name),
}
}
fn reset(&self) {
self.node.reset();
}
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
self.node.serialize()
}
}
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> DynAnyNode<_I, _O, N>
where
N: Node<'input, _I, Output = DynFuture<'input, _O>>,
{
pub const fn new(node: N) -> Self {
Self {
node,
_i: core::marker::PhantomData,
_o: core::marker::PhantomData,
}
}
}
pub struct DynAnyRefNode<I, O, Node> {
node: Node,
_i: PhantomData<(I, O)>,
}
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType + WasmNotSend + Sync, N: 'input> Node<'input, Any<'input>> for DynAnyRefNode<_I, _O, N>
where
N: for<'any_input> Node<'any_input, _I, Output = &'any_input _O>,
{
type Output = FutureAny<'input>;
fn eval(&'input self, input: Any<'input>) -> Self::Output {
let node_name = core::any::type_name::<N>();
let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyRefNode Input, {e} in:\n{node_name}"));
let result = self.node.eval(*input);
let output = async move { Box::new(result) as Any<'input> };
Box::pin(output)
}
fn reset(&self) {
self.node.reset();
}
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
self.node.serialize()
}
}
impl<_I, _O, S0> DynAnyRefNode<_I, _O, S0> {
pub const fn new(node: S0) -> Self {
Self { node, _i: core::marker::PhantomData }
}
}
pub struct DynAnyInRefNode<I, O, Node> {
node: Node,
_i: PhantomData<(I, O)>,
}
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyInRefNode<_I, _O, N>
where
N: for<'any_input> Node<'any_input, &'any_input _I, Output = DynFuture<'any_input, _O>>,
{
type Output = FutureAny<'input>;
fn eval(&'input self, input: Any<'input>) -> Self::Output {
{
let node_name = core::any::type_name::<N>();
let input: Box<&_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyInRefNode Input, {e} in:\n{node_name}"));
let result = self.node.eval(*input);
Box::pin(async move { Box::new(result.await) as Any<'_> })
}
}
}
impl<_I, _O, S0> DynAnyInRefNode<_I, _O, S0> {
pub const fn new(node: S0) -> Self {
Self { node, _i: core::marker::PhantomData }
}
}
pub struct FutureWrapperNode<Node> {
node: Node,
}
impl<'i, T: 'i + WasmNotSend, N> Node<'i, T> for FutureWrapperNode<N>
where
N: Node<'i, T, Output: WasmNotSend> + WasmNotSend,
{
type Output = DynFuture<'i, N::Output>;
fn eval(&'i self, input: T) -> Self::Output {
let result = self.node.eval(input);
Box::pin(async move { result })
}
fn reset(&self) {
self.node.reset();
}
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
self.node.serialize()
}
}
impl<N> FutureWrapperNode<N> {
pub const fn new(node: N) -> Self {
Self { node }
}
}
pub use graphene_core::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode};
pub trait IntoTypeErasedNode<'n> {
fn into_type_erased(self) -> TypeErasedBox<'n>;
@@ -155,60 +21,6 @@ where
}
}
pub struct DowncastNode<O, Node> {
node: Node,
_o: PhantomData<O>,
}
impl<N: Clone, O: StaticType> Clone for DowncastNode<O, N> {
fn clone(&self) -> Self {
Self { node: self.node.clone(), _o: self._o }
}
}
impl<N: Copy, O: StaticType> Copy for DowncastNode<O, N> {}
#[node_macro::node_fn(DowncastNode<_O>)]
fn downcast<N: 'input, _O: StaticType>(input: Any<'input>, node: &'input N) -> _O
where
N: for<'any_input> Node<'any_input, Any<'any_input>, Output = Any<'any_input>> + 'input,
{
let node_name = core::any::type_name::<N>();
let out = dyn_any::downcast(node.eval(input)).unwrap_or_else(|e| panic!("DowncastNode Input {e} in:\n{node_name}"));
*out
}
/// Boxes the input and downcasts the output.
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
#[derive(Clone)]
pub struct DowncastBothNode<I, O> {
node: SharedNodeContainer,
_i: PhantomData<I>,
_o: PhantomData<O>,
}
impl<'input, O: 'input + StaticType + WasmNotSend, I: 'input + StaticType + WasmNotSend> Node<'input, I> for DowncastBothNode<I, O> {
type Output = DynFuture<'input, O>;
#[inline]
fn eval(&'input self, input: I) -> Self::Output {
{
let node_name = self.node.node_name();
let input = Box::new(input);
let future = self.node.eval(input);
Box::pin(async move {
let out = dyn_any::downcast(future.await).unwrap_or_else(|e| panic!("DowncastBothNode Input {e} in: \n{node_name}"));
*out
})
}
}
}
impl<I, O> DowncastBothNode<I, O> {
pub const fn new(node: SharedNodeContainer) -> Self {
Self {
node,
_i: core::marker::PhantomData,
_o: core::marker::PhantomData,
}
}
}
pub struct ComposeTypeErased {
first: SharedNodeContainer,
second: SharedNodeContainer,
@@ -236,71 +48,3 @@ pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(),
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
DowncastBothNode::new(n)
}
pub struct PanicNode<I: WasmNotSend, O: WasmNotSend>(PhantomData<I>, PhantomData<O>);
impl<'i, I: 'i + WasmNotSend, O: 'i + WasmNotSend> Node<'i, I> for PanicNode<I, O> {
type Output = O;
fn eval(&'i self, _: I) -> Self::Output {
unimplemented!("This node should never be evaluated")
}
}
impl<I: WasmNotSend, O: WasmNotSend> PanicNode<I, O> {
pub const fn new() -> Self {
Self(PhantomData, PhantomData)
}
}
impl<I: WasmNotSend, O: WasmNotSend> Default for PanicNode<I, O> {
fn default() -> Self {
Self::new()
}
}
// TODO: Evaluate safety
unsafe impl<I: WasmNotSend, O: WasmNotSend> Sync for PanicNode<I, O> {}
#[cfg(test)]
mod test {
use super::*;
use graphene_core::{ops::AddPairNode, ops::IdentityNode};
#[test]
#[should_panic]
pub fn dyn_input_invalid_eval_panic() {
// let add = DynAnyNode::new(AddPairNode::new()).into_type_erased();
// add.eval(Box::new(&("32", 32_u32)));
let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(FutureWrapperNode { node: AddPairNode::new() });
let type_erased = Box::new(dyn_any) as TypeErasedBox;
let _ref_type_erased = type_erased.as_ref();
// let type_erased = Box::pin(dyn_any) as TypeErasedBox<'_>;
futures::executor::block_on(type_erased.eval(Box::new(&("32", 32_u32))));
}
#[test]
pub fn dyn_input_compose() {
// let add = DynAnyNode::new(AddPairNode::new()).into_type_erased();
// add.eval(Box::new(&("32", 32_u32)));
let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(FutureWrapperNode { node: AddPairNode::new() });
let type_erased = Box::new(dyn_any) as TypeErasedBox<'_>;
futures::executor::block_on(type_erased.eval(Box::new((4_u32, 2_u32))));
let id_node = FutureWrapperNode::new(IdentityNode::new());
let any_id = DynAnyNode::<u32, u32, _>::new(id_node);
let type_erased_id = Box::new(any_id) as TypeErasedBox;
let type_erased = ComposeTypeErased::new(NodeContainer::new(type_erased), NodeContainer::new(type_erased_id));
futures::executor::block_on(type_erased.eval(Box::new((4_u32, 2_u32))));
// let downcast: DowncastBothNode<(u32, u32), u32> = DowncastBothNode::new(type_erased.as_ref());
// downcast.eval((4_u32, 2_u32));
}
// TODO: Fix this test
// #[test]
// pub fn dyn_input_storage_composition() {
// // todo readd test
// let node = <graphene_core::ops::IdentityNode>::new();
// let any: DynAnyNode<Any<'_>, Any<'_>, _> = DynAnyNode::new(ValueNode::new(node));
// any.into_type_erased();
// }
}

View File

@@ -3,65 +3,19 @@ use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, Ex
use graphene_core::raster::adjustments::blend_colors;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::{Alpha, Color, Image, ImageFrame, Pixel, Sample};
use graphene_core::raster::{BlendMode, BlendNode};
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::raster::BlendMode;
use graphene_core::raster::{Alpha, BlendColorPairNode, Color, Image, ImageFrame, Pixel, Sample};
use graphene_core::transform::{Footprint, Transform, TransformMut};
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
use graphene_core::vector::VectorData;
use graphene_core::{Node, WasmNotSend};
use node_macro::node_fn;
use graphene_core::Node;
use glam::{DAffine2, DVec2};
use std::marker::PhantomData;
#[derive(Clone, Debug, PartialEq)]
pub struct ReduceNode<Initial, Lambda> {
pub initial: Initial,
pub lambda: Lambda,
}
#[node_fn(ReduceNode)]
fn reduce<I: Iterator, Lambda, T>(iter: I, initial: T, lambda: &'input Lambda) -> T
where
Lambda: for<'a> Node<'a, (T, I::Item), Output = T>,
{
iter.fold(initial, |a, x| lambda.eval((a, x)))
}
#[derive(Clone, Debug, PartialEq)]
pub struct ChainApplyNode<Value> {
pub value: Value,
}
#[node_fn(ChainApplyNode)]
async fn chain_apply<I: Iterator + WasmNotSend, T: WasmNotSend>(iter: I, value: T) -> T
where
I::Item: for<'a> Node<'a, T, Output = T>,
{
let mut value = value;
for lambda in iter {
value = lambda.eval(value);
}
value
}
#[derive(Clone, Debug, PartialEq)]
pub struct IntoIterNode<T> {
_t: PhantomData<T>,
}
#[node_fn(IntoIterNode<_T>)]
fn into_iter<'i: 'input, _T: Send + Sync>(vec: &'i Vec<_T>) -> Box<dyn Iterator<Item = &'i _T> + Send + Sync + 'i> {
Box::new(vec.iter())
}
#[derive(Clone, Debug, PartialEq)]
pub struct VectorPointsNode;
#[node_fn(VectorPointsNode)]
fn vector_points(vector: VectorData) -> Vec<DVec2> {
vector.point_domain.positions().to_vec()
#[node_macro::node(category("Debug"))]
fn vector_points(_: (), vector_data: VectorData) -> Vec<DVec2> {
vector_data.point_domain.positions().to_vec()
}
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -110,27 +64,8 @@ impl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BrushStampGeneratorNode<ColorNode, Hardness, Flow> {
pub color: ColorNode,
pub hardness: Hardness,
pub flow: Flow,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EraseNode<Flow> {
flow: Flow,
}
#[node_fn(EraseNode)]
fn erase(input: (Color, Color), flow: f64) -> Color {
let (input, brush) = input;
let alpha = input.a() * (1. - flow as f32 * brush.a());
Color::from_unassociated_alpha(input.r(), input.g(), input.b(), alpha)
}
#[node_fn(BrushStampGeneratorNode)]
fn brush_stamp_generator_node(diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
#[node_macro::node(skip_impl)]
fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
// Diameter
let radius = diameter / 2.;
@@ -148,29 +83,10 @@ fn brush_stamp_generator_node(diameter: f64, color: Color, hardness: f64, flow:
BrushStampGenerator { color, feather_exponent, transform }
}
#[derive(Clone, Debug, PartialEq)]
pub struct TranslateNode<Translatable> {
translatable: Translatable,
}
#[node_fn(TranslateNode)]
fn translate_node<Data: TransformMut>(offset: DVec2, mut translatable: Data) -> Data {
translatable.translate(offset);
translatable
}
#[derive(Debug, Clone, Copy)]
pub struct BlitNode<P, Texture, Positions, BlendFn> {
texture: Texture,
positions: Positions,
blend_mode: BlendFn,
_p: PhantomData<P>,
}
#[node_fn(BlitNode<_P>)]
fn blit_node<_P: Alpha + Pixel + std::fmt::Debug, BlendFn>(mut target: ImageFrame<_P>, texture: Image<_P>, positions: Vec<DVec2>, blend_mode: BlendFn) -> ImageFrame<_P>
#[node_macro::node(skip_impl)]
fn blit<P: Alpha + Pixel + std::fmt::Debug, BlendFn>(mut target: ImageFrame<P>, texture: Image<P>, positions: Vec<DVec2>, blend_mode: BlendFn) -> ImageFrame<P>
where
BlendFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P>,
BlendFn: for<'any_input> Node<'any_input, (P, P), Output = P>,
{
if positions.is_empty() {
return target;
@@ -216,8 +132,8 @@ pub fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
let stamp = BrushStampGeneratorNode::new(CopiedNode::new(brush_style.color), CopiedNode::new(brush_style.hardness), CopiedNode::new(brush_style.flow));
let stamp = stamp.eval(brush_style.diameter);
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
let blank_texture = EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)).eval(transform);
let normal_blend = BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
let blank_texture = EmptyImageNode::new(CopiedNode::new(transform), CopiedNode::new(Color::TRANSPARENT)).eval(());
let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
let blend_executor = BlendImageTupleNode::new(ValueNode::new(normal_blend));
blend_executor.eval((blank_texture, stamp)).image
}
@@ -282,14 +198,8 @@ pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Col
)
}
pub struct BrushNode<Bounds, Strokes, Cache> {
bounds: Bounds,
strokes: Strokes,
cache: Cache,
}
#[node_macro::node_fn(BrushNode)]
async fn brush(image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrame<Color> {
#[node_macro::node(category(""))]
fn brush(_footprint: Footprint, image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrame<Color> {
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
let image_bbox = Bbox::from_transform(image.transform).to_axis_aligned_bbox();
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
@@ -332,13 +242,13 @@ async fn brush(image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.0);
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
let normal_blend = BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(normal_blend));
let blit_target = if idx == 0 {
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
ExtendImageToBoundsNode::new(CopiedNode::new(stroke_to_layer)).eval(target)
} else {
EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)).eval(stroke_to_layer)
EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
};
blit_node.eval(blit_target)
};
@@ -371,14 +281,14 @@ async fn brush(image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec
match stroke.style.blend_mode {
BlendMode::Erase => {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::Erase), CopiedNode::new(100.));
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Erase), CopiedNode::new(100.));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
erase_restore_mask = blit_node.eval(erase_restore_mask);
}
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
BlendMode::Restore => {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::Restore), CopiedNode::new(100.));
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Restore), CopiedNode::new(100.));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
erase_restore_mask = blit_node.eval(erase_restore_mask);
}
@@ -387,7 +297,7 @@ async fn brush(image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec
}
}
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.0));
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.0));
let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params));
actual_image = blend_executor.eval((actual_image, erase_restore_mask));
}
@@ -397,34 +307,12 @@ async fn brush(image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec
#[cfg(test)]
mod test {
use super::*;
use crate::raster::*;
#[allow(unused_imports)]
use graphene_core::ops::{AddPairNode, CloneNode};
use graphene_core::raster::*;
use graphene_core::structural::Then;
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::value::{ClonedNode, ValueNode};
use graphene_core::transform::Transform;
use graphene_core::value::ClonedNode;
use glam::DAffine2;
#[test]
fn test_translate_node() {
let image = Image::new(10, 10, Color::TRANSPARENT);
let mut image = ImageFrame { image, ..Default::default() };
image.translate(DVec2::new(1., 2.));
let translate_node = TranslateNode::new(ClonedNode::new(image));
let image = translate_node.eval(DVec2::new(1., 2.));
assert_eq!(image.transform(), DAffine2::from_translation(DVec2::new(2., 4.)));
}
#[test]
fn test_reduce() {
let reduce_node = ReduceNode::new(ClonedNode::new(0u32), ValueNode::new(AddPairNode));
let sum = reduce_node.eval(vec![1, 2, 3, 4, 5].into_iter());
assert_eq!(sum, 15);
}
#[test]
fn test_brush_texture() {
let brush_texture_node = BrushStampGeneratorNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(100.), ClonedNode::new(100.));
@@ -434,26 +322,4 @@ mod test {
// center pixel should be BLACK
assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));
}
#[test]
fn test_brush() {
let brush_texture_node = BrushStampGeneratorNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(1.), ClonedNode::new(1.));
let image = brush_texture_node.eval(20.);
let trace = vec![DVec2::new(0., 0.), DVec2::new(10., 0.)];
let trace = ClonedNode::new(trace.into_iter());
let translate_node = TranslateNode::new(ClonedNode::new(image));
let frames = MapNode::new(ValueNode::new(translate_node));
let frames = trace.then(frames).eval(()).collect::<Vec<_>>();
assert_eq!(frames.len(), 2);
let background_bounds = ReduceNode::new(ClonedNode::new(None), ValueNode::new(MergeBoundingBoxNode::new()));
let background_bounds = background_bounds.eval(frames.clone().into_iter());
let background_bounds = ClonedNode::new(background_bounds.unwrap().to_transform());
let background_image = background_bounds.then(EmptyImageNode::new(ClonedNode::new(Color::TRANSPARENT)));
let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(BlendMode::Normal), ClonedNode::new(1.));
let final_image = ReduceNode::new(background_image, ValueNode::new(BlendImageTupleNode::new(ValueNode::new(blend_node))));
let final_image = final_image.eval(frames.into_iter());
assert_eq!(final_image.image.height, 20);
assert_eq!(final_image.image.width, 30);
drop(final_image);
}
}

View File

@@ -4,30 +4,21 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::*;
use graph_craft::proto::*;
use graphene_core::application_io::ApplicationIo;
use graphene_core::quantization::QuantizationChannels;
use graphene_core::raster::*;
use graphene_core::*;
use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput};
use glam::{DAffine2, DVec2, Mat2, Vec2};
#[cfg(feature = "quantization")]
use graphene_core::quantization::PackedPixel;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use crate::wasm_application_io::WasmApplicationIo;
pub struct GpuCompiler<TypingContext, ShaderIO> {
typing_context: TypingContext,
io: ShaderIO,
}
// TODO: Move to graph-craft
#[node_macro::node_fn(GpuCompiler)]
async fn compile_gpu(node: &'input DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result<compilation_client::Shader, String> {
#[node_macro::node(category("Debug: GPU"))]
async fn compile_gpu<'a: 'n>(_: (), node: &'a DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result<compilation_client::Shader, String> {
let mut typing_context = typing_context;
let compiler = graph_craft::graphene_compiler::Compiler {};
let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() };
@@ -69,40 +60,22 @@ impl Clone for ComputePass {
}
}
#[node_macro::node_impl(MapGpuNode)]
#[node_macro::old_node_impl(MapGpuNode)]
async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi<WasmApplicationIo>) -> ImageFrame<Color> {
log::debug!("Executing gpu node");
let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
#[cfg(feature = "quantization")]
let quantization = crate::quantization::generate_quantization_from_image_frame(&image);
#[cfg(not(feature = "quantization"))]
let quantization = QuantizationChannels::default();
log::debug!("quantization: {quantization:?}");
#[cfg(feature = "image-compare")]
let img: image::DynamicImage = image::Rgba32FImage::from_raw(image.image.width, image.image.height, bytemuck::cast_vec(image.image.data.clone()))
.unwrap()
.into();
#[cfg(feature = "quantization")]
let image = ImageFrame {
image: Image {
data: image.image.data.iter().map(|c| quantization::quantize_color(*c, quantization)).collect(),
width: image.image.width,
height: image.image.height,
base64_string: None,
},
transform: image.transform,
alpha_blending: image.alpha_blending,
};
// TODO: The cache should be based on the network topology not the node name
let compute_pass_descriptor = if self.cache.lock().as_ref().unwrap().contains_key("placeholder") {
self.cache.lock().as_ref().unwrap().get("placeholder").unwrap().clone()
} else {
let name = "placeholder".to_string();
let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, &image, executor, quantization).await else {
let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, &image, executor).await else {
log::error!("Error creating compute pass descriptor in 'map_gpu()");
return ImageFrame::empty();
};
@@ -122,13 +95,6 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
log::debug!("executed pipeline");
log::debug!("reading buffer");
let result = executor.read_output_buffer(compute_pass_descriptor.readback_buffer.clone().unwrap()).await.unwrap();
#[cfg(feature = "quantization")]
let colors = bytemuck::pod_collect_to_vec::<u8, PackedPixel>(result.as_slice());
#[cfg(feature = "quantization")]
log::debug!("first color: {:b}", colors[0].0);
#[cfg(feature = "quantization")]
let colors: Vec<_> = colors.iter().map(|c| quantization::dequantize_color(*c, quantization)).collect();
#[cfg(not(feature = "quantization"))]
let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice());
log::debug!("first color: {:?}", colors[0]);
@@ -161,32 +127,19 @@ impl<Node, EditorApi> MapGpuNode<Node, EditorApi> {
}
}
async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
node: DocumentNode,
image: &ImageFrame<T>,
executor: &&WgpuExecutor,
quantization: QuantizationChannels,
) -> Result<ComputePass, String> {
async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(node: DocumentNode, image: &ImageFrame<T>, executor: &&WgpuExecutor) -> Result<ComputePass, String> {
let compiler = graph_craft::graphene_compiler::Compiler {};
let inner_network = NodeNetwork::value_network(node);
log::debug!("inner_network: {inner_network:?}");
let network = NodeNetwork {
#[cfg(feature = "quantization")]
exports: vec![NodeInput::node(NodeId(5), 0)],
#[cfg(not(feature = "quantization"))]
exports: vec![NodeInput::node(NodeId(3), 0)],
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::Inline(InlineRust::new("i1[(_global_index.y * i0 + _global_index.x) as usize]".into(), concrete![Color]))],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(quantization::Quantization), 1)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
@@ -201,34 +154,19 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
},*/
/*
DocumentNode {
name: "GetNode".into(),
name: "Get Node".into(),
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::storage::GetNode".into()),
..Default::default()
},*/
#[cfg(feature = "quantization")]
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::quantization::DeQuantizeNode"),
..Default::default()
},
DocumentNode {
#[cfg(feature = "quantization")]
inputs: vec![NodeInput::node(NodeId(3), 0)],
#[cfg(not(feature = "quantization"))]
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::Network(inner_network),
..Default::default()
},
#[cfg(feature = "quantization")]
DocumentNode {
inputs: vec![NodeInput::node(NodeId(4), 0), NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::quantization::QuantizeNode"),
..Default::default()
},
/*
DocumentNode {
name: "SaveNode".into(),
name: "Save Node".into(),
inputs: vec![
NodeInput::node(NodeId(5), 0),
NodeInput::Inline(InlineRust::new(
@@ -256,23 +194,11 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
vec![concrete!(u32), concrete!(Color)],
vec![concrete!(Color)],
ShaderIO {
#[cfg(feature = "quantization")]
inputs: vec![
ShaderInput::UniformBuffer((), concrete!(u32)),
ShaderInput::StorageBuffer((), concrete!(PackedPixel)),
ShaderInput::UniformBuffer((), concrete!(quantization::QuantizationChannels)),
// ShaderInput::Constant(gpu_executor::GPUConstant::GlobalInvocationId),
ShaderInput::OutputBuffer((), concrete!(PackedPixel)),
],
#[cfg(not(feature = "quantization"))]
inputs: vec![
ShaderInput::UniformBuffer((), concrete!(u32)),
ShaderInput::StorageBuffer((), concrete!(Color)),
ShaderInput::OutputBuffer((), concrete!(Color)),
],
#[cfg(feature = "quantization")]
output: ShaderInput::OutputBuffer((), concrete!(PackedPixel)),
#[cfg(not(feature = "quantization"))]
output: ShaderInput::OutputBuffer((), concrete!(Color)),
},
)
@@ -281,6 +207,17 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
// return ImageFrame::empty();
let len: usize = image.image.data.len();
let storage_buffer = executor
.create_storage_buffer(
image.image.data.clone(),
StorageBufferOptions {
cpu_writable: false,
gpu_writable: true,
cpu_readable: false,
storage: true,
},
)
.unwrap();
/*
let canvas = editor_api.application_io.create_surface();
@@ -298,25 +235,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
return frame;*/
log::debug!("creating buffer");
let width_uniform = executor.create_uniform_buffer(image.image.width).unwrap();
#[cfg(not(feature = "quantization"))]
core::hint::black_box(quantization);
#[cfg(feature = "quantization")]
let quantization_uniform = executor.create_uniform_buffer(quantization).unwrap();
let storage_buffer = executor
.create_storage_buffer(
image.image.data.clone(),
StorageBufferOptions {
cpu_writable: false,
gpu_writable: true,
cpu_readable: false,
storage: true,
},
)
.unwrap();
let width_uniform = Arc::new(width_uniform);
#[cfg(feature = "quantization")]
let quantization_uniform = Arc::new(quantization_uniform);
let storage_buffer = Arc::new(storage_buffer);
let output_buffer = executor.create_output_buffer(len, concrete!(Color), false).unwrap();
let output_buffer = Arc::new(output_buffer);
@@ -324,10 +243,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
let readback_buffer = Arc::new(readback_buffer);
log::debug!("created buffer");
let bind_group = Bindgroup {
#[cfg(feature = "quantization")]
buffers: vec![width_uniform.clone(), storage_buffer.clone(), quantization_uniform.clone()],
#[cfg(not(feature = "quantization"))]
buffers: vec![width_uniform, storage_buffer],
buffers: vec![width_uniform.into(), storage_buffer],
};
let shader = Shader {
@@ -351,72 +267,9 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
readback_buffer: Some(readback_buffer),
})
}
/*
#[node_macro::node_fn(MapGpuNode)]
async fn map_gpu(inputs: Vec<ShaderInput<<NewExecutor as GpuExecutor>::BufferHandle>>, shader: &'any_input compilation_client::Shader) {
use graph_craft::executor::Executor;
let executor = NewExecutor::new().unwrap();
for input in shader.io.inputs.iter() {
let buffer = executor.create_storage_buffer(&self, data, options)
let buffer = executor.create_buffer(input.size).unwrap();
executor.write_buffer(buffer, input.data).unwrap();
}
todo!();
/*
let executor: GpuExecutor = GpuExecutor::new(Context::new().await.unwrap(), shader.into(), "gpu::eval".into()).unwrap();
let data: Vec<_> = input.into_iter().collect();
let result = executor.execute(Box::new(data)).unwrap();
let result = dyn_any::downcast::<Vec<_O>>(result).unwrap();
*result
*/
}
pub struct MapGpuSingleImageNode<N> {
node: N,
}
#[node_macro::node_fn(MapGpuSingleImageNode)]
fn map_gpu_single_image(input: Image<Color>, node: String) -> Image<Color> {
use graph_craft::document::*;
use graph_craft::ProtoNodeIdentifier;
let identifier = ProtoNodeIdentifier { name: std::borrow::Cow::Owned(node) };
let network = NodeNetwork {
inputs: vec![NodeId(0)],
disabled: vec![],
previous_outputs: None,
outputs: vec![NodeInput::node(NodeId(0), 0)],
nodes: [(
NodeId(0),
DocumentNode {
name: "Image Filter".into(),
inputs: vec![NodeInput::Network(concrete!(Color))],
implementation: DocumentNodeImplementation::ProtoNode(identifier),
metadata: DocumentNodeMetadata::default(),
..Default::default()
},
)]
.into_iter()
.collect(),
};
let value_network = ValueNode::new(network);
let map_node = MapGpuNode::new(value_network);
let data = map_node.eval(input.data.clone());
Image { data, ..input }
}
*/
#[derive(Debug, Clone, Copy)]
pub struct BlendGpuImageNode<Background, B, O> {
background: Background,
blend_mode: B,
opacity: O,
}
#[node_macro::node_fn(BlendGpuImageNode)]
async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrame<Color> {
#[node_macro::node(category("Debug: GPU"))]
async fn blend_gpu_image(_: (), foreground: ImageFrame<Color>, background: ImageFrame<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrame<Color> {
let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64);
let background_size = DVec2::new(background.image.width as f64, background.image.height as f64);
// Transforms a point from the background image to the foreground image

View File

@@ -1,17 +1,9 @@
use crate::Node;
pub struct GetNode;
#[node_macro::node_fn(GetNode)]
async fn get_node(url: String) -> reqwest::Response {
#[node_macro::node(category("Network"))]
async fn get_request(_: (), url: String) -> reqwest::Response {
reqwest::get(url).await.unwrap()
}
pub struct PostNode<Body> {
body: Body,
}
#[node_macro::node_fn(PostNode)]
async fn post_node(url: String, body: String) -> reqwest::Response {
#[node_macro::node(category("Network"))]
async fn post_request(_: (), url: String, body: String) -> reqwest::Response {
reqwest::Client::new().post(url).body(body).send().await.unwrap()
}

View File

@@ -1,23 +1,24 @@
use graphene_core::raster::ImageFrame;
use graphene_core::transform::Footprint;
use graphene_core::Color;
use graphene_core::Node;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ImageColorPaletteNode<MaxSize> {
max_size: MaxSize,
}
#[node_macro::node_fn(ImageColorPaletteNode)]
fn image_color_palette(frame: ImageFrame<Color>, max_size: u32) -> Vec<Color> {
const GRID: f32 = 3.0;
#[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>>,
#[min(1.)]
#[max(28.)]
max_size: u32,
) -> Vec<Color> {
const GRID: f32 = 3.;
let bins = GRID * GRID * GRID;
let mut histogram: Vec<usize> = vec![0; (bins + 1.0) as usize];
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.0) as usize];
for pixel in frame.image.data.iter() {
let image = image.eval(footprint).await;
for pixel in image.image.data.iter() {
let r = pixel.r() * GRID;
let g = pixel.g() * GRID;
let b = pixel.b() * GRID;
@@ -57,28 +58,34 @@ fn image_color_palette(frame: ImageFrame<Color>, max_size: u32) -> Vec<Color> {
palette.push(color);
}
return palette;
palette
}
#[cfg(test)]
mod test {
use super::*;
use graphene_core::{raster::Image, value::CopiedNode};
use graph_craft::generic::FnNode;
use graphene_core::{raster::Image, value::CopiedNode, Node};
#[test]
fn test_image_color_palette() {
assert_eq!(
ImageColorPaletteNode { max_size: CopiedNode(1u32) }.eval(ImageFrame {
image: Image {
width: 100,
height: 100,
data: vec![Color::from_rgbaf32(0.0, 0.0, 0.0, 1.0).unwrap(); 10000],
base64_string: None,
},
..Default::default()
let node = ImageColorPaletteNode {
max_size: CopiedNode(1u32),
image: FnNode::new(|_| {
Box::pin(async move {
ImageFrame {
image: Image {
width: 100,
height: 100,
data: vec![Color::from_rgbaf32(0.0, 0.0, 0.0, 1.0).unwrap(); 10000],
base64_string: None,
},
..Default::default()
}
})
}),
[Color::from_rgbaf32(0.0, 0.0, 0.0, 1.0).unwrap()]
);
};
assert_eq!(futures::executor::block_on(node.eval(())), [Color::from_rgbaf32(0.0, 0.0, 0.0, 1.0).unwrap()]);
}
}

View File

@@ -1,126 +0,0 @@
use std::collections::hash_map::HashMap;
use graphene_core::raster::{Color, ImageFrame};
use graphene_core::Node;
fn apply_mask(image_frame: &mut ImageFrame<Color>, x: usize, y: usize, multiplier: u8) {
let color = &mut image_frame.image.data[y * image_frame.image.width as usize + x];
let color8 = color.to_rgba8_srgb();
*color = Color::from_rgba8_srgb(color8[0] * multiplier, color8[1] * multiplier, color8[2] * multiplier, color8[3] * multiplier);
}
pub struct Mask {
pub data: Vec<u8>,
pub width: usize,
pub height: usize,
}
impl Mask {
fn sample(&self, u: f32, v: f32) -> u8 {
let x = (u * (self.width as f32)) as usize;
let y = (v * (self.height as f32)) as usize;
self.data[y * self.width + x]
}
}
fn image_segmentation(input_image: &ImageFrame<Color>, input_mask: &Mask) -> Vec<ImageFrame<Color>> {
const NUM_LABELS: usize = u8::MAX as usize;
let mut result = Vec::<ImageFrame<Color>>::with_capacity(NUM_LABELS);
let mut current_label = 0_usize;
let mut label_appeared = [false; NUM_LABELS + 1];
let mut max_label = 0_usize;
if input_mask.data.is_empty() {
warn!("The mask for the segmentation node is empty!");
return vec![ImageFrame::empty()];
}
result.push(input_image.clone());
let result_last = result.last_mut().unwrap();
for y in 0..input_image.image.height {
let v = (y as f32) / (input_image.image.height as f32);
for x in 0..input_image.image.width {
let u = (x as f32) / (input_image.image.width as f32);
let label = input_mask.sample(u, v) as usize;
let multiplier = (label == current_label) as u8;
apply_mask(result_last, x as usize, y as usize, multiplier);
if label < NUM_LABELS {
label_appeared[label] = true;
max_label = max_label.max(label);
}
}
}
if !label_appeared[current_label] {
result.pop();
}
for i in 1..=max_label.max(NUM_LABELS) {
current_label = i;
if !label_appeared[current_label] {
continue;
}
result.push(input_image.clone());
let result_last = result.last_mut().unwrap();
for y in 0..input_image.image.height {
let v = (y as f32) / (input_image.image.height as f32);
for x in 0..input_image.image.width {
let u = (x as f32) / (input_image.image.width as f32);
let label = input_mask.sample(u, v) as usize;
let multiplier = (label == current_label) as u8;
apply_mask(result_last, x as usize, y as usize, multiplier);
}
}
}
result
}
fn convert_image_to_mask(input: &ImageFrame<Color>) -> Vec<u8> {
let mut result = vec![0_u8; (input.image.width * input.image.height) as usize];
let mut colors = HashMap::<[u8; 4], usize>::new();
let mut last_value = 0_usize;
for (color, result) in input.image.data.iter().zip(result.iter_mut()) {
let color = color.to_rgba8_srgb();
if let Some(value) = colors.get(&color) {
*result = *value as u8;
} else {
if last_value > u8::MAX as usize {
warn!("The limit for number of segments ({}) has been exceeded!", u8::MAX);
break;
}
*result = last_value as u8;
colors.insert(color, last_value);
last_value += 1;
}
}
result
}
#[derive(Debug)]
pub struct ImageSegmentationNode<MaskImage> {
pub(crate) mask_image: MaskImage,
}
#[node_macro::node_fn(ImageSegmentationNode)]
pub(crate) fn image_segmentation(image: ImageFrame<Color>, mask_image: ImageFrame<Color>) -> Vec<ImageFrame<Color>> {
let mask_data = convert_image_to_mask(&mask_image);
let mask = Mask {
data: mask_data,
width: mask_image.image.width as usize,
height: mask_image.image.height as usize,
};
image_segmentation(&image, &mask)
}

View File

@@ -7,6 +7,8 @@ extern crate log;
pub mod raster;
pub mod text;
pub mod vector;
pub mod http;
@@ -16,13 +18,8 @@ pub mod any;
#[cfg(feature = "gpu")]
pub mod gpu_nodes;
#[cfg(feature = "quantization")]
pub mod quantization;
pub use graphene_core::*;
pub mod image_segmentation;
pub mod image_color_palette;
pub mod brush;

View File

@@ -1,105 +0,0 @@
use autoquant::packing::ErrorFunction;
use graphene_core::quantization::*;
use graphene_core::raster::{Color, ImageFrame};
use graphene_core::Node;
/// The `GenerateQuantizationNode` encodes the brightness of each channel of the image as an integer number
/// signified by the samples parameter. This node is used to asses the loss of visual information when
/// quantizing the image using different fit functions.
pub struct GenerateQuantizationNode<N, M> {
samples: N,
function: M,
}
#[node_macro::node_fn(GenerateQuantizationNode)]
fn generate_quantization_fn(image_frame: ImageFrame<Color>, samples: u32, function: u32) -> [Quantization; 4] {
generate_quantization_from_image_frame(&image_frame)
}
pub fn generate_quantization_from_image_frame(image_frame: &ImageFrame<Color>) -> [Quantization; 4] {
let image = &image_frame.image;
let len = image.data.len().min(10000);
let data = image
.data
.iter()
.enumerate()
.filter(|(i, _)| i % (image.data.len() / len) == 0)
.flat_map(|(_, x)| vec![x.r() as f64, x.g() as f64, x.b() as f64, x.a() as f64])
.collect::<Vec<_>>();
generate_quantization(data, len)
}
fn generate_quantization(data: Vec<f64>, samples: usize) -> [Quantization; 4] {
let red = create_distribution(data.clone(), samples, 0);
let green = create_distribution(data.clone(), samples, 1);
let blue = create_distribution(data.clone(), samples, 2);
let alpha = create_distribution(data, samples, 3);
let fit_red = autoquant::calculate_error_function(&red, 1, &red);
let fit_green = autoquant::calculate_error_function(&green, 1, &green);
let fit_blue = autoquant::calculate_error_function(&blue, 1, &blue);
let fit_alpha = autoquant::calculate_error_function(&alpha, 1, &alpha);
let red_error: ErrorFunction<10> = autoquant::packing::ErrorFunction::new(fit_red.as_slice());
let green_error: ErrorFunction<10> = autoquant::packing::ErrorFunction::new(fit_green.as_slice());
let blue_error: ErrorFunction<10> = autoquant::packing::ErrorFunction::new(fit_blue.as_slice());
let alpha_error: ErrorFunction<10> = autoquant::packing::ErrorFunction::new(fit_alpha.as_slice());
let merged: ErrorFunction<20> = autoquant::packing::merge_error_functions(&red_error, &green_error);
let merged: ErrorFunction<30> = autoquant::packing::merge_error_functions(&merged, &blue_error);
let merged: ErrorFunction<40> = autoquant::packing::merge_error_functions(&merged, &alpha_error);
let bin_size = 8;
let mut distributions = [red, green, blue, alpha].into_iter();
let bits = &merged.bits[bin_size];
core::array::from_fn(|i| {
let fit = autoquant::models::OptimizedLin::new(distributions.next().unwrap(), (1 << bits[i]) - 1);
let parameters = fit.parameters();
Quantization::new(parameters[0] as f32, parameters[1] as f32, bits[i] as u32)
})
}
/*
// TODO: make this work with generic size parameters
fn generate_quantization<const N: usize>(data: Vec<f64>, samples: usize, channels: usize) -> [Quantization; N] {
let mut quantizations = Vec::new();
let mut merged_error: Option<ErrorFunction<10>> = None;
let bin_size = 32;
for i in 0..channels {
let channel_data = create_distribution(data.clone(), samples, i);
let fit = autoquant::calculate_error_function(&channel_data, 0, &channel_data);
let error: ErrorFunction<10> = autoquant::packing::ErrorFunction::new(fit.as_slice());
// Merge current error function with previous ones
merged_error = match merged_error {
Some(prev_error) => Some(autoquant::packing::merge_error_functions(&prev_error, &error)),
None => Some(error.clone()),
};
println!("Merged: {merged_error:?}");
let bits = merged_error.as_ref().unwrap().bits.iter().map(|x| x[i]).collect::<Vec<_>>();
let model_fit = autoquant::models::OptimizedLin::new(channel_data, 1 << bits[bin_size]);
let parameters = model_fit.parameters();
let quantization = Quantization::new(parameters[0] as f32, parameters[1] as u32, bits[bin_size] as u32);
quantizations.push(quantization);
}
core::array::from_fn(|x| quantizations[x])
}*/
fn create_distribution(data: Vec<f64>, samples: usize, channel: usize) -> Vec<(f64, f64)> {
let data: Vec<f64> = data.chunks(4 * (data.len() / (4 * samples.min(data.len() / 4)))).map(|x| x[channel]).collect();
let max = *data.iter().max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)).unwrap();
let data: Vec<f64> = data.iter().map(|x| x / max).collect();
dbg!(max);
// let data = autoquant::generate_normal_distribution(3.0, 1.1, 1000);
// data.iter_mut().for_each(|x| *x = x.abs());
let mut dist = autoquant::integrate_distribution(data);
autoquant::drop_duplicates(&mut dist);
let dist = autoquant::normalize_distribution(dist.as_slice());
dist
}

View File

@@ -1,16 +1,15 @@
use crate::wasm_application_io::WasmEditorApi;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod};
use graph_craft::proto::DynFuture;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::{
Alpha, Bitmap, BitmapMut, BlendMode, BlendNode, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, ImageFrame, Linear, LinearChannel, Luminance, NoiseType, Pixel,
RGBMut, RedGreenBlue, Sample,
Alpha, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, ImageFrame, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue,
Sample,
};
use graphene_core::transform::{Footprint, Transform};
use graphene_core::value::CopiedNode;
use graphene_core::{AlphaBlending, Color, Node, WasmNotSend};
use graphene_core::{AlphaBlending, Color, Node};
use fastnoise_lite;
use glam::{DAffine2, DVec2, Vec2};
@@ -20,7 +19,6 @@ use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::marker::PhantomData;
use std::path::Path;
#[derive(Debug, DynAny)]
pub enum Error {
@@ -34,40 +32,9 @@ impl From<std::io::Error> for Error {
}
}
pub trait FileSystem {
fn open<P: AsRef<Path>>(&self, path: P) -> Result<Box<dyn std::io::Read>, Error>;
}
#[derive(Clone)]
pub struct StdFs;
impl FileSystem for StdFs {
fn open<P: AsRef<Path>>(&self, path: P) -> Result<Reader, Error> {
Ok(Box::new(std::fs::File::open(path)?))
}
}
type Reader = Box<dyn std::io::Read>;
pub struct FileNode<FileSystem> {
fs: FileSystem,
}
#[node_macro::node_fn(FileNode)]
fn file_node<P: AsRef<Path>, FS: FileSystem>(path: P, fs: FS) -> Result<Reader, Error> {
fs.open(path)
}
pub struct BufferNode;
#[node_macro::node_fn(BufferNode)]
fn buffer_node<R: std::io::Read>(reader: R) -> Result<Vec<u8>, Error> {
Ok(std::io::Read::bytes(reader).collect::<Result<Vec<_>, _>>()?)
}
pub struct SampleNode<ImageFrame> {
image_frame: ImageFrame,
}
#[node_macro::node_fn(SampleNode)]
fn sample(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFrame<Color> {
// resize the image using the image crate
#[node_macro::node(category("Debug: Raster"))]
fn sample_image(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFrame<Color> {
// Resize the image using the image crate
let image = image_frame.image;
let data = bytemuck::cast_vec(image.data);
@@ -129,7 +96,7 @@ pub struct MapImageNode<P, MapFn> {
_p: PhantomData<P>,
}
#[node_macro::node_fn(MapImageNode<_P>)]
#[node_macro::old_node_fn(MapImageNode<_P>)]
fn map_image<MapFn, _P, Img: BitmapMut<Pixel = _P>>(image: Img, map_fn: &'input MapFn) -> Img
where
MapFn: for<'any_input> Node<'any_input, _P, Output = _P> + 'input,
@@ -148,8 +115,8 @@ pub struct InsertChannelNode<P, S, Insertion, TargetChannel> {
_s: PhantomData<S>,
}
#[node_macro::node_fn(InsertChannelNode<_P, _S>)]
fn insert_channel_node<
#[node_macro::old_node_fn(InsertChannelNode<_P, _S>)]
fn insert_channel<
// _P is the color of the input image.
_P: RGBMut,
_S: Pixel + Luminance,
@@ -195,7 +162,7 @@ pub struct MaskImageNode<P, S, Stencil> {
_s: PhantomData<S>,
}
#[node_macro::node_fn(MaskImageNode<_P, _S>)]
#[node_macro::old_node_fn(MaskImageNode<_P, _S>)]
fn mask_image<
// _P is the color of the input image. It must have an alpha channel because that is going to
// be modified by the mask
@@ -247,7 +214,7 @@ pub struct BlendImageTupleNode<P, Fg, MapFn> {
_fg: PhantomData<Fg>,
}
#[node_macro::node_fn(BlendImageTupleNode<_P, _Fg>)]
#[node_macro::old_node_fn(BlendImageTupleNode<_P, _Fg>)]
fn blend_image_tuple<_P: Alpha + Pixel + Debug, MapFn, _Fg: Sample<Pixel = _P> + Transform>(images: (ImageFrame<_P>, _Fg), map_fn: &'input MapFn) -> ImageFrame<_P>
where
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input + Clone,
@@ -257,72 +224,6 @@ where
blend_image(foreground, background, map_fn)
}
#[derive(Debug, Clone, Copy)]
pub struct BlendImageNode<P, Background, MapFn> {
background: Background,
map_fn: MapFn,
_p: PhantomData<P>,
}
#[node_macro::node_fn(BlendImageNode<_P>)]
async fn blend_image_node<_P: Alpha + Pixel + Debug + WasmNotSend + Sync + 'static, MapFn, Forground: Sample<Pixel = _P> + Transform + Send>(
foreground: Forground,
background: ImageFrame<_P>,
map_fn: &'input MapFn,
) -> ImageFrame<_P>
where
for<'a> MapFn: Node<'a, (_P, _P), Output = _P> + 'input,
{
blend_new_image(foreground, background, map_fn)
}
#[derive(Debug, Clone, Copy)]
pub struct BlendReverseImageNode<P, Background, MapFn> {
background: Background,
map_fn: MapFn,
_p: PhantomData<P>,
}
#[node_macro::node_fn(BlendReverseImageNode<_P>)]
fn blend_image_node<_P: Alpha + Pixel + Debug, MapFn, Background: Transform + Sample<Pixel = _P>>(foreground: ImageFrame<_P>, background: Background, map_fn: &'input MapFn) -> ImageFrame<_P>
where
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input,
{
blend_new_image(background, foreground, map_fn)
}
fn blend_new_image<'input, _P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform>(foreground: Frame, background: ImageFrame<_P>, map_fn: &'input MapFn) -> ImageFrame<_P>
where
MapFn: Node<'input, (_P, _P), Output = _P>,
{
let foreground_aabb = Bbox::unit().affine_transform(foreground.transform()).to_axis_aligned_bbox();
let background_aabb = Bbox::unit().affine_transform(background.transform()).to_axis_aligned_bbox();
let Some(aabb) = foreground_aabb.union_non_empty(&background_aabb) else {
return ImageFrame::empty();
};
if background_aabb.contains(foreground_aabb.start) && background_aabb.contains(foreground_aabb.end) {
return blend_image(foreground, background, map_fn);
}
// Clamp the foreground image to the background image
let start = aabb.start.as_uvec2();
let end = aabb.end.as_uvec2();
let new_background = Image::new(end.x - start.x, end.y - start.y, _P::TRANSPARENT);
let size = DVec2::new(new_background.width as f64, new_background.height as f64);
let transfrom = DAffine2::from_scale_angle_translation(size, 0., start.as_dvec2());
let mut new_background = ImageFrame {
image: new_background,
transform: transfrom,
alpha_blending: background.alpha_blending,
};
new_background = blend_image(background, new_background, map_fn);
blend_image(foreground, new_background, map_fn)
}
fn blend_image<'input, _P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: BitmapMut<Pixel = _P> + Transform + Sample<Pixel = _P>>(
foreground: Frame,
background: Background,
@@ -370,30 +271,13 @@ where
background
}
#[derive(Debug, Clone, Copy)]
pub struct ExtendImageNode<Background> {
background: Background,
}
#[node_macro::node_fn(ExtendImageNode)]
fn extend_image_node(foreground: ImageFrame<Color>, background: ImageFrame<Color>) -> ImageFrame<Color> {
let foreground_aabb = Bbox::unit().affine_transform(foreground.transform()).to_axis_aligned_bbox();
let background_aabb = Bbox::unit().affine_transform(background.transform()).to_axis_aligned_bbox();
if foreground_aabb.contains(background_aabb.start) && foreground_aabb.contains(background_aabb.end) {
return foreground;
}
blend_image(foreground, background, &BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.)))
}
#[derive(Debug, Clone, Copy)]
pub struct ExtendImageToBoundsNode<Bounds> {
bounds: Bounds,
}
#[node_macro::node_fn(ExtendImageToBoundsNode)]
fn extend_image_to_bounds_node(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFrame<Color> {
#[node_macro::old_node_fn(ExtendImageToBoundsNode)]
fn extend_image_to_bounds(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFrame<Color> {
let image_aabb = Bbox::unit().affine_transform(image.transform()).to_axis_aligned_bbox();
let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();
if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {
@@ -401,7 +285,7 @@ fn extend_image_to_bounds_node(image: ImageFrame<Color>, bounds: DAffine2) -> Im
}
if image.image.width == 0 || image.image.height == 0 {
return EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)).eval(bounds);
return empty_image((), bounds, Color::TRANSPARENT);
}
let orig_image_scale = DVec2::new(image.image.width as f64, image.image.height as f64);
@@ -433,32 +317,8 @@ fn extend_image_to_bounds_node(image: ImageFrame<Color>, bounds: DAffine2) -> Im
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MergeBoundingBoxNode<Data> {
_data: PhantomData<Data>,
}
#[node_macro::node_fn(MergeBoundingBoxNode<_Data>)]
fn merge_bounding_box_node<_Data: Transform>(input: (Option<AxisAlignedBbox>, _Data)) -> Option<AxisAlignedBbox> {
let (initial_aabb, data) = input;
let snd_aabb = Bbox::unit().affine_transform(data.transform()).to_axis_aligned_bbox();
if let Some(fst_aabb) = initial_aabb {
fst_aabb.union_non_empty(&snd_aabb)
} else {
Some(snd_aabb)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct EmptyImageNode<P, FillColor> {
pub color: FillColor,
_p: PhantomData<P>,
}
#[node_macro::node_fn(EmptyImageNode<_P>)]
fn empty_image<_P: Pixel>(transform: DAffine2, color: _P) -> ImageFrame<_P> {
#[node_macro::node(category("Debug: Raster"))]
fn empty_image<P: Pixel>(_: (), transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame<P> {
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
@@ -575,7 +435,7 @@ pub struct ImageFrameNode<P, Transform> {
transform: Transform,
_p: PhantomData<P>,
}
#[node_macro::node_fn(ImageFrameNode<_P>)]
#[node_macro::old_node_fn(ImageFrameNode<_P>)]
fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> graphene_core::raster::ImageFrame<_P> {
graphene_core::raster::ImageFrame {
image,
@@ -584,42 +444,7 @@ fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> graphene_cor
}
}
#[derive(Debug, Clone, Copy)]
pub struct NoisePatternNode<
Clip,
Seed,
Scale,
NoiseType,
DomainWarpType,
DomainWarpAmplitude,
FractalType,
FractalOctaves,
FractalLacunarity,
FractalGain,
FractalWeightedStrength,
FractalPingPongStrength,
CellularDistanceFunction,
CellularReturnType,
CellularJitter,
> {
clip: Clip,
seed: Seed,
scale: Scale,
noise_type: NoiseType,
domain_warp_type: DomainWarpType,
domain_warp_amplitude: DomainWarpAmplitude,
fractal_type: FractalType,
fractal_octaves: FractalOctaves,
fractal_lacunarity: FractalLacunarity,
fractal_gain: FractalGain,
fractal_weighted_strength: FractalWeightedStrength,
fractal_ping_pong_strength: FractalPingPongStrength,
cellular_distance_function: CellularDistanceFunction,
cellular_return_type: CellularReturnType,
cellular_jitter: CellularJitter,
}
#[node_macro::node_fn(NoisePatternNode)]
#[node_macro::node(category("Raster: Generator"))]
#[allow(clippy::too_many_arguments)]
fn noise_pattern(
footprint: Footprint,
@@ -769,11 +594,8 @@ fn noise_pattern(
}
}
#[derive(Debug, Clone, Copy)]
pub struct MandelbrotNode;
#[node_macro::node_fn(MandelbrotNode)]
fn mandelbrot_node(footprint: Footprint) -> ImageFrame<Color> {
#[node_macro::node(category("Raster: Generator"))]
fn mandelbrot(footprint: Footprint) -> ImageFrame<Color> {
let viewport_bounds = footprint.viewport_bounds_in_local_space();
let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox();
@@ -801,7 +623,7 @@ fn mandelbrot_node(footprint: Footprint) -> ImageFrame<Color> {
let pos = Vec2::new(x as f32, y as f32);
let c = pos * scale + coordinate_offset;
let iter = mandelbrot(c, max_iter);
let iter = mandelbrot_impl(c, max_iter);
data.push(map_color(iter, max_iter));
}
}
@@ -818,7 +640,7 @@ fn mandelbrot_node(footprint: Footprint) -> ImageFrame<Color> {
}
#[inline(always)]
fn mandelbrot(c: Vec2, max_iter: usize) -> usize {
fn mandelbrot_impl(c: Vec2, max_iter: usize) -> usize {
let mut z = Vec2::new(0.0, 0.0);
for i in 0..max_iter {
z = Vec2::new(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
@@ -833,21 +655,3 @@ fn map_color(iter: usize, max_iter: usize) -> Color {
let v = iter as f32 / max_iter as f32;
Color::from_rgbaf32_unchecked(v, v, v, 1.)
}
#[cfg(test)]
mod test {
#[test]
fn load_image() {
// TODO: reenable this test
/*
let image = image_node::<&str>();
let grayscale_picture = image.then(MapResultNode::new(&image));
let export = export_image_node();
let picture = grayscale_picture.eval("test-image-1.png").expect("Failed to load image");
export.eval((picture, "test-image-1-result.png")).unwrap();
*/
}
}

View File

@@ -0,0 +1,9 @@
use graph_craft::wasm_application_io::WasmEditorApi;
pub use graphene_core::text::{bounding_box, load_face, to_path, Font, FontCache};
#[node_macro::node(category(""))]
fn text<'i: 'n>(_: (), editor: &'i WasmEditorApi, text: String, font_name: Font, #[default(24)] font_size: f64) -> crate::vector::VectorData {
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None), false)
}

View File

@@ -14,8 +14,8 @@ pub struct BooleanOperationNode<BooleanOp> {
operation: BooleanOp,
}
#[node_macro::node_fn(BooleanOperationNode)]
fn boolean_operation_node(group_of_paths: GraphicGroup, operation: BooleanOperation) -> VectorData {
#[node_macro::old_node_fn(BooleanOperationNode)]
fn boolean_operation(group_of_paths: GraphicGroup, operation: BooleanOperation) -> VectorData {
fn vector_from_image<T: Transform>(image_frame: T) -> VectorData {
let corner1 = DVec2::ZERO;
let corner2 = DVec2::new(1., 1.);

View File

@@ -11,7 +11,8 @@ use graphene_core::raster::ImageFrame;
use graphene_core::renderer::RenderMetadata;
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
use graphene_core::transform::Footprint;
use graphene_core::Node;
use graphene_core::vector::VectorData;
use graphene_core::GraphicGroup;
use graphene_core::{Color, WasmNotSend};
#[cfg(target_arch = "wasm32")]
@@ -27,24 +28,14 @@ use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")]
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
pub struct CreateSurfaceNode {}
#[node_macro::node_fn(CreateSurfaceNode)]
async fn create_surface_node<'a: 'input>(editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
#[node_macro::node(category("Debug: GPU"))]
async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
Arc::new(editor.application_io.as_ref().unwrap().create_window())
}
#[node_macro::node(category("Debug: GPU"))]
#[cfg(target_arch = "wasm32")]
pub struct DrawImageFrameNode<Surface> {
surface_handle: Surface,
}
#[node_macro::node_fn(DrawImageFrameNode)]
#[cfg(target_arch = "wasm32")]
async fn draw_image_frame_node<'a: 'input>(
image: ImageFrame<graphene_core::raster::SRGBA8>,
surface_handle: Arc<WasmSurfaceHandle>,
) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
async fn draw_image_frame(_: (), image: ImageFrame<graphene_core::raster::SRGBA8>, surface_handle: Arc<WasmSurfaceHandle>) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
let image_data = image.image.data;
let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
if image.image.width > 0 && image.image.height > 0 {
@@ -62,19 +53,13 @@ async fn draw_image_frame_node<'a: 'input>(
}
}
pub struct LoadResourceNode<Url> {
url: Url,
}
#[node_macro::node_fn(LoadResourceNode)]
async fn load_resource_node<'a: 'input>(editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> {
#[node_macro::node(category("Network"))]
async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> {
editor.application_io.as_ref().unwrap().load_resource(url).unwrap().await.unwrap()
}
pub struct DecodeImageNode;
#[node_macro::node_fn(DecodeImageNode)]
fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
#[node_macro::node(category("Raster"))]
fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrame<Color> {
let image = image::load_from_memory(data.as_ref()).expect("Failed to decode image");
let image = image.to_rgba32f();
let image = ImageFrame {
@@ -144,25 +129,21 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
RenderOutputType::CanvasFrame(frame)
}
#[node_macro::node(category(""))]
#[cfg(target_arch = "wasm32")]
pub struct RasterizeNode<Footprint, Surface> {
footprint: Footprint,
surface_handle: Surface,
}
#[node_macro::node_fn(RasterizeNode)]
#[cfg(target_arch = "wasm32")]
async fn rasterize<_T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend>(
mut data: _T,
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>,
footprint: Footprint,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
) -> ImageFrame<Color> {
let mut render = SvgRender::new();
if footprint.transform.matrix2.determinant() == 0. {
log::trace!("Invalid footprint received for rasterization");
return ImageFrame::default();
}
let mut data = data.eval(footprint).await;
let mut render = SvgRender::new();
let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox();
let size = aabb.size();
let resolution = footprint.resolution;
@@ -204,16 +185,23 @@ async fn rasterize<_T: GraphicElementRendered + graphene_core::transform::Transf
}
}
pub struct RenderNode<EditorApi, Data, Surface> {
editor_api: EditorApi,
data: Data,
_surface_handle: Surface,
}
#[node_macro::node_fn(RenderNode)]
async fn render_node<'a: 'input, T: 'input + GraphicElementRendered + WasmNotSend>(
#[node_macro::node(category(""))]
async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
render_config: RenderConfig,
editor_api: &'a WasmEditorApi,
#[implementations(
(Footprint, VectorData),
(Footprint, ImageFrame<Color>),
(Footprint, GraphicGroup),
(Footprint, graphene_core::Artboard),
(Footprint, graphene_core::ArtboardGroup),
(Footprint, Option<Color>),
(Footprint, Vec<Color>),
(Footprint, bool),
(Footprint, f32),
(Footprint, f64),
(Footprint, String),
)]
data: impl Node<Footprint, Output = T>,
_surface_handle: impl Node<(), Output = Option<wgpu_executor::WgpuSurface>>,
) -> RenderOutput {
@@ -222,9 +210,9 @@ async fn render_node<'a: 'input, T: 'input + GraphicElementRendered + WasmNotSen
let RenderConfig { hide_artboards, for_export, .. } = render_config;
let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
let data = self.data.eval(footprint).await;
let data = data.eval(footprint).await;
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
let surface_handle = self._surface_handle.eval(()).await;
let surface_handle = _surface_handle.eval(()).await;
let use_vello = editor_api.editor_preferences.use_vello();
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
let use_vello = use_vello && surface_handle.is_some();

View File

@@ -8,7 +8,6 @@ license = "MIT OR Apache-2.0"
default = []
serde = ["dep:serde", "graphene-std/serde", "glam/serde"]
gpu = ["graphene-std/gpu", "graphene-core/gpu", "graphene-std/wgpu"]
quantization = ["graphene-std/quantization"]
[dependencies]
# Local dependencies

View File

@@ -3,70 +3,10 @@ pub mod node_registry;
#[cfg(test)]
mod tests {
use graph_craft::document::value::TaggedValue;
use graphene_core::*;
use futures::executor::block_on;
#[test]
fn execute_add() {
use graph_craft::document::*;
use graph_craft::*;
fn add_network() -> NodeNetwork {
NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
(
NodeId(0),
DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(&u32), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::structural::ConsNode<_, _>")),
..Default::default()
},
),
(
NodeId(1),
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::AddPairNode")),
..Default::default()
},
),
]
.into_iter()
.collect(),
..Default::default()
}
}
let network = NodeNetwork {
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: [(
NodeId(0),
DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::value(graph_craft::document::value::TaggedValue::U32(1u32), false)],
implementation: DocumentNodeImplementation::Network(add_network()),
..Default::default()
},
)]
.into_iter()
.collect(),
..Default::default()
};
use crate::dynamic_executor::DynamicExecutor;
use graph_craft::graphene_compiler::{Compiler, Executor};
let compiler = Compiler {};
let protograph = compiler.compile_single(network).expect("Graph should be generated");
let exec = block_on(DynamicExecutor::new(protograph)).unwrap_or_else(|e| panic!("Failed to create executor: {e:?}"));
let result = block_on((&exec).execute(32_u32)).unwrap();
assert_eq!(result, TaggedValue::U32(33));
}
#[test]
fn double_number() {
use graph_craft::document::*;
@@ -89,7 +29,7 @@ mod tests {
NodeId(1),
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::AddNode<_>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::AddNode")),
..Default::default()
},
),

View File

@@ -4,29 +4,22 @@ use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFil
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
use graphene_core::fn_type;
use graphene_core::ops::IdentityNode;
use graphene_core::quantization::{PackedPixel, QuantizationChannels};
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::color::Color;
use graphene_core::raster::*;
use graphene_core::structural::Then;
use graphene_core::transform::Footprint;
use graphene_core::value::{ClonedNode, ValueNode};
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::VectorData;
#[cfg(target_arch = "wasm32")]
use graphene_core::WasmSurfaceHandleFrame;
use graphene_core::{concrete, generic, Artboard, ArtboardGroup, GraphicGroup};
use graphene_core::{concrete, generic, Artboard, GraphicGroup};
use graphene_core::{Cow, ProtoNodeIdentifier, Type};
use graphene_core::{Node, NodeIO, NodeIOTypes};
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
use graphene_std::application_io::{RenderConfig, TextureFrame};
use graphene_std::application_io::TextureFrame;
use graphene_std::raster::*;
use graphene_std::uuid::NodeId;
use graphene_std::vector::style::GradientStops;
use graphene_std::wasm_application_io::*;
use graphene_std::GraphicElement;
#[cfg(feature = "gpu")]
use wgpu_executor::{CommandBuffer, ShaderHandle, ShaderInputFrame, WgpuExecutor, WgpuShaderInput};
use wgpu_executor::{ShaderInputFrame, WgpuExecutor};
use wgpu_executor::{WgpuSurface, WindowHandle};
use glam::{DAffine2, DVec2, UVec2};
@@ -47,7 +40,6 @@ macro_rules! construct_node {
),*
);
node
}}
}
@@ -56,7 +48,6 @@ macro_rules! register_node {
register_node!($path, input: $input, fn_params: [ $(() => $type),*])
};
($path:ty, input: $input:ty, fn_params: [ $($arg:ty => $type:ty),*]) => {
vec![
(
ProtoNodeIdentifier::new(stringify!($path)),
|args| {
@@ -77,7 +68,6 @@ macro_rules! register_node {
node_io
},
)
]
};
}
macro_rules! async_node {
@@ -85,11 +75,10 @@ macro_rules! async_node {
// assign a Pin<Box<dyn Future<Output=T>>> type to the node, which is not what we want for now.
//
// This `params` variant of the macro wraps the normal `fn_params` variant and is used as a shorthand for writing `T` instead of `() => T`
($path:ty, input: $input:ty, output: $output:ty, params: [ $($type:ty),*]) => {
async_node!($path, input: $input, output: $output, fn_params: [ $(() => $type),*])
($path:ty, input: $input:ty, params: [$($type:ty),*]) => {
async_node!($path, input: $input, fn_params: [ $(() => $type),*])
};
($path:ty, input: $input:ty, output: $output:ty, fn_params: [ $($arg:ty => $type:ty),*]) => {
vec![
($path:ty, input: $input:ty, fn_params: [$($arg:ty => $type:ty),*]) => {
(
ProtoNodeIdentifier::new(stringify!($path)),
|mut args| {
@@ -107,215 +96,40 @@ macro_rules! async_node {
// TODO: Propagate the future type through the node graph
// let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(Type::Future(Box::new(concrete!($type)))))),*];
let params = vec![$(fn_type!($arg, $type)),*];
let mut node_io = NodeIO::<'_, $input>::to_node_io(&node, params);
let mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params);
node_io.input = concrete!(<$input as StaticType>::Static);
node_io.input = concrete!(<$input as StaticType>::Static); // Why are there 2 of them?
node_io.output = concrete!(<$output as StaticType>::Static);
node_io
},
)
]
};
}
macro_rules! raster_node {
($path:ty, params: [$($type:ty),*]) => {{
// this function could also be inlined but serves as a workaround for
// [wasm-pack#981](https://github.com/rustwasm/wasm-pack/issues/981).
// The non-inlining function leads to fewer locals in the resulting
// wasm binary. This issue currently only applies to debug builds, so
// we guard inlining to only happen on production builds for
// optimization purposes.
#[cfg_attr(debug_assertions, inline(never))]
#[cfg_attr(not(debug_assertions), inline)]
fn generate_triples() -> Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> {
vec![
(
ProtoNodeIdentifier::new(stringify!($path)),
|args| {
Box::pin(async move {
let node = construct_node!(args, $path, [$(() => $type),*]).await;
let node = graphene_std::any::FutureWrapperNode::new(node);
let any: DynAnyNode<Color, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
{
let params = vec![$(fn_type!($type)),*];
NodeIOTypes::new(concrete!(Color), concrete!(Color), params)
},
),
// TODO: Remove this one when we have automatic IntoNode insertion as part of the graph compilation process
(
ProtoNodeIdentifier::new(stringify!($path)),
|args| {
Box::pin(async move {
let node = construct_node!(args, $path, [$(() => $type),*]).await;
let map_node = graphene_core::ops::MapOptionNode::new(graphene_core::value::ValueNode::new(node));
let node = graphene_std::any::FutureWrapperNode::new(map_node);
let any: DynAnyNode<Option<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
{
let params = vec![$(fn_type!($type)),*];
NodeIOTypes::new(concrete!(Option<Color>), concrete!(Option<Color>), params)
},
),
(
ProtoNodeIdentifier::new(stringify!($path)),
|args| {
Box::pin(async move {
let node = construct_node!(args, $path, [$(() => $type),*]).await;
let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node));
let map_node = graphene_std::any::FutureWrapperNode::new(map_node);
let any: DynAnyNode<Image<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_node);
any.into_type_erased()
})
},
{
let params = vec![$(fn_type!($type)),*];
NodeIOTypes::new(concrete!(Image<Color>), concrete!(Image<Color>), params)
},
),
(
ProtoNodeIdentifier::new(stringify!($path)),
|args| {
Box::pin(async move {
let node = construct_node!(args, $path, [$(() => $type),*]).await;
let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node));
let map_node = graphene_std::any::FutureWrapperNode::new(map_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_node);
any.into_type_erased()
})
},
{
let params = vec![$(fn_type!($type)),*];
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), params)
},
)
]
}
generate_triples()
}};
}
// TODO: turn into hashmap
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let node_types: Vec<Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)>> = vec![
vec![(
let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
(
ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
|_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
NodeIOTypes::new(generic!(I), generic!(I), vec![]),
)],
// TODO: create macro to impl for all types
register_node!(graphene_core::structural::ConsNode<_, _>, input: u32, params: [u32]),
register_node!(graphene_core::structural::ConsNode<_, _>, input: u32, params: [&u32]),
register_node!(graphene_core::structural::ConsNode<_, _>, input: &u32, params: [u32]),
register_node!(graphene_core::structural::ConsNode<_, _>, input: &u32, params: [&u32]),
register_node!(graphene_core::ops::AddPairNode, input: (u32, u32), params: []),
register_node!(graphene_core::ops::AddPairNode, input: (u32, &u32), params: []),
register_node!(graphene_core::ops::CloneNode<_>, input: &ImageFrame<Color>, params: []),
register_node!(graphene_core::ops::AddNode<_>, input: u32, params: [u32]),
register_node!(graphene_core::ops::AddNode<_>, input: &u32, params: [u32]),
register_node!(graphene_core::ops::AddNode<_>, input: u32, params: [&u32]),
register_node!(graphene_core::ops::AddNode<_>, input: &u32, params: [&u32]),
register_node!(graphene_core::ops::AddNode<_>, input: f32, params: [f32]),
register_node!(graphene_core::ops::AddNode<_>, input: &f32, params: [f32]),
register_node!(graphene_core::ops::AddNode<_>, input: f32, params: [&f32]),
register_node!(graphene_core::ops::AddNode<_>, input: &f32, params: [&f32]),
register_node!(graphene_core::ops::AddNode<_>, input: f64, params: [f64]),
register_node!(graphene_core::ops::AddNode<_>, input: glam::DVec2, params: [glam::DVec2]),
register_node!(graphene_core::ops::SubtractNode<_>, input: u32, params: [u32]),
register_node!(graphene_core::ops::SubtractNode<_>, input: &u32, params: [u32]),
register_node!(graphene_core::ops::SubtractNode<_>, input: u32, params: [&u32]),
register_node!(graphene_core::ops::SubtractNode<_>, input: &u32, params: [&u32]),
register_node!(graphene_core::ops::SubtractNode<_>, input: f32, params: [f32]),
register_node!(graphene_core::ops::SubtractNode<_>, input: &f32, params: [f32]),
register_node!(graphene_core::ops::SubtractNode<_>, input: f32, params: [&f32]),
register_node!(graphene_core::ops::SubtractNode<_>, input: &f32, params: [&f32]),
register_node!(graphene_core::ops::SubtractNode<_>, input: f64, params: [f64]),
register_node!(graphene_core::ops::SubtractNode<_>, input: glam::DVec2, params: [glam::DVec2]),
register_node!(graphene_core::ops::DivideNode<_>, input: u32, params: [u32]),
register_node!(graphene_core::ops::DivideNode<_>, input: &u32, params: [u32]),
register_node!(graphene_core::ops::DivideNode<_>, input: u32, params: [&u32]),
register_node!(graphene_core::ops::DivideNode<_>, input: &u32, params: [&u32]),
register_node!(graphene_core::ops::DivideNode<_>, input: f32, params: [f32]),
register_node!(graphene_core::ops::DivideNode<_>, input: &f32, params: [f32]),
register_node!(graphene_core::ops::DivideNode<_>, input: f32, params: [&f32]),
register_node!(graphene_core::ops::DivideNode<_>, input: &f32, params: [&f32]),
register_node!(graphene_core::ops::DivideNode<_>, input: f64, params: [f64]),
register_node!(graphene_core::ops::DivideNode<_>, input: glam::DVec2, params: [f64]),
register_node!(graphene_core::ops::DivideNode<_>, input: glam::DVec2, params: [glam::DVec2]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: u32, params: [u32]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: &u32, params: [u32]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: u32, params: [&u32]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: &u32, params: [&u32]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: f32, params: [f32]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: &f32, params: [f32]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: f32, params: [&f32]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: &f32, params: [&f32]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: f64, params: [f64]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: glam::DVec2, params: [f64]),
register_node!(graphene_core::ops::MultiplyNode<_>, input: glam::DVec2, params: [glam::DVec2]),
register_node!(graphene_core::ops::ExponentNode<_>, input: u32, params: [u32]),
register_node!(graphene_core::ops::ExponentNode<_>, input: &u32, params: [u32]),
register_node!(graphene_core::ops::ExponentNode<_>, input: u32, params: [&u32]),
register_node!(graphene_core::ops::ExponentNode<_>, input: &u32, params: [&u32]),
register_node!(graphene_core::ops::ExponentNode<_>, input: f32, params: [f32]),
register_node!(graphene_core::ops::ExponentNode<_>, input: &f32, params: [f32]),
register_node!(graphene_core::ops::ExponentNode<_>, input: f32, params: [&f32]),
register_node!(graphene_core::ops::ExponentNode<_>, input: f64, params: [f64]),
register_node!(graphene_core::ops::FloorNode, input: f64, params: []),
register_node!(graphene_core::ops::CeilingNode, input: f64, params: []),
register_node!(graphene_core::ops::RoundNode, input: f64, params: []),
register_node!(graphene_core::ops::AbsoluteValue, input: f64, params: []),
register_node!(graphene_core::ops::LogarithmNode<_>, input: f64, params: [f64]),
register_node!(graphene_core::ops::NaturalLogarithmNode, input: f64, params: []),
register_node!(graphene_core::ops::SineNode, input: f64, params: []),
register_node!(graphene_core::ops::CosineNode, input: f64, params: []),
register_node!(graphene_core::ops::TangentNode, input: f64, params: []),
register_node!(graphene_core::ops::MaximumNode<_>, input: u32, params: [u32]),
register_node!(graphene_core::ops::MaximumNode<_>, input: f64, params: [f64]),
register_node!(graphene_core::ops::MinimumNode<_>, input: u32, params: [u32]),
register_node!(graphene_core::ops::MinimumNode<_>, input: f64, params: [f64]),
register_node!(graphene_core::ops::EqualsNode<_>, input: u32, params: [u32]),
register_node!(graphene_core::ops::EqualsNode<_>, input: f64, params: [f64]),
register_node!(graphene_core::ops::ModuloNode<_>, input: u32, params: [u32]),
register_node!(graphene_core::ops::ModuloNode<_>, input: &u32, params: [u32]),
register_node!(graphene_core::ops::ModuloNode<_>, input: u32, params: [&u32]),
register_node!(graphene_core::ops::ModuloNode<_>, input: &u32, params: [&u32]),
register_node!(graphene_core::ops::ModuloNode<_>, input: f64, params: [f64]),
register_node!(graphene_core::ops::ModuloNode<_>, input: &f64, params: [f64]),
register_node!(graphene_core::ops::ModuloNode<_>, input: f64, params: [&f64]),
register_node!(graphene_core::ops::ModuloNode<_>, input: &f64, params: [&f64]),
register_node!(graphene_core::ops::ConstructVector2<_, _>, input: (), params: [f64, f64]),
register_node!(graphene_core::ops::SomeNode, input: &WasmEditorApi, params: []),
register_node!(graphene_core::ops::UnwrapNode, input: Option<Color>, params: []),
register_node!(graphene_core::logic::LogToConsoleNode, input: bool, params: []),
register_node!(graphene_core::logic::LogToConsoleNode, input: f64, params: []),
register_node!(graphene_core::logic::LogToConsoleNode, input: f64, params: []),
register_node!(graphene_core::logic::LogToConsoleNode, input: u32, params: []),
register_node!(graphene_core::logic::LogToConsoleNode, input: u64, params: []),
register_node!(graphene_core::logic::LogToConsoleNode, input: String, params: []),
register_node!(graphene_core::logic::LogToConsoleNode, input: DVec2, params: []),
register_node!(graphene_core::logic::LogToConsoleNode, input: VectorData, params: []),
register_node!(graphene_core::logic::LogToConsoleNode, input: DAffine2, params: []),
register_node!(graphene_core::logic::LogicOrNode<_>, input: bool, params: [bool]),
register_node!(graphene_core::logic::LogicAndNode<_>, input: bool, params: [bool]),
register_node!(graphene_core::logic::LogicXorNode<_>, input: bool, params: [bool]),
register_node!(graphene_core::logic::LogicNotNode, input: bool, params: []),
async_node!(graphene_core::ops::IntoNode<_, ImageFrame<SRGBA8>>, input: ImageFrame<Color>, output: ImageFrame<SRGBA8>, params: []),
async_node!(graphene_core::ops::IntoNode<_, ImageFrame<Color>>, input: ImageFrame<SRGBA8>, output: ImageFrame<Color>, params: []),
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: ImageFrame<Color>, output: GraphicGroup, params: []),
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: VectorData, output: GraphicGroup, params: []),
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: GraphicGroup, output: GraphicGroup, params: []),
),
async_node!(graphene_core::ops::IntoNode<ImageFrame<SRGBA8>>, input: ImageFrame<Color>, params: []),
async_node!(graphene_core::ops::IntoNode<ImageFrame<Color>>, input: ImageFrame<SRGBA8>, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: ImageFrame<Color>, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: VectorData, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: GraphicGroup, params: []),
#[cfg(feature = "gpu")]
async_node!(graphene_core::ops::IntoNode<_, &WgpuExecutor>, input: &WasmEditorApi, output: &WgpuExecutor, params: []),
async_node!(graphene_core::ops::IntoNode<&WgpuExecutor>, input: &WasmEditorApi, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: VectorData, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: ImageFrame<Color>, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: GraphicGroup, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: GraphicGroup, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: VectorData, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: ImageFrame<Color>, params: []),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>, RedGreenBlue]),
register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>, RedGreenBlue]),
vec![(
(
ProtoNodeIdentifier::new("graphene_std::raster::CombineChannelsNode"),
|args| {
Box::pin(async move {
@@ -358,64 +172,21 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
concrete!(ImageFrame<Color>),
vec![fn_type!(ImageFrame<Color>), fn_type!(ImageFrame<Color>), fn_type!(ImageFrame<Color>), fn_type!(ImageFrame<Color>)],
),
)],
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: ImageFrame<Color>, fn_params: [Footprint => ImageFrame<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), output: ImageFrame<Color>, params: [ImageFrame<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: graphene_core::GraphicGroup, fn_params: [Footprint => graphene_core::GraphicGroup]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: graphene_core::GraphicElement, fn_params: [Footprint => graphene_core::GraphicElement]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => Artboard]),
async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: &WasmEditorApi, output: Arc<[u8]>, params: [String]),
register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []),
async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: &WasmEditorApi, output: Arc<WasmSurfaceHandle>, params: []),
#[cfg(target_arch = "wasm32")]
async_node!(
graphene_std::wasm_application_io::DrawImageFrameNode<_>,
input: ImageFrame<SRGBA8>,
output: WasmSurfaceHandleFrame,
params: [Arc<WasmSurfaceHandle>]
),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => ImageFrame<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [ImageFrame<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorData]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => VectorData]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => graphene_core::GraphicGroup]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => graphene_core::GraphicGroup]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => graphene_core::GraphicElement]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => graphene_core::GraphicElement]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => Artboard]),
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::UniformNode<_>, input: f32, output: WgpuShaderInput, params: [&WgpuExecutor]),
register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: (), params: [&WasmEditorApi]),
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::StorageNode<_>, input: Vec<u8>, output: WgpuShaderInput, params: [&WgpuExecutor]),
#[cfg(feature = "gpu")]
async_node!(
wgpu_executor::PushNode<_>,
input: Vec<WgpuShaderInput>,
output: Vec<WgpuShaderInput>,
params: [WgpuShaderInput]
),
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::CreateOutputBufferNode<_, _>, input: usize, output: WgpuShaderInput, params: [&WgpuExecutor, Type]),
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::CreateComputePassNode<_, _, _>, input: wgpu_executor::PipelineLayout, output: CommandBuffer, params: [&WgpuExecutor, WgpuShaderInput, gpu_executor::ComputePassDimensions]),
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::CreatePipelineLayoutNode<_, _, _>, input: ShaderHandle, output: wgpu_executor::PipelineLayout, params: [String, wgpu_executor::Bindgroup, Arc<WgpuShaderInput>]),
#[cfg(feature = "gpu")]
async_node!(
wgpu_executor::ExecuteComputePipelineNode<_>,
input: CommandBuffer,
output: (),
params: [&WgpuExecutor]
),
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::ReadOutputBufferNode<_, _>, input: Arc<WgpuShaderInput>, output: Vec<u8>, params: [&WgpuExecutor, ()]),
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::CreateGpuSurfaceNode, input: &WasmEditorApi, output: Option<wgpu_executor::WgpuSurface>, params: []),
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::RenderTextureNode<_, _, _>, input: Footprint, output: graphene_std::SurfaceFrame, fn_params: [Footprint => ShaderInputFrame, () => Option<wgpu_executor::WgpuSurface>, () =>&WgpuExecutor]),
#[cfg(feature = "gpu")]
async_node!(
wgpu_executor::UploadTextureNode<_>,
input: ImageFrame<Color>,
output: TextureFrame,
params: [&WgpuExecutor]
),
#[cfg(feature = "gpu")]
vec![(
ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode<_>"),
(
ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"),
|args| {
Box::pin(async move {
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
@@ -431,29 +202,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
concrete!(ImageFrame<Color>),
vec![fn_type!(graph_craft::document::DocumentNode), fn_type!(WasmEditorApi)],
),
)],
#[cfg(feature = "gpu")]
vec![(
ProtoNodeIdentifier::new("graphene_std::executor::BlendGpuImageNode<_, _, _>"),
|args| {
Box::pin(async move {
let background: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0].clone());
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone());
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2].clone());
let node = graphene_std::gpu_nodes::BlendGpuImageNode::new(background, blend_mode, opacity);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
NodeIOTypes::new(
concrete!(ImageFrame<Color>),
concrete!(ImageFrame<Color>),
vec![fn_type!(ImageFrame<Color>), fn_type!(BlendMode), fn_type!(f64)],
),
)],
vec![(
ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"),
),
(
ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode"),
|args| {
Box::pin(async move {
let node = ComposeTypeErased::new(args[0].clone(), args[1].clone());
@@ -467,58 +218,10 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
generic!(U),
vec![Type::Fn(Box::new(generic!(T)), Box::new(generic!(V))), Type::Fn(Box::new(generic!(V)), Box::new(generic!(U)))],
),
)],
register_node!(graphene_std::brush::IntoIterNode<_>, input: &Vec<BrushStroke>, params: []),
async_node!(graphene_std::brush::BrushNode<_, _, _>, input: ImageFrame<Color>, output: ImageFrame<Color>, params: [ImageFrame<Color>, Vec<BrushStroke>, BrushCache]),
),
// Filters
raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]),
raster_node!(graphene_core::raster::ExtractChannelNode<_>, params: [RedGreenBlueAlpha]),
raster_node!(graphene_core::raster::ExtractOpaqueNode<>, params: []),
raster_node!(graphene_core::raster::LevelsNode<_, _, _, _, _>, params: [f64, f64, f64, f64, f64]),
raster_node!(graphene_core::raster::BlendColorsNode<_, _, _>, params: [Color, BlendMode, f64]),
register_node!(graphene_core::raster::BlendColorsNode<_, _, _>, input: GradientStops, params: [GradientStops, BlendMode, f64]),
register_node!(graphene_std::image_segmentation::ImageSegmentationNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_std::image_color_palette::ImageColorPaletteNode<_>, input: ImageFrame<Color>, params: [u32]),
register_node!(graphene_core::raster::IndexNode<_>, input: Vec<ImageFrame<Color>>, params: [u32]),
register_node!(graphene_core::raster::adjustments::ColorFillNode<_>, input: ImageFrame<Color>, params: [Color]),
register_node!(graphene_core::raster::adjustments::ColorOverlayNode<_, _, _>, input: ImageFrame<Color>, params: [Color, BlendMode, f64]),
register_node!(graphene_core::raster::IndexNode<_>, input: Vec<Color>, params: [u32]),
/*
vec![(
ProtoNodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"),
|args| {
Box::pin(async move {
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0].clone());
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone());
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2].clone());
let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(blend_mode.eval(()).await), CopiedNode::new(opacity.eval(()).await));
let node = graphene_std::raster::BlendImageNode::new(image, FutureWrapperNode::new(ValueNode::new(blend_node)));
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
NodeIOTypes::new(
concrete!(ImageFrame<Color>),
concrete!(ImageFrame<Color>),
vec![fn_type!(ImageFrame<Color>), fn_type!(BlendMode), fn_type!(f64)],
),
)],*/
raster_node!(graphene_core::raster::BlackAndWhiteNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]),
raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]),
raster_node!(graphene_core::raster::InvertNode, params: []),
raster_node!(graphene_core::raster::ThresholdNode<_, _, _>, params: [f64, f64, LuminanceCalculation]),
raster_node!(graphene_core::raster::GradientMapNode<_, _>, params: [GradientStops, bool]),
raster_node!(graphene_core::raster::VibranceNode<_>, params: [f64]),
raster_node!(
graphene_core::raster::ChannelMixerNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>,
params: [bool, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64]
),
raster_node!(
graphene_core::raster::SelectiveColorNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>,
params: [RelativeAbsolute, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64]
),
vec![(
ProtoNodeIdentifier::new("graphene_core::raster::BrightnessContrastNode<_, _, _>"),
(
ProtoNodeIdentifier::new("graphene_core::raster::BrightnessContrastNode"),
|args| {
Box::pin(async move {
use graphene_core::raster::brightness_contrast::*;
@@ -545,54 +248,44 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
})
},
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(f64), fn_type!(f64), fn_type!(bool)]),
)],
vec![
(
ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode<_>"),
|args| {
use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
Box::pin(async move {
let curve = ClonedNode::new(curve.eval(()).await);
),
(
ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
|args| {
use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
Box::pin(async move {
let curve = ClonedNode::new(curve.eval(()).await);
let generate_curves_node = GenerateCurvesNode::<f32, _>::new(curve);
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Luma>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
any.into_type_erased()
})
},
NodeIOTypes::new(concrete!(ImageFrame<Luma>), concrete!(ImageFrame<Luma>), vec![fn_type!(graphene_core::raster::curve::Curve)]),
),
// TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color.
(
ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode<_>"),
|args| {
use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
Box::pin(async move {
let curve = ClonedNode::new(curve.eval(()).await);
let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32));
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Luma>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
any.into_type_erased()
})
},
NodeIOTypes::new(concrete!(ImageFrame<Luma>), concrete!(ImageFrame<Luma>), vec![fn_type!(graphene_core::raster::curve::Curve)]),
),
// TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color.
(
ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
|args| {
use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
Box::pin(async move {
let curve = ClonedNode::new(curve.eval(()).await);
let generate_curves_node = GenerateCurvesNode::<f32, _>::new(curve);
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
any.into_type_erased()
})
},
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(graphene_core::raster::curve::Curve)]),
),
],
raster_node!(graphene_core::raster::OpacityNode<_>, params: [f64]),
register_node!(graphene_core::raster::OpacityNode<_>, input: VectorData, params: [f64]),
register_node!(graphene_core::raster::OpacityNode<_>, input: GraphicGroup, params: [f64]),
register_node!(graphene_core::raster::BlendModeNode<_>, input: VectorData, params: [BlendMode]),
register_node!(graphene_core::raster::BlendModeNode<_>, input: GraphicGroup, params: [BlendMode]),
register_node!(graphene_core::raster::BlendModeNode<_>, input: ImageFrame<Color>, params: [BlendMode]),
raster_node!(graphene_core::raster::PosterizeNode<_>, params: [f64]),
raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f64, f64, f64]),
vec![(
ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>"),
let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32));
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
any.into_type_erased()
})
},
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(graphene_core::raster::curve::Curve)]),
),
(
ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"),
|args: Vec<graph_craft::proto::SharedNodeContainer>| {
Box::pin(async move {
use graphene_std::raster::ImaginateNode;
@@ -627,192 +320,63 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
fn_type!(u64),
],
),
)],
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Image<Color>, params: [Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ImageFrame<Color>, params: [ImageFrame<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: QuantizationChannels, params: [QuantizationChannels]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Vec<DVec2>, params: [Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Arc<WasmSurfaceHandle>, params: [Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: WindowHandle, params: [WindowHandle]),
),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [VectorData]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ImageFrame<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [WindowHandle]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ShaderInputFrame, params: [ShaderInputFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ShaderInputFrame]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: wgpu_executor::WgpuSurface, params: [wgpu_executor::WgpuSurface]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Option<wgpu_executor::WgpuSurface>, params: [Option<wgpu_executor::WgpuSurface>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: wgpu_executor::WindowHandle, params: [wgpu_executor::WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: graphene_std::SurfaceFrame, params: [graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: RenderOutput, params: [RenderOutput]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: Image<Color>, fn_params: [Footprint => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: ImageFrame<Color>, fn_params: [Footprint => ImageFrame<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: QuantizationChannels, fn_params: [Footprint => QuantizationChannels]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: Vec<DVec2>, fn_params: [Footprint => Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: Arc<WasmSurfaceHandle>, fn_params: [Footprint => Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: WindowHandle, fn_params: [Footprint => WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [wgpu_executor::WgpuSurface]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Option<wgpu_executor::WgpuSurface>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [wgpu_executor::WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [RenderOutput]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => VectorData]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ImageFrame<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => WindowHandle]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: ShaderInputFrame, fn_params: [Footprint => ShaderInputFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: wgpu_executor::WgpuSurface, fn_params: [Footprint => wgpu_executor::WgpuSurface]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: Option<wgpu_executor::WgpuSurface>, fn_params: [Footprint => Option<wgpu_executor::WgpuSurface>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: wgpu_executor::WindowHandle, fn_params: [Footprint => wgpu_executor::WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: graphene_std::SurfaceFrame, fn_params: [Footprint => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, output: graphene_std::SurfaceFrame, fn_params: [UVec2 => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, output: RenderOutput, fn_params: [Footprint => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: GraphicElement, fn_params: [Footprint => GraphicElement]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => GraphicGroup]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => wgpu_executor::WgpuSurface]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Option<wgpu_executor::WgpuSurface>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => wgpu_executor::WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroup]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorData]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: ShaderInputFrame, fn_params: [Footprint => ShaderInputFrame]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: WgpuSurface, fn_params: [Footprint => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: Option<WgpuSurface>, fn_params: [Footprint => Option<WgpuSurface>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: TextureFrame, fn_params: [Footprint => TextureFrame]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => Option<WgpuSurface>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]),
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, params: [DAffine2]),
register_node!(graphene_std::raster::NoisePatternNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _>, input: Footprint, params: [bool, u32, f64, NoiseType, DomainWarpType, f64, FractalType, u32, f64, f64, f64, f64, CellularDistanceFunction, CellularReturnType, f64]),
#[cfg(feature = "quantization")]
register_node!(graphene_std::quantization::GenerateQuantizationNode<_, _>, input: ImageFrame<Color>, params: [u32, u32]),
register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),
register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]),
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => ImageFrame<Color>, () => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => VectorData, () => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => GraphicGroup, () => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Artboard, () => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => ArtboardGroup, () => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Option<Color>, () => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Vec<Color>, () => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => bool, () => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => f32, () => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => f64, () => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => String, () => Option<WgpuSurface>]),
#[cfg(target_arch = "wasm32")]
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
#[cfg(target_arch = "wasm32")]
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: GraphicGroup, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
// async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: WasmSurfaceHandleFrame, fn_params: [Footprint => WasmSurfaceHandleFrame, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: ImageFrame<Color>, fn_params: [Footprint => ImageFrame<Color>, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: TextureFrame, fn_params: [Footprint => TextureFrame, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => GraphicGroup, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [VectorData]),
register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [DAffine2]),
register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame<Color>, params: [DAffine2]),
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [graphene_std::vector::style::Fill]),
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [Color]),
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [Option<Color>]),
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [graphene_std::vector::style::Gradient]),
register_node!(graphene_core::vector::AssignColorsNode<_, _, _, _, _, _, _>, input: GraphicGroup, params: [bool, bool, graphene_std::vector::style::GradientStops, bool, bool, u32, u32]),
register_node!(graphene_core::vector::AssignColorsNode<_, _, _, _, _, _, _>, input: VectorData, params: [bool, bool, graphene_std::vector::style::GradientStops, bool, bool, u32, u32]),
register_node!(graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>, input: VectorData, params: [Option<graphene_core::Color>, f64, Vec<f64>, f64, graphene_core::vector::style::LineCap, graphene_core::vector::style::LineJoin, f64]),
register_node!(graphene_core::vector::RepeatNode<_, _, _>, input: VectorData, params: [DVec2, f64, u32]),
register_node!(graphene_core::vector::BoundingBoxNode, input: VectorData, params: []),
register_node!(graphene_core::vector::SolidifyStrokeNode, input: VectorData, params: []),
register_node!(graphene_core::vector::CircularRepeatNode<_, _, _>, input: VectorData, params: [f64, f64, u32]),
register_node!(graphene_std::vector::BooleanOperationNode<_>, input: GraphicGroup, fn_params: [() => graphene_core::vector::misc::BooleanOperation]),
vec![(
ProtoNodeIdentifier::new("graphene_core::transform::CullNode<_>"),
|args| {
Box::pin(async move {
let mut args = args.clone();
args.reverse();
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<VectorData>(args.pop().expect("Not enough arguments provided to construct node")));
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
{
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::PanicNode::<(), VectorData>::new());
let params = vec![fn_type!((), VectorData)];
let mut node_io = <graphene_core::transform::CullNode<_> as NodeIO<'_, Footprint>>::to_node_io(&node, params);
node_io.input = concrete!(<Footprint as StaticType>::Static);
node_io
},
)],
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [Artboard]),
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [ImageFrame<Color>]),
vec![(
ProtoNodeIdentifier::new("graphene_core::transform::CullNode<_>"),
|args| {
Box::pin(async move {
let mut args = args.clone();
args.reverse();
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<ArtboardGroup>(args.pop().expect("Not enough arguments provided to construct node")));
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
{
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::PanicNode::<(), ArtboardGroup>::new());
let params = vec![fn_type!((), ArtboardGroup)];
let mut node_io = <graphene_core::transform::CullNode<_> as NodeIO<'_, Footprint>>::to_node_io(&node, params);
node_io.input = concrete!(<Footprint as StaticType>::Static);
node_io
},
)],
vec![(
ProtoNodeIdentifier::new("graphene_core::transform::CullNode<_>"),
|args| {
Box::pin(async move {
let mut args = args.clone();
args.reverse();
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<GraphicGroup>(args.pop().expect("Not enough arguments provided to construct node")));
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
{
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::PanicNode::<(), GraphicGroup>::new());
let params = vec![fn_type!((), GraphicGroup)];
let mut node_io = <graphene_core::transform::CullNode<_> as NodeIO<'_, Footprint>>::to_node_io(&node, params);
node_io.input = concrete!(<Footprint as StaticType>::Static);
node_io
},
)],
register_node!(graphene_std::raster::SampleNode<_>, input: Footprint, params: [ImageFrame<Color>]),
register_node!(graphene_std::raster::MandelbrotNode, input: Footprint, params: []),
async_node!(graphene_core::vector::CopyToPoints<_, _, _, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, Footprint => VectorData, () => f64, () => f64, () => f64, () => u32, () => f64, () => u32]),
async_node!(graphene_core::vector::CopyToPoints<_, _, _, _, _, _, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => VectorData, Footprint => GraphicGroup, () => f64, () => f64, () => f64, () => u32, () => f64, () => u32]),
async_node!(graphene_core::vector::SamplePoints<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => f64, () => f64, () => f64, () => bool, Footprint => Vec<f64>]),
register_node!(graphene_core::vector::PoissonDiskPoints<_, _>, input: VectorData, params: [f64, u32]),
register_node!(graphene_core::vector::LengthsOfSegmentsOfSubpaths, input: VectorData, params: []),
register_node!(graphene_core::vector::SplinesFromPointsNode, input: VectorData, params: []),
async_node!(graphene_core::vector::AreaNode<_>, input: (), output: f64, fn_params: [Footprint => VectorData]),
async_node!(graphene_core::vector::CentroidNode<_, _>, input: (), output: DVec2, fn_params: [Footprint => VectorData, () => graphene_core::vector::misc::CentroidType]),
async_node!(graphene_core::vector::MorphNode<_, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, Footprint => VectorData, () => u32, () => f64]),
register_node!(graphene_core::vector::generator_nodes::CircleGenerator<_>, input: (), params: [f64]),
register_node!(graphene_core::vector::generator_nodes::EllipseGenerator<_, _>, input: (), params: [f64, f64]),
register_node!(graphene_core::vector::generator_nodes::RectangleGenerator<_, _, _, _, _>, input: (), params: [f64, f64, bool, f64, bool]),
register_node!(graphene_core::vector::generator_nodes::RectangleGenerator<_, _, _, _, _>, input: (), params: [f64, f64, bool, [f64; 4], bool]),
register_node!(graphene_core::vector::generator_nodes::RegularPolygonGenerator<_, _>, input: (), params: [u32, f64]),
register_node!(graphene_core::vector::generator_nodes::StarGenerator<_, _, _>, input: (), params: [u32, f64, f64]),
register_node!(graphene_core::vector::generator_nodes::LineGenerator<_, _>, input: (), params: [DVec2, DVec2]),
register_node!(graphene_core::vector::generator_nodes::SplineGenerator<_>, input: (), params: [Vec<DVec2>]),
register_node!(
graphene_core::vector::generator_nodes::PathGenerator<_>,
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::vector::PointId>>,
params: [Vec<graphene_core::vector::PointId>]
),
register_node!(graphene_core::vector::PathModify<_>, input: VectorData, params: [graphene_core::vector::VectorModification]),
register_node!(graphene_core::text::TextGeneratorNode<_, _, _>, input: &WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
async_node!(graphene_core::ConstructLayerNode<_, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => GraphicGroup, Footprint => graphene_core::GraphicElement, () => Vec<NodeId>]),
register_node!(graphene_core::ToGraphicElementNode, input: graphene_core::vector::VectorData, params: []),
register_node!(graphene_core::ToGraphicElementNode, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ToGraphicElementNode, input: GraphicGroup, params: []),
register_node!(graphene_core::ToGraphicElementNode, input: TextureFrame, params: []),
register_node!(graphene_core::ToGraphicGroupNode, input: graphene_core::vector::VectorData, params: []),
register_node!(graphene_core::ToGraphicGroupNode, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ToGraphicGroupNode, input: GraphicGroup, params: []),
async_node!(graphene_core::ConstructArtboardNode<_, _, _, _, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => GraphicGroup, () => String, () => glam::IVec2, () => glam::IVec2, () => Color, () => bool]),
async_node!(graphene_core::AddArtboardNode<_, _, _>, input: Footprint, output: ArtboardGroup, fn_params: [Footprint => ArtboardGroup, Footprint => Artboard, () => Vec<NodeId>]),
];
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
for (id, c, types) in node_types.into_iter().flatten() {
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
for (constructor, types) in entry.iter() {
map.entry(id.clone().into()).or_default().insert(types.clone(), *constructor);
}
}
for (id, c, types) in node_types.into_iter() {
// TODO: this is a hack to remove the newline from the node new_name
// This occurs for the ChannelMixerNode presumably because of the long name.
// This might be caused by the stringify! macro
let new_name = id.name.replace('\n', " ");
let mut new_name = id.name.replace('\n', " ");
// Remove struct generics
if let Some((path, _generics)) = new_name.split_once("<") {
new_name = path.to_string();
}
let nid = ProtoNodeIdentifier { name: Cow::Owned(new_name) };
map.entry(nid).or_default().insert(types.clone(), c);
}

View File

@@ -15,6 +15,14 @@ proc-macro = true
[dependencies]
# Workspace dependencies
syn = { workspace = true }
syn = { workspace = true, features = ["extra-traits", "full", "printing", "parsing", "clone-impls", "proc-macro", "visit-mut"] }
proc-macro2 = { workspace = true }
quote = { workspace = true }
convert_case = { workspace = true }
indoc = "2.0.5"
proc-macro-crate = "3.1.0"
[dev-dependencies]
graphene-core = { workspace = true }

View File

@@ -0,0 +1,433 @@
use std::sync::atomic::AtomicU64;
use crate::parsing::*;
use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2;
use proc_macro_crate::FoundCrate;
use quote::{format_ident, quote};
use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Error, Ident, Token, WhereClause, WherePredicate};
static NODE_ID: AtomicU64 = AtomicU64::new(0);
pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStream2> {
let ParsedNodeFn {
attributes,
fn_name,
struct_name,
mod_name,
fn_generics,
where_clause,
input,
output_type,
is_async,
fields,
body,
crate_name: graphene_core_crate,
..
} = parsed;
let category = &attributes.category.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));
let mod_name = format_ident!("_{}_mod", mod_name);
let display_name = match &attributes.display_name.as_ref() {
Some(lit) => lit.value(),
None => struct_name.to_string().to_case(Case::Title),
};
let struct_name = format_ident!("{}Node", struct_name);
let struct_generics: Vec<Ident> = fields.iter().enumerate().map(|(i, _)| format_ident!("Node{}", i)).collect();
let input_ident = &input.pat_ident;
let input_type = &input.ty;
let field_idents: Vec<_> = fields
.iter()
.map(|field| match field {
ParsedField::Regular { pat_ident, .. } | ParsedField::Node { pat_ident, .. } => pat_ident,
})
.collect();
let field_names: Vec<_> = field_idents.iter().map(|pat_ident| &pat_ident.ident).collect();
let input_names: Vec<_> = fields
.iter()
.map(|field| match field {
ParsedField::Regular { name, .. } | ParsedField::Node { name, .. } => name,
})
.zip(field_names.iter())
.map(|zipped| match zipped {
(Some(name), _) => name.value(),
(_, name) => name.to_string().to_case(convert_case::Case::Title),
})
.collect();
let struct_fields = field_names.iter().zip(struct_generics.iter()).map(|(name, gen)| {
quote! { pub(super) #name: #gen }
});
let graphene_core = match graphene_core_crate {
FoundCrate::Itself => quote!(crate),
FoundCrate::Name(name) => {
let ident = Ident::new(name, proc_macro2::Span::call_site());
quote!( #ident )
}
};
let field_types: Vec<_> = fields
.iter()
.map(|field| match field {
ParsedField::Regular { ty, .. } => ty.clone(),
ParsedField::Node { output_type, input_type, .. } => match parsed.is_async {
true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output: core::future::Future<Output=#output_type> + #graphene_core::WasmNotSend>),
false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>),
},
})
.collect();
let value_sources: Vec<_> = fields
.iter()
.map(|field| match field {
ParsedField::Regular { value_source, .. } => match value_source {
ValueSource::Default(data) => quote!(ValueSource::Default(stringify!(#data))),
ValueSource::Scope(data) => quote!(ValueSource::Scope(#data)),
_ => quote!(ValueSource::None),
},
_ => quote!(ValueSource::None),
})
.collect();
let number_min_values: Vec<_> = fields
.iter()
.map(|field| match field {
ParsedField::Regular { number_min: Some(number_min), .. } => quote!(Some(#number_min)),
_ => quote!(None),
})
.collect();
let number_max_values: Vec<_> = fields
.iter()
.map(|field| match field {
ParsedField::Regular { number_max: Some(number_max), .. } => quote!(Some(#number_max)),
_ => quote!(None),
})
.collect();
let number_mode_range_values: Vec<_> = fields
.iter()
.map(|field| match field {
ParsedField::Regular {
number_mode_range: Some(number_mode_range),
..
} => quote!(Some(#number_mode_range)),
_ => quote!(None),
})
.collect();
let exposed: Vec<_> = fields
.iter()
.map(|field| match field {
ParsedField::Regular { exposed, .. } => quote!(#exposed),
_ => quote!(true),
})
.collect();
let eval_args = fields.iter().map(|field| match field {
ParsedField::Regular { pat_ident, .. } => {
let name = &pat_ident.ident;
quote! { let #name = self.#name.eval(()); }
}
ParsedField::Node { pat_ident, .. } => {
let name = &pat_ident.ident;
quote! { let #name = &self.#name; }
}
});
let all_implementation_types = fields.iter().flat_map(|field| match field {
ParsedField::Regular { implementations, .. } => implementations.into_iter().cloned().collect::<Vec<_>>(),
ParsedField::Node { implementations, .. } => implementations.into_iter().map(|tuple| syn::Type::Tuple(tuple.clone())).collect(),
});
let all_implementation_types = all_implementation_types.chain(input.implementations.iter().cloned());
let mut clauses = Vec::new();
for (field, name) in fields.iter().zip(struct_generics.iter()) {
clauses.push(match (field, *is_async) {
(ParsedField::Regular { ty, .. }, _) => quote!(#name: #graphene_core::Node<'n, (), Output = #ty> ),
(ParsedField::Node { input_type, output_type, .. }, false) => {
quote!(for<'all_input> #name: #graphene_core::Node<'all_input, #input_type, Output = #output_type> + #graphene_core::WasmNotSync)
}
(ParsedField::Node { input_type, output_type, .. }, true) => {
quote!(for<'all_input> #name: #graphene_core::Node<'all_input, #input_type, Output: core::future::Future<Output = #output_type> + #graphene_core::WasmNotSend> + #graphene_core::WasmNotSync)
}
});
}
let where_clause = where_clause.clone().unwrap_or(WhereClause {
where_token: Token![where](output_type.span()),
predicates: Default::default(),
});
let mut struct_where_clause = where_clause.clone();
let extra_where: Punctuated<WherePredicate, Comma> = parse_quote!(
#(#clauses,)*
#output_type: 'n,
);
struct_where_clause.predicates.extend(extra_where);
let new_args = struct_generics.iter().zip(field_names.iter()).map(|(gen, name)| {
quote! { #name: #gen }
});
let async_keyword = is_async.then(|| quote!(async));
let eval_impl = if *is_async {
quote! {
type Output = #graphene_core::registry::DynFuture<'n, #output_type>;
#[inline]
fn eval(&'n self, __input: #input_type) -> Self::Output {
#(#eval_args)*
Box::pin(self::#fn_name(__input #(, #field_names)*))
}
}
} else {
quote! {
type Output = #output_type;
#[inline]
fn eval(&'n self, __input: #input_type) -> Self::Output {
#(#eval_args)*
self::#fn_name(__input #(, #field_names)*)
}
}
};
let path = match parsed.attributes.path {
Some(ref path) => quote!(stringify!(#path).replace(' ', "")),
None => quote!(std::module_path!().rsplit_once("::").unwrap().0),
};
let identifier = quote!(format!("{}::{}", #path, stringify!(#struct_name)));
let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?;
Ok(quote! {
/// Underlying implementation for [#struct_name]
#[inline]
#[allow(clippy::too_many_arguments)]
#async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
#[automatically_derived]
impl<'n, #(#fn_generics,)* #(#struct_generics,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*>
#struct_where_clause
{
#eval_impl
}
#[doc(inline)]
pub use #mod_name::#struct_name;
#[doc(hidden)]
mod #mod_name {
use super::*;
use #graphene_core as gcore;
use gcore::{Node, NodeIOTypes, concrete, fn_type, future, ProtoNodeIdentifier, WasmNotSync, NodeIO};
use gcore::value::ClonedNode;
use gcore::ops::TypeNode;
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, ValueSource};
use gcore::ctor::ctor;
// Use the types specified in the implementation
#[cfg(__never_compiled)]
static _IMPORTS: core::marker::PhantomData<#(#all_implementation_types,)*> = core::marker::PhantomData;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct #struct_name<#(#struct_generics,)*> {
#(#struct_fields,)*
}
#[automatically_derived]
impl<'n, #(#struct_generics,)*> #struct_name<#(#struct_generics,)*>
{
#[allow(clippy::too_many_arguments)]
pub fn new(#(#new_args,)*) -> Self {
Self {
#(#field_names,)*
}
}
}
#register_node_impl
#[cfg_attr(not(target_arch = "wasm32"), ctor)]
fn register_metadata() {
let metadata = NodeMetadata {
display_name: #display_name,
category: #category,
fields: vec![
#(
FieldMetadata {
name: #input_names,
exposed: #exposed,
value_source: #value_sources,
number_min: #number_min_values,
number_max: #number_max_values,
number_mode_range: #number_mode_range_values,
},
)*
],
};
NODE_METADATA.lock().unwrap().insert(#identifier, metadata);
}
}
})
}
fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &TokenStream2) -> Result<TokenStream2, syn::Error> {
if parsed.attributes.skip_impl {
return Ok(quote!());
}
let mut constructors = Vec::new();
let unit = parse_quote!(());
let parameter_types: Vec<_> = parsed
.fields
.iter()
.map(|field| {
match field {
ParsedField::Regular { implementations, ty, .. } => {
if !implementations.is_empty() {
implementations.into_iter().map(|ty| (&unit, ty, false)).collect()
} else {
vec![(&unit, ty, false)]
}
}
ParsedField::Node {
implementations,
output_type,
input_type,
..
} => {
if !implementations.is_empty() {
implementations.into_iter().map(|tup| (&tup.elems[0], &tup.elems[1], true)).collect()
} else {
vec![(input_type, output_type, true)]
}
}
}
.into_iter()
.map(|(input, out, node)| (substitute_lifetimes(input.clone()), substitute_lifetimes(out.clone()), node))
.collect::<Vec<_>>()
})
.collect();
let max_implementations = parameter_types.iter().map(|x| x.len()).chain([parsed.input.implementations.len().max(1)]).max();
let future_node = (!parsed.is_async).then(|| quote!(let node = gcore::registry::FutureWrapperNode::new(node);));
for i in 0..max_implementations.unwrap_or(0) {
let mut temp_constructors = Vec::new();
let mut temp_node_io = Vec::new();
let mut panic_node_types = Vec::new();
for (j, types) in parameter_types.iter().enumerate() {
let field_name = field_names[j];
let (input_type, output_type, impl_node) = &types[i.min(types.len() - 1)];
let node = matches!(parsed.fields[j], ParsedField::Node { .. });
let downcast_node = quote!(
let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone());
);
temp_constructors.push(if node {
if !parsed.is_async {
return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters"));
}
downcast_node
} else {
quote!(
#downcast_node
let #field_name = #field_name.eval(()).await;
let #field_name = ClonedNode::new(#field_name);
let #field_name: TypeNode<_, #input_type, #output_type> = TypeNode::new(#field_name);
// try polling futures
)
});
temp_node_io.push(quote!(fn_type!(#input_type, #output_type, alias: #output_type)));
match parsed.is_async && *impl_node {
true => panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>)),
false => panic_node_types.push(quote!(#input_type, #output_type)),
};
}
let input_type = match parsed.input.implementations.is_empty() {
true => parsed.input.ty.clone(),
false => parsed.input.implementations[i.min(parsed.input.implementations.len() - 1)].clone(),
};
let node_io = if parsed.is_async { quote!(to_async_node_io) } else { quote!(to_node_io) };
constructors.push(quote!(
(
|args| {
Box::pin(async move {
#(#temp_constructors;)*
let node = #struct_name::new(#(#field_names,)*);
// try polling futures
#future_node
let any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node);
Box::new(any) as TypeErasedBox<'_>
})
}, {
let node = #struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*);
let params = vec![#(#temp_node_io,)*];
let mut node_io = NodeIO::<'_, #input_type>::#node_io(&node, params);
node_io
}
)
));
}
let registry_name = format_ident!("__node_registry_{}_{}", NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), struct_name);
Ok(quote! {
#[cfg_attr(not(target_arch = "wasm32"), ctor)]
fn register_node() {
let mut registry = NODE_REGISTRY.lock().unwrap();
registry.insert(
#identifier,
vec![
#(#constructors,)*
]
);
}
#[cfg(target_arch = "wasm32")]
#[no_mangle]
extern "C" fn #registry_name() {
register_node();
register_metadata();
}
})
}
use syn::{visit_mut::VisitMut, GenericArgument, Lifetime, Type};
struct LifetimeReplacer;
impl VisitMut for LifetimeReplacer {
fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
lifetime.ident = syn::Ident::new("_", lifetime.ident.span());
}
fn visit_type_mut(&mut self, ty: &mut Type) {
match ty {
Type::Reference(type_reference) => {
if let Some(lifetime) = &mut type_reference.lifetime {
self.visit_lifetime_mut(lifetime);
}
self.visit_type_mut(&mut type_reference.elem);
}
_ => syn::visit_mut::visit_type_mut(self, ty),
}
}
fn visit_generic_argument_mut(&mut self, arg: &mut GenericArgument) {
if let GenericArgument::Lifetime(lifetime) = arg {
self.visit_lifetime_mut(lifetime);
} else {
syn::visit_mut::visit_generic_argument_mut(self, arg);
}
}
}
#[must_use]
fn substitute_lifetimes(mut ty: Type) -> Type {
LifetimeReplacer.visit_type_mut(&mut ty);
ty
}

View File

@@ -6,6 +6,9 @@ use syn::{
PathSegment, PredicateType, ReturnType, Token, TraitBound, Type, TypeImplTrait, TypeParam, TypeParamBound, TypeTuple, WhereClause, WherePredicate,
};
mod codegen;
mod parsing;
/// A macro used to construct a proto node implementation from the given struct and the decorated function.
///
/// This works by generating two `impl` blocks for the given struct:
@@ -86,7 +89,7 @@ use syn::{
///
/// When a `let` declaration is generated automatically, this is called **automatic composition**. When opting out, this is called **manual composition**.
#[proc_macro_attribute]
pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
pub fn old_node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
// Performs the `node_impl` macro's functionality of attaching an `impl Node for TheGivenStruct` block to the node struct
let node_impl = node_impl_proxy(attr.clone(), item.clone());
@@ -99,15 +102,21 @@ pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
new_constructor
}
#[proc_macro_attribute]
pub fn node(attr: TokenStream, item: TokenStream) -> TokenStream {
// Performs the `node_impl` macro's functionality of attaching an `impl Node for TheGivenStruct` block to the node struct
parsing::new_node_fn(attr.into(), item.into()).into()
}
/// Attaches an `impl TheGivenStruct` block to the node struct, containing a `new` constructor method. This is almost always called by the combined [`node_fn`] macro instead of using this one, however it can be used separately if needed. See that macro's documentation for more information.
#[proc_macro_attribute]
pub fn node_new(attr: TokenStream, item: TokenStream) -> TokenStream {
pub fn old_node_new(attr: TokenStream, item: TokenStream) -> TokenStream {
node_new_impl(attr, item)
}
/// Attaches an `impl Node for TheGivenStruct` block to the node struct, containing an implementation of the node's `eval` method for a certain type signature. This can be called with multiple separate functions each having different type signatures. The [`node_fn`] macro calls this macro as well as defining a `new` constructor method on the node struct, which is a necessary part of defining a proto node; therefore you will most likely call that macro on the first decorated function and this macro on any additional decorated functions to provide additional type signatures for the proto node. See that macro's documentation for more information.
#[proc_macro_attribute]
pub fn node_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
pub fn old_node_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
node_impl_proxy(attr, item)
}

View File

@@ -0,0 +1,872 @@
use convert_case::{Case, Casing};
use indoc::indoc;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, ToTokens};
use syn::parse::{Parse, ParseStream, Parser};
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{Attribute, Error, ExprTuple, FnArg, GenericParam, Ident, ItemFn, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeTuple, WhereClause};
use crate::codegen::generate_node_code;
#[derive(Debug)]
pub(crate) struct ParsedNodeFn {
pub(crate) attributes: NodeFnAttributes,
pub(crate) fn_name: Ident,
pub(crate) struct_name: Ident,
pub(crate) mod_name: Ident,
pub(crate) fn_generics: Vec<GenericParam>,
pub(crate) where_clause: Option<WhereClause>,
pub(crate) input: Input,
pub(crate) output_type: Type,
pub(crate) is_async: bool,
pub(crate) fields: Vec<ParsedField>,
pub(crate) body: TokenStream2,
pub(crate) crate_name: proc_macro_crate::FoundCrate,
}
#[derive(Debug, Default)]
pub(crate) struct NodeFnAttributes {
pub(crate) category: Option<LitStr>,
pub(crate) display_name: Option<LitStr>,
pub(crate) path: Option<Path>,
pub(crate) skip_impl: bool,
// Add more attributes as needed
}
#[derive(Debug, Default)]
pub enum ValueSource {
#[default]
None,
Default(TokenStream2),
Scope(LitStr),
}
#[derive(Debug)]
pub(crate) enum ParsedField {
Regular {
pat_ident: PatIdent,
name: Option<LitStr>,
ty: Type,
exposed: bool,
value_source: ValueSource,
number_min: Option<LitFloat>,
number_max: Option<LitFloat>,
number_mode_range: Option<ExprTuple>,
implementations: Punctuated<Type, Comma>,
},
Node {
pat_ident: PatIdent,
name: Option<LitStr>,
input_type: Type,
output_type: Type,
implementations: Punctuated<TypeTuple, Comma>,
},
}
#[derive(Debug)]
pub(crate) struct Input {
pub(crate) pat_ident: PatIdent,
pub(crate) ty: Type,
pub(crate) implementations: Punctuated<Type, Comma>,
}
impl Parse for NodeFnAttributes {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut category = None;
let mut display_name = None;
let mut path = None;
let mut skip_impl = false;
let content = input;
// let content;
// syn::parenthesized!(content in input);
let nested = content.call(Punctuated::<Meta, Comma>::parse_terminated)?;
for meta in nested {
match meta {
Meta::List(meta) if meta.path.is_ident("category") => {
if category.is_some() {
return Err(Error::new_spanned(meta, "Multiple 'category' attributes are not allowed"));
}
let lit: LitStr = meta
.parse_args()
.map_err(|_| Error::new_spanned(meta, "Expected a string literal for 'category', e.g., category(\"Value\")"))?;
category = Some(lit);
}
Meta::List(meta) if meta.path.is_ident("name") => {
if display_name.is_some() {
return Err(Error::new_spanned(meta, "Multiple 'name' attributes are not allowed"));
}
let parsed_name: LitStr = meta.parse_args().map_err(|_| Error::new_spanned(meta, "Expected a string for 'name', e.g., name(\"Memoize\")"))?;
display_name = Some(parsed_name);
}
Meta::List(meta) if meta.path.is_ident("path") => {
if path.is_some() {
return Err(Error::new_spanned(meta, "Multiple 'path' attributes are not allowed"));
}
let parsed_path: Path = meta
.parse_args()
.map_err(|_| Error::new_spanned(meta, "Expected a valid path for 'path', e.g., path(crate::MemoizeNode)"))?;
path = Some(parsed_path);
}
Meta::Path(path) if path.is_ident("skip_impl") => {
if skip_impl {
return Err(Error::new_spanned(path, "Multiple 'skip_impl' attributes are not allowed"));
}
skip_impl = true;
}
_ => {
return Err(Error::new_spanned(
meta,
indoc!(
r#"
Unsupported attribute in `node`.
Supported attributes are 'category', 'path' and 'name'.
Example usage:
#[node_macro::node(category("Value"), name("Test Node"))]
"#
),
));
}
}
}
Ok(NodeFnAttributes {
category,
display_name,
path,
skip_impl,
})
}
}
fn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<ParsedNodeFn> {
let attributes = syn::parse2::<NodeFnAttributes>(attr.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse node_fn attributes: {}", e)))?;
let input_fn = syn::parse2::<ItemFn>(item.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse function: {}. Make sure it's a valid Rust function.", e)))?;
let fn_name = input_fn.sig.ident.clone();
let struct_name = format_ident!("{}", fn_name.to_string().to_case(Case::Pascal));
let mod_name = fn_name.clone();
let fn_generics = input_fn.sig.generics.params.into_iter().collect();
let is_async = input_fn.sig.asyncness.is_some();
let (input, fields) = parse_inputs(&input_fn.sig.inputs)?;
let output_type = parse_output(&input_fn.sig.output)?;
let where_clause = input_fn.sig.generics.where_clause;
let body = input_fn.block.to_token_stream();
let crate_name = proc_macro_crate::crate_name("graphene-core").map_err(|e| {
Error::new(
proc_macro2::Span::call_site(),
format!("Failed to find location of graphene_core. Make sure it is imported as a dependency: {}", e),
)
})?;
Ok(ParsedNodeFn {
attributes,
fn_name,
struct_name,
mod_name,
fn_generics,
input,
output_type,
is_async,
fields,
where_clause,
body,
crate_name,
})
}
fn parse_inputs(inputs: &Punctuated<FnArg, Comma>) -> syn::Result<(Input, Vec<ParsedField>)> {
let mut fields = Vec::new();
let mut input = None;
for (index, arg) in inputs.iter().enumerate() {
if let FnArg::Typed(PatType { pat, ty, attrs, .. }) = arg {
// Call argument
if index == 0 {
if extract_attribute(attrs, "default").is_some() {
return Err(Error::new_spanned(&attrs[0], "Call argument cannot be given a default value".to_string()));
}
if extract_attribute(attrs, "expose").is_some() {
return Err(Error::new_spanned(&attrs[0], "Call argument cannot be exposed".to_string()));
}
let pat_ident = match (**pat).clone() {
Pat::Ident(pat_ident) => pat_ident,
Pat::Wild(wild) => PatIdent {
attrs: wild.attrs,
by_ref: None,
mutability: None,
ident: wild.underscore_token.into(),
subpat: None,
},
_ => continue,
};
let implementations = extract_attribute(attrs, "implementations")
.map(|attr| parse_implementations(attr, &pat_ident.ident))
.transpose()?
.unwrap_or_default();
input = Some(Input {
pat_ident,
ty: (**ty).clone(),
implementations,
});
} else if let Pat::Ident(pat_ident) = &**pat {
let field = parse_field(pat_ident.clone(), (**ty).clone(), attrs).map_err(|e| Error::new_spanned(pat_ident, format!("Failed to parse argument '{}': {}", pat_ident.ident, e)))?;
fields.push(field);
} else {
return Err(Error::new_spanned(pat, "Expected a simple identifier for the field name"));
}
} else {
return Err(Error::new_spanned(arg, "Expected a typed argument (e.g., `x: i32`)"));
}
}
let input = input.ok_or_else(|| Error::new_spanned(inputs, "Expected at least one input argument. The first argument should be the node input type."))?;
Ok((input, fields))
}
fn parse_implementations<T: Parse>(attr: &Attribute, name: &Ident) -> syn::Result<Punctuated<T, Comma>> {
let content: TokenStream2 = attr
.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid implementations for argument '{}': {}", name, e)))?;
let parser = Punctuated::<T, Comma>::parse_terminated;
parser
.parse2(content)
.map_err(|e| Error::new_spanned(attr, format!("Failed to parse implementations for argument '{}': {}", name, e)))
}
fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Result<ParsedField> {
let ident = &pat_ident.ident;
let default_value = extract_attribute(attrs, "default")
.map(|attr| {
attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid `default` value for argument '{}': {}", ident, e)))
})
.transpose()?;
let scope = extract_attribute(attrs, "scope")
.map(|attr| {
attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid `scope` value for argument '{}': {}", ident, e)))
})
.transpose()?;
let name = extract_attribute(attrs, "name")
.map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `name` value for argument '{}': {}", ident, e))))
.transpose()?;
let exposed = extract_attribute(attrs, "expose").is_some();
let value_source = match (default_value, scope) {
(Some(_), Some(_)) => return Err(Error::new_spanned(&pat_ident, "Cannot have both `default` and `scope` attributes")),
(Some(default_value), _) => ValueSource::Default(default_value),
(_, Some(scope)) => ValueSource::Scope(scope),
_ => ValueSource::None,
};
let number_min = extract_attribute(attrs, "min")
.map(|attr| {
attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `min` value for argument '{}': {}", ident, e)))
})
.transpose()?;
let number_max = extract_attribute(attrs, "max")
.map(|attr| {
attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `max` value for argument '{}': {}", ident, e)))
})
.transpose()?;
let number_mode_range = extract_attribute(attrs, "range")
.map(|attr| {
attr.parse_args::<ExprTuple>().map_err(|e| {
Error::new_spanned(
attr,
format!(
"Invalid `range` tuple of min and max range slider values for argument '{}': {}\nUSAGE EXAMPLE: #[range((0., 100.))]",
ident, e
),
)
})
})
.transpose()?;
if let Some(range) = &number_mode_range {
if range.elems.len() != 2 {
return Err(Error::new_spanned(range, "Expected a tuple of two values for `range` for the min and max, respectively"));
}
}
let implementations = extract_attribute(attrs, "implementations")
.map(|attr| parse_implementations(attr, ident))
.transpose()?
.unwrap_or_default();
let (is_node, node_input_type, node_output_type) = parse_node_type(&ty);
if is_node {
let (input_type, output_type) = node_input_type
.zip(node_output_type)
.ok_or_else(|| Error::new_spanned(&ty, "Invalid Node type. Expected `impl Node<Input, Output = OutputType>`"))?;
if !matches!(&value_source, ValueSource::None) {
return Err(Error::new_spanned(&ty, "No default values for `impl Node` allowed"));
}
let implementations = extract_attribute(attrs, "implementations")
.map(|attr| parse_implementations(attr, ident))
.transpose()?
.unwrap_or_default();
Ok(ParsedField::Node {
pat_ident,
name,
input_type,
output_type,
implementations,
})
} else {
Ok(ParsedField::Regular {
pat_ident,
name,
exposed,
number_min,
number_max,
number_mode_range,
ty,
value_source,
implementations,
})
}
}
fn parse_node_type(ty: &Type) -> (bool, Option<Type>, Option<Type>) {
if let Type::ImplTrait(impl_trait) = ty {
for bound in &impl_trait.bounds {
if let syn::TypeParamBound::Trait(trait_bound) = bound {
if trait_bound.path.segments.last().map_or(false, |seg| seg.ident == "Node") {
if let syn::PathArguments::AngleBracketed(args) = &trait_bound.path.segments.last().unwrap().arguments {
let input_type = args.args.iter().find_map(|arg| if let syn::GenericArgument::Type(ty) = arg { Some(ty.clone()) } else { None });
let output_type = args.args.iter().find_map(|arg| {
if let syn::GenericArgument::AssocType(assoc_type) = arg {
if assoc_type.ident == "Output" {
Some(assoc_type.ty.clone())
} else {
None
}
} else {
None
}
});
return (true, input_type, output_type);
}
}
}
}
}
(false, None, None)
}
fn parse_output(output: &ReturnType) -> syn::Result<Type> {
match output {
ReturnType::Default => Ok(syn::parse_quote!(())),
ReturnType::Type(_, ty) => Ok((**ty).clone()),
}
}
fn extract_attribute<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Attribute> {
attrs.iter().find(|attr| attr.path().is_ident(name))
}
// Modify the new_node_fn function to use the code generation
pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
match parse_node_fn(attr, item.clone()).and_then(|x| generate_node_code(&x)) {
Ok(parsed) => {
/*let generated_code = generate_node_code(&parsed);
// panic!("{}", generated_code.to_string());
quote! {
// #item
#generated_code
}*/
parsed
}
Err(e) => {
// Return the error as a compile error
Error::new(e.span(), format!("Failed to parse node function: {}", e)).to_compile_error()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use proc_macro2::Span;
use proc_macro_crate::FoundCrate;
use quote::quote;
use syn::parse_quote;
fn pat_ident(name: &str) -> PatIdent {
PatIdent {
attrs: Vec::new(),
by_ref: None,
mutability: None,
ident: Ident::new(name, Span::call_site()),
subpat: None,
}
}
fn assert_parsed_node_fn(parsed: &ParsedNodeFn, expected: &ParsedNodeFn) {
assert_eq!(parsed.fn_name, expected.fn_name);
assert_eq!(parsed.struct_name, expected.struct_name);
assert_eq!(parsed.mod_name, expected.mod_name);
assert_eq!(parsed.is_async, expected.is_async);
assert_eq!(format!("{:?}", parsed.input), format!("{:?}", expected.input));
assert_eq!(format!("{:?}", parsed.output_type), format!("{:?}", expected.output_type));
assert_eq!(parsed.attributes.category, expected.attributes.category);
assert_eq!(parsed.attributes.display_name, expected.attributes.display_name);
assert_eq!(parsed.attributes.path, expected.attributes.path);
assert_eq!(parsed.attributes.skip_impl, expected.attributes.skip_impl);
assert_eq!(parsed.fields.len(), expected.fields.len());
for (parsed_field, expected_field) in parsed.fields.iter().zip(expected.fields.iter()) {
match (parsed_field, expected_field) {
(
ParsedField::Regular {
pat_ident: p_name,
ty: p_ty,
exposed: p_exp,
value_source: p_default,
..
},
ParsedField::Regular {
pat_ident: e_name,
ty: e_ty,
exposed: e_exp,
value_source: e_default,
..
},
) => {
assert_eq!(p_name, e_name);
assert_eq!(p_exp, e_exp);
match (p_default, e_default) {
(ValueSource::None, ValueSource::None) => {}
(ValueSource::Default(p), ValueSource::Default(e)) => {
assert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string());
}
(ValueSource::Scope(p), ValueSource::Scope(e)) => {
assert_eq!(p.value(), e.value());
}
_ => panic!("Mismatched default values"),
}
assert_eq!(format!("{:?}", p_ty), format!("{:?}", e_ty));
}
(
ParsedField::Node {
pat_ident: p_name,
input_type: p_input,
output_type: p_output,
..
},
ParsedField::Node {
pat_ident: e_name,
input_type: e_input,
output_type: e_output,
..
},
) => {
assert_eq!(p_name, e_name);
assert_eq!(format!("{:?}", p_input), format!("{:?}", e_input));
assert_eq!(format!("{:?}", p_output), format!("{:?}", e_output));
}
_ => panic!("Mismatched field types"),
}
}
}
#[test]
fn test_basic_node() {
let attr = quote!(category("Math: Arithmetic"), path(graphene_core::TestNode), skip_impl);
let input = quote!(
fn add(a: f64, b: f64) -> f64 {
a + b
}
);
let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn {
attributes: NodeFnAttributes {
category: Some(parse_quote!("Math: Arithmetic")),
display_name: None,
path: Some(parse_quote!(graphene_core::TestNode)),
skip_impl: true,
},
fn_name: Ident::new("add", Span::call_site()),
struct_name: Ident::new("Add", Span::call_site()),
mod_name: Ident::new("add", Span::call_site()),
fn_generics: vec![],
where_clause: None,
input: Input {
pat_ident: pat_ident("a"),
ty: parse_quote!(f64),
implementations: Punctuated::new(),
},
output_type: parse_quote!(f64),
is_async: false,
fields: vec![ParsedField::Regular {
pat_ident: pat_ident("b"),
name: None,
ty: parse_quote!(f64),
exposed: false,
value_source: ValueSource::None,
number_min: None,
number_max: None,
number_mode_range: None,
implementations: Punctuated::new(),
}],
body: TokenStream2::new(),
crate_name: FoundCrate::Itself,
};
assert_parsed_node_fn(&parsed, &expected);
}
#[test]
fn test_node_with_impl_node() {
let attr = quote!(category("General"));
let input = quote!(
fn transform<T: 'static>(footprint: Footprint, transform_target: impl Node<Footprint, Output = T>, translate: DVec2) -> T {
// Implementation details...
}
);
let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn {
attributes: NodeFnAttributes {
category: Some(parse_quote!("General")),
display_name: None,
path: None,
skip_impl: false,
},
fn_name: Ident::new("transform", Span::call_site()),
struct_name: Ident::new("Transform", Span::call_site()),
mod_name: Ident::new("transform", Span::call_site()),
fn_generics: vec![parse_quote!(T: 'static)],
where_clause: None,
input: Input {
pat_ident: pat_ident("footprint"),
ty: parse_quote!(Footprint),
implementations: Punctuated::new(),
},
output_type: parse_quote!(T),
is_async: false,
fields: vec![
ParsedField::Node {
pat_ident: pat_ident("transform_target"),
name: None,
input_type: parse_quote!(Footprint),
output_type: parse_quote!(T),
implementations: Punctuated::new(),
},
ParsedField::Regular {
pat_ident: pat_ident("translate"),
name: None,
ty: parse_quote!(DVec2),
exposed: false,
value_source: ValueSource::None,
number_min: None,
number_max: None,
number_mode_range: None,
implementations: Punctuated::new(),
},
],
body: TokenStream2::new(),
crate_name: FoundCrate::Itself,
};
assert_parsed_node_fn(&parsed, &expected);
}
#[test]
fn test_node_with_default_values() {
let attr = quote!(category("Vector: Shape"));
let input = quote!(
fn circle(_: (), #[default(50.)] radius: f64) -> VectorData {
// Implementation details...
}
);
let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn {
attributes: NodeFnAttributes {
category: Some(parse_quote!("Vector: Shape")),
display_name: None,
path: None,
skip_impl: false,
},
fn_name: Ident::new("circle", Span::call_site()),
struct_name: Ident::new("Circle", Span::call_site()),
mod_name: Ident::new("circle", Span::call_site()),
fn_generics: vec![],
where_clause: None,
input: Input {
pat_ident: pat_ident("_"),
ty: parse_quote!(()),
implementations: Punctuated::new(),
},
output_type: parse_quote!(VectorData),
is_async: false,
fields: vec![ParsedField::Regular {
pat_ident: pat_ident("radius"),
name: None,
ty: parse_quote!(f64),
exposed: false,
value_source: ValueSource::Default(quote!(50.)),
number_min: None,
number_max: None,
number_mode_range: None,
implementations: Punctuated::new(),
}],
body: TokenStream2::new(),
crate_name: FoundCrate::Itself,
};
assert_parsed_node_fn(&parsed, &expected);
}
#[test]
fn test_node_with_implementations() {
let attr = quote!(category("Raster: Adjustment"));
let input = quote!(
fn levels<P: Pixel>(image: ImageFrame<P>, #[implementations(f32, f64)] shadows: f64) -> ImageFrame<P> {
// Implementation details...
}
);
let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn {
attributes: NodeFnAttributes {
category: Some(parse_quote!("Raster: Adjustment")),
display_name: None,
path: None,
skip_impl: false,
},
fn_name: Ident::new("levels", Span::call_site()),
struct_name: Ident::new("Levels", Span::call_site()),
mod_name: Ident::new("levels", Span::call_site()),
fn_generics: vec![parse_quote!(P: Pixel)],
where_clause: None,
input: Input {
pat_ident: pat_ident("image"),
ty: parse_quote!(ImageFrame<P>),
implementations: Punctuated::new(),
},
output_type: parse_quote!(ImageFrame<P>),
is_async: false,
fields: vec![ParsedField::Regular {
pat_ident: pat_ident("shadows"),
name: None,
ty: parse_quote!(f64),
exposed: false,
value_source: ValueSource::None,
number_min: None,
number_max: None,
number_mode_range: None,
implementations: {
let mut p = Punctuated::new();
p.push(parse_quote!(f32));
p.push(parse_quote!(f64));
p
},
}],
body: TokenStream2::new(),
crate_name: FoundCrate::Itself,
};
assert_parsed_node_fn(&parsed, &expected);
}
#[test]
fn test_number_min_max_range_mode() {
let attr = quote!(category("Math: Arithmetic"), path(graphene_core::TestNode));
let input = quote!(
fn add(
a: f64,
#[range((0., 100.))]
#[min(-500.)]
#[max(500.)]
b: f64,
) -> f64 {
a + b
}
);
let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn {
attributes: NodeFnAttributes {
category: Some(parse_quote!("Math: Arithmetic")),
display_name: None,
path: Some(parse_quote!(graphene_core::TestNode)),
skip_impl: false,
},
fn_name: Ident::new("add", Span::call_site()),
struct_name: Ident::new("Add", Span::call_site()),
mod_name: Ident::new("add", Span::call_site()),
fn_generics: vec![],
where_clause: None,
input: Input {
pat_ident: pat_ident("a"),
ty: parse_quote!(f64),
implementations: Punctuated::new(),
},
output_type: parse_quote!(f64),
is_async: false,
fields: vec![ParsedField::Regular {
pat_ident: pat_ident("b"),
name: None,
ty: parse_quote!(f64),
exposed: false,
value_source: ValueSource::None,
number_min: Some(parse_quote!(-500.)),
number_max: Some(parse_quote!(500.)),
number_mode_range: Some(parse_quote!((0., 100.))),
implementations: Punctuated::new(),
}],
body: TokenStream2::new(),
crate_name: FoundCrate::Itself,
};
assert_parsed_node_fn(&parsed, &expected);
}
#[test]
fn test_async_node() {
let attr = quote!(category("IO"));
let input = quote!(
async fn load_image(api: &WasmEditorApi, #[expose] path: String) -> ImageFrame<Color> {
// Implementation details...
}
);
let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn {
attributes: NodeFnAttributes {
category: Some(parse_quote!("IO")),
display_name: None,
path: None,
skip_impl: false,
},
fn_name: Ident::new("load_image", Span::call_site()),
struct_name: Ident::new("LoadImage", Span::call_site()),
mod_name: Ident::new("load_image", Span::call_site()),
fn_generics: vec![],
where_clause: None,
input: Input {
pat_ident: pat_ident("api"),
ty: parse_quote!(&WasmEditorApi),
implementations: Punctuated::new(),
},
output_type: parse_quote!(ImageFrame<Color>),
is_async: true,
fields: vec![ParsedField::Regular {
pat_ident: pat_ident("path"),
name: None,
ty: parse_quote!(String),
exposed: true,
value_source: ValueSource::None,
number_min: None,
number_max: None,
number_mode_range: None,
implementations: Punctuated::new(),
}],
body: TokenStream2::new(),
crate_name: FoundCrate::Itself,
};
assert_parsed_node_fn(&parsed, &expected);
}
#[test]
fn test_node_with_custom_name() {
let attr = quote!(category("Custom"), name("CustomNode2"));
let input = quote!(
fn custom_node(input: i32) -> i32 {
input * 2
}
);
let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn {
attributes: NodeFnAttributes {
category: Some(parse_quote!("Custom")),
display_name: Some(parse_quote!("CustomNode2")),
path: None,
skip_impl: false,
},
fn_name: Ident::new("custom_node", Span::call_site()),
struct_name: Ident::new("CustomNode", Span::call_site()),
mod_name: Ident::new("custom_node", Span::call_site()),
fn_generics: vec![],
where_clause: None,
input: Input {
pat_ident: pat_ident("input"),
ty: parse_quote!(i32),
implementations: Punctuated::new(),
},
output_type: parse_quote!(i32),
is_async: false,
fields: vec![],
body: TokenStream2::new(),
crate_name: FoundCrate::Itself,
};
assert_parsed_node_fn(&parsed, &expected);
}
#[test]
#[should_panic(expected = "Multiple 'category' attributes are not allowed")]
fn test_multiple_categories() {
let attr = quote!(category("Math: Arithmetic"), category("General"));
let input = quote!(
fn add(a: i32, b: i32) -> i32 {
a + b
}
);
parse_node_fn(attr, input).unwrap();
}
#[test]
#[should_panic(expected = "Call argument cannot be given a default value")]
fn test_default_value_for_first_arg() {
let attr = quote!(category("Invalid"));
let input = quote!(
fn invalid_node(#[default(())] node: impl Node<(), Output = i32>) -> i32 {
node.eval(())
}
);
parse_node_fn(attr, input).unwrap();
}
#[test]
#[should_panic(expected = "No default values for `impl Node` allowed")]
fn test_default_value_for_impl_node() {
let attr = quote!(category("Invalid"));
let input = quote!(
fn invalid_node(_: (), #[default(())] node: impl Node<(), Output = i32>) -> i32 {
node.eval(())
}
);
parse_node_fn(attr, input).unwrap();
}
#[test]
#[should_panic(expected = "Unsupported attribute in `node`")]
fn test_unsupported_attribute() {
let attr = quote!(unsupported("Value"));
let input = quote!(
fn test_node(input: i32) -> i32 {
input
}
);
parse_node_fn(attr, input).unwrap();
}
}

View File

@@ -788,7 +788,7 @@ pub struct Shader<'a> {
pub io: ShaderIO,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, dyn_any::DynAny)]
pub struct ShaderIO {
pub inputs: Vec<AbstractShaderInput>,
pub output: AbstractShaderInput,
@@ -828,21 +828,13 @@ impl<T> ShaderInputNode<T> {
}
}
pub struct UniformNode<Executor> {
executor: Executor,
}
#[node_macro::node_fn(UniformNode)]
async fn uniform_node<'a: 'input, T: ToUniformBuffer + Send>(data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput {
#[node_macro::node(category(""))]
async fn uniform<'a: 'n, T: ToUniformBuffer + Send + 'n>(_: (), #[implementations(f32, DAffine2)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput {
executor.create_uniform_buffer(data).unwrap()
}
pub struct StorageNode<Executor> {
executor: Executor,
}
#[node_macro::node_fn(StorageNode)]
async fn storage_node<'a: 'input, T: ToStorageBuffer + Send>(data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput {
#[node_macro::node(category(""))]
async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: (), #[implementations(Vec<u8>)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput {
executor
.create_storage_buffer(
data,
@@ -856,98 +848,57 @@ async fn storage_node<'a: 'input, T: ToStorageBuffer + Send>(data: T, executor:
.unwrap()
}
pub struct PushNode<Value> {
value: Value,
}
#[node_macro::node_fn(PushNode)]
async fn push_node<T: Send>(mut vec: Vec<T>, value: T) {
vec.push(value);
}
pub struct CreateOutputBufferNode<Executor, Ty> {
executor: Executor,
ty: Ty,
}
#[node_macro::node_fn(CreateOutputBufferNode)]
async fn create_output_buffer_node<'a: 'input>(size: usize, executor: &'a WgpuExecutor, ty: Type) -> Arc<WgpuShaderInput> {
#[node_macro::node(category(""))]
async fn create_output_buffer<'a: 'n>(_: (), size: usize, executor: &'a WgpuExecutor, ty: Type) -> Arc<WgpuShaderInput> {
Arc::new(executor.create_output_buffer(size, ty, true).unwrap())
}
pub struct CreateComputePassNode<Executor, Output, Instances> {
executor: Executor,
output: Output,
instances: Instances,
}
#[node_macro::node_fn(CreateComputePassNode)]
async fn create_compute_pass_node<'a: 'input>(layout: PipelineLayout, executor: &'a WgpuExecutor, output: WgpuShaderInput, instances: ComputePassDimensions) -> CommandBuffer {
#[node_macro::node(skip_impl)]
async fn create_compute_pass<'a: 'n>(_: (), layout: PipelineLayout, executor: &'a WgpuExecutor, output: WgpuShaderInput, instances: ComputePassDimensions) -> CommandBuffer {
executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap()
}
pub struct CreatePipelineLayoutNode<EntryPoint, Bindgroup, OutputBuffer> {
entry_point: EntryPoint,
bind_group: Bindgroup,
output_buffer: OutputBuffer,
}
#[node_macro::node_fn(CreatePipelineLayoutNode)]
async fn create_pipeline_layout_node(shader: ShaderHandle, entry_point: String, bind_group: Bindgroup, output_buffer: Arc<WgpuShaderInput>) -> PipelineLayout {
#[node_macro::node(category("Debug: GPU"))]
async fn create_pipeline_layout(
_: (),
shader: impl Node<(), Output = ShaderHandle>,
entry_point: String,
bind_group: impl Node<(), Output = Bindgroup>,
output_buffer: Arc<WgpuShaderInput>,
) -> PipelineLayout {
PipelineLayout {
shader: shader.into(),
shader: shader.eval(()).await.into(),
entry_point,
bind_group: bind_group.into(),
bind_group: bind_group.eval(()).await.into(),
output_buffer,
}
}
pub struct ExecuteComputePipelineNode<Executor> {
executor: Executor,
}
#[node_macro::node_fn(ExecuteComputePipelineNode)]
async fn execute_compute_pipeline_node<'a: 'input>(encoder: CommandBuffer, executor: &'a WgpuExecutor) {
executor.execute_compute_pipeline(encoder).unwrap();
}
pub struct ReadOutputBufferNode<Executor, ComputePass> {
executor: Executor,
_compute_pass: ComputePass,
}
#[node_macro::node_fn(ReadOutputBufferNode)]
async fn read_output_buffer_node<'a: 'input>(buffer: Arc<WgpuShaderInput>, executor: &'a WgpuExecutor, _compute_pass: ()) -> Vec<u8> {
#[node_macro::node(category(""))]
async fn read_output_buffer<'a: 'n>(_: (), buffer: Arc<WgpuShaderInput>, executor: &'a WgpuExecutor, _compute_pass: ()) -> Vec<u8> {
executor.read_output_buffer(buffer).await.unwrap()
}
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
pub struct CreateGpuSurfaceNode;
#[node_macro::node_fn(CreateGpuSurfaceNode)]
async fn create_gpu_surface<'a: 'input, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + 'a + Send + Sync>(editor_api: &'a EditorApi<Io>) -> Option<WgpuSurface> {
#[node_macro::node(skip_impl)]
fn create_gpu_surface<'a: 'n, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + 'a + Send + Sync>(_: (), editor_api: &'a EditorApi<Io>) -> Option<WgpuSurface> {
let canvas = editor_api.application_io.as_ref()?.window()?;
let executor = editor_api.application_io.as_ref()?.gpu_executor()?;
Some(Arc::new(executor.create_surface(canvas).ok()?))
}
pub struct RenderTextureNode<Image, Surface, EditorApi> {
image: Image,
surface: Surface,
executor: EditorApi,
}
#[derive(DynAny, Clone, Debug)]
pub struct ShaderInputFrame {
shader_input: Arc<WgpuShaderInput>,
transform: DAffine2,
}
#[node_macro::node_fn(RenderTextureNode)]
async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node<Footprint, Output = ShaderInputFrame>, surface: Option<WgpuSurface>, executor: &'a WgpuExecutor) -> SurfaceFrame {
#[node_macro::node(category(""))]
async fn render_texture<'a: 'n>(_: (), footprint: Footprint, image: impl Node<Footprint, Output = ShaderInputFrame>, surface: Option<WgpuSurface>, executor: &'a WgpuExecutor) -> SurfaceFrame {
let surface = surface.unwrap();
let surface_id = surface.window_id;
let image = self.image.eval(footprint).await;
let image = image.eval(footprint).await;
let transform = image.transform;
executor.create_render_pass(footprint, image, surface).unwrap();
@@ -959,12 +910,8 @@ async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node<
}
}
pub struct UploadTextureNode<Executor> {
executor: Executor,
}
#[node_macro::node_fn(UploadTextureNode)]
async fn upload_texture<'a: 'input>(input: ImageFrame<Color>, executor: &'a WgpuExecutor) -> TextureFrame {
#[node_macro::node(category(""))]
async fn upload_texture<'a: 'n>(_: (), input: ImageFrame<Color>, executor: &'a WgpuExecutor) -> TextureFrame {
// let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect();
let new_data = input.image.data.into_iter().map(SRGBA8::from).collect();
let new_image = Image {