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

317
Cargo.lock generated
View file

@ -191,15 +191,6 @@ version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8"
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "arboard"
version = "3.4.0"
@ -219,45 +210,6 @@ dependencies = [
"x11rb",
]
[[package]]
name = "argmin"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5698c8cd3510117a4e6b96749a8061ba7dce1a19578ce4ecdb12dd36d94a7f8d"
dependencies = [
"anyhow",
"argmin-math",
"bincode",
"instant",
"num-traits",
"paste",
"rand 0.8.5",
"rand_xoshiro",
"serde",
"serde_json",
"slog",
"slog-async",
"slog-json",
"slog-term",
"thiserror",
]
[[package]]
name = "argmin-math"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75f2b0dada81340718682df780c9a696b090b6ef7e83c3dcc770af6de9302995"
dependencies = [
"anyhow",
"cfg-if",
"nalgebra 0.31.4",
"num-complex",
"num-integer",
"num-traits",
"rand 0.8.5",
"thiserror",
]
[[package]]
name = "arrayref"
version = "0.3.8"
@ -474,20 +426,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "autoquant"
version = "0.1.0"
source = "git+https://github.com/truedoctor/autoquant#dfb14dcc7fb92521a5ef7b37122178b203585424"
dependencies = [
"anyhow",
"argmin",
"argmin-math",
"log",
"nalgebra 0.30.1",
"num",
"rayon",
]
[[package]]
name = "axum"
version = "0.7.5"
@ -1106,6 +1044,7 @@ name = "compilation-client"
version = "0.1.0"
dependencies = [
"anyhow",
"dyn-any",
"gpu-compiler-bin-wrapper",
"gpu-executor",
"graph-craft",
@ -1146,6 +1085,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -1445,7 +1393,7 @@ version = "0.99.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
dependencies = [
"convert_case",
"convert_case 0.4.0",
"proc-macro2",
"quote",
"rustc_version",
@ -1541,6 +1489,7 @@ dependencies = [
"dyn-any-derive",
"glam",
"log",
"reqwest 0.12.7",
]
[[package]]
@ -2457,6 +2406,7 @@ dependencies = [
"base64 0.22.1",
"bezier-rs",
"bytemuck",
"ctor",
"dyn-any",
"glam",
"half",
@ -2486,7 +2436,6 @@ dependencies = [
name = "graphene-std"
version = "0.1.0"
dependencies = [
"autoquant",
"base64 0.22.1",
"bezier-rs",
"bytemuck",
@ -2549,6 +2498,7 @@ dependencies = [
"async-mutex",
"bezier-rs",
"bitflags 2.6.0",
"convert_case 0.6.0",
"derivative",
"dyn-any",
"env_logger",
@ -3193,6 +3143,12 @@ dependencies = [
"serde",
]
[[package]]
name = "indoc"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "infer"
version = "0.13.0"
@ -3605,16 +3561,6 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "matrixmultiply"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a"
dependencies = [
"autocfg",
"rawpointer",
]
[[package]]
name = "memchr"
version = "2.7.4"
@ -3737,50 +3683,6 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "nalgebra"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb2d0de08694bed883320212c18ee3008576bfe8c306f4c3c4a58b4876998be"
dependencies = [
"approx",
"matrixmultiply",
"nalgebra-macros",
"num-complex",
"num-rational",
"num-traits",
"simba",
"typenum",
]
[[package]]
name = "nalgebra"
version = "0.31.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20bd243ab3dbb395b39ee730402d2e5405e448c75133ec49cc977762c4cba3d1"
dependencies = [
"approx",
"matrixmultiply",
"nalgebra-macros",
"num-complex",
"num-rational",
"num-traits",
"serde",
"simba",
"typenum",
]
[[package]]
name = "nalgebra-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "native-tls"
version = "0.2.12"
@ -3897,6 +3799,10 @@ dependencies = [
name = "node-macro"
version = "0.0.0"
dependencies = [
"convert_case 0.6.0",
"graphene-core",
"indoc",
"proc-macro-crate 3.2.0",
"proc-macro2",
"quote",
"syn 2.0.77",
@ -3941,40 +3847,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
"serde",
]
[[package]]
name = "num-conv"
version = "0.1.0"
@ -3992,37 +3864,6 @@ dependencies = [
"syn 2.0.77",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@ -4916,7 +4757,6 @@ dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
"serde",
]
[[package]]
@ -4955,7 +4795,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.15",
"serde",
]
[[package]]
@ -4976,16 +4815,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_xoshiro"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
dependencies = [
"rand_core 0.6.4",
"serde",
]
[[package]]
name = "range-alloc"
version = "0.1.3"
@ -5018,12 +4847,6 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "rayon"
version = "1.10.0"
@ -5450,15 +5273,6 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "safe_arch"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a"
dependencies = [
"bytemuck",
]
[[package]]
name = "same-file"
version = "1.0.6"
@ -5758,19 +5572,6 @@ dependencies = [
"libc",
]
[[package]]
name = "simba"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3fd720c48c53cace224ae62bef1bbff363a70c68c4802a78b5cc6159618176"
dependencies = [
"approx",
"num-complex",
"num-traits",
"paste",
"wide",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
@ -5817,49 +5618,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "slog"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06"
[[package]]
name = "slog-async"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c8038f898a2c79507940990f05386455b3a317d8f18d4caea7cbc3d5096b84"
dependencies = [
"crossbeam-channel",
"slog",
"take_mut",
"thread_local",
]
[[package]]
name = "slog-json"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e1e53f61af1e3c8b852eef0a9dee29008f55d6dd63794f3f12cef786cf0f219"
dependencies = [
"serde",
"serde_json",
"slog",
"time",
]
[[package]]
name = "slog-term"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8"
dependencies = [
"is-terminal",
"slog",
"term",
"thread_local",
"time",
]
[[package]]
name = "slotmap"
version = "1.0.7"
@ -6223,12 +5981,6 @@ dependencies = [
"syn 2.0.77",
]
[[package]]
name = "take_mut"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
[[package]]
name = "tao"
version = "0.16.10"
@ -6539,17 +6291,6 @@ dependencies = [
"utf-8",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.4.1"
@ -7688,16 +7429,6 @@ dependencies = [
"web-sys",
]
[[package]]
name = "wide"
version = "0.7.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690"
dependencies = [
"bytemuck",
"safe_arch",
]
[[package]]
name = "widestring"
version = "1.1.0"

View file

@ -27,7 +27,7 @@ resolver = "2"
[workspace.dependencies]
# Local dependencies
dyn-any = { path = "libraries/dyn-any", features = ["derive", "glam"] }
dyn-any = { path = "libraries/dyn-any", features = ["derive", "glam", "reqwest"] }
graphene-core = { path = "node-graph/gcore" }
graph-craft = { path = "node-graph/graph-craft", features = ["serde"] }
wgpu-executor = { path = "node-graph/wgpu-executor" }
@ -46,6 +46,8 @@ futures = "0.3"
env_logger = "0.11"
log = "0.4"
bitflags = { version = "2.4", features = ["serde"] }
ctor = "0.2"
convert_case = "0.6"
derivative = "2.2"
tempfile = "3.6"
thiserror = "1.0"
@ -105,7 +107,6 @@ graphene-core = { opt-level = 1 }
graphene-std = { opt-level = 1 }
interpreted-executor = { opt-level = 1 } # This is a mitigation for https://github.com/rustwasm/wasm-pack/issues/981 which is needed because the node_registry function is too large
graphite-proc-macros = { opt-level = 1 }
autoquant = { opt-level = 3 }
image = { opt-level = 2 }
rustc-hash = { opt-level = 3 }
serde_derive = { opt-level = 1 }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -22,10 +22,6 @@ gpu = [
]
resvg = ["graphene-std/resvg"]
vello = ["graphene-std/vello", "resvg", "graphene-core/vello"]
quantization = [
"graphene-std/quantization",
"interpreted-executor/quantization",
]
[dependencies]
# Local dependencies
@ -39,6 +35,7 @@ graphene-std = { path = "../node-graph/gstd", features = ["serde"] }
js-sys = { workspace = true }
log = { workspace = true }
bitflags = { workspace = true }
convert_case = { workspace = true }
thiserror = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

View file

@ -311,6 +311,11 @@ impl Default for LayoutGroup {
Self::Row { widgets: Vec::new() }
}
}
impl From<Vec<WidgetHolder>> for LayoutGroup {
fn from(widgets: Vec<WidgetHolder>) -> LayoutGroup {
LayoutGroup::Row { widgets }
}
}
impl LayoutGroup {
/// Applies a tooltip to all widgets in this row or column without a tooltip.

View file

@ -198,6 +198,7 @@ pub struct NumberInput {
#[derivative(Default(value = "1."))]
pub step: f64,
// TODO: Make this (and range_max) apply to both Range and Increment modes when dragging with the mouse
#[serde(rename = "rangeMin")]
pub range_min: Option<f64>,

View file

@ -25,11 +25,11 @@ use crate::node_graph_executor::NodeGraphExecutor;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeNetwork, OldNodeNetwork};
use graphene_core::raster::BlendMode;
use graphene_core::raster::ImageFrame;
use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::vector::style::ViewMode;
use glam::{DAffine2, DVec2};
pub struct DocumentMessageData<'a> {
pub document_id: DocumentId,
pub ipp: &'a InputPreprocessorMessageHandler,
@ -726,9 +726,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
let center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - ipp.viewport_bounds.top_left));
let center_in_viewport_layerspace = center_in_viewport;
// Scale the image to fit into a 512x512 box
let image_size = image_size / DVec2::splat((image_size.max_element() / 512.).max(1.));
// Make layer the size of the image
let fit_image_size = DAffine2::from_scale_angle_translation(image_size, 0., image_size / -2.);

View file

@ -7,8 +7,10 @@ use crate::messages::prelude::*;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::imaginate_input::{ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus};
use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus};
use graph_craft::Type;
use graphene_core::memo::IORecord;
use graphene_core::raster::curve::Curve;
use graphene_core::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
SelectiveColorChoice,
@ -16,13 +18,14 @@ use graphene_core::raster::{
use graphene_core::text::Font;
use graphene_core::vector::misc::CentroidType;
use graphene_core::vector::style::{GradientType, LineCap, LineJoin};
use graphene_std::vector::style::{Fill, FillChoice};
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
use glam::{DAffine2, DVec2, IVec2, UVec2};
use graphene_std::transform::Footprint;
use graphene_std::vector::misc::BooleanOperation;
use graphene_std::vector::VectorData;
pub fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
pub(crate) fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
let widget = TextLabel::new(text).widget_holder();
vec![LayoutGroup::Row { widgets: vec![widget] }]
}
@ -84,6 +87,158 @@ fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, na
widgets
}
pub(crate) fn property_from_type(
document_node: &DocumentNode,
node_id: NodeId,
index: usize,
name: &str,
ty: &Type,
_context: &mut NodePropertiesContext,
number_options: (Option<f64>, Option<f64>, Option<(f64, f64)>),
) -> Vec<LayoutGroup> {
let (mut number_min, mut number_max, range) = number_options;
let mut number_input = NumberInput::default();
if let Some((range_start, range_end)) = range {
number_min = Some(range_start);
number_max = Some(range_end);
number_input = number_input.mode_range().min(range_start).max(range_end);
}
let min = |x: f64| number_min.unwrap_or(x);
let max = |x: f64| number_max.unwrap_or(x);
let mut extra_widgets = vec![];
let widgets = match ty {
Type::Concrete(concrete_type) => {
match concrete_type.alias.as_ref().map(|x| x.as_ref()) {
// Aliased types (ambiguous values)
Some("Percentage") => number_widget(document_node, node_id, index, name, number_input.percentage().min(min(0.)).max(max(100.)), true).into(),
Some("SignedPercentage") => number_widget(document_node, node_id, index, name, number_input.percentage().min(min(-100.)).max(max(100.)), true).into(),
Some("Angle") => number_widget(document_node, node_id, index, name, number_input.mode_range().min(min(-180.)).max(max(180.)).unit("°"), true).into(),
Some("PixelLength") => number_widget(document_node, node_id, index, name, number_input.min(min(0.)).unit("px"), true).into(),
Some("Length") => number_widget(document_node, node_id, index, name, number_input.min(min(0.)), true).into(),
Some("Fraction") => number_widget(document_node, node_id, index, name, number_input.min(min(0.)).max(max(1.)), true).into(),
Some("IntegerCount") => number_widget(document_node, node_id, index, name, number_input.int().min(min(1.)), true).into(),
Some("SeedValue") => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)), true).into(),
Some("Resolution") => vec2_widget(document_node, node_id, index, name, "W", "H", "px", Some(64.), add_blank_assist),
// For all other types, use TypeId-based matching
_ => {
if let Some(internal_id) = concrete_type.id {
use std::any::TypeId;
match internal_id {
x if x == TypeId::of::<bool>() => bool_widget(document_node, node_id, index, name, CheckboxInput::default(), true).into(),
x if x == TypeId::of::<f64>() => number_widget(document_node, node_id, index, name, number_input.min(min(f64::NEG_INFINITY)).max(max(f64::INFINITY)), true).into(),
x if x == TypeId::of::<u32>() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)).max(max(f64::from(u32::MAX))), true).into(),
x if x == TypeId::of::<u64>() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)), true).into(),
x if x == TypeId::of::<String>() => text_widget(document_node, node_id, index, name, true).into(),
x if x == TypeId::of::<Color>() => color_widget(document_node, node_id, index, name, ColorButton::default(), true),
x if x == TypeId::of::<Option<Color>>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(true), true),
x if x == TypeId::of::<DVec2>() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist),
x if x == TypeId::of::<UVec2>() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", Some(0.), add_blank_assist),
x if x == TypeId::of::<IVec2>() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist),
x if x == TypeId::of::<Vec<f64>>() => vec_f64_input(document_node, node_id, index, name, TextInput::default(), true).into(),
x if x == TypeId::of::<Vec<DVec2>>() => vec_dvec2_input(document_node, node_id, index, name, TextInput::default(), true).into(),
x if x == TypeId::of::<Font>() => {
let (font_widgets, style_widgets) = font_inputs(document_node, node_id, index, name, false);
font_widgets.into_iter().chain(style_widgets.unwrap_or_default()).collect::<Vec<_>>().into()
}
x if x == TypeId::of::<Curve>() => curves_widget(document_node, node_id, index, name, true),
x if x == TypeId::of::<GradientStops>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true),
x if x == TypeId::of::<VectorData>() => vector_widget(document_node, node_id, index, name, true).into(),
x if x == TypeId::of::<Footprint>() => {
let widgets = footprint_widget(document_node, node_id, index);
let (last, rest) = widgets.split_last().expect("Footprint widget should return multiple rows");
extra_widgets = rest.to_vec();
last.clone()
}
x if x == TypeId::of::<BlendMode>() => blend_mode(document_node, node_id, index, name, true),
x if x == TypeId::of::<RedGreenBlue>() => color_channel(document_node, node_id, index, name, true),
x if x == TypeId::of::<RedGreenBlueAlpha>() => rgba_channel(document_node, node_id, index, name, true),
x if x == TypeId::of::<NoiseType>() => noise_type(document_node, node_id, index, name, true),
x if x == TypeId::of::<FractalType>() => fractal_type(document_node, node_id, index, name, true, false),
x if x == TypeId::of::<CellularDistanceFunction>() => cellular_distance_function(document_node, node_id, index, name, true, false),
x if x == TypeId::of::<CellularReturnType>() => cellular_return_type(document_node, node_id, index, name, true, false),
x if x == TypeId::of::<DomainWarpType>() => domain_warp_type(document_node, node_id, index, name, true, false),
x if x == TypeId::of::<RelativeAbsolute>() => vec![DropdownInput::new(vec![vec![
MenuListEntry::new("Relative")
.label("Relative")
.on_update(update_value(|_| TaggedValue::RelativeAbsolute(RelativeAbsolute::Relative), node_id, index)),
MenuListEntry::new("Absolute")
.label("Absolute")
.on_update(update_value(|_| TaggedValue::RelativeAbsolute(RelativeAbsolute::Absolute), node_id, index)),
]])
.widget_holder()]
.into(),
x if x == TypeId::of::<LineCap>() => line_cap_widget(document_node, node_id, index, name, true),
x if x == TypeId::of::<LineJoin>() => line_join_widget(document_node, node_id, index, name, true),
x if x == TypeId::of::<FillType>() => vec![DropdownInput::new(vec![vec![
MenuListEntry::new("Solid")
.label("Solid")
.on_update(update_value(|_| TaggedValue::FillType(FillType::Solid), node_id, index)),
MenuListEntry::new("Gradient")
.label("Gradient")
.on_update(update_value(|_| TaggedValue::FillType(FillType::Gradient), node_id, index)),
]])
.widget_holder()]
.into(),
x if x == TypeId::of::<GradientType>() => vec![DropdownInput::new(vec![vec![
MenuListEntry::new("Linear")
.label("Linear")
.on_update(update_value(|_| TaggedValue::GradientType(GradientType::Linear), node_id, index)),
MenuListEntry::new("Radial")
.label("Radial")
.on_update(update_value(|_| TaggedValue::GradientType(GradientType::Radial), node_id, index)),
]])
.widget_holder()]
.into(),
x if x == TypeId::of::<BooleanOperation>() => boolean_operation_radio_buttons(document_node, node_id, index, name, true),
x if x == TypeId::of::<CentroidType>() => centroid_widget(document_node, node_id, index),
x if x == TypeId::of::<LuminanceCalculation>() => luminance_calculation(document_node, node_id, index, name, true),
x if x == TypeId::of::<ImaginateSamplingMethod>() => vec![DropdownInput::new(
ImaginateSamplingMethod::list()
.into_iter()
.map(|method| {
vec![MenuListEntry::new(format!("{:?}", method)).label(method.to_string()).on_update(update_value(
move |_| TaggedValue::ImaginateSamplingMethod(method),
node_id,
index,
))]
})
.collect(),
)
.widget_holder()]
.into(),
x if x == TypeId::of::<ImaginateMaskStartingFill>() => vec![DropdownInput::new(
ImaginateMaskStartingFill::list()
.into_iter()
.map(|fill| {
vec![MenuListEntry::new(format!("{:?}", fill)).label(fill.to_string()).on_update(update_value(
move |_| TaggedValue::ImaginateMaskStartingFill(fill),
node_id,
index,
))]
})
.collect(),
)
.widget_holder()]
.into(),
_ => vec![TextLabel::new(format!("Unsupported type: {}", concrete_type.name)).widget_holder()].into(),
}
} else {
vec![TextLabel::new(format!("Unsupported type: {}", concrete_type.name)).widget_holder()].into()
}
}
}
}
Type::Generic(_) => vec![TextLabel::new("Generic type (not supported)").widget_holder()].into(),
Type::Fn(_, out) => return property_from_type(document_node, node_id, index, name, out, _context, number_options),
Type::Future(_) => vec![TextLabel::new("Future type (not supported)").widget_holder()].into(),
};
extra_widgets.push(widgets);
extra_widgets
}
fn text_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
@ -498,6 +653,14 @@ fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na
.on_commit(commit_value)
.widget_holder(),
]),
Some(&TaggedValue::U64(x)) => widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
number_props
.value(Some(x as f64))
.on_update(update_value(move |x: &NumberInput| TaggedValue::U64((x.value.unwrap()) as u64), node_id, index))
.on_commit(commit_value)
.widget_holder(),
]),
_ => {}
}
@ -930,137 +1093,27 @@ fn centroid_widget(document_node: &DocumentNode, node_id: NodeId, index: usize)
LayoutGroup::Row { widgets }
}
pub fn levels_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let input_shadows = number_widget(document_node, node_id, 1, "Shadows", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
let input_midtones = number_widget(document_node, node_id, 2, "Midtones", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
let input_highlights = number_widget(document_node, node_id, 3, "Highlights", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
let output_minimums = number_widget(document_node, node_id, 4, "Output Minimums", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
let output_maximums = number_widget(document_node, node_id, 5, "Output Maximums", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
vec![
LayoutGroup::Row { widgets: input_shadows },
LayoutGroup::Row { widgets: input_midtones },
LayoutGroup::Row { widgets: input_highlights },
LayoutGroup::Row { widgets: output_minimums },
LayoutGroup::Row { widgets: output_maximums },
]
}
pub fn black_and_white_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
const MIN: f64 = -200.;
const MAX: f64 = 300.;
// TODO: Add tint color (blended above using the "Color" blend mode)
let tint = color_widget(document_node, node_id, 1, "Tint", ColorButton::default(), true);
let r_weight = number_widget(document_node, node_id, 2, "Reds", NumberInput::default().mode_range().min(MIN).max(MAX).unit("%"), true);
let y_weight = number_widget(document_node, node_id, 3, "Yellows", NumberInput::default().mode_range().min(MIN).max(MAX).unit("%"), true);
let g_weight = number_widget(document_node, node_id, 4, "Greens", NumberInput::default().mode_range().min(MIN).max(MAX).unit("%"), true);
let c_weight = number_widget(document_node, node_id, 5, "Cyans", NumberInput::default().mode_range().min(MIN).max(MAX).unit("%"), true);
let b_weight = number_widget(document_node, node_id, 6, "Blues", NumberInput::default().mode_range().min(MIN).max(MAX).unit("%"), true);
let m_weight = number_widget(document_node, node_id, 7, "Magentas", NumberInput::default().mode_range().min(MIN).max(MAX).unit("%"), true);
vec![
tint,
LayoutGroup::Row { widgets: r_weight },
LayoutGroup::Row { widgets: y_weight },
LayoutGroup::Row { widgets: g_weight },
LayoutGroup::Row { widgets: c_weight },
LayoutGroup::Row { widgets: b_weight },
LayoutGroup::Row { widgets: m_weight },
]
}
pub fn blend_color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let under = color_widget(document_node, node_id, 1, "Under", ColorButton::default(), true);
let blend_mode = blend_mode(document_node, node_id, 2, "Blend Mode", true);
let opacity = number_widget(document_node, node_id, 3, "Opacity", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
vec![under, blend_mode, LayoutGroup::Row { widgets: opacity }]
}
pub fn blend_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let backdrop = color_widget(document_node, node_id, 1, "Backdrop", ColorButton::default(), true);
let blend_mode = blend_mode(document_node, node_id, 2, "Blend Mode", true);
let opacity = number_widget(document_node, node_id, 3, "Opacity", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
vec![backdrop, blend_mode, LayoutGroup::Row { widgets: opacity }]
}
pub fn number_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 0, "Number", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn percentage_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 0, "Percentage", NumberInput::default().min(0.).max(100.).mode_range(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn vector2_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let x = number_widget(document_node, node_id, 1, "X", NumberInput::default(), true);
let y = number_widget(document_node, node_id, 2, "Y", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets: x }, LayoutGroup::Row { widgets: y }]
}
pub fn boolean_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = bool_widget(document_node, node_id, 0, "Bool", CheckboxInput::default(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
vec![color_widget(document_node, node_id, 0, "Color", ColorButton::default(), true)]
}
pub fn gradient_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
vec![color_widget(document_node, node_id, 0, "Gradient", ColorButton::default().allow_none(false), true)]
}
pub fn load_image_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn load_image_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let url = text_widget(document_node, node_id, 1, "URL", true);
vec![LayoutGroup::Row { widgets: url }]
}
pub fn output_properties(_document_node: &DocumentNode, _node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let label = TextLabel::new("Graphics fed into the Output are drawn in the viewport").widget_holder();
vec![LayoutGroup::Row { widgets: vec![label] }]
}
pub fn mask_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn mask_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let mask = color_widget(document_node, node_id, 1, "Stencil", ColorButton::default(), true);
vec![mask]
}
pub fn color_channel_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
vec![color_channel(document_node, node_id, 0, "Channel", true)]
}
pub fn luminance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let luminance_calc = luminance_calculation(document_node, node_id, 1, "Luminance Calc", true);
vec![luminance_calc]
}
pub fn insert_channel_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn insert_channel_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color_channel = color_channel(document_node, node_id, 2, "Into", true);
vec![color_channel]
}
pub fn extract_channel_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color_channel = rgba_channel(document_node, node_id, 1, "From", true);
vec![color_channel]
}
// Noise Type is commented out for now as there is only one type of noise (White Noise).
// As soon as there are more types of noise, this should be uncommented.
pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
// Get the current values of the inputs of interest so they can set whether certain inputs are disabled based on various conditions.
let current_noise_type = match &document_node.inputs[3].as_value() {
Some(&TaggedValue::NoiseType(noise_type)) => Some(noise_type),
@ -1208,62 +1261,52 @@ pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _
]
}
pub fn hue_saturation_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let hue_shift = number_widget(document_node, node_id, 1, "Hue Shift", NumberInput::default().min(-180.).max(180.).unit("°"), true);
let saturation_shift = number_widget(document_node, node_id, 2, "Saturation Shift", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
let lightness_shift = number_widget(document_node, node_id, 3, "Lightness Shift", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
pub(crate) fn brightness_contrast_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let is_use_classic = match &document_node.inputs[3].as_value() {
Some(&TaggedValue::Bool(value)) => value,
_ => false,
};
let ((b_min, b_max), (c_min, c_max)) = if is_use_classic { ((-100., 100.), (-100., 100.)) } else { ((-100., 150.), (-50., 100.)) };
vec![
LayoutGroup::Row { widgets: hue_shift },
LayoutGroup::Row { widgets: saturation_shift },
LayoutGroup::Row { widgets: lightness_shift },
]
}
pub fn brightness_contrast_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let brightness = number_widget(document_node, node_id, 1, "Brightness", NumberInput::default().min(-150.).max(150.), true);
let contrast = number_widget(document_node, node_id, 2, "Contrast", NumberInput::default().min(-100.).max(100.), true);
let use_legacy = bool_widget(document_node, node_id, 3, "Use Legacy", CheckboxInput::default(), true);
let brightness = number_widget(
document_node,
node_id,
1,
"Brightness",
NumberInput::default().mode_range().range_min(Some(b_min)).range_max(Some(b_max)).unit("%").display_decimal_places(2),
true,
);
let contrast = number_widget(
document_node,
node_id,
2,
"Contrast",
NumberInput::default().mode_range().range_min(Some(c_min)).range_max(Some(c_max)).unit("%").display_decimal_places(2),
true,
);
let use_classic = bool_widget(document_node, node_id, 3, "Use Classic", CheckboxInput::default(), true);
vec![
LayoutGroup::Row { widgets: brightness },
LayoutGroup::Row { widgets: contrast },
LayoutGroup::Row { widgets: use_legacy },
LayoutGroup::Row { widgets: use_classic },
]
}
pub fn curves_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn curves_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let curves = curves_widget(document_node, node_id, 1, "Curve", true);
vec![curves]
}
pub fn _blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn _blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let radius = number_widget(document_node, node_id, 1, "Radius", NumberInput::default().min(0.).max(20.).int(), true);
let sigma = number_widget(document_node, node_id, 2, "Sigma", NumberInput::default().min(0.).max(10000.), true);
vec![LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: sigma }]
}
pub fn threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let thereshold_min = number_widget(document_node, node_id, 1, "Min Luminance", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
let thereshold_max = number_widget(document_node, node_id, 2, "Max Luminance", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
let luminance_calc = luminance_calculation(document_node, node_id, 3, "Luminance Calc", true);
vec![LayoutGroup::Row { widgets: thereshold_min }, LayoutGroup::Row { widgets: thereshold_max }, luminance_calc]
}
pub fn gradient_map_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let gradient_index = 1;
let reverse_index = 2;
let gradient_row = color_widget(document_node, node_id, gradient_index, "Gradient", ColorButton::default().allow_none(false), true);
let reverse_row = bool_widget(document_node, node_id, reverse_index, "Reverse", CheckboxInput::default(), true);
vec![gradient_row, LayoutGroup::Row { widgets: reverse_row }]
}
pub fn assign_colors_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn assign_colors_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let fill_index = 1;
let stroke_index = 2;
let gradient_index = 3;
@ -1303,22 +1346,7 @@ pub fn assign_colors_properties(document_node: &DocumentNode, node_id: NodeId, _
]
}
pub fn vibrance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let vibrance_index = 1;
let vibrance = number_widget(
document_node,
node_id,
vibrance_index,
"Vibrance",
NumberInput::default().mode_range().min(-100.).max(100.).unit("%"),
true,
);
vec![LayoutGroup::Row { widgets: vibrance }]
}
pub fn channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
// Monochrome
let monochrome_index = 1;
let monochrome = bool_widget(document_node, node_id, monochrome_index, "Monochrome", CheckboxInput::default(), true);
@ -1418,7 +1446,7 @@ pub fn channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _
layout
}
pub fn selective_color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn selective_color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
// Colors choice
let colors_index = 38;
let mut colors = vec![TextLabel::new("Colors").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
@ -1508,40 +1536,13 @@ pub fn selective_color_properties(document_node: &DocumentNode, node_id: NodeId,
}
#[cfg(feature = "gpu")]
pub fn _gpu_map_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn _gpu_map_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let map = text_widget(document_node, node_id, 1, "Map", true);
vec![LayoutGroup::Row { widgets: map }]
}
pub fn opacity_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let gamma = number_widget(document_node, node_id, 1, "Factor", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
vec![LayoutGroup::Row { widgets: gamma }]
}
pub fn blend_mode_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
vec![blend_mode(document_node, node_id, 1, "Blend Mode", true)]
}
pub fn blend_mode_value_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
vec![blend_mode(document_node, node_id, 0, "Blend Mode", true)]
}
pub fn posterize_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let value = number_widget(document_node, node_id, 1, "Levels", NumberInput::default().min(2.).max(255.).int(), true);
vec![LayoutGroup::Row { widgets: value }]
}
#[cfg(feature = "quantization")]
pub fn quantize_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let value = number_widget(document_node, node_id, 1, "Levels", NumberInput::default().min(1.).max(1000.).int(), true);
let index = number_widget(document_node, node_id, 1, "Fit Fn Index", NumberInput::default().min(0.).max(2.).int(), true);
vec![LayoutGroup::Row { widgets: value }, LayoutGroup::Row { widgets: index }]
}
pub fn exposure_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn exposure_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let exposure = number_widget(document_node, node_id, 1, "Exposure", NumberInput::default().min(-20.).max(20.), true);
let offset = number_widget(document_node, node_id, 2, "Offset", NumberInput::default().min(-0.5).max(0.5), true);
let gamma_input = NumberInput::default().min(0.01).max(9.99).increment_step(0.1);
@ -1554,82 +1555,7 @@ pub fn exposure_properties(document_node: &DocumentNode, node_id: NodeId, _conte
]
}
pub fn add_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 1, "Addend", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn subtract_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 1, "Subtrahend", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn divide_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 1, "Divisor", NumberInput::new(Some(1.)), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn multiply_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 1, "Multiplicand", NumberInput::new(Some(1.)), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn exponent_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 1, "Power", NumberInput::new(Some(2.)), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn log_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 1, "Base", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn max_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 1, "Maximum", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn min_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 1, "Minimum", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn eq_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 1, "Equals", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn modulo_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = number_widget(document_node, node_id, 1, "Modulo", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn circle_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
vec![LayoutGroup::Row {
widgets: number_widget(document_node, node_id, 1, "Radius", NumberInput::default(), true),
}]
}
pub fn ellipse_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let operand = |name: &str, index| {
let widgets = number_widget(document_node, node_id, index, name, NumberInput::default(), true);
LayoutGroup::Row { widgets }
};
vec![operand("Radius X", 1), operand("Radius Y", 2)]
}
pub fn rectangle_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn rectangle_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let size_x_index = 1;
let size_y_index = 2;
let corner_rounding_type_index = 3;
@ -1754,37 +1680,17 @@ pub fn rectangle_properties(document_node: &DocumentNode, node_id: NodeId, _cont
]
}
pub fn regular_polygon_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let points = number_widget(document_node, node_id, 1, "Points", NumberInput::default().min(3.), true);
let radius = number_widget(document_node, node_id, 2, "Radius", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets: points }, LayoutGroup::Row { widgets: radius }]
}
pub fn star_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let points = number_widget(document_node, node_id, 1, "Points", NumberInput::default().min(2.), true);
let radius = number_widget(document_node, node_id, 2, "Radius", NumberInput::default(), true);
let inner_radius = number_widget(document_node, node_id, 3, "Inner Radius", NumberInput::default(), true);
vec![LayoutGroup::Row { widgets: points }, LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: inner_radius }]
}
pub fn line_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn line_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let operand = |name: &str, index| vec2_widget(document_node, node_id, index, name, "X", "Y", "px", None, add_blank_assist);
vec![operand("Start", 1), operand("End", 2)]
}
pub fn spline_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn spline_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
vec![LayoutGroup::Row {
widgets: vec_dvec2_input(document_node, node_id, 1, "Points", TextInput::default().centered(true), true),
}]
}
pub fn logic_operator_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let widgets = bool_widget(document_node, node_id, 0, "Operand B", CheckboxInput::default(), true);
vec![LayoutGroup::Row { widgets }]
}
pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", " px", None, add_blank_assist);
let rotation = {
@ -1817,11 +1723,11 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
vec![translation, rotation, scale]
}
pub fn rasterize_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn rasterize_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
footprint_widget(document_node, node_id, 1)
}
pub fn text_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn text_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let text = text_area_widget(document_node, node_id, 1, "Text", true);
let (font, style) = font_inputs(document_node, node_id, 2, "Font", true);
let size = number_widget(document_node, node_id, 3, "Size", NumberInput::default().unit(" px").min(1.), true);
@ -1834,7 +1740,7 @@ pub fn text_properties(document_node: &DocumentNode, node_id: NodeId, _context:
result
}
pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let imaginate_node = [context.selection_network_path, &[node_id]].concat();
let resolve_input = |name: &str| {
@ -2328,7 +2234,7 @@ fn unknown_node_properties(reference: &String) -> Vec<LayoutGroup> {
string_properties(format!("Node '{}' cannot be found in library", reference))
}
pub fn node_no_properties(_document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn node_no_properties(_document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
string_properties(if context.network_interface.is_layer(&node_id, context.selection_network_path) {
"Layer has no properties"
} else {
@ -2336,13 +2242,13 @@ pub fn node_no_properties(_document_node: &DocumentNode, node_id: NodeId, contex
})
}
pub fn index_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn index_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let index = number_widget(document_node, node_id, 1, "Index", NumberInput::default().min(0.), true);
vec![LayoutGroup::Row { widgets: index }]
}
pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> LayoutGroup {
pub(crate) fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> LayoutGroup {
let reference = context.network_interface.reference(&node_id, context.selection_network_path).clone();
let layout = if let Some(ref reference) = reference {
match super::document_node_definitions::resolve_document_node_type(reference) {
@ -2361,66 +2267,7 @@ pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId, c
}
}
pub fn stroke_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color_index = 1;
let weight_index = 2;
let dash_lengths_index = 3;
let dash_offset_index = 4;
let line_cap_index = 5;
let line_join_index = 6;
let miter_limit_index = 7;
let color = color_widget(document_node, node_id, color_index, "Color", ColorButton::default(), true);
let weight = number_widget(document_node, node_id, weight_index, "Weight", NumberInput::default().unit("px").min(0.), true);
let dash_lengths_val = match &document_node.inputs[dash_lengths_index].as_value() {
Some(TaggedValue::VecF64(x)) => x,
_ => &vec![],
};
let dash_lengths = vec_f64_input(document_node, node_id, dash_lengths_index, "Dash Lengths", TextInput::default().centered(true), true);
let number_input = NumberInput::default().unit("px").disabled(dash_lengths_val.is_empty());
let dash_offset = number_widget(document_node, node_id, dash_offset_index, "Dash Offset", number_input, true);
let line_cap = line_cap_widget(document_node, node_id, line_cap_index, "Line Cap", true);
let line_join = line_join_widget(document_node, node_id, line_join_index, "Line Join", true);
let line_join_val = match &document_node.inputs[line_join_index].as_value() {
Some(TaggedValue::LineJoin(x)) => x,
_ => &LineJoin::Miter,
};
let number_input = NumberInput::default().min(0.).disabled(line_join_val != &LineJoin::Miter);
let miter_limit = number_widget(document_node, node_id, miter_limit_index, "Miter Limit", number_input, true);
vec![
color,
LayoutGroup::Row { widgets: weight },
LayoutGroup::Row { widgets: dash_lengths },
LayoutGroup::Row { widgets: dash_offset },
line_cap,
line_join,
LayoutGroup::Row { widgets: miter_limit },
]
}
pub fn repeat_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let direction = vec2_widget(document_node, node_id, 1, "Direction", "X", "Y", " px", None, add_blank_assist);
let angle = number_widget(document_node, node_id, 2, "Angle", NumberInput::default().unit("°"), true);
let instances = number_widget(document_node, node_id, 3, "Instances", NumberInput::default().min(1.).is_integer(true), true);
vec![direction, LayoutGroup::Row { widgets: angle }, LayoutGroup::Row { widgets: instances }]
}
pub fn circular_repeat_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let angle_offset = number_widget(document_node, node_id, 1, "Angle Offset", NumberInput::default().unit("°"), true);
let radius = number_widget(document_node, node_id, 2, "Radius", NumberInput::default(), true); // TODO: What units?
let instances = number_widget(document_node, node_id, 3, "Instances", NumberInput::default().min(1.).is_integer(true), true);
vec![
LayoutGroup::Row { widgets: angle_offset },
LayoutGroup::Row { widgets: radius },
LayoutGroup::Row { widgets: instances },
]
}
pub fn boolean_operation_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn boolean_operation_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let group_of_paths_index = 0;
let operation_index = 1;
@ -2434,7 +2281,7 @@ pub fn boolean_operation_properties(document_node: &DocumentNode, node_id: NodeI
vec![LayoutGroup::Row { widgets }, operation]
}
pub fn copy_to_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn copy_to_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let instance_index = 1;
let random_scale_min_index = 2;
let random_scale_max_index = 3;
@ -2493,7 +2340,7 @@ pub fn copy_to_points_properties(document_node: &DocumentNode, node_id: NodeId,
]
}
pub fn sample_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn sample_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let spacing = number_widget(document_node, node_id, 1, "Spacing", NumberInput::default().min(1.).unit(" px"), true);
let start_offset = number_widget(document_node, node_id, 2, "Start Offset", NumberInput::default().min(0.).unit(" px"), true);
let stop_offset = number_widget(document_node, node_id, 3, "Stop Offset", NumberInput::default().min(0.).unit(" px"), true);
@ -2507,7 +2354,7 @@ pub fn sample_points_properties(document_node: &DocumentNode, node_id: NodeId, _
]
}
pub fn poisson_disk_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn poisson_disk_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let separation_disk_diameter_index = 1;
let seed_index = 2;
@ -2525,18 +2372,8 @@ pub fn poisson_disk_points_properties(document_node: &DocumentNode, node_id: Nod
vec![LayoutGroup::Row { widgets: spacing }, LayoutGroup::Row { widgets: seed }]
}
pub fn morph_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let start_index = number_widget(document_node, node_id, 2, "Start Index", NumberInput::default().min(0.), true);
let time = number_widget(document_node, node_id, 3, "Time", NumberInput::default().min(0.).max(1.).mode_range(), true);
vec![
LayoutGroup::Row { widgets: start_index }.with_tooltip("The index of point on the target that morphs to the first point of the source"),
LayoutGroup::Row { widgets: time }.with_tooltip("Linear time of transition - 0. is source, 1. is target"),
]
}
/// Fill Node Widgets LayoutGroup
pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let fill_index = 1;
let backup_color_index = 2;
let backup_gradient_index = 3;
@ -2711,7 +2548,46 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context:
widgets
}
pub fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub fn stroke_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color_index = 1;
let weight_index = 2;
let dash_lengths_index = 3;
let dash_offset_index = 4;
let line_cap_index = 5;
let line_join_index = 6;
let miter_limit_index = 7;
let color = color_widget(document_node, node_id, color_index, "Color", ColorButton::default(), true);
let weight = number_widget(document_node, node_id, weight_index, "Weight", NumberInput::default().unit("px").min(0.), true);
let dash_lengths_val = match &document_node.inputs[dash_lengths_index].as_value() {
Some(TaggedValue::VecF64(x)) => x,
_ => &vec![],
};
let dash_lengths = vec_f64_input(document_node, node_id, dash_lengths_index, "Dash Lengths", TextInput::default().centered(true), true);
let number_input = NumberInput::default().unit("px").disabled(dash_lengths_val.is_empty());
let dash_offset = number_widget(document_node, node_id, dash_offset_index, "Dash Offset", number_input, true);
let line_cap = line_cap_widget(document_node, node_id, line_cap_index, "Line Cap", true);
let line_join = line_join_widget(document_node, node_id, line_join_index, "Line Join", true);
let line_join_val = match &document_node.inputs[line_join_index].as_value() {
Some(TaggedValue::LineJoin(x)) => x,
_ => &LineJoin::Miter,
};
let number_input = NumberInput::default().min(0.).disabled(line_join_val != &LineJoin::Miter);
let miter_limit = number_widget(document_node, node_id, miter_limit_index, "Miter Limit", number_input, true);
vec![
color,
LayoutGroup::Row { widgets: weight },
LayoutGroup::Row { widgets: dash_lengths },
LayoutGroup::Row { widgets: dash_offset },
line_cap,
line_join,
LayoutGroup::Row { widgets: miter_limit },
]
}
pub(crate) fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let location = vec2_widget(document_node, node_id, 2, "Location", "X", "Y", " px", None, add_blank_assist);
let dimensions = vec2_widget(document_node, node_id, 3, "Dimensions", "W", "H", " px", None, add_blank_assist);
let background = color_widget(document_node, node_id, 4, "Background", ColorButton::default().allow_none(false), true);
@ -2722,27 +2598,7 @@ pub fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _conte
vec![location, dimensions, background, clip_row]
}
pub fn color_fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub(crate) fn color_fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color = color_widget(document_node, node_id, 1, "Color", ColorButton::default(), true);
vec![color]
}
pub fn color_overlay_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color = color_widget(document_node, node_id, 1, "Color", ColorButton::default(), true);
let blend_mode = blend_mode(document_node, node_id, 2, "Blend Mode", true);
let opacity = number_widget(document_node, node_id, 3, "Opacity", NumberInput::default().percentage(), true);
vec![color, blend_mode, LayoutGroup::Row { widgets: opacity }]
}
pub fn image_color_palette(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let size = number_widget(document_node, node_id, 1, "Max Size", NumberInput::default().int().min(1.).max(28.), true);
vec![LayoutGroup::Row { widgets: size }]
}
pub fn centroid_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let centroid_type = centroid_widget(document_node, node_id, 1);
vec![centroid_type]
}

View file

@ -17,7 +17,7 @@ pub enum FrontendGraphDataType {
impl FrontendGraphDataType {
pub fn with_type(input: &Type) -> Self {
match TaggedValue::from_type(input) {
match TaggedValue::from_type_or_none(input) {
TaggedValue::Image(_) | TaggedValue::ImageFrame(_) => Self::Raster,
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
TaggedValue::U32(_)

View file

@ -426,12 +426,12 @@ impl NodeNetworkInterface {
};
} else {
// Disconnect node input if it is not connected to another node in new_ids
let tagged_value = TaggedValue::from_type(&self.input_type(&InputConnector::node(*node_id, input_index), network_path).0);
let tagged_value = TaggedValue::from_type_or_none(&self.input_type(&InputConnector::node(*node_id, input_index), network_path).0);
*input = NodeInput::value(tagged_value, true);
}
} else if let &mut NodeInput::Network { .. } = input {
// Always disconnect network node input
let tagged_value = TaggedValue::from_type(&self.input_type(&InputConnector::node(*node_id, input_index), network_path).0);
let tagged_value = TaggedValue::from_type_or_none(&self.input_type(&InputConnector::node(*node_id, input_index), network_path).0);
*input = NodeInput::value(tagged_value, true);
}
}
@ -1445,7 +1445,12 @@ impl NodeNetworkInterface {
/// Gets the type for a random protonode implementation (used if there is no type from the compiled network)
fn random_protonode_implementation(protonode: &graph_craft::ProtoNodeIdentifier) -> Option<&graphene_std::NodeIOTypes> {
let Some(node_io_hashmap) = NODE_REGISTRY.get(protonode) else {
let mut protonode = protonode.clone();
// TODO: Remove
if let Some((path, _generics)) = protonode.name.split_once('<') {
protonode = path.to_string().to_string().into();
}
let Some(node_io_hashmap) = NODE_REGISTRY.get(&protonode) else {
log::error!("Could not get hashmap for proto node: {protonode:?}");
return None;
};
@ -3120,6 +3125,19 @@ impl NodeNetworkInterface {
node_metadata.persistent_metadata.network_metadata = metadata.network_metadata;
}
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
pub fn set_manual_compostion(&mut self, node_id: &NodeId, network_path: &[NodeId], manual_composition: Option<Type>) {
let Some(network) = self.network_mut(network_path) else {
log::error!("Could not get nested network in set_implementation");
return;
};
let Some(node) = network.nodes.get_mut(node_id) else {
log::error!("Could not get node in set_implementation");
return;
};
node.manual_composition = manual_composition;
}
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
pub fn replace_inputs(&mut self, node_id: &NodeId, inputs: Vec<NodeInput>, network_path: &[NodeId]) -> Vec<NodeInput> {
let Some(network) = self.network_mut(network_path) else {
@ -3386,7 +3404,7 @@ impl NodeNetworkInterface {
}
}
let tagged_value = TaggedValue::from_type(&self.input_type(input_connector, network_path).0);
let tagged_value = TaggedValue::from_type_or_none(&self.input_type(input_connector, network_path).0);
let value_input = NodeInput::value(tagged_value, true);

View file

@ -6,6 +6,7 @@ use crate::consts::DEFAULT_DOCUMENT_NAME;
use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::DocumentMessageData;
use crate::messages::prelude::*;
@ -423,7 +424,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
.get(node_id)
.and_then(|node| node.persistent_metadata.reference.as_ref())
{
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let node_definition = resolve_document_node_type(reference).unwrap();
let default_definition_node = node_definition.default_node_template();
document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation);
document
@ -447,10 +448,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
let node_ids = document.network_interface.network(&[]).unwrap().nodes.keys().cloned().collect::<Vec<_>>();
for node_id in &node_ids {
let Some(node) = document.network_interface.network(&[]).unwrap().nodes.get(node_id) else {
log::error!("could not get node in deserialize_document");
continue;
};
let Some(node_metadata) = document.network_interface.network_metadata(&[]).unwrap().persistent_metadata.node_metadata.get(node_id) else {
log::error!("could not get node metadata for node {node_id} in deserialize_document");
continue;
@ -461,8 +458,24 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
continue;
};
if let Some(node_definition) = resolve_document_node_type(reference) {
let document_node = node_definition.default_node_template().document_node;
document.network_interface.set_manual_compostion(node_id, &[], document_node.manual_composition);
// if ["Fill", "Stroke", "Splines from Points", "Sample Subpaths", "Sample Points", "Copy to Points", "Path", "Scatter Points"].contains(&reference.as_str()) {
// document.network_interface.set_implementation(node_id, &[], document_node.implementation);
// }
document.network_interface.replace_implementation(node_id, &[], document_node.implementation);
document
.network_interface
.replace_implementation_metadata(node_id, &[], node_definition.default_node_template().persistent_node_metadata);
}
let Some(node) = document.network_interface.network(&[]).unwrap().nodes.get(node_id) else {
log::error!("could not get node in deserialize_document");
continue;
};
if reference == "Fill" && node.inputs.len() == 8 {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let node_definition = resolve_document_node_type(reference).unwrap();
let document_node = node_definition.default_node_template().document_node;
document.network_interface.replace_implementation(node_id, &[], document_node.implementation.clone());
@ -518,7 +531,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
}
// Upgrade construct layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946
// Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946
if reference == "Merge" || reference == "Artboard" {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let new_merge_node = node_definition.default_node_template();

View file

@ -13,7 +13,7 @@ use crate::messages::prelude::*;
use crate::node_graph_executor::NodeGraphExecutor;
use graphene_core::raster::color::Color;
use graphene_std::text::FontCache;
use graphene_core::text::FontCache;
use std::fmt::{self, Debug};

View file

@ -230,7 +230,7 @@ impl NodeRuntime {
self.monitor_nodes = proto_network
.nodes
.iter()
.filter(|(_, node)| node.identifier == "graphene_core::memo::MonitorNode<_, _, _>".into())
.filter(|(_, node)| node.identifier == "graphene_core::memo::MonitorNode".into())
.map(|(_, node)| node.original_location.path.clone().unwrap_or_default())
.collect::<Vec<_>>();
@ -576,7 +576,7 @@ impl NodeGraphExecutor {
});
responses.add(NodeGraphMessage::SendGraph);
return Err("Node graph evaluation failed".to_string());
return Err(format!("Node graph evaluation failed:\n{e}"));
}
Ok(result) => result,
};

View file

@ -17,7 +17,6 @@ default = ["custom-protocol"]
# DO NOT remove this
custom-protocol = ["tauri/custom-protocol"]
gpu = ["graphite-editor/gpu"]
quantization = ["graphite-editor/quantization"]
[dependencies]
# Local dependencies

View file

@ -461,6 +461,7 @@
<LayoutCol class="table">
{#if rulersVisible}
<LayoutRow class="ruler-or-scrollbar top-ruler">
<LayoutCol class="ruler-corner"></LayoutCol>
<RulerInput origin={rulerOrigin.x} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Horizontal" bind:this={rulerHorizontal} />
</LayoutRow>
{/if}
@ -653,8 +654,23 @@
flex: 0 0 auto;
}
.ruler-corner {
background: var(--color-2-mildblack);
width: 16px;
position: relative;
&::after {
content: "";
background: var(--color-5-dullgray);
position: absolute;
width: 1px;
height: 1px;
right: 0;
bottom: 0;
}
}
.top-ruler .ruler-input {
padding-left: 16px;
margin-right: 16px;
}

View file

@ -99,7 +99,7 @@
});
});
const START_CATEGORIES_ORDER = ["General", "Value", "Math", "Style"];
const START_CATEGORIES_ORDER = ["UNCATEGORIZED", "General", "Value", "Math", "Style"];
const END_CATEGORIES_ORDER = ["Debug"];
return Array.from(categories)
.sort((a, b) => a[0].localeCompare(b[0]))

View file

@ -62,6 +62,8 @@
height: 0;
margin: 0;
opacity: 0;
// Firefox weirdly applies a 2px border which causes this element to take up a 4x4 square of space, so this removes it from the flow to prevent it from offsetting the label
position: absolute;
}
// Unchecked

View file

@ -116,16 +116,19 @@
<style lang="scss" global>
.ruler-input {
flex: 1 1 100%;
background: var(--color-4-dimgray);
background: var(--color-2-mildblack);
overflow: hidden;
position: relative;
box-sizing: border-box;
&.horizontal {
height: 16px;
border-bottom: 1px solid var(--color-5-dullgray);
}
&.vertical {
width: 16px;
border-right: 1px solid var(--color-5-dullgray);
svg text {
text-anchor: end;
@ -137,7 +140,7 @@
path {
stroke-width: 1px;
stroke: var(--color-6-lowergray);
stroke: var(--color-5-dullgray);
}
text {

View file

@ -91,7 +91,12 @@ export function createDocumentState(editor: Editor) {
});
});
editor.subscriptions.subscribeJsMessage(TriggerDelayedZoomCanvasToFitAll, () => {
// TODO: This is horribly hacky
setTimeout(() => editor.handle.zoomCanvasToFitAll(), 0);
setTimeout(() => editor.handle.zoomCanvasToFitAll(), 1);
setTimeout(() => editor.handle.zoomCanvasToFitAll(), 10);
setTimeout(() => editor.handle.zoomCanvasToFitAll(), 50);
setTimeout(() => editor.handle.zoomCanvasToFitAll(), 100);
});
return {

View file

@ -19,7 +19,11 @@ export async function initWasm() {
// Import the WASM module JS bindings and wrap them in the panic proxy
// eslint-disable-next-line import/no-cycle
await init();
const wasm = await init();
for (const [name, f] of Object.entries(wasm)) {
if (name.startsWith("__node_registry")) f();
}
wasmImport = await wasmMemory();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).imageCanvases = {};

View file

@ -735,7 +735,7 @@ impl EditorHandle {
for node_id in nodes_to_upgrade {
document
.network_interface
.replace_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>"));
.replace_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ToArtboardNode"));
document
.network_interface
.add_input(&node_id, &[], TaggedValue::IVec2(glam::IVec2::default()), false, 2, "".to_string());

View file

@ -30,6 +30,7 @@ dyn-any-derive = { path = "derive", optional = true }
# Optional dependencies
log = { version = "0.4", optional = true }
glam = { version = "0.28", optional = true, default-features = false }
reqwest = { version = "0.12", optional = true, default-features = false }
[package.metadata.docs.rs]
all-features = true

View file

@ -46,7 +46,7 @@ pub fn system_desc_derive(input: TokenStream) -> TokenStream {
let old_params = &generics.params.iter().collect::<Vec<_>>();
quote! {
unsafe impl<'dyn_any, #(#old_params,)*> StaticType for #struct_name <#(#dyn_params,)*> {
unsafe impl<'dyn_any, #(#old_params,)*> dyn_any::StaticType for #struct_name <#(#dyn_params,)*> {
type Static = #struct_name <#(#static_params,)*>;
}
}

View file

@ -295,16 +295,21 @@ impl_type!(
Quat, Affine2, Affine3A, DAffine2, DAffine3, DQuat
);
#[cfg(feature = "reqwest")]
use reqwest::Response;
#[cfg(feature = "reqwest")]
impl_type!(Response);
#[cfg(feature = "alloc")]
unsafe impl<T: crate::StaticType + ?Sized> crate::StaticType for Box<T> {
type Static = Box<<T as crate::StaticType>::Static>;
}
#[test]
fn test_tuple_of_boxes() {
let tuple = (Box::new(&1 as &dyn DynAny<'static>), Box::new(&2 as &dyn DynAny<'static>));
let tuple = (Box::new(&1u32 as &dyn DynAny<'static>), Box::new(&2u32 as &dyn DynAny<'static>));
let dyn_any = &tuple as &dyn DynAny;
assert_eq!(&1, downcast_ref(*downcast_ref::<(Box<&dyn DynAny>, Box<&dyn DynAny>)>(dyn_any).unwrap().0).unwrap());
assert_eq!(&2, downcast_ref(*downcast_ref::<(Box<&dyn DynAny>, Box<&dyn DynAny>)>(dyn_any).unwrap().1).unwrap());
assert_eq!(&1, downcast_ref::<u32>(*downcast_ref::<(Box<&dyn DynAny>, Box<&dyn DynAny>)>(dyn_any).unwrap().0).unwrap());
assert_eq!(&2, downcast_ref::<u32>(*downcast_ref::<(Box<&dyn DynAny>, Box<&dyn DynAny>)>(dyn_any).unwrap().1).unwrap());
}
macro_rules! impl_tuple {

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 {