mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
Replace Footprint/() call arguments with dynamically-bound Contexts (#2232)
* Implement experimental Context struct and traits * Add Ctx super trait * Checkpoint * Return Any instead of DynAny * Fix send implementation for inputs with lifetimes * Port more nodes * Uncomment nodes * Port more nodes * Port vector nodes * Partial progress (the stuff I'm more sure about) * Partial progress (the stuff that's not compiling and I'm not sure about) * Fix more errors * First pass of fixing errors introduced by rebase * Port wasm application io * Fix brush node types * Add type annotation * Fix warnings and wasm compilation * Change types for Document Node definitions * Improve debugging for footprint not found errors * Forward context in append artboard node * Fix thumbnails * Fix loading most demo artwork * Wrap output type of all nodes in future * Encode futures as part of the type * Fix document node definitions for future types * Remove Clippy warnings * Fix more things * Fix opening demo art with manual composition upgrading * Set correct type for manual composition * Fix brush * Fix tests * Update docs for deps * Fix up some node signature issues * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
parent
0c1e96b9c6
commit
4ff2bdb04f
43 changed files with 1338 additions and 1545 deletions
|
@ -6,7 +6,7 @@
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/node:1": {}
|
"ghcr.io/devcontainers/features/node:1": {}
|
||||||
},
|
},
|
||||||
"onCreateCommand": "cargo install cargo-watch wasm-pack cargo-about && cargo install -f wasm-bindgen-cli@0.2.99",
|
"onCreateCommand": "cargo install cargo-watch wasm-pack cargo-about && cargo install -f wasm-bindgen-cli@0.2.100",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
// NOTE: Keep this in sync with `.vscode/extensions.json`
|
// NOTE: Keep this in sync with `.vscode/extensions.json`
|
||||||
|
|
59
Cargo.lock
generated
59
Cargo.lock
generated
|
@ -1397,6 +1397,12 @@ dependencies = [
|
||||||
"syn 2.0.98",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diff"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
@ -2400,6 +2406,7 @@ dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"pretty_assertions",
|
||||||
"reqwest 0.12.12",
|
"reqwest 0.12.12",
|
||||||
"rustc-hash 2.1.0",
|
"rustc-hash 2.1.0",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -3505,9 +3512,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.76"
|
version = "0.3.77"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
@ -4907,6 +4914,16 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
|
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty_assertions"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
|
||||||
|
dependencies = [
|
||||||
|
"diff",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
|
@ -7502,20 +7519,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.99"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.99"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
|
@ -7527,9 +7545,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.49"
|
version = "0.4.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
|
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
@ -7540,9 +7558,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.99"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
|
@ -7550,9 +7568,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.99"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -7563,9 +7581,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.99"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-streams"
|
name = "wasm-streams"
|
||||||
|
@ -7691,9 +7712,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.76"
|
version = "0.3.77"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
|
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
@ -8824,6 +8845,12 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yansi"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
|
|
|
@ -63,10 +63,10 @@ spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git" }
|
||||||
wgpu-types = "23"
|
wgpu-types = "23"
|
||||||
wgpu = "23"
|
wgpu = "23"
|
||||||
once_cell = "1.13" # Remove when `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) is stabilized in Rust 1.80 and we bump our MSRV
|
once_cell = "1.13" # Remove when `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) is stabilized in Rust 1.80 and we bump our MSRV
|
||||||
wasm-bindgen = "=0.2.99" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/project-setup/_index.md`. We pin this version because wasm-bindgen upgrades may break various things.
|
wasm-bindgen = "=0.2.100" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/project-setup/_index.md`. We pin this version because wasm-bindgen upgrades may break various things.
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
js-sys = "=0.3.76"
|
js-sys = "=0.3.77"
|
||||||
web-sys = "=0.3.76"
|
web-sys = "=0.3.77"
|
||||||
winit = "0.29"
|
winit = "0.29"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
tokio = { version = "1.29", features = ["fs", "io-std"] }
|
tokio = { version = "1.29", features = ["fs", "io-std"] }
|
||||||
|
@ -80,6 +80,7 @@ base64 = "0.22"
|
||||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||||
rustybuzz = "0.20"
|
rustybuzz = "0.20"
|
||||||
spirv = "0.3"
|
spirv = "0.3"
|
||||||
|
pretty_assertions = "1.4.1"
|
||||||
fern = { version = "0.7", features = ["colored"] }
|
fern = { version = "0.7", features = ["colored"] }
|
||||||
num_enum = "0.7"
|
num_enum = "0.7"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
|
|
|
@ -293,9 +293,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(concrete!(ArtboardGroup))), 0),
|
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(ArtboardGroup))), 0),
|
||||||
NodeInput::node(NodeId(1), 0),
|
NodeInput::node(NodeId(1), 0),
|
||||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||||
],
|
],
|
||||||
|
@ -390,7 +390,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Load Image",
|
identifier: "Load Image",
|
||||||
category: "Raster: Generator",
|
category: "Network",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
|
@ -398,20 +398,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
|
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DecodeImageNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DecodeImageNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")),
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Context)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -484,7 +484,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
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()
|
..Default::default()
|
||||||
|
@ -554,7 +554,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -638,20 +638,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::scope("editor-api")],
|
inputs: vec![NodeInput::scope("editor-api")],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
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")),
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RasterizeNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RasterizeNode")),
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -724,7 +724,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
category: "Raster",
|
category: "Raster",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Context)),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::raster::NoisePatternNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::raster::NoisePatternNode")),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::Bool(false), false),
|
NodeInput::value(TaggedValue::Bool(false), false),
|
||||||
|
@ -1000,7 +1000,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
NodeInput::network(concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>), 2),
|
NodeInput::network(concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>), 2),
|
||||||
NodeInput::network(concrete!(BrushCache), 3),
|
NodeInput::network(concrete!(BrushCache), 3),
|
||||||
],
|
],
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Context)),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::brush::BrushNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::brush::BrushNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}]
|
}]
|
||||||
|
@ -1062,7 +1062,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"),
|
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"),
|
||||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
|
@ -1081,7 +1081,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"),
|
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"),
|
||||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Context)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
|
@ -1103,7 +1103,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
nodes: vec![DocumentNode {
|
nodes: vec![DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 1)],
|
inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 1)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")),
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Context)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}]
|
}]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -1158,12 +1158,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::node(NodeId(0), 0)],
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UniformNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UniformNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -1242,7 +1242,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -1321,7 +1321,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -1406,7 +1406,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -1515,7 +1515,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -1595,7 +1595,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -1665,13 +1665,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::scope("editor-api")],
|
inputs: vec![NodeInput::scope("editor-api")],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -1736,7 +1736,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::network(concrete!(ShaderInputFrame), 0),
|
NodeInput::network(concrete!(ShaderInputFrame), 0),
|
||||||
NodeInput::network(concrete!(Arc<wgpu_executor::Surface>), 1),
|
NodeInput::network(concrete!(Arc<wgpu_executor::Surface>), 1),
|
||||||
|
@ -1947,35 +1947,36 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
// Aims for interoperable compatibility with:
|
// Aims for interoperable compatibility with:
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Max%20input%20range-,Curves,-Curves%20settings%20files
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Max%20input%20range-,Curves,-Curves%20settings%20files
|
||||||
DocumentNodeDefinition {
|
// TODO: Fix this, it's currently broken
|
||||||
identifier: "Curves",
|
// DocumentNodeDefinition {
|
||||||
category: "Raster: Adjustment",
|
// identifier: "Curves",
|
||||||
node_template: NodeTemplate {
|
// category: "Raster: Adjustment",
|
||||||
document_node: DocumentNode {
|
// node_template: NodeTemplate {
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
|
// document_node: DocumentNode {
|
||||||
inputs: vec![
|
// implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
|
||||||
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
|
// inputs: vec![
|
||||||
NodeInput::value(TaggedValue::Curve(Default::default()), false),
|
// NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
|
||||||
],
|
// NodeInput::value(TaggedValue::Curve(Default::default()), false),
|
||||||
..Default::default()
|
// ],
|
||||||
},
|
// ..Default::default()
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
// },
|
||||||
input_properties: vec!["Image".into(), "Curve".into()],
|
// persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
output_names: vec!["Image".to_string()],
|
// input_properties: vec!["Image".into(), "Curve".into()],
|
||||||
..Default::default()
|
// output_names: vec!["Image".to_string()],
|
||||||
},
|
// ..Default::default()
|
||||||
},
|
// },
|
||||||
description: Cow::Borrowed("TODO"),
|
// },
|
||||||
properties: None,
|
// description: Cow::Borrowed("TODO"),
|
||||||
},
|
// properties: None,
|
||||||
(*IMAGINATE_NODE).clone(),
|
// },
|
||||||
|
// (*IMAGINATE_NODE).clone(),
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Line",
|
identifier: "Line",
|
||||||
category: "Vector: Shape",
|
category: "Vector: Shape",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::LineNode"),
|
implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::LineNode"),
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::None, false),
|
NodeInput::value(TaggedValue::None, false),
|
||||||
NodeInput::value(TaggedValue::DVec2(DVec2::new(0., -50.)), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::new(0., -50.)), false),
|
||||||
|
@ -2089,7 +2090,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"),
|
implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"),
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::scope("editor-api"),
|
NodeInput::scope("editor-api"),
|
||||||
NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false),
|
NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false),
|
||||||
|
@ -2190,7 +2191,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
NodeInput::network(concrete!(DVec2), 4),
|
NodeInput::network(concrete!(DVec2), 4),
|
||||||
NodeInput::network(concrete!(DVec2), 5),
|
NodeInput::network(concrete!(DVec2), 5),
|
||||||
],
|
],
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Context)),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::TransformNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::TransformNode")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
@ -2350,7 +2351,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
NodeInput::value(TaggedValue::F64(0.), false),
|
NodeInput::value(TaggedValue::F64(0.), false),
|
||||||
NodeInput::value(TaggedValue::U32(0), false),
|
NodeInput::value(TaggedValue::U32(0), false),
|
||||||
],
|
],
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Context)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
|
@ -2699,7 +2700,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, (field, node_io_ty))| {
|
.map(|(index, (field, node_io_ty))| {
|
||||||
let ty = field.default_type.as_ref().unwrap_or(node_io_ty);
|
let ty = field.default_type.as_ref().unwrap_or(node_io_ty);
|
||||||
let exposed = if index == 0 { *ty != fn_type!(()) } else { field.exposed };
|
let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed };
|
||||||
|
|
||||||
match field.value_source {
|
match field.value_source {
|
||||||
RegistryValueSource::None => {}
|
RegistryValueSource::None => {}
|
||||||
|
@ -2753,7 +2754,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
|
|
||||||
pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentNodeDefinition {
|
pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentNodeDefinition {
|
||||||
identifier: "Imaginate",
|
identifier: "Imaginate",
|
||||||
category: "Raster: Generator",
|
category: "Raster",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
|
@ -2762,7 +2763,7 @@ pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentN
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 0)],
|
inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 0)],
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
|
@ -260,7 +260,7 @@ pub(crate) fn property_from_type(
|
||||||
}
|
}
|
||||||
Type::Generic(_) => vec![TextLabel::new("Generic type (not supported)").widget_holder()].into(),
|
Type::Generic(_) => vec![TextLabel::new("Generic type (not supported)").widget_holder()].into(),
|
||||||
Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, context),
|
Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, context),
|
||||||
Type::Future(_) => vec![TextLabel::new("Future type (not supported)").widget_holder()].into(),
|
Type::Future(out) => return property_from_type(node_id, index, out, number_options, context),
|
||||||
};
|
};
|
||||||
|
|
||||||
extra_widgets.push(widgets);
|
extra_widgets.push(widgets);
|
||||||
|
|
|
@ -575,6 +575,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
|
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
|
||||||
|
log::error!("could not get reference in deserialize_document");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -584,6 +585,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
};
|
};
|
||||||
let inputs_count = node.inputs.len();
|
let inputs_count = node.inputs.len();
|
||||||
|
|
||||||
|
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition
|
||||||
|
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) {
|
||||||
|
document.network_interface.set_manual_compostion(node_id, &[], graph_craft::concrete!(graphene_std::Context).into());
|
||||||
|
}
|
||||||
|
|
||||||
// Upgrade Fill nodes to the format change in #1778
|
// Upgrade Fill nodes to the format change in #1778
|
||||||
if reference == "Fill" && inputs_count == 8 {
|
if reference == "Fill" && inputs_count == 8 {
|
||||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||||
|
|
|
@ -15,6 +15,7 @@ use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment};
|
||||||
use graphene_core::text::FontCache;
|
use graphene_core::text::FontCache;
|
||||||
use graphene_core::transform::Footprint;
|
use graphene_core::transform::Footprint;
|
||||||
use graphene_core::vector::style::ViewMode;
|
use graphene_core::vector::style::ViewMode;
|
||||||
|
use graphene_core::Context;
|
||||||
use graphene_std::renderer::{format_transform_matrix, RenderMetadata};
|
use graphene_std::renderer::{format_transform_matrix, RenderMetadata};
|
||||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||||
|
@ -286,17 +287,17 @@ impl NodeRuntime {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(io) = introspected_data.downcast_ref::<IORecord<Footprint, graphene_core::GraphicElement>>() {
|
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_core::GraphicElement>>() {
|
||||||
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
||||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), graphene_core::GraphicElement>>() {
|
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), graphene_core::GraphicElement>>() {
|
||||||
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
||||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Footprint, graphene_core::Artboard>>() {
|
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_core::Artboard>>() {
|
||||||
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
||||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), graphene_core::Artboard>>() {
|
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), graphene_core::Artboard>>() {
|
||||||
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
||||||
}
|
}
|
||||||
// Insert the vector modify if we are dealing with vector data
|
// Insert the vector modify if we are dealing with vector data
|
||||||
else if let Some(record) = introspected_data.downcast_ref::<IORecord<Footprint, VectorDataTable>>() {
|
else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
|
||||||
self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone());
|
self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone());
|
||||||
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<(), VectorDataTable>>() {
|
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<(), VectorDataTable>>() {
|
||||||
self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone());
|
self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone());
|
||||||
|
|
|
@ -58,6 +58,13 @@ pub trait DynAny<'a>: 'a {
|
||||||
fn type_id(&self) -> TypeId;
|
fn type_id(&self) -> TypeId;
|
||||||
#[cfg(feature = "log-bad-types")]
|
#[cfg(feature = "log-bad-types")]
|
||||||
fn type_name(&self) -> &'static str;
|
fn type_name(&self) -> &'static str;
|
||||||
|
fn reborrow_box<'short>(self: Box<Self>) -> Box<dyn DynAny<'short> + 'short>
|
||||||
|
where
|
||||||
|
'a: 'short;
|
||||||
|
fn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + Send + Sync + 'short)
|
||||||
|
where
|
||||||
|
'a: 'short,
|
||||||
|
Self: Send + Sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: StaticType + 'a> DynAny<'a> for T {
|
impl<'a, T: StaticType + 'a> DynAny<'a> for T {
|
||||||
|
@ -68,6 +75,20 @@ impl<'a, T: StaticType + 'a> DynAny<'a> for T {
|
||||||
fn type_name(&self) -> &'static str {
|
fn type_name(&self) -> &'static str {
|
||||||
core::any::type_name::<T>()
|
core::any::type_name::<T>()
|
||||||
}
|
}
|
||||||
|
fn reborrow_box<'short>(self: Box<Self>) -> Box<dyn DynAny<'short> + 'short>
|
||||||
|
where
|
||||||
|
'a: 'short,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + Send + Sync + 'short)
|
||||||
|
where
|
||||||
|
'a: 'short,
|
||||||
|
Self: Send + Sync,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn downcast_ref<'a, V: StaticType + 'a>(i: &'a dyn DynAny<'a>) -> Option<&'a V> {
|
pub fn downcast_ref<'a, V: StaticType + 'a>(i: &'a dyn DynAny<'a>) -> Option<&'a V> {
|
||||||
if i.type_id() == core::any::TypeId::of::<<V as StaticType>::Static>() {
|
if i.type_id() == core::any::TypeId::of::<<V as StaticType>::Static>() {
|
||||||
|
|
307
node-graph/gcore/src/context.rs
Normal file
307
node-graph/gcore/src/context.rs
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
use crate::transform::Footprint;
|
||||||
|
|
||||||
|
use core::{any::Any, borrow::Borrow, panic::Location};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub trait Ctx: Clone + Send {}
|
||||||
|
|
||||||
|
pub trait ExtractFootprint {
|
||||||
|
#[track_caller]
|
||||||
|
fn try_footprint(&self) -> Option<&Footprint>;
|
||||||
|
#[track_caller]
|
||||||
|
fn footprint(&self) -> &Footprint {
|
||||||
|
self.try_footprint().unwrap_or_else(|| {
|
||||||
|
log::error!("Context did not have a footprint, called from: {}", Location::caller());
|
||||||
|
&const { Footprint::empty() }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ExtractTime {
|
||||||
|
fn try_time(&self) -> Option<f64>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ExtractIndex {
|
||||||
|
fn try_index(&self) -> Option<usize>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consider returning a slice or something like that
|
||||||
|
pub trait ExtractVarArgs {
|
||||||
|
// Call this lifetime 'b so it is less likely to coflict when auto generating the function signature for implementation
|
||||||
|
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult>;
|
||||||
|
fn varargs_len(&self) -> Result<usize, VarArgsResult>;
|
||||||
|
}
|
||||||
|
// Consider returning a slice or something like that
|
||||||
|
pub trait CloneVarArgs: ExtractVarArgs {
|
||||||
|
// fn box_clone(&self) -> Vec<DynBox>;
|
||||||
|
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractVarArgs {}
|
||||||
|
|
||||||
|
impl<T: ?Sized + ExtractFootprint + ExtractIndex + ExtractTime + ExtractVarArgs> ExtractAll for T {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum VarArgsResult {
|
||||||
|
IndexOutOfBounds,
|
||||||
|
NoVarArgs,
|
||||||
|
}
|
||||||
|
impl<T: Ctx> Ctx for Option<T> {}
|
||||||
|
impl<T: Ctx + Sync> Ctx for &T {}
|
||||||
|
impl Ctx for () {}
|
||||||
|
impl Ctx for Footprint {}
|
||||||
|
impl ExtractFootprint for () {
|
||||||
|
fn try_footprint(&self) -> Option<&Footprint> {
|
||||||
|
log::error!("tried to extract footprint form (), {}", Location::caller());
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ExtractFootprint + Ctx + Sync + Send> ExtractFootprint for &T {
|
||||||
|
fn try_footprint(&self) -> Option<&Footprint> {
|
||||||
|
(*self).try_footprint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ExtractFootprint + Sync> ExtractFootprint for Option<T> {
|
||||||
|
fn try_footprint(&self) -> Option<&Footprint> {
|
||||||
|
self.as_ref().and_then(|x| x.try_footprint())
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn footprint(&self) -> &Footprint {
|
||||||
|
self.try_footprint().unwrap_or_else(|| {
|
||||||
|
log::warn!("trying to extract footprint from context None {} ", Location::caller());
|
||||||
|
&const { Footprint::empty() }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: ExtractTime + Sync> ExtractTime for Option<T> {
|
||||||
|
fn try_time(&self) -> Option<f64> {
|
||||||
|
self.as_ref().and_then(|x| x.try_time())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: ExtractIndex> ExtractIndex for Option<T> {
|
||||||
|
fn try_index(&self) -> Option<usize> {
|
||||||
|
self.as_ref().and_then(|x| x.try_index())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: ExtractVarArgs + Sync> ExtractVarArgs for Option<T> {
|
||||||
|
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||||
|
let Some(ref inner) = self else { return Err(VarArgsResult::NoVarArgs) };
|
||||||
|
inner.vararg(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||||
|
let Some(ref inner) = self else { return Err(VarArgsResult::NoVarArgs) };
|
||||||
|
inner.varargs_len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: ExtractFootprint + Sync> ExtractFootprint for Arc<T> {
|
||||||
|
fn try_footprint(&self) -> Option<&Footprint> {
|
||||||
|
(**self).try_footprint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: ExtractTime + Sync> ExtractTime for Arc<T> {
|
||||||
|
fn try_time(&self) -> Option<f64> {
|
||||||
|
(**self).try_time()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: ExtractIndex> ExtractIndex for Arc<T> {
|
||||||
|
fn try_index(&self) -> Option<usize> {
|
||||||
|
(**self).try_index()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: ExtractVarArgs + Sync> ExtractVarArgs for Arc<T> {
|
||||||
|
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||||
|
(**self).vararg(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||||
|
(**self).varargs_len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: CloneVarArgs + Sync> CloneVarArgs for Option<T> {
|
||||||
|
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {
|
||||||
|
self.as_ref().and_then(CloneVarArgs::arc_clone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ExtractVarArgs + Sync> ExtractVarArgs for &T {
|
||||||
|
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||||
|
(*self).vararg(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||||
|
(*self).varargs_len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: CloneVarArgs + Sync> CloneVarArgs for Arc<T> {
|
||||||
|
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {
|
||||||
|
(**self).arc_clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ctx for ContextImpl<'_> {}
|
||||||
|
impl Ctx for Arc<OwnedContextImpl> {}
|
||||||
|
|
||||||
|
impl ExtractFootprint for ContextImpl<'_> {
|
||||||
|
fn try_footprint(&self) -> Option<&Footprint> {
|
||||||
|
self.footprint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ExtractTime for ContextImpl<'_> {
|
||||||
|
fn try_time(&self) -> Option<f64> {
|
||||||
|
self.time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ExtractIndex for ContextImpl<'_> {
|
||||||
|
fn try_index(&self) -> Option<usize> {
|
||||||
|
self.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ExtractVarArgs for ContextImpl<'_> {
|
||||||
|
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||||
|
let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };
|
||||||
|
inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||||
|
let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };
|
||||||
|
Ok(inner.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtractFootprint for OwnedContextImpl {
|
||||||
|
fn try_footprint(&self) -> Option<&Footprint> {
|
||||||
|
self.footprint.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ExtractTime for OwnedContextImpl {
|
||||||
|
fn try_time(&self) -> Option<f64> {
|
||||||
|
self.time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ExtractIndex for OwnedContextImpl {
|
||||||
|
fn try_index(&self) -> Option<usize> {
|
||||||
|
self.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ExtractVarArgs for OwnedContextImpl {
|
||||||
|
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||||
|
let Some(ref inner) = self.varargs else {
|
||||||
|
let Some(ref parent) = self.parent else {
|
||||||
|
return Err(VarArgsResult::NoVarArgs);
|
||||||
|
};
|
||||||
|
return parent.vararg(index);
|
||||||
|
};
|
||||||
|
inner.get(index).map(|x| x.as_ref()).ok_or(VarArgsResult::IndexOutOfBounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||||
|
let Some(ref inner) = self.varargs else {
|
||||||
|
let Some(ref parent) = self.parent else {
|
||||||
|
return Err(VarArgsResult::NoVarArgs);
|
||||||
|
};
|
||||||
|
return parent.varargs_len();
|
||||||
|
};
|
||||||
|
Ok(inner.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CloneVarArgs for Arc<OwnedContextImpl> {
|
||||||
|
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {
|
||||||
|
Some(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Context<'a> = Option<Arc<OwnedContextImpl>>;
|
||||||
|
type DynRef<'a> = &'a (dyn Any + Send + Sync);
|
||||||
|
type DynBox = Box<dyn Any + Send + Sync>;
|
||||||
|
|
||||||
|
#[derive(dyn_any::DynAny)]
|
||||||
|
pub struct OwnedContextImpl {
|
||||||
|
footprint: Option<crate::transform::Footprint>,
|
||||||
|
varargs: Option<Arc<[DynBox]>>,
|
||||||
|
parent: Option<Arc<dyn ExtractVarArgs + Sync + Send>>,
|
||||||
|
// This could be converted into a single enum to save extra bytes
|
||||||
|
index: Option<usize>,
|
||||||
|
time: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OwnedContextImpl {
|
||||||
|
#[track_caller]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::hash::Hash for OwnedContextImpl {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.footprint.hash(state);
|
||||||
|
self.index.hash(state);
|
||||||
|
self.time.map(|x| x.to_bits()).hash(state);
|
||||||
|
self.parent.as_ref().map(|x| Arc::as_ptr(x).addr()).hash(state);
|
||||||
|
self.varargs.as_ref().map(|x| Arc::as_ptr(x).addr()).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedContextImpl {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn from<T: ExtractAll + CloneVarArgs>(value: T) -> Self {
|
||||||
|
let footprint = value.try_footprint().copied();
|
||||||
|
let index = value.try_index();
|
||||||
|
let time = value.try_time();
|
||||||
|
let parent = value.arc_clone();
|
||||||
|
OwnedContextImpl {
|
||||||
|
footprint,
|
||||||
|
varargs: None,
|
||||||
|
parent,
|
||||||
|
index,
|
||||||
|
time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn empty() -> Self {
|
||||||
|
OwnedContextImpl {
|
||||||
|
footprint: None,
|
||||||
|
varargs: None,
|
||||||
|
parent: None,
|
||||||
|
index: None,
|
||||||
|
time: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedContextImpl {
|
||||||
|
pub fn set_footprint(&mut self, footprint: Footprint) {
|
||||||
|
self.footprint = Some(footprint);
|
||||||
|
}
|
||||||
|
pub fn with_footprint(mut self, footprint: Footprint) -> Self {
|
||||||
|
self.footprint = Some(footprint);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn into_context(self) -> Option<Arc<Self>> {
|
||||||
|
Some(Arc::new(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Copy, dyn_any::DynAny)]
|
||||||
|
pub struct ContextImpl<'a> {
|
||||||
|
pub(crate) footprint: Option<&'a crate::transform::Footprint>,
|
||||||
|
varargs: Option<&'a [DynRef<'a>]>,
|
||||||
|
// This could be converted into a single enum to save extra bytes
|
||||||
|
index: Option<usize>,
|
||||||
|
time: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ContextImpl<'a> {
|
||||||
|
pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl (Borrow<[DynRef<'f>]>)>) -> ContextImpl<'f>
|
||||||
|
where
|
||||||
|
'a: 'f,
|
||||||
|
{
|
||||||
|
ContextImpl {
|
||||||
|
footprint: Some(new_footprint),
|
||||||
|
varargs: varargs.map(|x| x.borrow()),
|
||||||
|
..*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
use crate::Node;
|
use crate::Node;
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct FnNode<T: Fn(I) -> O, I, O>(T, PhantomData<(I, O)>);
|
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> {
|
impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode<T, I, O> {
|
||||||
|
|
|
@ -2,10 +2,10 @@ use crate::application_io::{TextureFrame, TextureFrameTable};
|
||||||
use crate::instances::Instances;
|
use crate::instances::Instances;
|
||||||
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
||||||
use crate::raster::BlendMode;
|
use crate::raster::BlendMode;
|
||||||
use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut};
|
use crate::transform::{Transform, TransformMut};
|
||||||
use crate::uuid::NodeId;
|
use crate::uuid::NodeId;
|
||||||
use crate::vector::{VectorData, VectorDataTable};
|
use crate::vector::{VectorData, VectorDataTable};
|
||||||
use crate::Color;
|
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||||
|
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
|
|
||||||
|
@ -280,28 +280,8 @@ impl ArtboardGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn layer<F: 'n + Send + Copy>(
|
async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
|
||||||
#[implementations(
|
let mut stack = stack.one_item().clone();
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
)]
|
|
||||||
stack: impl Node<F, Output = GraphicGroupTable>,
|
|
||||||
#[implementations(
|
|
||||||
() -> GraphicElement,
|
|
||||||
Footprint -> GraphicElement,
|
|
||||||
)]
|
|
||||||
element: impl Node<F, Output = GraphicElement>,
|
|
||||||
node_path: Vec<NodeId>,
|
|
||||||
) -> GraphicGroupTable {
|
|
||||||
let mut element = element.eval(footprint).await;
|
|
||||||
let stack = stack.eval(footprint).await;
|
|
||||||
let stack = stack.one_item();
|
|
||||||
let mut stack = stack.clone();
|
|
||||||
|
|
||||||
if stack.transform.matrix2.determinant() != 0. {
|
if stack.transform.matrix2.determinant() != 0. {
|
||||||
*element.transform_mut() = stack.transform.inverse() * element.transform();
|
*element.transform_mut() = stack.transform.inverse() * element.transform();
|
||||||
|
@ -318,72 +298,36 @@ async fn layer<F: 'n + Send + Copy>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
|
async fn to_element<Data: Into<GraphicElement> + 'n>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
GraphicGroupTable,
|
||||||
(),
|
VectorDataTable,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
TextureFrameTable,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
data: Data,
|
||||||
#[implementations(
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> TextureFrameTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> TextureFrameTable,
|
|
||||||
)]
|
|
||||||
data: impl Node<F, Output = Data>,
|
|
||||||
) -> GraphicElement {
|
) -> GraphicElement {
|
||||||
data.eval(footprint).await.into()
|
data.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
async fn to_group<F: 'n + Send, Data: Into<GraphicGroupTable> + 'n>(
|
async fn to_group<Data: Into<GraphicGroupTable> + 'n>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
GraphicGroupTable,
|
||||||
(),
|
VectorDataTable,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
TextureFrameTable,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
element: Data,
|
||||||
#[implementations(
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> TextureFrameTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> TextureFrameTable,
|
|
||||||
)]
|
|
||||||
element: impl Node<F, Output = Data>,
|
|
||||||
) -> GraphicGroupTable {
|
) -> GraphicGroupTable {
|
||||||
element.eval(footprint).await.into()
|
element.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
async fn flatten_group<F: 'n + Send>(
|
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
|
||||||
#[implementations(
|
let nested_group = group.one_item().clone();
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
)]
|
|
||||||
group: impl Node<F, Output = GraphicGroupTable>,
|
|
||||||
fully_flatten: bool,
|
|
||||||
) -> GraphicGroupTable {
|
|
||||||
let nested_group = group.eval(footprint).await;
|
|
||||||
let nested_group = nested_group.one_item();
|
|
||||||
let nested_group = nested_group.clone();
|
|
||||||
|
|
||||||
let mut flat_group = GraphicGroup::default();
|
let mut flat_group = GraphicGroup::default();
|
||||||
|
|
||||||
|
@ -420,34 +364,28 @@ async fn flatten_group<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroupTable> + 'n>(
|
async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>(
|
||||||
|
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Context -> GraphicGroupTable,
|
||||||
(),
|
Context -> VectorDataTable,
|
||||||
(),
|
Context -> ImageFrameTable<Color>,
|
||||||
(),
|
Context -> TextureFrameTable,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
mut footprint: F,
|
contents: impl Node<Context<'static>, Output = Data>,
|
||||||
#[implementations(
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> TextureFrame,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> TextureFrame,
|
|
||||||
)]
|
|
||||||
contents: impl Node<F, Output = Data>,
|
|
||||||
label: String,
|
label: String,
|
||||||
location: IVec2,
|
location: IVec2,
|
||||||
dimensions: IVec2,
|
dimensions: IVec2,
|
||||||
background: Color,
|
background: Color,
|
||||||
clip: bool,
|
clip: bool,
|
||||||
) -> Artboard {
|
) -> Artboard {
|
||||||
footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2()));
|
let footprint = ctx.try_footprint().copied();
|
||||||
let graphic_group = contents.eval(footprint).await;
|
let mut new_ctx = OwnedContextImpl::from(ctx);
|
||||||
|
if let Some(mut footprint) = footprint {
|
||||||
|
footprint.translate(location.as_dvec2());
|
||||||
|
new_ctx = new_ctx.with_footprint(footprint);
|
||||||
|
}
|
||||||
|
let graphic_group = contents.eval(new_ctx.into_context()).await;
|
||||||
|
|
||||||
Artboard {
|
Artboard {
|
||||||
graphic_group: graphic_group.into(),
|
graphic_group: graphic_group.into(),
|
||||||
|
@ -460,28 +398,16 @@ async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroupTable
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn append_artboard<F: 'n + Send + Copy>(
|
async fn append_artboard(ctx: impl Ctx, mut artboards: ArtboardGroup, artboard: Artboard, node_path: Vec<NodeId>) -> ArtboardGroup {
|
||||||
#[implementations(
|
// let mut artboards = artboards.eval(ctx.clone()).await;
|
||||||
(),
|
// let artboard = artboard.eval(ctx).await;
|
||||||
Footprint,
|
// let foot = ctx.footprint();
|
||||||
)]
|
// log::debug!("{:?}", foot);
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> ArtboardGroup,
|
|
||||||
Footprint -> ArtboardGroup,
|
|
||||||
)]
|
|
||||||
artboards: impl Node<F, Output = ArtboardGroup>,
|
|
||||||
#[implementations(
|
|
||||||
() -> Artboard,
|
|
||||||
Footprint -> Artboard,
|
|
||||||
)]
|
|
||||||
artboard: impl Node<F, Output = Artboard>,
|
|
||||||
node_path: Vec<NodeId>,
|
|
||||||
) -> ArtboardGroup {
|
|
||||||
let artboard = artboard.eval(footprint).await;
|
|
||||||
let mut artboards = artboards.eval(footprint).await;
|
|
||||||
|
|
||||||
// Get the penultimate element of the node path, or None if the path is too short
|
// Get the penultimate element of the node path, or None if the path is too short
|
||||||
|
|
||||||
|
// TODO: Delete this line
|
||||||
|
let _ctx = ctx;
|
||||||
|
|
||||||
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||||
artboards.append_artboard(artboard, encapsulating_node_id);
|
artboards.append_artboard(artboard, encapsulating_node_id);
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub use crate as graphene_core;
|
||||||
pub use ctor;
|
pub use ctor;
|
||||||
|
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
|
pub mod context;
|
||||||
pub mod generic;
|
pub mod generic;
|
||||||
pub mod instances;
|
pub mod instances;
|
||||||
pub mod logic;
|
pub mod logic;
|
||||||
|
@ -48,6 +49,7 @@ pub mod application_io;
|
||||||
#[cfg(feature = "reflections")]
|
#[cfg(feature = "reflections")]
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
|
|
||||||
|
pub use context::*;
|
||||||
use core::any::TypeId;
|
use core::any::TypeId;
|
||||||
pub use memo::MemoHash;
|
pub use memo::MemoHash;
|
||||||
pub use raster::Color;
|
pub use raster::Color;
|
||||||
|
@ -56,7 +58,7 @@ pub use types::Cow;
|
||||||
// pub trait Node: for<'n> NodeIO<'n> {
|
// pub trait Node: for<'n> NodeIO<'n> {
|
||||||
/// The node trait allows for defining any node. Nodes can only take one call argument input, however they can store references to other nodes inside the struct.
|
/// The node trait allows for defining any node. Nodes can only take one call argument input, however they can store references to other nodes inside the struct.
|
||||||
/// See `node-graph/README.md` for information on how to define a new node.
|
/// See `node-graph/README.md` for information on how to define a new node.
|
||||||
pub trait Node<'i, Input: 'i>: 'i {
|
pub trait Node<'i, Input> {
|
||||||
type Output: 'i;
|
type Output: 'i;
|
||||||
/// Evaluates the node with the single specified input.
|
/// Evaluates the node with the single specified input.
|
||||||
fn eval(&'i self, input: Input) -> Self::Output;
|
fn eval(&'i self, input: Input) -> Self::Output;
|
||||||
|
@ -79,10 +81,10 @@ mod types;
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
||||||
pub trait NodeIO<'i, Input: 'i>: 'i + Node<'i, Input>
|
pub trait NodeIO<'i, Input>: Node<'i, Input>
|
||||||
where
|
where
|
||||||
Self::Output: 'i + StaticTypeSized,
|
Self::Output: 'i + StaticTypeSized,
|
||||||
Input: 'i + StaticTypeSized,
|
Input: StaticTypeSized,
|
||||||
{
|
{
|
||||||
fn input_type(&self) -> TypeId {
|
fn input_type(&self) -> TypeId {
|
||||||
TypeId::of::<Input::Static>()
|
TypeId::of::<Input::Static>()
|
||||||
|
@ -112,8 +114,7 @@ where
|
||||||
{
|
{
|
||||||
NodeIOTypes {
|
NodeIOTypes {
|
||||||
call_argument: concrete!(<Input as StaticTypeSized>::Static),
|
call_argument: concrete!(<Input as StaticTypeSized>::Static),
|
||||||
// TODO return actual future type
|
return_value: future!(<<Self::Output as Future>::Output as StaticTypeSized>::Static),
|
||||||
return_value: concrete!(<<Self::Output as Future>::Output as StaticTypeSized>::Static),
|
|
||||||
inputs,
|
inputs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +123,7 @@ where
|
||||||
impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N
|
impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N
|
||||||
where
|
where
|
||||||
N::Output: 'i + StaticTypeSized,
|
N::Output: 'i + StaticTypeSized,
|
||||||
I: 'i + StaticTypeSized,
|
I: StaticTypeSized,
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,13 +153,13 @@ use dyn_any::StaticTypeSized;
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + 'i>> {
|
impl<'i, I, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + 'i>> {
|
||||||
type Output = O;
|
type Output = O;
|
||||||
fn eval(&'i self, input: I) -> O {
|
fn eval(&'i self, input: I) -> O {
|
||||||
(**self).eval(input)
|
(**self).eval(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> {
|
impl<'i, I, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> {
|
||||||
type Output = O;
|
type Output = O;
|
||||||
fn eval(&'i self, input: I) -> O {
|
fn eval(&'i self, input: I) -> O {
|
||||||
(**self).eval(input)
|
(**self).eval(input)
|
||||||
|
|
|
@ -1,57 +1,40 @@
|
||||||
use crate::transform::Footprint;
|
|
||||||
use crate::vector::VectorDataTable;
|
use crate::vector::VectorDataTable;
|
||||||
|
use crate::Context;
|
||||||
|
use crate::Ctx;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
async fn log_to_console<T: core::fmt::Debug, F: Send + 'n>(
|
fn log_to_console<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> T {
|
||||||
#[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
|
|
||||||
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2,
|
|
||||||
)]
|
|
||||||
value: impl Node<F, Output = T>,
|
|
||||||
) -> T {
|
|
||||||
#[cfg(not(target_arch = "spirv"))]
|
#[cfg(not(target_arch = "spirv"))]
|
||||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||||
let value = value.eval(footprint).await;
|
|
||||||
debug!("{:#?}", value);
|
debug!("{:#?}", value);
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"), skip_impl)]
|
||||||
async fn to_string<T: core::fmt::Debug + 'n, F: Send + 'n>(
|
fn to_string<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
|
||||||
#[implementations((), (), (), (), (), (), Footprint)] footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2,
|
|
||||||
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2,
|
|
||||||
)]
|
|
||||||
value: impl Node<F, Output = T>,
|
|
||||||
) -> String {
|
|
||||||
let value = value.eval(footprint).await;
|
|
||||||
format!("{:?}", value)
|
format!("{:?}", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
async fn switch<T, F: Send + 'n>(
|
async fn switch<T, C: Send + 'n + Clone>(
|
||||||
#[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F,
|
#[implementations(Context)] ctx: C,
|
||||||
condition: bool,
|
condition: bool,
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations(
|
#[implementations(
|
||||||
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
|
Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2,
|
||||||
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2
|
|
||||||
)]
|
)]
|
||||||
if_true: impl Node<F, Output = T>,
|
if_true: impl Node<C, Output = T>,
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations(
|
#[implementations(
|
||||||
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
|
Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2,
|
||||||
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2
|
|
||||||
)]
|
)]
|
||||||
if_false: impl Node<F, Output = T>,
|
if_false: impl Node<C, Output = T>,
|
||||||
) -> T {
|
) -> T {
|
||||||
if condition {
|
if condition {
|
||||||
if_true.eval(footprint).await
|
// We can't remove these calls because we only want to evaluate the branch that we actually need
|
||||||
|
if_true.eval(ctx).await
|
||||||
} else {
|
} else {
|
||||||
if_false.eval(footprint).await
|
if_false.eval(ctx).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::raster::image::ImageFrameTable;
|
||||||
use crate::raster::BlendMode;
|
use crate::raster::BlendMode;
|
||||||
use crate::registry::types::Percentage;
|
use crate::registry::types::Percentage;
|
||||||
use crate::vector::style::GradientStops;
|
use crate::vector::style::GradientStops;
|
||||||
|
use crate::Ctx;
|
||||||
use crate::{Color, Node};
|
use crate::{Color, Node};
|
||||||
|
|
||||||
use math_parser::ast;
|
use math_parser::ast;
|
||||||
|
@ -39,7 +40,7 @@ impl ValueProvider for MathNodeContext {
|
||||||
/// Calculates a mathematical expression with input values "A" and "B"
|
/// Calculates a mathematical expression with input values "A" and "B"
|
||||||
#[node_macro::node(category("General"), properties("math_properties"))]
|
#[node_macro::node(category("General"), properties("math_properties"))]
|
||||||
fn math<U: num_traits::float::Float>(
|
fn math<U: num_traits::float::Float>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
/// The value of "A" when calculating the expression
|
/// The value of "A" when calculating the expression
|
||||||
#[implementations(f64, f32)]
|
#[implementations(f64, f32)]
|
||||||
operand_a: U,
|
operand_a: U,
|
||||||
|
@ -84,7 +85,7 @@ fn math<U: num_traits::float::Float>(
|
||||||
/// The addition operation (+) calculates the sum of two numbers.
|
/// The addition operation (+) calculates the sum of two numbers.
|
||||||
#[node_macro::node(category("Math: Arithmetic"))]
|
#[node_macro::node(category("Math: Arithmetic"))]
|
||||||
fn add<U: Add<T>, T>(
|
fn add<U: Add<T>, T>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] augend: U,
|
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] augend: U,
|
||||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] addend: T,
|
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] addend: T,
|
||||||
) -> <U as Add<T>>::Output {
|
) -> <U as Add<T>>::Output {
|
||||||
|
@ -94,7 +95,7 @@ fn add<U: Add<T>, T>(
|
||||||
/// The subtraction operation (-) calculates the difference between two numbers.
|
/// The subtraction operation (-) calculates the difference between two numbers.
|
||||||
#[node_macro::node(category("Math: Arithmetic"))]
|
#[node_macro::node(category("Math: Arithmetic"))]
|
||||||
fn subtract<U: Sub<T>, T>(
|
fn subtract<U: Sub<T>, T>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U,
|
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U,
|
||||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T,
|
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T,
|
||||||
) -> <U as Sub<T>>::Output {
|
) -> <U as Sub<T>>::Output {
|
||||||
|
@ -104,7 +105,7 @@ fn subtract<U: Sub<T>, T>(
|
||||||
/// The multiplication operation (×) calculates the product of two numbers.
|
/// The multiplication operation (×) calculates the product of two numbers.
|
||||||
#[node_macro::node(category("Math: Arithmetic"))]
|
#[node_macro::node(category("Math: Arithmetic"))]
|
||||||
fn multiply<U: Mul<T>, T>(
|
fn multiply<U: Mul<T>, T>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U,
|
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U,
|
||||||
#[default(1.)]
|
#[default(1.)]
|
||||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)]
|
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)]
|
||||||
|
@ -116,7 +117,7 @@ fn multiply<U: Mul<T>, T>(
|
||||||
/// The division operation (÷) calculates the quotient of two numbers.
|
/// The division operation (÷) calculates the quotient of two numbers.
|
||||||
#[node_macro::node(category("Math: Arithmetic"))]
|
#[node_macro::node(category("Math: Arithmetic"))]
|
||||||
fn divide<U: Div<T>, T>(
|
fn divide<U: Div<T>, T>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U,
|
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U,
|
||||||
#[default(1.)]
|
#[default(1.)]
|
||||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)]
|
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)]
|
||||||
|
@ -128,7 +129,7 @@ fn divide<U: Div<T>, T>(
|
||||||
/// The modulo operation (%) calculates the remainder from the division of two numbers. The sign of the result shares the sign of the numerator unless "Always Positive" is enabled.
|
/// The modulo operation (%) calculates the remainder from the division of two numbers. The sign of the result shares the sign of the numerator unless "Always Positive" is enabled.
|
||||||
#[node_macro::node(category("Math: Arithmetic"))]
|
#[node_macro::node(category("Math: Arithmetic"))]
|
||||||
fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy>(
|
fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U,
|
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U,
|
||||||
#[default(2.)]
|
#[default(2.)]
|
||||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)]
|
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)]
|
||||||
|
@ -145,7 +146,7 @@ fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy
|
||||||
/// The exponent operation (^) calculates the result of raising a number to a power.
|
/// The exponent operation (^) calculates the result of raising a number to a power.
|
||||||
#[node_macro::node(category("Math: Arithmetic"))]
|
#[node_macro::node(category("Math: Arithmetic"))]
|
||||||
fn exponent<U: Pow<T>, T>(
|
fn exponent<U: Pow<T>, T>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U,
|
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U,
|
||||||
#[default(2.)]
|
#[default(2.)]
|
||||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
|
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
|
||||||
|
@ -157,7 +158,7 @@ fn exponent<U: Pow<T>, T>(
|
||||||
/// The square root operation (√) calculates the nth root of a number, equivalent to raising the number to the power of 1/n.
|
/// The square root operation (√) calculates the nth root of a number, equivalent to raising the number to the power of 1/n.
|
||||||
#[node_macro::node(category("Math: Arithmetic"))]
|
#[node_macro::node(category("Math: Arithmetic"))]
|
||||||
fn root<U: num_traits::float::Float>(
|
fn root<U: num_traits::float::Float>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[default(2.)]
|
#[default(2.)]
|
||||||
#[implementations(f64, f32)]
|
#[implementations(f64, f32)]
|
||||||
radicand: U,
|
radicand: U,
|
||||||
|
@ -177,7 +178,7 @@ fn root<U: num_traits::float::Float>(
|
||||||
/// The logarithmic function (log) calculates the logarithm of a number with a specified base. If the natural logarithm function (ln) is desired, set the base to "e".
|
/// The logarithmic function (log) calculates the logarithm of a number with a specified base. If the natural logarithm function (ln) is desired, set the base to "e".
|
||||||
#[node_macro::node(category("Math: Arithmetic"))]
|
#[node_macro::node(category("Math: Arithmetic"))]
|
||||||
fn logarithm<U: num_traits::float::Float>(
|
fn logarithm<U: num_traits::float::Float>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, f32)] value: U,
|
#[implementations(f64, f32)] value: U,
|
||||||
#[default(2.)]
|
#[default(2.)]
|
||||||
#[implementations(f64, f32)]
|
#[implementations(f64, f32)]
|
||||||
|
@ -196,7 +197,7 @@ fn logarithm<U: num_traits::float::Float>(
|
||||||
|
|
||||||
/// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length.
|
/// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length.
|
||||||
#[node_macro::node(category("Math: Trig"))]
|
#[node_macro::node(category("Math: Trig"))]
|
||||||
fn sine<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
fn sine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||||
if radians {
|
if radians {
|
||||||
theta.sin()
|
theta.sin()
|
||||||
} else {
|
} else {
|
||||||
|
@ -206,7 +207,7 @@ fn sine<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta:
|
||||||
|
|
||||||
/// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
|
/// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
|
||||||
#[node_macro::node(category("Math: Trig"))]
|
#[node_macro::node(category("Math: Trig"))]
|
||||||
fn cosine<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
fn cosine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||||
if radians {
|
if radians {
|
||||||
theta.cos()
|
theta.cos()
|
||||||
} else {
|
} else {
|
||||||
|
@ -216,7 +217,7 @@ fn cosine<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta
|
||||||
|
|
||||||
/// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length.
|
/// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length.
|
||||||
#[node_macro::node(category("Math: Trig"))]
|
#[node_macro::node(category("Math: Trig"))]
|
||||||
fn tangent<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
fn tangent<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||||
if radians {
|
if radians {
|
||||||
theta.tan()
|
theta.tan()
|
||||||
} else {
|
} else {
|
||||||
|
@ -226,7 +227,7 @@ fn tangent<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] thet
|
||||||
|
|
||||||
/// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value.
|
/// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value.
|
||||||
#[node_macro::node(category("Math: Trig"))]
|
#[node_macro::node(category("Math: Trig"))]
|
||||||
fn sine_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
fn sine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||||
if radians {
|
if radians {
|
||||||
value.asin()
|
value.asin()
|
||||||
} else {
|
} else {
|
||||||
|
@ -236,7 +237,7 @@ fn sine_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)]
|
||||||
|
|
||||||
/// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value.
|
/// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value.
|
||||||
#[node_macro::node(category("Math: Trig"))]
|
#[node_macro::node(category("Math: Trig"))]
|
||||||
fn cosine_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
fn cosine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||||
if radians {
|
if radians {
|
||||||
value.acos()
|
value.acos()
|
||||||
} else {
|
} else {
|
||||||
|
@ -246,7 +247,7 @@ fn cosine_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f32
|
||||||
|
|
||||||
/// The inverse tangent trigonometric function (atan) calculates the angle whose tangent is the specified value.
|
/// The inverse tangent trigonometric function (atan) calculates the angle whose tangent is the specified value.
|
||||||
#[node_macro::node(category("Math: Trig"))]
|
#[node_macro::node(category("Math: Trig"))]
|
||||||
fn tangent_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
fn tangent_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||||
if radians {
|
if radians {
|
||||||
value.atan()
|
value.atan()
|
||||||
} else {
|
} else {
|
||||||
|
@ -257,7 +258,7 @@ fn tangent_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f3
|
||||||
/// The inverse tangent trigonometric function (atan2) calculates the angle whose tangent is the ratio of the two specified values.
|
/// The inverse tangent trigonometric function (atan2) calculates the angle whose tangent is the ratio of the two specified values.
|
||||||
#[node_macro::node(name("Tangent Inverse 2-Argument"), category("Math: Trig"))]
|
#[node_macro::node(name("Tangent Inverse 2-Argument"), category("Math: Trig"))]
|
||||||
fn tangent_inverse_2_argument<U: num_traits::float::Float>(
|
fn tangent_inverse_2_argument<U: num_traits::float::Float>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, f32)] y: U,
|
#[implementations(f64, f32)] y: U,
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations(f64, f32)]
|
#[implementations(f64, f32)]
|
||||||
|
@ -274,7 +275,7 @@ fn tangent_inverse_2_argument<U: num_traits::float::Float>(
|
||||||
/// The random function (rand) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed.
|
/// The random function (rand) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[node_macro::node(category("Math: Numeric"))]
|
||||||
fn random<U: num_traits::float::Float>(
|
fn random<U: num_traits::float::Float>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
_primary: (),
|
_primary: (),
|
||||||
seed: u64,
|
seed: u64,
|
||||||
#[implementations(f64, f32)]
|
#[implementations(f64, f32)]
|
||||||
|
@ -293,45 +294,45 @@ fn random<U: num_traits::float::Float>(
|
||||||
|
|
||||||
/// Convert a number to an integer of the type u32, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented.
|
/// Convert a number to an integer of the type u32, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented.
|
||||||
#[node_macro::node(name("To u32"), category("Math: Numeric"))]
|
#[node_macro::node(name("To u32"), category("Math: Numeric"))]
|
||||||
fn to_u32<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> u32 {
|
fn to_u32<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u32 {
|
||||||
let value = U::clamp(value, U::from(0.).unwrap(), U::from(u32::MAX as f64).unwrap());
|
let value = U::clamp(value, U::from(0.).unwrap(), U::from(u32::MAX as f64).unwrap());
|
||||||
value.to_u32().unwrap()
|
value.to_u32().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a number to an integer of the type u64, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented.
|
/// Convert a number to an integer of the type u64, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented.
|
||||||
#[node_macro::node(name("To u64"), category("Math: Numeric"))]
|
#[node_macro::node(name("To u64"), category("Math: Numeric"))]
|
||||||
fn to_u64<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> u64 {
|
fn to_u64<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u64 {
|
||||||
let value = U::clamp(value, U::from(0.).unwrap(), U::from(u64::MAX as f64).unwrap());
|
let value = U::clamp(value, U::from(0.).unwrap(), U::from(u64::MAX as f64).unwrap());
|
||||||
value.to_u64().unwrap()
|
value.to_u64().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero.
|
/// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[node_macro::node(category("Math: Numeric"))]
|
||||||
fn round<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
|
fn round<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||||
value.round()
|
value.round()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole.
|
/// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[node_macro::node(category("Math: Numeric"))]
|
||||||
fn floor<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
|
fn floor<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||||
value.floor()
|
value.floor()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole.
|
/// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[node_macro::node(category("Math: Numeric"))]
|
||||||
fn ceiling<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
|
fn ceiling<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||||
value.ceil()
|
value.ceil()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute value function (abs) removes the negative sign from an input value, if present.
|
/// The absolute value function (abs) removes the negative sign from an input value, if present.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[node_macro::node(category("Math: Numeric"))]
|
||||||
fn absolute_value<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
|
fn absolute_value<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||||
value.abs()
|
value.abs()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The minimum function (min) picks the smaller of two numbers.
|
/// The minimum function (min) picks the smaller of two numbers.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[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 {
|
fn min<T: core::cmp::PartialOrd>(_: impl Ctx, #[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 {
|
match value < other_value {
|
||||||
true => value,
|
true => value,
|
||||||
false => other_value,
|
false => other_value,
|
||||||
|
@ -340,7 +341,7 @@ fn min<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32,
|
||||||
|
|
||||||
/// The maximum function (max) picks the larger of two numbers.
|
/// The maximum function (max) picks the larger of two numbers.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[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 {
|
fn max<T: core::cmp::PartialOrd>(_: impl Ctx, #[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 {
|
match value > other_value {
|
||||||
true => value,
|
true => value,
|
||||||
false => other_value,
|
false => other_value,
|
||||||
|
@ -350,7 +351,7 @@ fn max<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32,
|
||||||
/// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed.
|
/// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[node_macro::node(category("Math: Numeric"))]
|
||||||
fn clamp<T: core::cmp::PartialOrd>(
|
fn clamp<T: core::cmp::PartialOrd>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T,
|
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T,
|
||||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T,
|
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T,
|
||||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T,
|
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T,
|
||||||
|
@ -368,7 +369,7 @@ fn clamp<T: core::cmp::PartialOrd>(
|
||||||
/// The equality operation (==) compares two values and returns true if they are equal, or false if they are not.
|
/// The equality operation (==) compares two values and returns true if they are equal, or false if they are not.
|
||||||
#[node_macro::node(category("Math: Logic"))]
|
#[node_macro::node(category("Math: Logic"))]
|
||||||
fn equals<U: core::cmp::PartialEq<T>, T>(
|
fn equals<U: core::cmp::PartialEq<T>, T>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
|
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
|
||||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)]
|
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)]
|
||||||
#[min(100.)]
|
#[min(100.)]
|
||||||
|
@ -381,7 +382,7 @@ fn equals<U: core::cmp::PartialEq<T>, T>(
|
||||||
/// The inequality operation (!=) compares two values and returns true if they are not equal, or false if they are.
|
/// The inequality operation (!=) compares two values and returns true if they are not equal, or false if they are.
|
||||||
#[node_macro::node(category("Math: Logic"))]
|
#[node_macro::node(category("Math: Logic"))]
|
||||||
fn not_equals<U: core::cmp::PartialEq<T>, T>(
|
fn not_equals<U: core::cmp::PartialEq<T>, T>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
|
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
|
||||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)]
|
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)]
|
||||||
#[min(100.)]
|
#[min(100.)]
|
||||||
|
@ -393,98 +394,98 @@ fn not_equals<U: core::cmp::PartialEq<T>, T>(
|
||||||
|
|
||||||
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
|
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
|
||||||
#[node_macro::node(category("Math: Logic"))]
|
#[node_macro::node(category("Math: Logic"))]
|
||||||
fn logical_or(_: (), value: bool, other_value: bool) -> bool {
|
fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool {
|
||||||
value || other_value
|
value || other_value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false.
|
/// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false.
|
||||||
#[node_macro::node(category("Math: Logic"))]
|
#[node_macro::node(category("Math: Logic"))]
|
||||||
fn logical_and(_: (), value: bool, other_value: bool) -> bool {
|
fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool {
|
||||||
value && other_value
|
value && other_value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The logical not operation (!) reverses true and false value of the input.
|
/// The logical not operation (!) reverses true and false value of the input.
|
||||||
#[node_macro::node(category("Math: Logic"))]
|
#[node_macro::node(category("Math: Logic"))]
|
||||||
fn logical_not(_: (), input: bool) -> bool {
|
fn logical_not(_: impl Ctx, input: bool) -> bool {
|
||||||
!input
|
!input
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a bool value which may be set to true or false.
|
/// Constructs a bool value which may be set to true or false.
|
||||||
#[node_macro::node(category("Value"))]
|
#[node_macro::node(category("Value"))]
|
||||||
fn bool_value(_: (), _primary: (), #[name("Bool")] bool_value: bool) -> bool {
|
fn bool_value(_: impl Ctx, _primary: (), #[name("Bool")] bool_value: bool) -> bool {
|
||||||
bool_value
|
bool_value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a number value which may be set to any real number.
|
/// Constructs a number value which may be set to any real number.
|
||||||
#[node_macro::node(category("Value"))]
|
#[node_macro::node(category("Value"))]
|
||||||
fn number_value(_: (), _primary: (), number: f64) -> f64 {
|
fn number_value(_: impl Ctx, _primary: (), number: f64) -> f64 {
|
||||||
number
|
number
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider.
|
/// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider.
|
||||||
#[node_macro::node(category("Value"))]
|
#[node_macro::node(category("Value"))]
|
||||||
fn percentage_value(_: (), _primary: (), percentage: Percentage) -> f64 {
|
fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 {
|
||||||
percentage
|
percentage
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a two-dimensional vector value which may be set to any XY coordinate.
|
/// Constructs a two-dimensional vector value which may be set to any XY coordinate.
|
||||||
#[node_macro::node(category("Value"))]
|
#[node_macro::node(name("Vector2 Value"), category("Value"))]
|
||||||
fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> DVec2 {
|
fn vector2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 {
|
||||||
DVec2::new(x, y)
|
DVec2::new(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a color value which may be set to any color, or no color.
|
/// Constructs a color value which may be set to any color, or no color.
|
||||||
#[node_macro::node(category("Value"))]
|
#[node_macro::node(category("Value"))]
|
||||||
fn color_value(_: (), _primary: (), #[default(Color::BLACK)] color: Option<Color>) -> Option<Color> {
|
fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Option<Color>) -> Option<Color> {
|
||||||
color
|
color
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors.
|
/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors.
|
||||||
#[node_macro::node(category("Value"))]
|
#[node_macro::node(category("Value"))]
|
||||||
fn gradient_value(_: (), _primary: (), gradient: GradientStops) -> GradientStops {
|
fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> GradientStops {
|
||||||
gradient
|
gradient
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a blend mode choice value which may be set to any of the available blend modes in order to tell another node which blending operation to use.
|
/// Constructs a blend mode choice value which may be set to any of the available blend modes in order to tell another node which blending operation to use.
|
||||||
#[node_macro::node(category("Value"))]
|
#[node_macro::node(category("Value"))]
|
||||||
fn blend_mode_value(_: (), _primary: (), blend_mode: BlendMode) -> BlendMode {
|
fn blend_mode_value(_: impl Ctx, _primary: (), blend_mode: BlendMode) -> BlendMode {
|
||||||
blend_mode
|
blend_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
|
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
fn size_of(_: (), ty: crate::Type) -> Option<usize> {
|
fn size_of(_: impl Ctx, ty: crate::Type) -> Option<usize> {
|
||||||
ty.size()
|
ty.size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option.
|
/// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option.
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
fn some<T>(_: (), #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option<T> {
|
fn some<T>(_: impl Ctx, #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option<T> {
|
||||||
Some(input)
|
Some(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None.
|
/// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None.
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
fn unwrap<T: Default>(_: (), #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>, Option<Color>)] input: Option<T>) -> T {
|
fn unwrap<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>, Option<Color>)] input: Option<T>) -> T {
|
||||||
input.unwrap_or_default()
|
input.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Meant for debugging purposes, not general use. Clones the input value.
|
/// Meant for debugging purposes, not general use. Clones the input value.
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrameTable<Color>)] value: &'i T) -> T {
|
fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&ImageFrameTable<Color>)] value: &'i T) -> T {
|
||||||
value.clone()
|
value.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Math: Vector"))]
|
#[node_macro::node(category("Math: Vector"))]
|
||||||
fn dot_product(vector_a: DVec2, vector_b: DVec2) -> f64 {
|
fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 {
|
||||||
vector_a.dot(vector_b)
|
vector_a.dot(vector_b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Rename to "Passthrough"
|
// TODO: Rename to "Passthrough"
|
||||||
/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes.
|
/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes.
|
||||||
#[node_macro::node(skip_impl)]
|
#[node_macro::node(skip_impl)]
|
||||||
fn identity<'i, T: 'i>(value: T) -> T {
|
fn identity<'i, T: 'i + Send>(value: T) -> T {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,13 +538,13 @@ where
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{generic::*, structural::*, value::*};
|
use crate::generic::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn dot_product_function() {
|
pub fn dot_product_function() {
|
||||||
let vector_a = glam::DVec2::new(1., 2.);
|
let vector_a = glam::DVec2::new(1., 2.);
|
||||||
let vector_b = glam::DVec2::new(3., 4.);
|
let vector_b = glam::DVec2::new(3., 4.);
|
||||||
assert_eq!(dot_product(vector_a, vector_b), 11.);
|
assert_eq!(dot_product((), vector_a, vector_b), 11.);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -572,8 +573,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn identity_node() {
|
pub fn identity_node() {
|
||||||
let value = ValueNode(4u32).then(IdentityNode::new());
|
assert_eq!(identity(&4), &4);
|
||||||
assert_eq!(value.eval(()), &4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
pub use self::color::{Color, Luma, SRGBA8};
|
pub use self::color::{Color, Luma, SRGBA8};
|
||||||
use crate::raster::image::ImageFrameTable;
|
use crate::raster::image::ImageFrameTable;
|
||||||
use crate::registry::types::Percentage;
|
use crate::registry::types::Percentage;
|
||||||
use crate::transform::Footprint;
|
|
||||||
use crate::vector::VectorDataTable;
|
use crate::vector::VectorDataTable;
|
||||||
|
use crate::Ctx;
|
||||||
use crate::GraphicGroupTable;
|
use crate::GraphicGroupTable;
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
@ -182,6 +182,9 @@ pub trait Alpha {
|
||||||
}
|
}
|
||||||
fn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self;
|
fn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self;
|
||||||
}
|
}
|
||||||
|
pub trait AlphaMut: Alpha {
|
||||||
|
fn set_alpha(&mut self, value: Self::AlphaChannel);
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Depth {
|
pub trait Depth {
|
||||||
type DepthChannel: Channel;
|
type DepthChannel: Channel;
|
||||||
|
@ -228,6 +231,12 @@ pub trait Bitmap {
|
||||||
type Pixel: Pixel;
|
type Pixel: Pixel;
|
||||||
fn width(&self) -> u32;
|
fn width(&self) -> u32;
|
||||||
fn height(&self) -> u32;
|
fn height(&self) -> u32;
|
||||||
|
fn dimensions(&self) -> (u32, u32) {
|
||||||
|
(self.width(), self.height())
|
||||||
|
}
|
||||||
|
fn dim(&self) -> (u32, u32) {
|
||||||
|
self.dimensions()
|
||||||
|
}
|
||||||
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel>;
|
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,51 +325,31 @@ impl SetBlendMode for ImageFrameTable<Color> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Style"))]
|
#[node_macro::node(category("Style"))]
|
||||||
async fn blend_mode<F: 'n + Send, T: SetBlendMode>(
|
fn blend_mode<T: SetBlendMode>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
GraphicGroupTable,
|
||||||
(),
|
VectorDataTable,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut value: T,
|
||||||
#[implementations(
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
)]
|
|
||||||
value: impl Node<F, Output = T>,
|
|
||||||
blend_mode: BlendMode,
|
blend_mode: BlendMode,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut value = value.eval(footprint).await;
|
|
||||||
value.set_blend_mode(blend_mode);
|
value.set_blend_mode(blend_mode);
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Style"))]
|
#[node_macro::node(category("Style"))]
|
||||||
async fn opacity<F: 'n + Send, T: MultiplyAlpha>(
|
fn opacity<T: MultiplyAlpha>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
GraphicGroupTable,
|
||||||
(),
|
VectorDataTable,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut value: T,
|
||||||
#[implementations(
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
)]
|
|
||||||
value: impl Node<F, Output = T>,
|
|
||||||
#[default(100.)] factor: Percentage,
|
#[default(100.)] factor: Percentage,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut value = value.eval(footprint).await;
|
|
||||||
let opacity_multiplier = factor / 100.;
|
let opacity_multiplier = factor / 100.;
|
||||||
value.multiply_alpha(opacity_multiplier);
|
value.multiply_alpha(opacity_multiplier);
|
||||||
value
|
value
|
||||||
|
|
|
@ -6,9 +6,9 @@ use crate::raster::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
|
||||||
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
||||||
use crate::raster::{Channel, Color, Pixel};
|
use crate::raster::{Channel, Color, Pixel};
|
||||||
use crate::registry::types::{Angle, Percentage, SignedPercentage};
|
use crate::registry::types::{Angle, Percentage, SignedPercentage};
|
||||||
use crate::transform::Footprint;
|
|
||||||
use crate::vector::style::GradientStops;
|
use crate::vector::style::GradientStops;
|
||||||
use crate::vector::VectorDataTable;
|
use crate::vector::VectorDataTable;
|
||||||
|
use crate::{Ctx, Node};
|
||||||
use crate::{GraphicElement, GraphicGroupTable};
|
use crate::{GraphicElement, GraphicGroupTable};
|
||||||
|
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
|
@ -284,26 +284,16 @@ impl From<BlendMode> for vello::peniko::Mix {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn luminance<F: 'n + Send, T: Adjust<Color>>(
|
fn luminance<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut input: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
input: impl Node<F, Output = T>,
|
|
||||||
luminance_calc: LuminanceCalculation,
|
luminance_calc: LuminanceCalculation,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
|
||||||
input.adjust(|color| {
|
input.adjust(|color| {
|
||||||
let luminance = match luminance_calc {
|
let luminance = match luminance_calc {
|
||||||
LuminanceCalculation::SRGB => color.luminance_srgb(),
|
LuminanceCalculation::SRGB => color.luminance_srgb(),
|
||||||
|
@ -318,26 +308,16 @@ async fn luminance<F: 'n + Send, T: Adjust<Color>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Raster"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
|
fn extract_channel<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut input: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
input: impl Node<F, Output = T>,
|
|
||||||
channel: RedGreenBlueAlpha,
|
channel: RedGreenBlueAlpha,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
|
||||||
input.adjust(|color| {
|
input.adjust(|color| {
|
||||||
let extracted_value = match channel {
|
let extracted_value = match channel {
|
||||||
RedGreenBlueAlpha::Red => color.r(),
|
RedGreenBlueAlpha::Red => color.r(),
|
||||||
|
@ -351,25 +331,15 @@ async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Raster"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
|
fn make_opaque<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut input: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
input: impl Node<F, Output = T>,
|
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
|
||||||
input.adjust(|color| {
|
input.adjust(|color| {
|
||||||
if color.a() == 0. {
|
if color.a() == 0. {
|
||||||
return color.with_alpha(1.);
|
return color.with_alpha(1.);
|
||||||
|
@ -385,31 +355,21 @@ async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
|
||||||
// Algorithm from:
|
// Algorithm from:
|
||||||
// https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels
|
// https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn levels<F: 'n + Send, T: Adjust<Color>>(
|
fn levels<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut image: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
image: impl Node<F, Output = T>,
|
|
||||||
#[default(0.)] shadows: Percentage,
|
#[default(0.)] shadows: Percentage,
|
||||||
#[default(50.)] midtones: Percentage,
|
#[default(50.)] midtones: Percentage,
|
||||||
#[default(100.)] highlights: Percentage,
|
#[default(100.)] highlights: Percentage,
|
||||||
#[default(0.)] output_minimums: Percentage,
|
#[default(0.)] output_minimums: Percentage,
|
||||||
#[default(100.)] output_maximums: Percentage,
|
#[default(100.)] output_maximums: Percentage,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = image.eval(footprint).await;
|
image.adjust(|color| {
|
||||||
input.adjust(|color| {
|
|
||||||
let color = color.to_gamma_srgb();
|
let color = color.to_gamma_srgb();
|
||||||
|
|
||||||
// Input Range (Range: 0-1)
|
// Input Range (Range: 0-1)
|
||||||
|
@ -451,7 +411,7 @@ async fn levels<F: 'n + Send, T: Adjust<Color>>(
|
||||||
|
|
||||||
color.to_linear_srgb()
|
color.to_linear_srgb()
|
||||||
});
|
});
|
||||||
input
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aims for interoperable compatibility with:
|
// Aims for interoperable compatibility with:
|
||||||
|
@ -462,23 +422,14 @@ async fn levels<F: 'n + Send, T: Adjust<Color>>(
|
||||||
// https://stackoverflow.com/a/55233732/775283
|
// https://stackoverflow.com/a/55233732/775283
|
||||||
// Works the same for gamma and linear color
|
// Works the same for gamma and linear color
|
||||||
#[node_macro::node(name("Black & White"), category("Raster: Adjustment"))]
|
#[node_macro::node(name("Black & White"), category("Raster: Adjustment"))]
|
||||||
async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
|
async fn black_and_white<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut image: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
image: impl Node<F, Output = T>,
|
|
||||||
#[default(Color::BLACK)] tint: Color,
|
#[default(Color::BLACK)] tint: Color,
|
||||||
#[default(40.)]
|
#[default(40.)]
|
||||||
#[range((-200., 300.))]
|
#[range((-200., 300.))]
|
||||||
|
@ -499,8 +450,7 @@ async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
|
||||||
#[range((-200., 300.))]
|
#[range((-200., 300.))]
|
||||||
magentas: Percentage,
|
magentas: Percentage,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = image.eval(footprint).await;
|
image.adjust(|color| {
|
||||||
input.adjust(|color| {
|
|
||||||
let color = color.to_gamma_srgb();
|
let color = color.to_gamma_srgb();
|
||||||
|
|
||||||
let reds = reds as f32 / 100.;
|
let reds = reds as f32 / 100.;
|
||||||
|
@ -537,35 +487,25 @@ async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
|
||||||
|
|
||||||
color.to_linear_srgb()
|
color.to_linear_srgb()
|
||||||
});
|
});
|
||||||
input
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aims for interoperable compatibility with:
|
// Aims for interoperable compatibility with:
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings
|
||||||
#[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))]
|
#[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))]
|
||||||
async fn hue_saturation<F: 'n + Send, T: Adjust<Color>>(
|
async fn hue_saturation<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut input: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
input: impl Node<F, Output = T>,
|
|
||||||
hue_shift: Angle,
|
hue_shift: Angle,
|
||||||
saturation_shift: SignedPercentage,
|
saturation_shift: SignedPercentage,
|
||||||
lightness_shift: SignedPercentage,
|
lightness_shift: SignedPercentage,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
|
||||||
input.adjust(|color| {
|
input.adjust(|color| {
|
||||||
let color = color.to_gamma_srgb();
|
let color = color.to_gamma_srgb();
|
||||||
|
|
||||||
|
@ -588,25 +528,15 @@ async fn hue_saturation<F: 'n + Send, T: Adjust<Color>>(
|
||||||
// Aims for interoperable compatibility with:
|
// Aims for interoperable compatibility with:
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn invert<F: 'n + Send, T: Adjust<Color>>(
|
async fn invert<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut input: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
input: impl Node<F, Output = T>,
|
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
|
||||||
input.adjust(|color| {
|
input.adjust(|color| {
|
||||||
let color = color.to_gamma_srgb();
|
let color = color.to_gamma_srgb();
|
||||||
|
|
||||||
|
@ -620,29 +550,19 @@ async fn invert<F: 'n + Send, T: Adjust<Color>>(
|
||||||
// Aims for interoperable compatibility with:
|
// Aims for interoperable compatibility with:
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn threshold<F: 'n + Send, T: Adjust<Color>>(
|
async fn threshold<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut image: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
image: impl Node<F, Output = T>,
|
|
||||||
#[default(50.)] min_luminance: Percentage,
|
#[default(50.)] min_luminance: Percentage,
|
||||||
#[default(100.)] max_luminance: Percentage,
|
#[default(100.)] max_luminance: Percentage,
|
||||||
luminance_calc: LuminanceCalculation,
|
luminance_calc: LuminanceCalculation,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = image.eval(footprint).await;
|
image.adjust(|color| {
|
||||||
input.adjust(|color| {
|
|
||||||
let min_luminance = Color::srgb_to_linear(min_luminance as f32 / 100.);
|
let min_luminance = Color::srgb_to_linear(min_luminance as f32 / 100.);
|
||||||
let max_luminance = Color::srgb_to_linear(max_luminance as f32 / 100.);
|
let max_luminance = Color::srgb_to_linear(max_luminance as f32 / 100.);
|
||||||
|
|
||||||
|
@ -660,7 +580,7 @@ async fn threshold<F: 'n + Send, T: Adjust<Color>>(
|
||||||
Color::BLACK
|
Color::BLACK
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
input
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Blend<P: Pixel> {
|
trait Blend<P: Pixel> {
|
||||||
|
@ -723,44 +643,35 @@ impl Blend<Color> for GradientStops {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Raster"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
async fn blend<F: 'n + Send + Copy, T: Blend<Color> + Send>(
|
async fn blend<T: Blend<Color> + Send>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
over: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
over: impl Node<F, Output = T>,
|
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations(
|
#[implementations(
|
||||||
() -> Color,
|
Color,
|
||||||
() -> ImageFrameTable<Color>,
|
ImageFrameTable<Color>,
|
||||||
() -> GradientStops,
|
GradientStops,
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
)]
|
||||||
under: impl Node<F, Output = T>,
|
under: T,
|
||||||
blend_mode: BlendMode,
|
blend_mode: BlendMode,
|
||||||
#[default(100.)] opacity: Percentage,
|
#[default(100.)] opacity: Percentage,
|
||||||
) -> T {
|
) -> T {
|
||||||
let over = over.eval(footprint).await;
|
|
||||||
let under = under.eval(footprint).await;
|
|
||||||
|
|
||||||
over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.))
|
over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""), skip_impl)]
|
||||||
fn blend_color_pair(input: (Color, Color), blend_mode: BlendMode, opacity: Percentage) -> Color {
|
fn blend_color_pair<BlendModeNode, OpacityNode>(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color
|
||||||
|
where
|
||||||
|
BlendModeNode: Node<'n, (), Output = BlendMode> + 'n,
|
||||||
|
OpacityNode: Node<'n, (), Output = Percentage> + 'n,
|
||||||
|
{
|
||||||
|
let blend_mode = blend_mode.eval(());
|
||||||
|
let opacity = opacity.eval(());
|
||||||
blend_colors(input.0, input.1, blend_mode, opacity / 100.)
|
blend_colors(input.0, input.1, blend_mode, opacity / 100.)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,35 +768,24 @@ pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode,
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0)
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0)
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn gradient_map<F: 'n + Send, T: Adjust<Color>>(
|
async fn gradient_map<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut image: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
image: impl Node<F, Output = T>,
|
|
||||||
gradient: GradientStops,
|
gradient: GradientStops,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = image.eval(footprint).await;
|
image.adjust(|color| {
|
||||||
|
|
||||||
input.adjust(|color| {
|
|
||||||
let intensity = color.luminance_srgb();
|
let intensity = color.luminance_srgb();
|
||||||
let intensity = if reverse { 1. - intensity } else { intensity };
|
let intensity = if reverse { 1. - intensity } else { intensity };
|
||||||
gradient.evalute(intensity as f64)
|
gradient.evalute(intensity as f64)
|
||||||
});
|
});
|
||||||
|
|
||||||
input
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aims for interoperable compatibility with:
|
// Aims for interoperable compatibility with:
|
||||||
|
@ -896,27 +796,17 @@ async fn gradient_map<F: 'n + Send, T: Adjust<Color>>(
|
||||||
// https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters
|
// https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters
|
||||||
// The results of this implementation are very close to correct, but not quite perfect
|
// The results of this implementation are very close to correct, but not quite perfect
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
|
async fn vibrance<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut image: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
image: impl Node<F, Output = T>,
|
|
||||||
vibrance: SignedPercentage,
|
vibrance: SignedPercentage,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = image.eval(footprint).await;
|
image.adjust(|color| {
|
||||||
input.adjust(|color| {
|
|
||||||
let vibrance = vibrance as f32 / 100.;
|
let vibrance = vibrance as f32 / 100.;
|
||||||
// Slow the effect down by half when it's negative, since artifacts begin appearing past -50%.
|
// Slow the effect down by half when it's negative, since artifacts begin appearing past -50%.
|
||||||
// So this scales the 0% to -50% range to 0% to -100%.
|
// So this scales the 0% to -50% range to 0% to -100%.
|
||||||
|
@ -963,7 +853,7 @@ async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
|
||||||
altered_color.map_rgb(|c| c * (1. - factor) + luminance * factor)
|
altered_color.map_rgb(|c| c * (1. - factor) + luminance * factor)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
input
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -1196,23 +1086,14 @@ impl DomainWarpType {
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr
|
||||||
#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"))]
|
#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"))]
|
||||||
async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
|
async fn channel_mixer<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut image: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
image: impl Node<F, Output = T>,
|
|
||||||
|
|
||||||
monochrome: bool,
|
monochrome: bool,
|
||||||
#[default(40.)]
|
#[default(40.)]
|
||||||
|
@ -1270,8 +1151,7 @@ async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
|
||||||
// Display-only properties (not used within the node)
|
// Display-only properties (not used within the node)
|
||||||
_output_channel: RedGreenBlue,
|
_output_channel: RedGreenBlue,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = image.eval(footprint).await;
|
image.adjust(|color| {
|
||||||
input.adjust(|color| {
|
|
||||||
let color = color.to_gamma_srgb();
|
let color = color.to_gamma_srgb();
|
||||||
|
|
||||||
let (r, g, b, a) = color.components();
|
let (r, g, b, a) = color.components();
|
||||||
|
@ -1296,7 +1176,7 @@ async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
|
||||||
|
|
||||||
color.to_linear_srgb()
|
color.to_linear_srgb()
|
||||||
});
|
});
|
||||||
input
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -1356,24 +1236,15 @@ impl core::fmt::Display for SelectiveColorChoice {
|
||||||
//
|
//
|
||||||
// Algorithm based on:
|
// Algorithm based on:
|
||||||
// https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html
|
// https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"))]
|
||||||
async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
|
async fn selective_color<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut image: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
image: impl Node<F, Output = T>,
|
|
||||||
mode: RelativeAbsolute,
|
mode: RelativeAbsolute,
|
||||||
#[name("(Reds) Cyan")] r_c: f64,
|
#[name("(Reds) Cyan")] r_c: f64,
|
||||||
#[name("(Reds) Magenta")] r_m: f64,
|
#[name("(Reds) Magenta")] r_m: f64,
|
||||||
|
@ -1413,8 +1284,7 @@ async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
|
||||||
#[name("(Blacks) Black")] k_k: f64,
|
#[name("(Blacks) Black")] k_k: f64,
|
||||||
_colors: SelectiveColorChoice,
|
_colors: SelectiveColorChoice,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = image.eval(footprint).await;
|
image.adjust(|color| {
|
||||||
input.adjust(|color| {
|
|
||||||
let color = color.to_gamma_srgb();
|
let color = color.to_gamma_srgb();
|
||||||
|
|
||||||
let (r, g, b, a) = color.components();
|
let (r, g, b, a) = color.components();
|
||||||
|
@ -1488,7 +1358,7 @@ async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
|
||||||
|
|
||||||
color.to_linear_srgb()
|
color.to_linear_srgb()
|
||||||
});
|
});
|
||||||
input
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) trait MultiplyAlpha {
|
pub(super) trait MultiplyAlpha {
|
||||||
|
@ -1534,28 +1404,18 @@ where
|
||||||
// https://www.axiomx.com/posterize.htm
|
// https://www.axiomx.com/posterize.htm
|
||||||
// This algorithm produces fully accurate output in relation to the industry standard.
|
// This algorithm produces fully accurate output in relation to the industry standard.
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn posterize<F: 'n + Send, T: Adjust<Color>>(
|
async fn posterize<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut input: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
input: impl Node<F, Output = T>,
|
|
||||||
#[default(4)]
|
#[default(4)]
|
||||||
#[min(2.)]
|
#[min(2.)]
|
||||||
levels: u32,
|
levels: u32,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
|
||||||
input.adjust(|color| {
|
input.adjust(|color| {
|
||||||
let color = color.to_gamma_srgb();
|
let color = color.to_gamma_srgb();
|
||||||
|
|
||||||
|
@ -1577,30 +1437,20 @@ async fn posterize<F: 'n + Send, T: Adjust<Color>>(
|
||||||
// Algorithm based on:
|
// Algorithm based on:
|
||||||
// https://geraldbakker.nl/psnumbers/exposure.html
|
// https://geraldbakker.nl/psnumbers/exposure.html
|
||||||
#[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"))]
|
#[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"))]
|
||||||
async fn exposure<F: 'n + Send, T: Adjust<Color>>(
|
async fn exposure<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut input: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
input: impl Node<F, Output = T>,
|
|
||||||
exposure: f64,
|
exposure: f64,
|
||||||
offset: f64,
|
offset: f64,
|
||||||
#[default(1.)]
|
#[default(1.)]
|
||||||
#[range((0.01, 10.))]
|
#[range((0.01, 10.))]
|
||||||
gamma_correction: f64,
|
gamma_correction: f64,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
|
||||||
input.adjust(|color| {
|
input.adjust(|color| {
|
||||||
let adjusted = color
|
let adjusted = color
|
||||||
// Exposure
|
// Exposure
|
||||||
|
@ -1619,7 +1469,7 @@ const WINDOW_SIZE: usize = 1024;
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
fn generate_curves<C: Channel + super::Linear>(_: (), curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
|
fn generate_curves<C: Channel + super::Linear>(_: impl Ctx, curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
|
||||||
use bezier_rs::{Bezier, TValue};
|
use bezier_rs::{Bezier, TValue};
|
||||||
|
|
||||||
let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle];
|
let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle];
|
||||||
|
@ -1660,31 +1510,21 @@ fn generate_curves<C: Channel + super::Linear>(_: (), curve: Curve, #[implementa
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
|
fn color_overlay<T: Adjust<Color>>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Color,
|
||||||
(),
|
ImageFrameTable<Color>,
|
||||||
(),
|
GradientStops,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut image: T,
|
||||||
#[implementations(
|
|
||||||
() -> Color,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> GradientStops,
|
|
||||||
Footprint -> Color,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> GradientStops,
|
|
||||||
)]
|
|
||||||
image: impl Node<F, Output = T>,
|
|
||||||
#[default(Color::BLACK)] color: Color,
|
#[default(Color::BLACK)] color: Color,
|
||||||
blend_mode: BlendMode,
|
blend_mode: BlendMode,
|
||||||
#[default(100.)] opacity: Percentage,
|
#[default(100.)] opacity: Percentage,
|
||||||
) -> T {
|
) -> T {
|
||||||
let opacity = (opacity as f32 / 100.).clamp(0., 1.);
|
let opacity = (opacity as f32 / 100.).clamp(0., 1.);
|
||||||
|
|
||||||
let mut input = image.eval(footprint).await;
|
image.adjust(|pixel| {
|
||||||
input.adjust(|pixel| {
|
|
||||||
let image = pixel.map_rgb(|channel| channel * (1. - opacity));
|
let image = pixel.map_rgb(|channel| channel * (1. - opacity));
|
||||||
|
|
||||||
// The apply blend mode function divides rgb by the alpha channel for the background. This undoes that.
|
// The apply blend mode function divides rgb by the alpha channel for the background. This undoes that.
|
||||||
|
@ -1693,7 +1533,7 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
|
||||||
|
|
||||||
Color::from_rgbaf32_unchecked(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a())
|
Color::from_rgbaf32_unchecked(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a())
|
||||||
});
|
});
|
||||||
input
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[cfg(feature = "alloc")]
|
// #[cfg(feature = "alloc")]
|
||||||
|
@ -1702,10 +1542,11 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
|
||||||
// #[cfg(feature = "alloc")]
|
// #[cfg(feature = "alloc")]
|
||||||
// mod index_node {
|
// mod index_node {
|
||||||
// use crate::raster::{Color, ImageFrame};
|
// use crate::raster::{Color, ImageFrame};
|
||||||
|
// use crate::Ctx;
|
||||||
|
|
||||||
// #[node_macro::node(category(""))]
|
// #[node_macro::node(category(""))]
|
||||||
// pub fn index<T: Default + Clone>(
|
// pub fn index<T: Default + Clone>(
|
||||||
// _: (),
|
// _: impl Ctx,
|
||||||
// #[implementations(Vec<ImageFrame<Color>>, Vec<Color>)]
|
// #[implementations(Vec<ImageFrame<Color>>, Vec<Color>)]
|
||||||
// #[widget(ParsedWidgetOverride::Hidden)]
|
// #[widget(ParsedWidgetOverride::Hidden)]
|
||||||
// input: Vec<T>,
|
// input: Vec<T>,
|
||||||
|
@ -1752,7 +1593,7 @@ mod test {
|
||||||
// 100% of the output should come from the multiplied value
|
// 100% of the output should come from the multiplied value
|
||||||
let opacity = 100_f64;
|
let opacity = 100_f64;
|
||||||
|
|
||||||
let result = super::color_overlay((), &FutureWrapperNode(ImageFrameTable::new(image.clone())), overlay_color, BlendMode::Multiply, opacity).await;
|
let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity);
|
||||||
let result = result.one_item();
|
let result = result.one_item();
|
||||||
|
|
||||||
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
|
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};
|
use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};
|
||||||
use super::{Alpha, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
|
use super::{Alpha, AlphaMut, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
|
||||||
|
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
@ -257,6 +257,11 @@ impl RGBMut for Color {
|
||||||
self.blue = blue;
|
self.blue = blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AlphaMut for Color {
|
||||||
|
fn set_alpha(&mut self, value: Self::AlphaChannel) {
|
||||||
|
self.alpha = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Pixel for Color {
|
impl Pixel for Color {
|
||||||
#[cfg(not(target_arch = "spirv"))]
|
#[cfg(not(target_arch = "spirv"))]
|
||||||
|
|
|
@ -132,14 +132,15 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode<I, Root> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{ops::IdentityNode, value::ValueNode};
|
use crate::generic::FnNode;
|
||||||
|
use crate::value::ValueNode;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compose() {
|
fn compose() {
|
||||||
let value = ValueNode::new(4u32);
|
let value = ValueNode::new(4u32);
|
||||||
let compose = value.then(IdentityNode::new());
|
let compose = value.then(FnNode::new(|x| x));
|
||||||
assert_eq!(compose.eval(()), &4u32);
|
assert_eq!(compose.eval(()), &4u32);
|
||||||
let type_erased = &compose as &dyn for<'i> Node<'i, (), Output = &'i u32>;
|
let type_erased = &compose as &dyn Node<'_, (), Output = &'_ u32>;
|
||||||
assert_eq!(type_erased.eval(()), &4u32);
|
assert_eq!(type_erased.eval(()), &4u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +149,7 @@ mod test {
|
||||||
let value = ValueNode::new(5);
|
let value = ValueNode::new(5);
|
||||||
|
|
||||||
assert_eq!(value.eval(()), &5);
|
assert_eq!(value.eval(()), &5);
|
||||||
let id = IdentityNode::new();
|
let id = FnNode::new(|x| x);
|
||||||
|
|
||||||
let compose = ComposeNode::new(&value, &id);
|
let compose = ComposeNode::new(&value, &id);
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::application_io::{TextureFrame, TextureFrameTable};
|
use crate::application_io::TextureFrameTable;
|
||||||
use crate::raster::bbox::AxisAlignedBbox;
|
use crate::raster::bbox::AxisAlignedBbox;
|
||||||
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
||||||
use crate::raster::Pixel;
|
use crate::raster::Pixel;
|
||||||
use crate::vector::{VectorData, VectorDataTable};
|
use crate::vector::{VectorData, VectorDataTable};
|
||||||
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable};
|
use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl};
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
|
@ -244,6 +244,12 @@ pub struct Footprint {
|
||||||
|
|
||||||
impl Default for Footprint {
|
impl Default for Footprint {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
Self::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Footprint {
|
||||||
|
pub const fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
transform: DAffine2::IDENTITY,
|
transform: DAffine2::IDENTITY,
|
||||||
resolution: glam::UVec2::new(1920, 1080),
|
resolution: glam::UVec2::new(1920, 1080),
|
||||||
|
@ -251,9 +257,6 @@ impl Default for Footprint {
|
||||||
ignore_modifications: false,
|
ignore_modifications: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Footprint {
|
|
||||||
pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox {
|
pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox {
|
||||||
let inverse = self.transform.inverse();
|
let inverse = self.transform.inverse();
|
||||||
let start = inverse.transform_point2((0., 0.).into());
|
let start = inverse.transform_point2((0., 0.).into());
|
||||||
|
@ -277,7 +280,7 @@ impl From<()> for Footprint {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
fn cull<T>(_footprint: Footprint, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable<Color>, ArtboardGroup)] data: T) -> T {
|
fn cull<T>(_: impl Ctx, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable<Color>, ArtboardGroup)] data: T) -> T {
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,26 +304,15 @@ impl ApplyTransform for () {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Sync, T: 'n + TransformMut>(
|
async fn transform<T: 'n + TransformMut + 'static>(
|
||||||
|
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
Context -> VectorDataTable,
|
||||||
(),
|
Context -> GraphicGroupTable,
|
||||||
(),
|
Context -> ImageFrameTable<Color>,
|
||||||
(),
|
Context -> TextureFrameTable,
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
mut input: I,
|
transform_target: impl Node<Context<'static>, Output = T>,
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
() -> TextureFrame,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> TextureFrame,
|
|
||||||
)]
|
|
||||||
transform_target: impl Node<I, Output = T>,
|
|
||||||
translate: DVec2,
|
translate: DVec2,
|
||||||
rotate: f64,
|
rotate: f64,
|
||||||
scale: DVec2,
|
scale: DVec2,
|
||||||
|
@ -328,22 +320,27 @@ async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Syn
|
||||||
_pivot: DVec2,
|
_pivot: DVec2,
|
||||||
) -> T {
|
) -> T {
|
||||||
let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
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();
|
let footprint = ctx.try_footprint().copied();
|
||||||
if !footprint.ignore_modifications {
|
|
||||||
input.apply_transform(&modification);
|
let mut ctx = OwnedContextImpl::from(ctx);
|
||||||
|
if let Some(mut footprint) = footprint {
|
||||||
|
if !footprint.ignore_modifications {
|
||||||
|
footprint.apply_transform(&modification);
|
||||||
|
}
|
||||||
|
ctx = ctx.with_footprint(footprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data = transform_target.eval(input).await;
|
let mut transform_target = transform_target.eval(ctx.into_context()).await;
|
||||||
|
|
||||||
let data_transform = data.transform_mut();
|
let data_transform = transform_target.transform_mut();
|
||||||
*data_transform = modification * (*data_transform);
|
*data_transform = modification * (*data_transform);
|
||||||
|
|
||||||
data
|
transform_target
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
fn replace_transform<Data: TransformMut, TransformInput: Transform>(
|
fn replace_transform<Data: TransformMut, TransformInput: Transform>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(VectorDataTable, ImageFrameTable<Color>, GraphicGroupTable)] mut data: Data,
|
#[implementations(VectorDataTable, ImageFrameTable<Color>, GraphicGroupTable)] mut data: Data,
|
||||||
#[implementations(DAffine2)] transform: TransformInput,
|
#[implementations(DAffine2)] transform: TransformInput,
|
||||||
) -> Data {
|
) -> Data {
|
||||||
|
|
|
@ -53,6 +53,9 @@ macro_rules! future {
|
||||||
($type:ty) => {{
|
($type:ty) => {{
|
||||||
$crate::Type::Future(Box::new(concrete!($type)))
|
$crate::Type::Future(Box::new(concrete!($type)))
|
||||||
}};
|
}};
|
||||||
|
($type:ty, $name:ty) => {
|
||||||
|
$crate::Type::Future(Box::new(concrete!($type, $name)))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -67,6 +70,18 @@ macro_rules! fn_type {
|
||||||
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type)))
|
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type)))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! fn_type_fut {
|
||||||
|
($type:ty) => {
|
||||||
|
$crate::Type::Fn(Box::new(concrete!(())), Box::new(future!($type)))
|
||||||
|
};
|
||||||
|
($in_type:ty, $type:ty, alias: $outname:ty) => {
|
||||||
|
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(future!($type, $outname)))
|
||||||
|
};
|
||||||
|
($in_type:ty, $type:ty) => {
|
||||||
|
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(future!($type)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, Default)]
|
#[derive(Clone, PartialEq, Eq, Hash, Default)]
|
||||||
pub struct NodeIOTypes {
|
pub struct NodeIOTypes {
|
||||||
|
@ -134,6 +149,7 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer:
|
||||||
let name = String::deserialize(deserializer)?;
|
let name = String::deserialize(deserializer)?;
|
||||||
let name = match name.as_str() {
|
let name = match name.as_str() {
|
||||||
"f32" => "f64".to_string(),
|
"f32" => "f64".to_string(),
|
||||||
|
"graphene_core::transform::Footprint" => "core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>".to_string(),
|
||||||
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::graphic_element::Instances<graphene_core::graphic_element::GraphicGroup>".to_string(),
|
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::graphic_element::Instances<graphene_core::graphic_element::GraphicGroup>".to_string(),
|
||||||
"graphene_core::vector::vector_data::VectorData" => "graphene_core::graphic_element::Instances<graphene_core::vector::vector_data::VectorData>".to_string(),
|
"graphene_core::vector::vector_data::VectorData" => "graphene_core::graphic_element::Instances<graphene_core::vector::vector_data::VectorData>".to_string(),
|
||||||
"graphene_core::raster::image::ImageFrame<Color>" => "graphene_core::graphic_element::Instances<graphene_core::raster::image::ImageFrame<Color>>".to_string(),
|
"graphene_core::raster::image::ImageFrame<Color>" => "graphene_core::graphic_element::Instances<graphene_core::raster::image::ImageFrame<Color>>".to_string(),
|
||||||
|
@ -189,7 +205,7 @@ pub enum Type {
|
||||||
/// Runtime type information for a function. Given some input, gives some output.
|
/// Runtime type information for a function. Given some input, gives some output.
|
||||||
/// See the example and explanation in the `ComposeNode` implementation within the node registry for more info.
|
/// See the example and explanation in the `ComposeNode` implementation within the node registry for more info.
|
||||||
Fn(Box<Type>, Box<Type>),
|
Fn(Box<Type>, Box<Type>),
|
||||||
/// Not used at the moment.
|
/// Represents a future which promises to return the inner type.
|
||||||
Future(Box<Type>),
|
Future(Box<Type>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +296,7 @@ impl Type {
|
||||||
Self::Generic(_) => self,
|
Self::Generic(_) => self,
|
||||||
Self::Concrete(_) => self,
|
Self::Concrete(_) => self,
|
||||||
Self::Fn(_, output) => output.nested_type(),
|
Self::Fn(_, output) => output.nested_type(),
|
||||||
Self::Future(_) => self,
|
Self::Future(output) => output.nested_type(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ use core::{
|
||||||
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
pub struct IntNode<const N: u32>;
|
pub struct IntNode<const N: u32>;
|
||||||
|
|
||||||
impl<'i, const N: u32> Node<'i, ()> for IntNode<N> {
|
impl<'i, const N: u32, I> Node<'i, I> for IntNode<N> {
|
||||||
type Output = u32;
|
type Output = u32;
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
fn eval(&'i self, _input: I) -> Self::Output {
|
||||||
N
|
N
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,10 @@ impl<'i, const N: u32> Node<'i, ()> for IntNode<N> {
|
||||||
#[derive(Default, Debug, Clone, Copy)]
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
pub struct ValueNode<T>(pub T);
|
pub struct ValueNode<T>(pub T);
|
||||||
|
|
||||||
impl<'i, T: 'i> Node<'i, ()> for ValueNode<T> {
|
impl<'i, T: 'i, I> Node<'i, I> for ValueNode<T> {
|
||||||
type Output = &'i T;
|
type Output = &'i T;
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
fn eval(&'i self, _input: I) -> Self::Output {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,10 +77,10 @@ impl<T> RefCellMutNode<T> {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct OnceCellNode<T>(pub Cell<T>);
|
pub struct OnceCellNode<T>(pub Cell<T>);
|
||||||
|
|
||||||
impl<'i, T: Default + 'i> Node<'i, ()> for OnceCellNode<T> {
|
impl<'i, T: Default + 'i, I> Node<'i, I> for OnceCellNode<T> {
|
||||||
type Output = T;
|
type Output = T;
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
fn eval(&'i self, _input: I) -> Self::Output {
|
||||||
self.0.replace(T::default())
|
self.0.replace(T::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,10 +94,10 @@ impl<T> OnceCellNode<T> {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct ClonedNode<T: Clone>(pub T);
|
pub struct ClonedNode<T: Clone>(pub T);
|
||||||
|
|
||||||
impl<'i, T: Clone + 'i> Node<'i, ()> for ClonedNode<T> {
|
impl<'i, T: Clone + 'i, I> Node<'i, I> for ClonedNode<T> {
|
||||||
type Output = T;
|
type Output = T;
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
fn eval(&'i self, _input: I) -> Self::Output {
|
||||||
self.0.clone()
|
self.0.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,10 +140,10 @@ impl<T: Clone> DebugClonedNode<T> {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct CopiedNode<T: Copy>(pub T);
|
pub struct CopiedNode<T: Copy>(pub T);
|
||||||
|
|
||||||
impl<'i, T: Copy + 'i> Node<'i, ()> for CopiedNode<T> {
|
impl<'i, T: Copy + 'i, I> Node<'i, I> for CopiedNode<T> {
|
||||||
type Output = T;
|
type Output = T;
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
fn eval(&'i self, _input: I) -> Self::Output {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,9 +157,9 @@ impl<T: Copy> CopiedNode<T> {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DefaultNode<T>(PhantomData<T>);
|
pub struct DefaultNode<T>(PhantomData<T>);
|
||||||
|
|
||||||
impl<'i, T: Default + 'i> Node<'i, ()> for DefaultNode<T> {
|
impl<'i, T: Default + 'i, I> Node<'i, I> for DefaultNode<T> {
|
||||||
type Output = T;
|
type Output = T;
|
||||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
fn eval(&'i self, _input: I) -> Self::Output {
|
||||||
T::default()
|
T::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_node() {
|
fn test_default_node() {
|
||||||
let node = DefaultNode::<u32>::new();
|
let node = DefaultNode::<u32>::new();
|
||||||
assert_eq!(node.eval(()), 0);
|
assert_eq!(node.eval(42), 0);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::unit_cmp)]
|
#[allow(clippy::unit_cmp)]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::transform::Footprint;
|
|
||||||
use crate::vector::{HandleId, PointId, VectorData, VectorDataTable};
|
use crate::vector::{HandleId, PointId, VectorData, VectorDataTable};
|
||||||
|
use crate::Ctx;
|
||||||
|
|
||||||
use bezier_rs::Subpath;
|
use bezier_rs::Subpath;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
@ -35,12 +35,12 @@ impl CornerRadius for [f64; 4] {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Shape"))]
|
#[node_macro::node(category("Vector: Shape"))]
|
||||||
fn circle<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
|
fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
|
||||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Shape"))]
|
#[node_macro::node(category("Vector: Shape"))]
|
||||||
fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable {
|
fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable {
|
||||||
let radius = DVec2::new(radius_x, radius_y);
|
let radius = DVec2::new(radius_x, radius_y);
|
||||||
let corner1 = -radius;
|
let corner1 = -radius;
|
||||||
let corner2 = radius;
|
let corner2 = radius;
|
||||||
|
@ -58,8 +58,8 @@ fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _prima
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
|
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
|
||||||
fn rectangle<F: 'n + Send, T: CornerRadius>(
|
fn rectangle<T: CornerRadius>(
|
||||||
#[implementations((), Footprint)] _footprint: F,
|
_: impl Ctx,
|
||||||
_primary: (),
|
_primary: (),
|
||||||
#[default(100)] width: f64,
|
#[default(100)] width: f64,
|
||||||
#[default(100)] height: f64,
|
#[default(100)] height: f64,
|
||||||
|
@ -71,8 +71,8 @@ fn rectangle<F: 'n + Send, T: CornerRadius>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Shape"))]
|
#[node_macro::node(category("Vector: Shape"))]
|
||||||
fn regular_polygon<F: 'n + Send>(
|
fn regular_polygon(
|
||||||
#[implementations((), Footprint)] _footprint: F,
|
_: impl Ctx,
|
||||||
_primary: (),
|
_primary: (),
|
||||||
#[default(6)]
|
#[default(6)]
|
||||||
#[min(3.)]
|
#[min(3.)]
|
||||||
|
@ -85,8 +85,8 @@ fn regular_polygon<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Shape"))]
|
#[node_macro::node(category("Vector: Shape"))]
|
||||||
fn star<F: 'n + Send>(
|
fn star(
|
||||||
#[implementations((), Footprint)] _footprint: F,
|
_: impl Ctx,
|
||||||
_primary: (),
|
_primary: (),
|
||||||
#[default(5)]
|
#[default(5)]
|
||||||
#[min(2.)]
|
#[min(2.)]
|
||||||
|
@ -102,15 +102,14 @@ fn star<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Shape"))]
|
#[node_macro::node(category("Vector: Shape"))]
|
||||||
fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable {
|
fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable {
|
||||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
|
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
|
fn path(_: impl Ctx, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
|
||||||
let mut vector_data = VectorData::from_subpaths(path_data, false);
|
let mut vector_data = VectorData::from_subpaths(path_data, false);
|
||||||
|
|
||||||
vector_data.colinear_manipulators = colinear_manipulators
|
vector_data.colinear_manipulators = colinear_manipulators
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
|
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::transform::Footprint;
|
|
||||||
use crate::uuid::generate_uuid;
|
use crate::uuid::generate_uuid;
|
||||||
|
use crate::Ctx;
|
||||||
|
|
||||||
use bezier_rs::BezierHandles;
|
use bezier_rs::BezierHandles;
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
|
@ -424,20 +424,7 @@ impl core::hash::Hash for VectorModification {
|
||||||
|
|
||||||
/// A node that applies a procedural modification to some [`VectorData`].
|
/// A node that applies a procedural modification to some [`VectorData`].
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn path_modify<F: 'n + Send + Sync + Clone>(
|
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
input: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
|
||||||
modification: Box<VectorModification>,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
let mut vector_data = vector_data.eval(input).await;
|
|
||||||
let vector_data = vector_data.one_item_mut();
|
let vector_data = vector_data.one_item_mut();
|
||||||
|
|
||||||
modification.apply(vector_data);
|
modification.apply(vector_data);
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::renderer::GraphicElementRendered;
|
||||||
use crate::transform::{Footprint, Transform, TransformMut};
|
use crate::transform::{Footprint, Transform, TransformMut};
|
||||||
use crate::vector::style::LineJoin;
|
use crate::vector::style::LineJoin;
|
||||||
use crate::vector::PointDomain;
|
use crate::vector::PointDomain;
|
||||||
use crate::{Color, GraphicElement, GraphicGroup, GraphicGroupTable};
|
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl};
|
||||||
|
|
||||||
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
|
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
@ -43,21 +43,11 @@ impl VectorIterMut for VectorDataTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||||
async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
async fn assign_colors<T: VectorIterMut>(
|
||||||
#[implementations(
|
_: impl Ctx,
|
||||||
(),
|
#[implementations(GraphicGroupTable, VectorDataTable)]
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
#[widget(ParsedWidgetOverride::Hidden)]
|
#[widget(ParsedWidgetOverride::Hidden)]
|
||||||
vector_group: impl Node<F, Output = T>,
|
mut vector_group: T,
|
||||||
#[default(true)] fill: bool,
|
#[default(true)] fill: bool,
|
||||||
stroke: bool,
|
stroke: bool,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] gradient: GradientStops,
|
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] gradient: GradientStops,
|
||||||
|
@ -66,8 +56,6 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
||||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue,
|
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32,
|
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut vector_group = vector_group.eval(footprint).await;
|
|
||||||
|
|
||||||
let length = vector_group.vector_iter_mut().count();
|
let length = vector_group.vector_iter_mut().count();
|
||||||
let gradient = if reverse { gradient.reversed() } else { gradient };
|
let gradient = if reverse { gradient.reversed() } else { gradient };
|
||||||
|
|
||||||
|
@ -99,54 +87,20 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
|
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
|
||||||
async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
async fn fill<FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||||
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
VectorDataTable,
|
||||||
(),
|
VectorDataTable,
|
||||||
(),
|
VectorDataTable,
|
||||||
(),
|
VectorDataTable,
|
||||||
(),
|
GraphicGroupTable,
|
||||||
(),
|
GraphicGroupTable,
|
||||||
(),
|
GraphicGroupTable,
|
||||||
(),
|
GraphicGroupTable
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
mut vector_data: TargetTy,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = TargetTy>,
|
|
||||||
#[implementations(
|
|
||||||
Fill,
|
|
||||||
Option<Color>,
|
|
||||||
Color,
|
|
||||||
Gradient,
|
|
||||||
Fill,
|
|
||||||
Option<Color>,
|
|
||||||
Color,
|
|
||||||
Gradient,
|
|
||||||
Fill,
|
Fill,
|
||||||
Option<Color>,
|
Option<Color>,
|
||||||
Color,
|
Color,
|
||||||
|
@ -161,44 +115,19 @@ async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIter
|
||||||
_backup_color: Option<Color>,
|
_backup_color: Option<Color>,
|
||||||
_backup_gradient: Gradient,
|
_backup_gradient: Gradient,
|
||||||
) -> TargetTy {
|
) -> TargetTy {
|
||||||
let mut target = vector_data.eval(footprint).await;
|
|
||||||
let fill: Fill = fill.into();
|
let fill: Fill = fill.into();
|
||||||
for (target, _transform) in target.vector_iter_mut() {
|
for (target, _transform) in vector_data.vector_iter_mut() {
|
||||||
target.style.set_fill(fill.clone());
|
target.style.set_fill(fill.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
target
|
vector_data
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
|
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
|
||||||
async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
async fn stroke<ColorTy: Into<Option<Color>> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||||
|
_: impl Ctx,
|
||||||
|
#[implementations(VectorDataTable, VectorDataTable, GraphicGroupTable, GraphicGroupTable)] mut vector_data: TargetTy,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
|
||||||
(),
|
|
||||||
(),
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = TargetTy>,
|
|
||||||
#[implementations(
|
|
||||||
Option<Color>,
|
|
||||||
Color,
|
|
||||||
Option<Color>,
|
|
||||||
Color,
|
|
||||||
Option<Color>,
|
Option<Color>,
|
||||||
Color,
|
Color,
|
||||||
Option<Color>,
|
Option<Color>,
|
||||||
|
@ -213,7 +142,6 @@ async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy
|
||||||
line_join: LineJoin,
|
line_join: LineJoin,
|
||||||
#[default(4.)] miter_limit: f64,
|
#[default(4.)] miter_limit: f64,
|
||||||
) -> TargetTy {
|
) -> TargetTy {
|
||||||
let mut target = vector_data.eval(footprint).await;
|
|
||||||
let stroke = Stroke {
|
let stroke = Stroke {
|
||||||
color: color.into(),
|
color: color.into(),
|
||||||
weight,
|
weight,
|
||||||
|
@ -224,37 +152,24 @@ async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy
|
||||||
line_join_miter_limit: miter_limit,
|
line_join_miter_limit: miter_limit,
|
||||||
transform: DAffine2::IDENTITY,
|
transform: DAffine2::IDENTITY,
|
||||||
};
|
};
|
||||||
for (target, transform) in target.vector_iter_mut() {
|
for (target, transform) in vector_data.vector_iter_mut() {
|
||||||
target.style.set_stroke(Stroke { transform, ..stroke.clone() });
|
target.style.set_stroke(Stroke { transform, ..stroke.clone() });
|
||||||
}
|
}
|
||||||
|
|
||||||
target
|
vector_data
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
async fn repeat<I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||||
#[implementations(
|
_: impl Ctx,
|
||||||
(),
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
// TODO: Implement other GraphicElementRendered types.
|
// TODO: Implement other GraphicElementRendered types.
|
||||||
#[implementations(
|
#[implementations(VectorDataTable, GraphicGroupTable)] instance: I,
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
)]
|
|
||||||
instance: impl Node<F, Output = I>,
|
|
||||||
#[default(100., 100.)]
|
#[default(100., 100.)]
|
||||||
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
||||||
direction: DVec2,
|
direction: DVec2,
|
||||||
angle: Angle,
|
angle: Angle,
|
||||||
#[default(4)] instances: IntegerCount,
|
#[default(4)] instances: IntegerCount,
|
||||||
) -> GraphicGroupTable {
|
) -> GraphicGroupTable {
|
||||||
let instance = instance.eval(footprint).await;
|
|
||||||
let first_vector_transform = instance.transform();
|
let first_vector_transform = instance.transform();
|
||||||
|
|
||||||
let angle = angle.to_radians();
|
let angle = angle.to_radians();
|
||||||
|
@ -285,27 +200,14 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
async fn circular_repeat<I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||||
#[implementations(
|
_: impl Ctx,
|
||||||
(),
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
// TODO: Implement other GraphicElementRendered types.
|
// TODO: Implement other GraphicElementRendered types.
|
||||||
#[implementations(
|
#[implementations(VectorDataTable, GraphicGroupTable)] instance: I,
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
)]
|
|
||||||
instance: impl Node<F, Output = I>,
|
|
||||||
angle_offset: Angle,
|
angle_offset: Angle,
|
||||||
#[default(5)] radius: f64,
|
#[default(5)] radius: f64,
|
||||||
#[default(5)] instances: IntegerCount,
|
#[default(5)] instances: IntegerCount,
|
||||||
) -> GraphicGroupTable {
|
) -> GraphicGroupTable {
|
||||||
let instance = instance.eval(footprint).await;
|
|
||||||
let first_vector_transform = instance.transform();
|
let first_vector_transform = instance.transform();
|
||||||
let instances = instances.max(1);
|
let instances = instances.max(1);
|
||||||
|
|
||||||
|
@ -334,27 +236,12 @@ async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + T
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatElement + TransformMut + Send + 'n>(
|
async fn copy_to_points<I: GraphicElementRendered + TransformMut + Send + 'n>(
|
||||||
#[implementations(
|
_: impl Ctx,
|
||||||
(),
|
points: VectorDataTable,
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
points: impl Node<F, Output = VectorDataTable>,
|
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations(
|
#[implementations(VectorDataTable, GraphicGroupTable)]
|
||||||
() -> VectorDataTable,
|
instance: I,
|
||||||
() -> GraphicGroupTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
)]
|
|
||||||
instance: impl Node<F, Output = I>,
|
|
||||||
#[default(1)] random_scale_min: f64,
|
#[default(1)] random_scale_min: f64,
|
||||||
#[default(1)] random_scale_max: f64,
|
#[default(1)] random_scale_max: f64,
|
||||||
random_scale_bias: f64,
|
random_scale_bias: f64,
|
||||||
|
@ -362,11 +249,8 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
|
||||||
random_rotation: Angle,
|
random_rotation: Angle,
|
||||||
random_rotation_seed: SeedValue,
|
random_rotation_seed: SeedValue,
|
||||||
) -> GraphicGroupTable {
|
) -> GraphicGroupTable {
|
||||||
let points = points.eval(footprint).await;
|
|
||||||
let points = points.one_item();
|
let points = points.one_item();
|
||||||
|
|
||||||
let instance = instance.eval(footprint).await;
|
|
||||||
|
|
||||||
let instance_transform = instance.transform();
|
let instance_transform = instance.transform();
|
||||||
|
|
||||||
let random_scale_difference = random_scale_max - random_scale_min;
|
let random_scale_difference = random_scale_max - random_scale_min;
|
||||||
|
@ -421,19 +305,7 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn bounding_box<F: 'n + Send>(
|
async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
let vector_data = vector_data.eval(footprint).await;
|
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
|
|
||||||
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap();
|
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap();
|
||||||
|
@ -445,30 +317,16 @@ async fn bounding_box<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
|
||||||
async fn offset_path<F: 'n + Send>(
|
async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
|
||||||
distance: f64,
|
|
||||||
line_join: LineJoin,
|
|
||||||
#[default(4.)] miter_limit: f64,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
let vector_data = vector_data.eval(footprint).await;
|
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
|
|
||||||
|
let subpaths = vector_data.stroke_bezier_paths();
|
||||||
let mut result = VectorData::empty();
|
let mut result = VectorData::empty();
|
||||||
result.style = vector_data.style.clone();
|
result.style = vector_data.style.clone();
|
||||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
|
|
||||||
// Perform operation on all subpaths in this shape.
|
// Perform operation on all subpaths in this shape.
|
||||||
for mut subpath in vector_data.stroke_bezier_paths() {
|
for mut subpath in subpaths {
|
||||||
subpath.apply_transform(vector_data.transform);
|
subpath.apply_transform(vector_data.transform);
|
||||||
|
|
||||||
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
|
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
|
||||||
|
@ -489,19 +347,7 @@ async fn offset_path<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn solidify_stroke<F: 'n + Send>(
|
async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
let vector_data = vector_data.eval(footprint).await;
|
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
|
|
||||||
let transform = &vector_data.transform;
|
let transform = &vector_data.transform;
|
||||||
|
@ -551,20 +397,8 @@ async fn solidify_stroke<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn flatten_vector_elements<F: 'n + Send>(
|
async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupTable) -> VectorDataTable {
|
||||||
#[implementations(
|
let graphic_group_input = graphic_group_input.one_item();
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
)]
|
|
||||||
graphic_group_input: impl Node<F, Output = GraphicGroupTable>,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
let graphic_group = graphic_group_input.eval(footprint).await;
|
|
||||||
let graphic_group = graphic_group.one_item();
|
|
||||||
|
|
||||||
// A node based solution to support passing through vector data could be a network node with a cache node connected to
|
// A node based solution to support passing through vector data could be a network node with a cache node connected to
|
||||||
// a flatten vector elements connected to an if else node, another connection from the cache directly
|
// a flatten vector elements connected to an if else node, another connection from the cache directly
|
||||||
|
@ -587,7 +421,7 @@ async fn flatten_vector_elements<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = VectorData::empty();
|
let mut result = VectorData::empty();
|
||||||
concat_group(graphic_group, DAffine2::IDENTITY, &mut result);
|
concat_group(graphic_group_input, DAffine2::IDENTITY, &mut result);
|
||||||
// TODO: This leads to incorrect stroke widths when flattening groups with different transforms.
|
// TODO: This leads to incorrect stroke widths when flattening groups with different transforms.
|
||||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
|
|
||||||
|
@ -614,34 +448,10 @@ impl ConcatElement for GraphicGroupTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||||
async fn sample_points<F: 'n + Send + Copy>(
|
async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, subpath_segment_lengths: Vec<f64>) -> VectorDataTable {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
|
||||||
spacing: f64,
|
|
||||||
start_offset: f64,
|
|
||||||
stop_offset: f64,
|
|
||||||
adaptive_spacing: bool,
|
|
||||||
#[implementations(
|
|
||||||
() -> Vec<f64>,
|
|
||||||
Footprint -> Vec<f64>,
|
|
||||||
)]
|
|
||||||
subpath_segment_lengths: impl Node<F, Output = Vec<f64>>,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
// Limit the smallest spacing to something sensible to avoid freezing the application.
|
// Limit the smallest spacing to something sensible to avoid freezing the application.
|
||||||
let spacing = spacing.max(0.01);
|
let spacing = spacing.max(0.01);
|
||||||
|
|
||||||
// Evaluate vector data and subpath segment lengths asynchronously.
|
|
||||||
let vector_data = vector_data.eval(footprint).await;
|
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
let subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await;
|
|
||||||
|
|
||||||
// Create an iterator over the bezier segments with enumeration and peeking capability.
|
// Create an iterator over the bezier segments with enumeration and peeking capability.
|
||||||
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
|
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
|
||||||
|
@ -786,23 +596,14 @@ async fn sample_points<F: 'n + Send + Copy>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||||
async fn poisson_disk_points<F: 'n + Send>(
|
async fn poisson_disk_points(
|
||||||
#[implementations(
|
_: impl Ctx,
|
||||||
(),
|
vector_data: VectorDataTable,
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
|
||||||
#[default(10.)]
|
#[default(10.)]
|
||||||
#[min(0.01)]
|
#[min(0.01)]
|
||||||
separation_disk_diameter: f64,
|
separation_disk_diameter: f64,
|
||||||
seed: SeedValue,
|
seed: SeedValue,
|
||||||
) -> VectorDataTable {
|
) -> VectorDataTable {
|
||||||
let vector_data = vector_data.eval(footprint).await;
|
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
|
|
||||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||||
|
@ -846,19 +647,7 @@ async fn poisson_disk_points<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||||
async fn subpath_segment_lengths<F: 'n + Send>(
|
async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec<f64> {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
|
||||||
) -> Vec<f64> {
|
|
||||||
let vector_data = vector_data.eval(footprint).await;
|
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
|
|
||||||
vector_data
|
vector_data
|
||||||
|
@ -868,20 +657,7 @@ async fn subpath_segment_lengths<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))]
|
||||||
async fn spline<F: 'n + Send>(
|
async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
// Evaluate the vector data within the given footprint.
|
|
||||||
let mut vector_data = vector_data.eval(footprint).await;
|
|
||||||
let vector_data = vector_data.one_item_mut();
|
let vector_data = vector_data.one_item_mut();
|
||||||
|
|
||||||
// Exit early if there are no points to generate splines from.
|
// Exit early if there are no points to generate splines from.
|
||||||
|
@ -923,21 +699,7 @@ async fn spline<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn jitter_points<F: 'n + Send>(
|
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
|
||||||
#[default(5.)] amount: f64,
|
|
||||||
seed: SeedValue,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
let vector_data = vector_data.eval(footprint).await;
|
|
||||||
let mut vector_data = vector_data.one_item().clone();
|
let mut vector_data = vector_data.one_item().clone();
|
||||||
|
|
||||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||||
|
@ -986,31 +748,16 @@ async fn jitter_points<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn morph<F: 'n + Send + Copy>(
|
async fn morph(
|
||||||
#[implementations(
|
_: impl Ctx,
|
||||||
(),
|
source: VectorDataTable,
|
||||||
Footprint,
|
#[expose] target: VectorDataTable,
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
source: impl Node<F, Output = VectorDataTable>,
|
|
||||||
#[expose]
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
target: impl Node<F, Output = VectorDataTable>,
|
|
||||||
#[range((0., 1.))]
|
#[range((0., 1.))]
|
||||||
#[default(0.5)]
|
#[default(0.5)]
|
||||||
time: Fraction,
|
time: Fraction,
|
||||||
#[min(0.)] start_index: IntegerCount,
|
#[min(0.)] start_index: IntegerCount,
|
||||||
) -> VectorDataTable {
|
) -> VectorDataTable {
|
||||||
let source = source.eval(footprint).await;
|
|
||||||
let source = source.one_item();
|
let source = source.one_item();
|
||||||
let target = target.eval(footprint).await;
|
|
||||||
let target = target.one_item();
|
let target = target.one_item();
|
||||||
|
|
||||||
let mut result = VectorData::empty();
|
let mut result = VectorData::empty();
|
||||||
|
@ -1202,30 +949,16 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn bevel<F: 'n + Send + Copy>(
|
fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorDataTable,
|
|
||||||
Footprint -> VectorDataTable,
|
|
||||||
)]
|
|
||||||
source: impl Node<F, Output = VectorDataTable>,
|
|
||||||
#[default(10.)] distance: Length,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
let source = source.eval(footprint).await;
|
|
||||||
let source = source.one_item();
|
let source = source.one_item();
|
||||||
|
|
||||||
let result = bevel_algorithm(source.clone(), distance);
|
VectorDataTable::new(bevel_algorithm(source.clone(), distance))
|
||||||
|
|
||||||
VectorDataTable::new(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>) -> f64 {
|
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 {
|
||||||
let vector_data = vector_data.eval(Footprint::default()).await;
|
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
|
||||||
|
let vector_data = vector_data.eval(new_ctx).await;
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
|
|
||||||
let mut area = 0.;
|
let mut area = 0.;
|
||||||
|
@ -1237,8 +970,9 @@ async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
|
async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
|
||||||
let vector_data = vector_data.eval(Footprint::default()).await;
|
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
|
||||||
|
let vector_data = vector_data.eval(new_ctx).await;
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
|
|
||||||
if centroid_type == CentroidType::Area {
|
if centroid_type == CentroidType::Area {
|
||||||
|
@ -1303,16 +1037,16 @@ mod test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorDataTable> {
|
fn vector_node(data: Subpath<PointId>) -> VectorDataTable {
|
||||||
FutureWrapperNode(VectorDataTable::new(VectorData::from_subpath(data)))
|
VectorDataTable::new(VectorData::from_subpath(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn repeat() {
|
async fn repeat() {
|
||||||
let direction = DVec2::X * 1.5;
|
let direction = DVec2::X * 1.5;
|
||||||
let instances = 3;
|
let instances = 3;
|
||||||
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||||
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
|
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
assert_eq!(vector_data.region_bezier_paths().count(), 3);
|
assert_eq!(vector_data.region_bezier_paths().count(), 3);
|
||||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||||
|
@ -1323,8 +1057,8 @@ mod test {
|
||||||
async fn repeat_transform_position() {
|
async fn repeat_transform_position() {
|
||||||
let direction = DVec2::new(12., 10.);
|
let direction = DVec2::new(12., 10.);
|
||||||
let instances = 8;
|
let instances = 8;
|
||||||
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||||
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
|
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||||
|
@ -1333,8 +1067,8 @@ mod test {
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn circle_repeat() {
|
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;
|
let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
|
||||||
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
|
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||||
|
@ -1346,10 +1080,7 @@ mod test {
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn bounding_box() {
|
async fn bounding_box() {
|
||||||
let bounding_box = BoundingBoxNode {
|
let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await;
|
||||||
vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)),
|
|
||||||
};
|
|
||||||
let bounding_box = bounding_box.eval(Footprint::default()).await;
|
|
||||||
let bounding_box = bounding_box.one_item();
|
let bounding_box = bounding_box.one_item();
|
||||||
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
|
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
|
||||||
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
|
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
|
||||||
|
@ -1375,8 +1106,8 @@ mod test {
|
||||||
let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.);
|
let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.);
|
||||||
let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE);
|
let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE);
|
||||||
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
|
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
|
||||||
let copy_to_points = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await;
|
let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await;
|
||||||
let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(copy_to_points)).await;
|
let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), copy_to_points).await;
|
||||||
let flattened_copy_to_points = flattened_copy_to_points.one_item();
|
let flattened_copy_to_points = flattened_copy_to_points.one_item();
|
||||||
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
|
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
|
||||||
for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() {
|
for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() {
|
||||||
|
@ -1390,7 +1121,7 @@ mod test {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn sample_points() {
|
async fn sample_points() {
|
||||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
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;
|
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await;
|
||||||
let sample_points = sample_points.one_item();
|
let sample_points = sample_points.one_item();
|
||||||
assert_eq!(sample_points.point_domain.positions().len(), 4);
|
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.]) {
|
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
||||||
|
@ -1400,7 +1131,7 @@ mod test {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn adaptive_spacing() {
|
async fn adaptive_spacing() {
|
||||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
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;
|
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await;
|
||||||
let sample_points = sample_points.one_item();
|
let sample_points = sample_points.one_item();
|
||||||
assert_eq!(sample_points.point_domain.positions().len(), 4);
|
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.]) {
|
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
||||||
|
@ -1411,7 +1142,7 @@ mod test {
|
||||||
async fn poisson() {
|
async fn poisson() {
|
||||||
let sample_points = super::poisson_disk_points(
|
let sample_points = super::poisson_disk_points(
|
||||||
Footprint::default(),
|
Footprint::default(),
|
||||||
&vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
|
vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
|
||||||
10. * std::f64::consts::SQRT_2,
|
10. * std::f64::consts::SQRT_2,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
|
@ -1429,12 +1160,12 @@ mod test {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn lengths() {
|
async fn lengths() {
|
||||||
let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||||
let lengths = subpath_segment_lengths(Footprint::default(), &vector_node(subpath)).await;
|
let lengths = subpath_segment_lengths(Footprint::default(), vector_node(subpath)).await;
|
||||||
assert_eq!(lengths, vec![100.]);
|
assert_eq!(lengths, vec![100.]);
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn spline() {
|
async fn spline() {
|
||||||
let spline = super::spline(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
|
let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
|
||||||
let spline = spline.one_item();
|
let spline = spline.one_item();
|
||||||
assert_eq!(spline.stroke_bezier_paths().count(), 1);
|
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.)]);
|
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
|
||||||
|
@ -1443,7 +1174,7 @@ mod test {
|
||||||
async fn morph() {
|
async fn morph() {
|
||||||
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
||||||
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
|
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;
|
let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await;
|
||||||
let sample_points = sample_points.one_item();
|
let sample_points = sample_points.one_item();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&sample_points.point_domain.positions()[..4],
|
&sample_points.point_domain.positions()[..4],
|
||||||
|
@ -1452,7 +1183,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn contains_segment(vector: &VectorData, target: bezier_rs::Bezier) {
|
fn contains_segment(vector: VectorData, target: bezier_rs::Bezier) {
|
||||||
let segments = vector.segment_bezier_iter().map(|x| x.1);
|
let segments = vector.segment_bezier_iter().map(|x| x.1);
|
||||||
let count = segments.filter(|bezier| bezier.abs_diff_eq(&target, 0.01) || bezier.reversed().abs_diff_eq(&target, 0.01)).count();
|
let count = segments.filter(|bezier| bezier.abs_diff_eq(&target, 0.01) || bezier.reversed().abs_diff_eq(&target, 0.01)).count();
|
||||||
assert_eq!(count, 1, "Incorrect number of {target:#?} in {:#?}", vector.segment_bezier_iter().collect::<Vec<_>>());
|
assert_eq!(count, 1, "Incorrect number of {target:#?} in {:#?}", vector.segment_bezier_iter().collect::<Vec<_>>());
|
||||||
|
@ -1461,42 +1192,42 @@ mod test {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn bevel_rect() {
|
async fn bevel_rect() {
|
||||||
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
||||||
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
|
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
|
||||||
let beveled = beveled.one_item();
|
let beveled = beveled.one_item();
|
||||||
|
|
||||||
assert_eq!(beveled.point_domain.positions().len(), 8);
|
assert_eq!(beveled.point_domain.positions().len(), 8);
|
||||||
assert_eq!(beveled.segment_domain.ids().len(), 8);
|
assert_eq!(beveled.segment_domain.ids().len(), 8);
|
||||||
|
|
||||||
// Segments
|
// Segments
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(95., 0.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(95., 0.)));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(95., 100.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(95., 100.)));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 5.), DVec2::new(0., 95.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 5.), DVec2::new(0., 95.)));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 5.), DVec2::new(100., 95.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 5.), DVec2::new(100., 95.)));
|
||||||
|
|
||||||
// Joins
|
// Joins
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(0., 5.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(0., 5.)));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(95., 0.), DVec2::new(100., 5.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(95., 0.), DVec2::new(100., 5.)));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 95.), DVec2::new(95., 100.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 95.), DVec2::new(95., 100.)));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(0., 95.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(0., 95.)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn bevel_open_curve() {
|
async fn bevel_open_curve() {
|
||||||
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
|
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
|
||||||
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false);
|
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false);
|
||||||
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
|
let beveled = super::bevel((), vector_node(source), 5.);
|
||||||
let beveled = beveled.one_item();
|
let beveled = beveled.one_item();
|
||||||
|
|
||||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||||
assert_eq!(beveled.segment_domain.ids().len(), 3);
|
assert_eq!(beveled.segment_domain.ids().len(), 3);
|
||||||
|
|
||||||
// Segments
|
// Segments
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-100., 0.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-100., 0.)));
|
||||||
let trimmed = curve.trim(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.));
|
let trimmed = curve.trim(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.));
|
||||||
contains_segment(&beveled, trimmed);
|
contains_segment(beveled.clone(), trimmed);
|
||||||
|
|
||||||
// Join
|
// Join
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -1506,7 +1237,7 @@ mod test {
|
||||||
let mut vector_data = VectorData::from_subpath(source);
|
let mut vector_data = VectorData::from_subpath(source);
|
||||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
|
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
|
||||||
vector_data.transform = transform;
|
vector_data.transform = transform;
|
||||||
let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(VectorDataTable::new(vector_data)), 5.).await;
|
let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.);
|
||||||
let beveled = beveled.one_item();
|
let beveled = beveled.one_item();
|
||||||
|
|
||||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||||
|
@ -1514,31 +1245,31 @@ mod test {
|
||||||
assert_eq!(beveled.transform, transform);
|
assert_eq!(beveled.transform, transform);
|
||||||
|
|
||||||
// Segments
|
// Segments
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.)));
|
||||||
let trimmed = curve.trim(bezier_rs::TValue::Euclidean(0.5 / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.));
|
let trimmed = curve.trim(bezier_rs::TValue::Euclidean(0.5 / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.));
|
||||||
contains_segment(&beveled, trimmed);
|
contains_segment(beveled.clone(), trimmed);
|
||||||
|
|
||||||
// Join
|
// Join
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn bevel_too_high() {
|
async fn bevel_too_high() {
|
||||||
let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false);
|
let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false);
|
||||||
let beveled = super::bevel(Footprint::default(), &vector_node(source), 999.).await;
|
let beveled = super::bevel(Footprint::default(), vector_node(source), 999.);
|
||||||
let beveled = beveled.one_item();
|
let beveled = beveled.one_item();
|
||||||
|
|
||||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||||
|
|
||||||
// Segments
|
// Segments
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 0.), DVec2::new(50., 0.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 0.), DVec2::new(50., 0.)));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(100., 50.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(100., 50.)));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.)));
|
||||||
|
|
||||||
// Joins
|
// Joins
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(50., 0.), DVec2::new(100., 50.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(50., 0.), DVec2::new(100., 50.)));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -1546,18 +1277,18 @@ mod test {
|
||||||
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
|
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
|
||||||
let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO);
|
let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO);
|
||||||
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false);
|
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false);
|
||||||
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
|
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
|
||||||
let beveled = beveled.one_item();
|
let beveled = beveled.one_item();
|
||||||
|
|
||||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||||
|
|
||||||
// Segments
|
// Segments
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-100., 0.), DVec2::new(-5., 0.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-100., 0.), DVec2::new(-5., 0.)));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(0., 0.)));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(0., 0.)));
|
||||||
contains_segment(&beveled, point);
|
contains_segment(beveled.clone(), point);
|
||||||
let [start, end] = curve.split(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))));
|
let [start, end] = curve.split(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))));
|
||||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(start.start, start.end));
|
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(start.start, start.end));
|
||||||
contains_segment(&beveled, end);
|
contains_segment(beveled.clone(), end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ winit = { workspace = true }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
graph-craft = { workspace = true, features = ["loading"] }
|
graph-craft = { workspace = true, features = ["loading"] }
|
||||||
|
pretty_assertions = { workspace = true }
|
||||||
|
|
||||||
# Required dependencies
|
# Required dependencies
|
||||||
criterion = { version = "0.5", features = ["html_reports"]}
|
criterion = { version = "0.5", features = ["html_reports"]}
|
||||||
|
|
|
@ -304,7 +304,7 @@ impl DocumentNode {
|
||||||
match first {
|
match first {
|
||||||
NodeInput::Value { tagged_value, .. } => {
|
NodeInput::Value { tagged_value, .. } => {
|
||||||
assert_eq!(self.inputs.len(), 0, "A value node cannot have any inputs. Current inputs: {:?}", self.inputs);
|
assert_eq!(self.inputs.len(), 0, "A value node cannot have any inputs. Current inputs: {:?}", self.inputs);
|
||||||
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
|
(ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context<'static>)), ConstructionArgs::Value(tagged_value))
|
||||||
}
|
}
|
||||||
NodeInput::Node { node_id, output_index, lambda } => {
|
NodeInput::Node { node_id, output_index, lambda } => {
|
||||||
assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node");
|
assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node");
|
||||||
|
@ -1567,7 +1567,7 @@ mod test {
|
||||||
NodeId(14),
|
NodeId(14),
|
||||||
ProtoNode {
|
ProtoNode {
|
||||||
identifier: "graphene_core::value::ClonedNode".into(),
|
identifier: "graphene_core::value::ClonedNode".into(),
|
||||||
input: ProtoNodeInput::None,
|
input: ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context)),
|
||||||
construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()),
|
construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()),
|
||||||
original_location: OriginalLocation {
|
original_location: OriginalLocation {
|
||||||
path: Some(vec![NodeId(1), NodeId(4)]),
|
path: Some(vec![NodeId(1), NodeId(4)]),
|
||||||
|
@ -1589,7 +1589,7 @@ mod test {
|
||||||
|
|
||||||
println!("{:#?}", resolved_network[0]);
|
println!("{:#?}", resolved_network[0]);
|
||||||
println!("{construction_network:#?}");
|
println!("{construction_network:#?}");
|
||||||
assert_eq!(resolved_network[0], construction_network);
|
pretty_assertions::assert_eq!(resolved_network[0], construction_network);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flat_network() -> NodeNetwork {
|
fn flat_network() -> NodeNetwork {
|
||||||
|
|
|
@ -102,8 +102,8 @@ macro_rules! tagged_value {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Type::Fn(_, output) => TaggedValue::from_type(output),
|
Type::Fn(_, output) => TaggedValue::from_type(output),
|
||||||
Type::Future(_) => {
|
Type::Future(output) => {
|
||||||
None
|
TaggedValue::from_type(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,7 +270,7 @@ impl TaggedValue {
|
||||||
Some(ty)
|
Some(ty)
|
||||||
}
|
}
|
||||||
Type::Fn(_, output) => TaggedValue::from_primitive_string(string, output),
|
Type::Fn(_, output) => TaggedValue::from_primitive_string(string, output),
|
||||||
Type::Future(_) => None,
|
Type::Future(fut) => TaggedValue::from_primitive_string(string, fut),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,7 @@ impl Default for ProtoNode {
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum ProtoNodeInput {
|
pub enum ProtoNodeInput {
|
||||||
/// [`ProtoNode`]s do not require any input, e.g. the value node just takes in [`ConstructionArgs`].
|
/// This input will be converted to `()` as the call argument.
|
||||||
None,
|
None,
|
||||||
/// A ManualComposition input represents an input that opts out of being resolved through the `ComposeNode`, which first runs the previous (upstream) node, then passes that evaluated
|
/// A ManualComposition input represents an input that opts out of being resolved through the `ComposeNode`, which first runs the previous (upstream) node, then passes that evaluated
|
||||||
/// result to this node. Instead, ManualComposition lets this node actually consume the provided input instead of passing it to its predecessor.
|
/// result to this node. Instead, ManualComposition lets this node actually consume the provided input instead of passing it to its predecessor.
|
||||||
|
@ -203,6 +203,7 @@ impl ProtoNode {
|
||||||
if self.skip_deduplication {
|
if self.skip_deduplication {
|
||||||
self.original_location.path.hash(&mut hasher);
|
self.original_location.path.hash(&mut hasher);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::mem::discriminant(&self.input).hash(&mut hasher);
|
std::mem::discriminant(&self.input).hash(&mut hasher);
|
||||||
match self.input {
|
match self.input {
|
||||||
ProtoNodeInput::None => (),
|
ProtoNodeInput::None => (),
|
||||||
|
@ -212,6 +213,7 @@ impl ProtoNode {
|
||||||
ProtoNodeInput::Node(id) => (id, false).hash(&mut hasher),
|
ProtoNodeInput::Node(id) => (id, false).hash(&mut hasher),
|
||||||
ProtoNodeInput::NodeLambda(id) => (id, true).hash(&mut hasher),
|
ProtoNodeInput::NodeLambda(id) => (id, true).hash(&mut hasher),
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(NodeId(hasher.finish()))
|
Some(NodeId(hasher.finish()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +226,7 @@ impl ProtoNode {
|
||||||
Self {
|
Self {
|
||||||
identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"),
|
identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"),
|
||||||
construction_args: value,
|
construction_args: value,
|
||||||
input: ProtoNodeInput::None,
|
input: ProtoNodeInput::ManualComposition(concrete!(Context)),
|
||||||
original_location: OriginalLocation {
|
original_location: OriginalLocation {
|
||||||
path: Some(path),
|
path: Some(path),
|
||||||
inputs_exposed: vec![false; inputs_exposed],
|
inputs_exposed: vec![false; inputs_exposed],
|
||||||
|
@ -552,10 +554,11 @@ impl core::fmt::Debug for GraphErrorType {
|
||||||
GraphErrorType::NoImplementations => write!(f, "No implementations found"),
|
GraphErrorType::NoImplementations => write!(f, "No implementations found"),
|
||||||
GraphErrorType::NoConstructor => write!(f, "No construct found for node"),
|
GraphErrorType::NoConstructor => write!(f, "No construct found for node"),
|
||||||
GraphErrorType::InvalidImplementations { inputs, error_inputs } => {
|
GraphErrorType::InvalidImplementations { inputs, error_inputs } => {
|
||||||
let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}", index + 1);
|
let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}, found: {_found}", index + 1);
|
||||||
let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::<Vec<_>>().join("\n");
|
let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::<Vec<_>>().join("\n").replace("Option<Arc<OwnedContextImpl>>", "Context");
|
||||||
let mut errors = error_inputs.iter().map(format_error_list).collect::<Vec<_>>();
|
let mut errors = error_inputs.iter().map(format_error_list).collect::<Vec<_>>();
|
||||||
errors.sort();
|
errors.sort();
|
||||||
|
let inputs = inputs.replace("Option<Arc<OwnedContextImpl>>", "Context");
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"This node isn't compatible with the com-\n\
|
"This node isn't compatible with the com-\n\
|
||||||
|
@ -651,9 +654,9 @@ impl TypingContext {
|
||||||
let inputs = match node.construction_args {
|
let inputs = match node.construction_args {
|
||||||
// If the node has a value input we can infer the return type from it
|
// If the node has a value input we can infer the return type from it
|
||||||
ConstructionArgs::Value(ref v) => {
|
ConstructionArgs::Value(ref v) => {
|
||||||
assert!(matches!(node.input, ProtoNodeInput::None));
|
assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context)));
|
||||||
// TODO: This should return a reference to the value
|
// TODO: This should return a reference to the value
|
||||||
let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![v.ty()]);
|
let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]);
|
||||||
self.inferred.insert(node_id, types.clone());
|
self.inferred.insert(node_id, types.clone());
|
||||||
return Ok(types);
|
return Ok(types);
|
||||||
}
|
}
|
||||||
|
@ -696,6 +699,8 @@ impl TypingContext {
|
||||||
match (from, to) {
|
match (from, to) {
|
||||||
// Direct comparison of two concrete types.
|
// Direct comparison of two concrete types.
|
||||||
(Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2,
|
(Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2,
|
||||||
|
// Check inner type for futures
|
||||||
|
(Type::Future(type1), Type::Future(type2)) => type1 == type2,
|
||||||
// Loose comparison of function types, where loose means that functions are considered on a "greater than or equal to" basis of its function type's generality.
|
// Loose comparison of function types, where loose means that functions are considered on a "greater than or equal to" basis of its function type's generality.
|
||||||
// That means we compare their types with a contravariant relationship, which means that a more general type signature may be substituted for a more specific type signature.
|
// That means we compare their types with a contravariant relationship, which means that a more general type signature may be substituted for a more specific type signature.
|
||||||
// For example, we allow `T -> V` to be substituted with `T' -> V` or `() -> V` where T' and () are more specific than T.
|
// For example, we allow `T -> V` to be substituted with `T' -> V` or `() -> V` where T' and () are more specific than T.
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, ExtendImageToBoundsNode};
|
use crate::raster::{blend_image_closure, BlendImageTupleNode, ExtendImageToBoundsNode};
|
||||||
|
|
||||||
|
use graph_craft::generic::FnNode;
|
||||||
|
use graph_craft::proto::FutureWrapperNode;
|
||||||
use graphene_core::raster::adjustments::blend_colors;
|
use graphene_core::raster::adjustments::blend_colors;
|
||||||
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
|
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
|
||||||
use graphene_core::raster::brush_cache::BrushCache;
|
use graphene_core::raster::brush_cache::BrushCache;
|
||||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||||
use graphene_core::raster::BlendMode;
|
use graphene_core::raster::BlendMode;
|
||||||
use graphene_core::raster::{Alpha, BlendColorPairNode, Color, Image, Pixel, Sample};
|
use graphene_core::raster::{Alpha, Color, Image, Pixel, Sample};
|
||||||
use graphene_core::transform::{Footprint, Transform, TransformMut};
|
use graphene_core::transform::{Transform, TransformMut};
|
||||||
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
|
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
|
||||||
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
|
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
|
||||||
use graphene_core::vector::VectorDataTable;
|
use graphene_core::vector::VectorDataTable;
|
||||||
use graphene_core::Node;
|
use graphene_core::{Ctx, Node};
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
fn vector_points(_: (), vector_data: VectorDataTable) -> Vec<DVec2> {
|
fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec<DVec2> {
|
||||||
let vector_data = vector_data.one_item();
|
let vector_data = vector_data.one_item();
|
||||||
|
|
||||||
vector_data.point_domain.positions().to_vec()
|
vector_data.point_domain.positions().to_vec()
|
||||||
|
@ -131,14 +133,21 @@ where
|
||||||
target
|
target
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
|
pub async 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 = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, 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 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(transform), CopiedNode::new(Color::TRANSPARENT)).eval(());
|
use crate::raster::empty_image;
|
||||||
let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
|
let blank_texture = empty_image((), transform, Color::TRANSPARENT);
|
||||||
let blend_executor = BlendImageTupleNode::new(ValueNode::new(normal_blend));
|
// let normal_blend = BlendColorPairNode::new(
|
||||||
blend_executor.eval((blank_texture, stamp)).image
|
// FutureWrapperNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal))),
|
||||||
|
// FutureWrapperNode::new(ValueNode::new(CopiedNode::new(100.))),
|
||||||
|
// );
|
||||||
|
// normal_blend.eval((Color::default(), Color::default()));
|
||||||
|
// use crate::raster::blend_image_tuple;
|
||||||
|
// blend_image_tuple((blank_texture, stamp), &normal_blend).await.image;
|
||||||
|
crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.)).image
|
||||||
|
// let blend_executoc = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(normal_blend)));
|
||||||
|
// blend_executor.eval((blank_texture, stamp)).image
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! inline_blend_funcs {
|
macro_rules! inline_blend_funcs {
|
||||||
|
@ -202,7 +211,7 @@ pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Col
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
fn brush(_: Footprint, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
|
async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
|
||||||
let image = image.one_item().clone();
|
let image = image.one_item().clone();
|
||||||
|
|
||||||
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
|
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
|
||||||
|
@ -225,11 +234,13 @@ fn brush(_: Footprint, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Co
|
||||||
for (idx, stroke) in brush_plan.strokes.into_iter().enumerate() {
|
for (idx, stroke) in brush_plan.strokes.into_iter().enumerate() {
|
||||||
// Create brush texture.
|
// Create brush texture.
|
||||||
// TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes.
|
// TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes.
|
||||||
let brush_texture = cache.get_cached_brush(&stroke.style).unwrap_or_else(|| {
|
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||||
let tex = create_brush_texture(&stroke.style);
|
if brush_texture.is_none() {
|
||||||
|
let tex = create_brush_texture(&stroke.style).await;
|
||||||
cache.store_brush(stroke.style.clone(), tex.clone());
|
cache.store_brush(stroke.style.clone(), tex.clone());
|
||||||
tex
|
brush_texture = Some(tex);
|
||||||
});
|
}
|
||||||
|
let brush_texture = brush_texture.unwrap();
|
||||||
|
|
||||||
// Compute transformation from stroke texture space into layer space, and create the stroke texture.
|
// Compute transformation from stroke texture space into layer space, and create the stroke texture.
|
||||||
let skip = if idx == 0 { brush_plan.first_stroke_point_skip } else { 0 };
|
let skip = if idx == 0 { brush_plan.first_stroke_point_skip } else { 0 };
|
||||||
|
@ -246,16 +257,23 @@ fn brush(_: Footprint, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Co
|
||||||
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);
|
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);
|
||||||
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
|
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
|
||||||
|
|
||||||
let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
|
// let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.)));
|
||||||
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(normal_blend));
|
let normal_blend = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Normal, 1.));
|
||||||
|
let blit_node = BlitNode::new(
|
||||||
|
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
|
||||||
|
FutureWrapperNode::new(ClonedNode::new(positions)),
|
||||||
|
FutureWrapperNode::new(ClonedNode::new(normal_blend)),
|
||||||
|
);
|
||||||
let blit_target = if idx == 0 {
|
let blit_target = if idx == 0 {
|
||||||
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
|
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
|
||||||
ExtendImageToBoundsNode::new(CopiedNode::new(stroke_to_layer)).eval(target)
|
ExtendImageToBoundsNode::new(CopiedNode::new(stroke_to_layer)).eval(target)
|
||||||
} else {
|
} else {
|
||||||
EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
|
use crate::raster::empty_image;
|
||||||
|
empty_image((), stroke_to_layer, Color::TRANSPARENT)
|
||||||
|
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
|
||||||
};
|
};
|
||||||
|
|
||||||
blit_node.eval(blit_target)
|
blit_node.eval(blit_target).await
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cache image before doing final blend, and store final stroke texture.
|
// Cache image before doing final blend, and store final stroke texture.
|
||||||
|
@ -277,34 +295,44 @@ fn brush(_: Footprint, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Co
|
||||||
let mut erase_restore_mask = opaque_image;
|
let mut erase_restore_mask = opaque_image;
|
||||||
|
|
||||||
for stroke in erase_restore_strokes {
|
for stroke in erase_restore_strokes {
|
||||||
let brush_texture = cache.get_cached_brush(&stroke.style).unwrap_or_else(|| {
|
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||||
let tex = create_brush_texture(&stroke.style);
|
if brush_texture.is_none() {
|
||||||
|
let tex = create_brush_texture(&stroke.style).await;
|
||||||
cache.store_brush(stroke.style.clone(), tex.clone());
|
cache.store_brush(stroke.style.clone(), tex.clone());
|
||||||
tex
|
brush_texture = Some(tex);
|
||||||
});
|
}
|
||||||
|
let brush_texture = brush_texture.unwrap();
|
||||||
let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect();
|
let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect();
|
||||||
|
|
||||||
match stroke.style.blend_mode {
|
match stroke.style.blend_mode {
|
||||||
BlendMode::Erase => {
|
BlendMode::Erase => {
|
||||||
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Erase), CopiedNode::new(100.));
|
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Erase, 1.));
|
||||||
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
|
let blit_node = BlitNode::new(
|
||||||
erase_restore_mask = blit_node.eval(erase_restore_mask);
|
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
|
||||||
|
FutureWrapperNode::new(ClonedNode::new(positions)),
|
||||||
|
FutureWrapperNode::new(ClonedNode::new(blend_params)),
|
||||||
|
);
|
||||||
|
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
|
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
|
||||||
BlendMode::Restore => {
|
BlendMode::Restore => {
|
||||||
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Restore), CopiedNode::new(100.));
|
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 1.));
|
||||||
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
|
let blit_node = BlitNode::new(
|
||||||
erase_restore_mask = blit_node.eval(erase_restore_mask);
|
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
|
||||||
|
FutureWrapperNode::new(ClonedNode::new(positions)),
|
||||||
|
FutureWrapperNode::new(ClonedNode::new(blend_params)),
|
||||||
|
);
|
||||||
|
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.));
|
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));
|
||||||
let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params));
|
let blend_executor = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(blend_params)));
|
||||||
actual_image = blend_executor.eval((actual_image, erase_restore_mask));
|
actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageFrameTable::new(actual_image)
|
ImageFrameTable::new(actual_image)
|
||||||
|
@ -315,15 +343,13 @@ mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use graphene_core::transform::Transform;
|
use graphene_core::transform::Transform;
|
||||||
use graphene_core::value::ClonedNode;
|
|
||||||
|
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_brush_texture() {
|
fn test_brush_texture() {
|
||||||
let brush_texture_node = BrushStampGeneratorNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(100.), ClonedNode::new(100.));
|
|
||||||
let size = 20.;
|
let size = 20.;
|
||||||
let image = brush_texture_node.eval(size);
|
let image = brush_stamp_generator(size, Color::BLACK, 100., 100.);
|
||||||
assert_eq!(image.transform(), DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil()), 0., -DVec2::splat(size / 2.)));
|
assert_eq!(image.transform(), DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil()), 0., -DVec2::splat(size / 2.)));
|
||||||
// center pixel should be BLACK
|
// center pixel should be BLACK
|
||||||
assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));
|
assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));
|
||||||
|
|
|
@ -1,28 +1,14 @@
|
||||||
use graph_craft::proto::types::Percentage;
|
use graph_craft::proto::types::Percentage;
|
||||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||||
use graphene_core::raster::Image;
|
use graphene_core::raster::Image;
|
||||||
use graphene_core::transform::Footprint;
|
use graphene_core::{Color, Ctx};
|
||||||
use graphene_core::Color;
|
|
||||||
|
|
||||||
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
|
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
|
||||||
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
|
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
#[node_macro::node(category("Raster: Filter"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
async fn dehaze<F: 'n + Send + Sync>(
|
async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Percentage) -> ImageFrameTable<Color> {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
)]
|
|
||||||
image_frame: impl Node<F, Output = ImageFrameTable<Color>>,
|
|
||||||
strength: Percentage,
|
|
||||||
) -> ImageFrameTable<Color> {
|
|
||||||
let image_frame = image_frame.eval(footprint).await;
|
|
||||||
let image_frame = image_frame.one_item();
|
let image_frame = image_frame.one_item();
|
||||||
|
|
||||||
// Prepare the image data for processing
|
// Prepare the image data for processing
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::wasm_application_io::WasmApplicationIo;
|
||||||
|
|
||||||
// TODO: Move to graph-craft
|
// TODO: Move to graph-craft
|
||||||
#[node_macro::node(category("Debug: GPU"))]
|
#[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> {
|
async fn compile_gpu<'a: 'n>(_: impl Ctx, node: &'a DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result<compilation_client::Shader, String> {
|
||||||
let mut typing_context = typing_context;
|
let mut typing_context = typing_context;
|
||||||
let compiler = graph_craft::graphene_compiler::Compiler {};
|
let compiler = graph_craft::graphene_compiler::Compiler {};
|
||||||
let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() };
|
let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() };
|
||||||
|
@ -279,7 +279,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug: GPU"))]
|
#[node_macro::node(category("Debug: GPU"))]
|
||||||
async fn blend_gpu_image(_: (), foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
|
async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
|
||||||
let foreground = foreground.one_item();
|
let foreground = foreground.one_item();
|
||||||
let background = background.one_item();
|
let background = background.one_item();
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
use graphene_core::Ctx;
|
||||||
|
|
||||||
#[node_macro::node(category("Network"))]
|
#[node_macro::node(category("Network"))]
|
||||||
async fn get_request(_: (), url: String) -> reqwest::Response {
|
async fn get_request(_: impl Ctx, url: String) -> reqwest::Response {
|
||||||
reqwest::get(url).await.unwrap()
|
reqwest::get(url).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Network"))]
|
#[node_macro::node(category("Network"))]
|
||||||
async fn post_request(_: (), url: String, body: String) -> reqwest::Response {
|
async fn post_request(_: impl Ctx, url: String, body: String) -> reqwest::Response {
|
||||||
reqwest::Client::new().post(url).body(body).send().await.unwrap()
|
reqwest::Client::new().post(url).body(body).send().await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,10 @@
|
||||||
use graphene_core::raster::image::ImageFrameTable;
|
use graphene_core::raster::image::ImageFrameTable;
|
||||||
use graphene_core::transform::Footprint;
|
use graphene_core::{Color, Ctx};
|
||||||
use graphene_core::Color;
|
|
||||||
|
|
||||||
#[node_macro::node(category("Raster"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
async fn image_color_palette<F: 'n + Send>(
|
async fn image_color_palette(
|
||||||
#[implementations(
|
_: impl Ctx,
|
||||||
(),
|
image: ImageFrameTable<Color>,
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> ImageFrameTable<Color>,
|
|
||||||
Footprint -> ImageFrameTable<Color>,
|
|
||||||
)]
|
|
||||||
image: impl Node<F, Output = ImageFrameTable<Color>>,
|
|
||||||
#[min(1.)]
|
#[min(1.)]
|
||||||
#[max(28.)]
|
#[max(28.)]
|
||||||
max_size: u32,
|
max_size: u32,
|
||||||
|
@ -25,7 +16,6 @@ async fn image_color_palette<F: 'n + Send>(
|
||||||
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
|
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
|
||||||
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
|
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
|
||||||
|
|
||||||
let image = image.eval(footprint).await;
|
|
||||||
let image = image.one_item();
|
let image = image.one_item();
|
||||||
|
|
||||||
for pixel in image.image.data.iter() {
|
for pixel in image.image.data.iter() {
|
||||||
|
@ -75,30 +65,24 @@ async fn image_color_palette<F: 'n + Send>(
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use graph_craft::generic::FnNode;
|
|
||||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||||
use graphene_core::raster::Image;
|
use graphene_core::raster::Image;
|
||||||
use graphene_core::value::CopiedNode;
|
|
||||||
use graphene_core::Node;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_image_color_palette() {
|
fn test_image_color_palette() {
|
||||||
let node = ImageColorPaletteNode {
|
let result = image_color_palette(
|
||||||
max_size: CopiedNode(1u32),
|
(),
|
||||||
image: FnNode::new(|_| {
|
ImageFrameTable::new(ImageFrame {
|
||||||
Box::pin(async move {
|
image: Image {
|
||||||
ImageFrameTable::new(ImageFrame {
|
width: 100,
|
||||||
image: Image {
|
height: 100,
|
||||||
width: 100,
|
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
|
||||||
height: 100,
|
base64_string: None,
|
||||||
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
|
},
|
||||||
base64_string: None,
|
..Default::default()
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
};
|
1,
|
||||||
assert_eq!(futures::executor::block_on(node.eval(())), [Color::from_rgbaf32(0., 0., 0., 1.).unwrap()]);
|
);
|
||||||
|
assert_eq!(futures::executor::block_on(result), [Color::from_rgbaf32(0., 0., 0., 1.).unwrap()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ use dyn_any::DynAny;
|
||||||
use graphene_core::raster::bbox::Bbox;
|
use graphene_core::raster::bbox::Bbox;
|
||||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||||
use graphene_core::raster::{
|
use graphene_core::raster::{
|
||||||
Alpha, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Sample,
|
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue,
|
||||||
|
Sample,
|
||||||
};
|
};
|
||||||
use graphene_core::transform::{Footprint, Transform};
|
use graphene_core::transform::Transform;
|
||||||
use graphene_core::{AlphaBlending, Color, Node};
|
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node};
|
||||||
|
|
||||||
use fastnoise_lite;
|
use fastnoise_lite;
|
||||||
use glam::{DAffine2, DVec2, Vec2};
|
use glam::{DAffine2, DVec2, Vec2};
|
||||||
|
@ -28,13 +29,14 @@ impl From<std::io::Error> for Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug: Raster"))]
|
#[node_macro::node(category("Debug: Raster"))]
|
||||||
fn sample_image(footprint: Footprint, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
|
fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
|
||||||
let image_frame = image_frame.one_item();
|
let image_frame = image_frame.one_item();
|
||||||
|
|
||||||
// Resize the image using the image crate
|
// Resize the image using the image crate
|
||||||
let image = &image_frame.image;
|
let image = &image_frame.image;
|
||||||
let data = bytemuck::cast_vec(image.data.clone());
|
let data = bytemuck::cast_vec(image.data.clone());
|
||||||
|
|
||||||
|
let footprint = ctx.footprint();
|
||||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||||
let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox();
|
let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox();
|
||||||
let intersection = viewport_bounds.intersect(&image_bounds);
|
let intersection = viewport_bounds.intersect(&image_bounds);
|
||||||
|
@ -107,15 +109,7 @@ where
|
||||||
image
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[node_macro::node]
|
||||||
pub struct InsertChannelNode<P, S, Insertion, TargetChannel> {
|
|
||||||
insertion: Insertion,
|
|
||||||
target_channel: TargetChannel,
|
|
||||||
_p: PhantomData<P>,
|
|
||||||
_s: PhantomData<S>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node_macro::old_node_fn(InsertChannelNode<_P, _S>)]
|
|
||||||
fn insert_channel<
|
fn insert_channel<
|
||||||
// _P is the color of the input image.
|
// _P is the color of the input image.
|
||||||
_P: RGBMut,
|
_P: RGBMut,
|
||||||
|
@ -124,8 +118,9 @@ fn insert_channel<
|
||||||
Input: BitmapMut<Pixel = _P>,
|
Input: BitmapMut<Pixel = _P>,
|
||||||
Insertion: Bitmap<Pixel = _S>,
|
Insertion: Bitmap<Pixel = _S>,
|
||||||
>(
|
>(
|
||||||
mut image: Input,
|
_: impl Ctx,
|
||||||
insertion: Insertion,
|
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||||
|
#[implementations(ImageFrameTable<Color>)] insertion: Insertion,
|
||||||
target_channel: RedGreenBlue,
|
target_channel: RedGreenBlue,
|
||||||
) -> Input
|
) -> Input
|
||||||
where
|
where
|
||||||
|
@ -154,15 +149,60 @@ where
|
||||||
|
|
||||||
image
|
image
|
||||||
}
|
}
|
||||||
|
#[node_macro::node]
|
||||||
|
fn combine_channels<
|
||||||
|
// _P is the color of the input image.
|
||||||
|
_P: RGBMut + AlphaMut,
|
||||||
|
_S: Pixel + Luminance,
|
||||||
|
// Input image
|
||||||
|
Input: BitmapMut<Pixel = _P>,
|
||||||
|
Red: Bitmap<Pixel = _S>,
|
||||||
|
Green: Bitmap<Pixel = _S>,
|
||||||
|
Blue: Bitmap<Pixel = _S>,
|
||||||
|
Alpha: Bitmap<Pixel = _S>,
|
||||||
|
>(
|
||||||
|
_: impl Ctx,
|
||||||
|
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||||
|
#[implementations(ImageFrameTable<Color>)] red: Red,
|
||||||
|
#[implementations(ImageFrameTable<Color>)] green: Green,
|
||||||
|
#[implementations(ImageFrameTable<Color>)] blue: Blue,
|
||||||
|
#[implementations(ImageFrameTable<Color>)] alpha: Alpha,
|
||||||
|
) -> Input
|
||||||
|
where
|
||||||
|
_P::ColorChannel: Linear,
|
||||||
|
{
|
||||||
|
let dimensions = [red.dim(), green.dim(), blue.dim(), alpha.dim()];
|
||||||
|
if dimensions.iter().all(|&(x, _)| x == 0) {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
if dimensions.iter().any(|&(x, y)| x != image.width() || y != image.height()) {
|
||||||
pub struct MaskImageNode<P, S, Stencil> {
|
log::warn!("Stencil and image have different sizes. This is not supported.");
|
||||||
stencil: Stencil,
|
return image;
|
||||||
_p: PhantomData<P>,
|
}
|
||||||
_s: PhantomData<S>,
|
|
||||||
|
for y in 0..image.height() {
|
||||||
|
for x in 0..image.width() {
|
||||||
|
let image_pixel = image.get_pixel_mut(x, y).unwrap();
|
||||||
|
if let Some(r) = red.get_pixel(x, y) {
|
||||||
|
image_pixel.set_red(r.l().cast_linear_channel());
|
||||||
|
}
|
||||||
|
if let Some(g) = green.get_pixel(x, y) {
|
||||||
|
image_pixel.set_green(g.l().cast_linear_channel());
|
||||||
|
}
|
||||||
|
if let Some(b) = blue.get_pixel(x, y) {
|
||||||
|
image_pixel.set_blue(b.l().cast_linear_channel());
|
||||||
|
}
|
||||||
|
if let Some(a) = alpha.get_pixel(x, y) {
|
||||||
|
image_pixel.set_alpha(a.l().cast_linear_channel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::old_node_fn(MaskImageNode<_P, _S>)]
|
#[node_macro::node()]
|
||||||
fn mask_image<
|
fn mask_image<
|
||||||
// _P is the color of the input image. It must have an alpha channel because that is going to
|
// _P is the color of the input image. It must have an alpha channel because that is going to
|
||||||
// be modified by the mask
|
// be modified by the mask
|
||||||
|
@ -175,8 +215,9 @@ fn mask_image<
|
||||||
// Stencil
|
// Stencil
|
||||||
Stencil: Transform + Sample<Pixel = _S>,
|
Stencil: Transform + Sample<Pixel = _S>,
|
||||||
>(
|
>(
|
||||||
mut image: Input,
|
_: impl Ctx,
|
||||||
stencil: Stencil,
|
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||||
|
#[implementations(ImageFrameTable<Color>)] stencil: Stencil,
|
||||||
) -> Input {
|
) -> Input {
|
||||||
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
|
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
|
||||||
let mask_size = stencil.transform().decompose_scale();
|
let mask_size = stencil.transform().decompose_scale();
|
||||||
|
@ -207,17 +248,17 @@ fn mask_image<
|
||||||
image
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
// #[derive(Debug, Clone, Copy)]
|
||||||
pub struct BlendImageTupleNode<P, Fg, MapFn> {
|
// pub struct BlendImageTupleNode<P, Fg, MapFn> {
|
||||||
map_fn: MapFn,
|
// map_fn: MapFn,
|
||||||
_p: PhantomData<P>,
|
// _p: PhantomData<P>,
|
||||||
_fg: PhantomData<Fg>,
|
// _fg: PhantomData<Fg>,
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[node_macro::old_node_fn(BlendImageTupleNode<_P, _Fg>)]
|
#[node_macro::node(skip_impl)]
|
||||||
fn blend_image_tuple<_P: Alpha + Pixel + Debug, MapFn, _Fg: Sample<Pixel = _P> + Transform>(images: (ImageFrame<_P>, _Fg), map_fn: &'input MapFn) -> ImageFrame<_P>
|
async fn blend_image_tuple<_P: Alpha + Pixel + Debug + Send, MapFn, _Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n>(images: (ImageFrame<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrame<_P>
|
||||||
where
|
where
|
||||||
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input + Clone,
|
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone,
|
||||||
{
|
{
|
||||||
let (background, foreground) = images;
|
let (background, foreground) = images;
|
||||||
|
|
||||||
|
@ -319,7 +360,7 @@ fn extend_image_to_bounds(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFr
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug: Raster"))]
|
#[node_macro::node(category("Debug: Raster"))]
|
||||||
fn empty_image<P: Pixel>(_: (), transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame<P> {
|
fn empty_image<P: Pixel>(_: impl Ctx, transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame<P> {
|
||||||
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
|
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
|
||||||
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
|
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
|
||||||
|
|
||||||
|
@ -431,10 +472,10 @@ fn empty_image<P: Pixel>(_: (), transform: DAffine2, #[implementations(Color)] c
|
||||||
// tiling: Tiling: bool,
|
// tiling: Tiling: bool,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[node_macro::node(category("Raster: Generator"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn noise_pattern(
|
fn noise_pattern(
|
||||||
footprint: Footprint,
|
ctx: impl ExtractFootprint + Ctx,
|
||||||
_primary: (),
|
_primary: (),
|
||||||
clip: bool,
|
clip: bool,
|
||||||
seed: u32,
|
seed: u32,
|
||||||
|
@ -452,6 +493,7 @@ fn noise_pattern(
|
||||||
cellular_return_type: CellularReturnType,
|
cellular_return_type: CellularReturnType,
|
||||||
cellular_jitter: f64,
|
cellular_jitter: f64,
|
||||||
) -> ImageFrameTable<Color> {
|
) -> ImageFrameTable<Color> {
|
||||||
|
let footprint = ctx.footprint();
|
||||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||||
|
|
||||||
let mut size = viewport_bounds.size();
|
let mut size = viewport_bounds.size();
|
||||||
|
@ -585,8 +627,9 @@ fn noise_pattern(
|
||||||
ImageFrameTable::new(result)
|
ImageFrameTable::new(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Raster: Generator"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
fn mandelbrot(footprint: Footprint) -> ImageFrameTable<Color> {
|
fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
|
||||||
|
let footprint = ctx.footprint();
|
||||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||||
|
|
||||||
let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox();
|
let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox();
|
||||||
|
|
|
@ -3,10 +3,11 @@ use crate::vector::{VectorData, VectorDataTable};
|
||||||
use graph_craft::wasm_application_io::WasmEditorApi;
|
use graph_craft::wasm_application_io::WasmEditorApi;
|
||||||
use graphene_core::text::TypesettingConfig;
|
use graphene_core::text::TypesettingConfig;
|
||||||
pub use graphene_core::text::{bounding_box, load_face, to_path, Font, FontCache};
|
pub use graphene_core::text::{bounding_box, load_face, to_path, Font, FontCache};
|
||||||
|
use graphene_core::Ctx;
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
fn text<'i: 'n>(
|
fn text<'i: 'n>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
editor: &'i WasmEditorApi,
|
editor: &'i WasmEditorApi,
|
||||||
text: String,
|
text: String,
|
||||||
font_name: Font,
|
font_name: Font,
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use crate::transform::Footprint;
|
|
||||||
|
|
||||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||||
use graphene_core::vector::misc::BooleanOperation;
|
use graphene_core::vector::misc::BooleanOperation;
|
||||||
use graphene_core::vector::style::Fill;
|
use graphene_core::vector::style::Fill;
|
||||||
pub use graphene_core::vector::*;
|
pub use graphene_core::vector::*;
|
||||||
use graphene_core::{transform::Transform, GraphicGroup};
|
use graphene_core::{transform::Transform, GraphicGroup};
|
||||||
use graphene_core::{Color, GraphicElement, GraphicGroupTable};
|
use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable};
|
||||||
pub use path_bool as path_bool_lib;
|
pub use path_bool as path_bool_lib;
|
||||||
use path_bool::{FillRule, PathBooleanOperation};
|
use path_bool::{FillRule, PathBooleanOperation};
|
||||||
|
|
||||||
|
@ -13,22 +11,7 @@ use glam::{DAffine2, DVec2};
|
||||||
use std::ops::Mul;
|
use std::ops::Mul;
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn boolean_operation<F: 'n + Send>(
|
async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable {
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> GraphicGroupTable,
|
|
||||||
Footprint -> GraphicGroupTable,
|
|
||||||
)]
|
|
||||||
group_of_paths: impl Node<F, Output = GraphicGroupTable>,
|
|
||||||
operation: BooleanOperation,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
let group_of_paths = group_of_paths.eval(footprint).await;
|
|
||||||
let group_of_paths = group_of_paths.one_item();
|
|
||||||
|
|
||||||
fn vector_from_image<T: Transform>(image_frame: T) -> VectorData {
|
fn vector_from_image<T: Transform>(image_frame: T) -> VectorData {
|
||||||
let corner1 = DVec2::ZERO;
|
let corner1 = DVec2::ZERO;
|
||||||
let corner2 = DVec2::new(1., 1.);
|
let corner2 = DVec2::new(1., 1.);
|
||||||
|
@ -193,6 +176,7 @@ async fn boolean_operation<F: 'n + Send>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let group_of_paths = group_of_paths.one_item();
|
||||||
// The first index is the bottom of the stack
|
// The first index is the bottom of the stack
|
||||||
let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(group_of_paths), operation);
|
let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(group_of_paths), operation);
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,7 @@ use graphene_core::renderer::RenderMetadata;
|
||||||
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
|
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
|
||||||
use graphene_core::transform::Footprint;
|
use graphene_core::transform::Footprint;
|
||||||
use graphene_core::vector::VectorDataTable;
|
use graphene_core::vector::VectorDataTable;
|
||||||
use graphene_core::GraphicGroupTable;
|
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend};
|
||||||
use graphene_core::{Color, WasmNotSend};
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
@ -27,7 +26,7 @@ use wasm_bindgen::JsCast;
|
||||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
||||||
|
|
||||||
#[node_macro::node(category("Debug: GPU"))]
|
#[node_macro::node(category("Debug: GPU"))]
|
||||||
async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
|
async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
|
||||||
Arc::new(editor.application_io.as_ref().unwrap().create_window())
|
Arc::new(editor.application_io.as_ref().unwrap().create_window())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc<WasmSur
|
||||||
// #[node_macro::node(category("Debug: GPU"))]
|
// #[node_macro::node(category("Debug: GPU"))]
|
||||||
// #[cfg(target_arch = "wasm32")]
|
// #[cfg(target_arch = "wasm32")]
|
||||||
// async fn draw_image_frame(
|
// async fn draw_image_frame(
|
||||||
// _: (),
|
// _: impl Ctx,
|
||||||
// image: ImageFrameTable<graphene_core::raster::SRGBA8>,
|
// image: ImageFrameTable<graphene_core::raster::SRGBA8>,
|
||||||
// surface_handle: Arc<WasmSurfaceHandle>,
|
// surface_handle: Arc<WasmSurfaceHandle>,
|
||||||
// ) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
|
// ) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
|
||||||
|
@ -60,7 +59,7 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc<WasmSur
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[node_macro::node(category("Network"))]
|
#[node_macro::node(category("Network"))]
|
||||||
async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> {
|
async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> {
|
||||||
let Some(api) = editor.application_io.as_ref() else {
|
let Some(api) = editor.application_io.as_ref() else {
|
||||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||||
};
|
};
|
||||||
|
@ -74,8 +73,8 @@ async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] edito
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Raster"))]
|
#[node_macro::node(category("Network"))]
|
||||||
fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrameTable<Color> {
|
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
|
||||||
let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
|
let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
|
||||||
return ImageFrameTable::default();
|
return ImageFrameTable::default();
|
||||||
};
|
};
|
||||||
|
@ -156,13 +155,13 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>(
|
async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Footprint -> VectorDataTable,
|
VectorDataTable,
|
||||||
Footprint -> ImageFrameTable<Color>,
|
ImageFrameTable<Color>,
|
||||||
Footprint -> GraphicGroupTable,
|
GraphicGroupTable,
|
||||||
)]
|
)]
|
||||||
data: impl Node<Footprint, Output = T>,
|
mut data: T,
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
|
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
|
||||||
) -> ImageFrameTable<Color> {
|
) -> ImageFrameTable<Color> {
|
||||||
|
@ -171,7 +170,6 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
|
||||||
return ImageFrameTable::default();
|
return ImageFrameTable::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data = data.eval(footprint).await;
|
|
||||||
let mut render = SvgRender::new();
|
let mut render = SvgRender::new();
|
||||||
let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox();
|
let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox();
|
||||||
let size = aabb.size();
|
let size = aabb.size();
|
||||||
|
@ -218,31 +216,34 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
|
async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
|
||||||
render_config: RenderConfig,
|
render_config: RenderConfig,
|
||||||
editor_api: &'a WasmEditorApi,
|
editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Footprint -> VectorDataTable,
|
Context -> VectorDataTable,
|
||||||
Footprint -> ImageFrameTable<Color>,
|
Context -> ImageFrameTable<Color>,
|
||||||
Footprint -> GraphicGroupTable,
|
Context -> GraphicGroupTable,
|
||||||
Footprint -> graphene_core::Artboard,
|
Context -> graphene_core::Artboard,
|
||||||
Footprint -> graphene_core::ArtboardGroup,
|
Context -> graphene_core::ArtboardGroup,
|
||||||
Footprint -> Option<Color>,
|
Context -> Option<Color>,
|
||||||
Footprint -> Vec<Color>,
|
Context -> Vec<Color>,
|
||||||
Footprint -> bool,
|
Context -> bool,
|
||||||
Footprint -> f32,
|
Context -> f32,
|
||||||
Footprint -> f64,
|
Context -> f64,
|
||||||
Footprint -> String,
|
Context -> String,
|
||||||
)]
|
)]
|
||||||
data: impl Node<Footprint, Output = T>,
|
data: impl Node<Context<'static>, Output = T>,
|
||||||
_surface_handle: impl Node<(), Output = Option<wgpu_executor::WgpuSurface>>,
|
_surface_handle: impl Node<Context<'static>, Output = Option<wgpu_executor::WgpuSurface>>,
|
||||||
) -> RenderOutput {
|
) -> RenderOutput {
|
||||||
let footprint = render_config.viewport;
|
let footprint = render_config.viewport;
|
||||||
|
let ctx = OwnedContextImpl::default().with_footprint(footprint).into_context();
|
||||||
|
ctx.footprint();
|
||||||
|
|
||||||
let RenderConfig { hide_artboards, for_export, .. } = render_config;
|
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 render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
|
||||||
|
|
||||||
let data = data.eval(footprint).await;
|
let data = data.eval(ctx.clone()).await;
|
||||||
|
let editor_api = editor_api.eval(ctx.clone()).await;
|
||||||
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
|
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
|
||||||
let surface_handle = _surface_handle.eval(()).await;
|
let surface_handle = _surface_handle.eval(ctx.clone()).await;
|
||||||
let use_vello = editor_api.editor_preferences.use_vello();
|
let use_vello = editor_api.editor_preferences.use_vello();
|
||||||
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
|
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
|
||||||
let use_vello = use_vello && surface_handle.is_some();
|
let use_vello = use_vello && surface_handle.is_some();
|
||||||
|
|
|
@ -2,74 +2,29 @@ use dyn_any::StaticType;
|
||||||
use graph_craft::document::value::RenderOutput;
|
use graph_craft::document::value::RenderOutput;
|
||||||
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
|
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
|
||||||
use graphene_core::fn_type;
|
use graphene_core::fn_type;
|
||||||
use graphene_core::ops::IdentityNode;
|
|
||||||
use graphene_core::raster::color::Color;
|
use graphene_core::raster::color::Color;
|
||||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
use graphene_core::raster::image::ImageFrameTable;
|
||||||
use graphene_core::raster::*;
|
use graphene_core::raster::*;
|
||||||
use graphene_core::structural::Then;
|
|
||||||
use graphene_core::transform::Footprint;
|
|
||||||
use graphene_core::value::{ClonedNode, ValueNode};
|
use graphene_core::value::{ClonedNode, ValueNode};
|
||||||
use graphene_core::vector::VectorDataTable;
|
use graphene_core::vector::VectorDataTable;
|
||||||
use graphene_core::{concrete, generic, Artboard, GraphicGroupTable};
|
use graphene_core::{concrete, generic, Artboard, GraphicGroupTable};
|
||||||
|
use graphene_core::{fn_type_fut, future};
|
||||||
use graphene_core::{Cow, ProtoNodeIdentifier, Type};
|
use graphene_core::{Cow, ProtoNodeIdentifier, Type};
|
||||||
use graphene_core::{Node, NodeIO, NodeIOTypes};
|
use graphene_core::{Node, NodeIO, NodeIOTypes};
|
||||||
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
|
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
|
||||||
use graphene_std::application_io::TextureFrame;
|
use graphene_std::application_io::TextureFrame;
|
||||||
use graphene_std::raster::*;
|
|
||||||
use graphene_std::wasm_application_io::*;
|
use graphene_std::wasm_application_io::*;
|
||||||
|
use graphene_std::Context;
|
||||||
use graphene_std::{GraphicElement, GraphicGroup};
|
use graphene_std::{GraphicElement, GraphicGroup};
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
use wgpu_executor::{ShaderInputFrame, WgpuExecutor};
|
use wgpu_executor::{ShaderInputFrame, WgpuExecutor};
|
||||||
use wgpu_executor::{WgpuSurface, WindowHandle};
|
use wgpu_executor::{WgpuSurface, WindowHandle};
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2, UVec2};
|
use glam::{DVec2, UVec2};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
macro_rules! construct_node {
|
|
||||||
($args: ident, $path:ty, [$($arg:ty => $type:ty),*]) => { async move {
|
|
||||||
let mut args = $args.clone();
|
|
||||||
args.reverse();
|
|
||||||
let node = <$path>::new($(
|
|
||||||
{
|
|
||||||
let node = graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"));
|
|
||||||
let value = node.eval(()).await;
|
|
||||||
graphene_core::value::ClonedNode::new(value)
|
|
||||||
}
|
|
||||||
),*
|
|
||||||
);
|
|
||||||
node
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! register_node {
|
|
||||||
($path:ty, input: $input:ty, params: [ $($type:ty),*]) => {
|
|
||||||
register_node!($path, input: $input, fn_params: [ $(() => $type),*])
|
|
||||||
};
|
|
||||||
($path:ty, input: $input:ty, fn_params: [ $($arg:ty => $type:ty),*]) => {
|
|
||||||
(
|
|
||||||
ProtoNodeIdentifier::new(stringify!($path)),
|
|
||||||
|args| {
|
|
||||||
Box::pin(async move {
|
|
||||||
let node = construct_node!(args, $path, [$($arg => $type),*]).await;
|
|
||||||
let node = graphene_std::any::FutureWrapperNode::new(node);
|
|
||||||
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);
|
|
||||||
Box::new(any) as TypeErasedBox
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
let node = <$path>::new($(
|
|
||||||
graphene_std::any::PanicNode::<(), $type>::new()
|
|
||||||
),*);
|
|
||||||
let params = vec![$(fn_type!((), $type)),*];
|
|
||||||
let mut node_io = <$path as NodeIO<'_, $input>>::to_node_io(&node, params);
|
|
||||||
node_io.call_argument = concrete!(<$input as StaticType>::Static);
|
|
||||||
node_io
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
macro_rules! async_node {
|
macro_rules! async_node {
|
||||||
// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly)
|
// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly)
|
||||||
// TODO: assign a Pin<Box<dyn Future<Output=T>>> type to the node, which is not what we want for now.
|
// TODO: assign a Pin<Box<dyn Future<Output=T>>> type to the node, which is not what we want for now.
|
||||||
|
@ -95,7 +50,7 @@ macro_rules! async_node {
|
||||||
),*);
|
),*);
|
||||||
// TODO: Propagate the future type through the node graph
|
// 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![$(Type::Fn(Box::new(concrete!(())), Box::new(Type::Future(Box::new(concrete!($type)))))),*];
|
||||||
let params = vec![$(fn_type!($arg, $type)),*];
|
let params = vec![$(fn_type_fut!($arg, $type)),*];
|
||||||
let mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params);
|
let mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params);
|
||||||
node_io.call_argument = concrete!(<$input as StaticType>::Static);
|
node_io.call_argument = concrete!(<$input as StaticType>::Static);
|
||||||
node_io
|
node_io
|
||||||
|
@ -107,11 +62,11 @@ macro_rules! async_node {
|
||||||
// TODO: turn into hashmap
|
// TODO: turn into hashmap
|
||||||
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
|
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
|
||||||
let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
|
let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
|
||||||
(
|
// (
|
||||||
ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
|
// ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
|
||||||
|_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
|
// |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
|
||||||
NodeIOTypes::new(generic!(I), generic!(I), vec![]),
|
// NodeIOTypes::new(generic!(I), generic!(I), vec![]),
|
||||||
),
|
// ),
|
||||||
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<SRGBA8>>, input: ImageFrameTable<Color>, params: []),
|
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<SRGBA8>>, input: ImageFrameTable<Color>, params: []),
|
||||||
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<Color>>, input: ImageFrameTable<SRGBA8>, params: []),
|
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<Color>>, input: ImageFrameTable<SRGBA8>, params: []),
|
||||||
async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: ImageFrameTable<Color>, params: []),
|
async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: ImageFrameTable<Color>, params: []),
|
||||||
|
@ -123,78 +78,31 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: GraphicGroupTable, params: []),
|
async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: GraphicGroupTable, params: []),
|
||||||
async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: VectorDataTable, params: []),
|
async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: VectorDataTable, params: []),
|
||||||
async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: ImageFrameTable<Color>, params: []),
|
async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: ImageFrameTable<Color>, params: []),
|
||||||
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Color>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
|
||||||
register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Color>, RedGreenBlue]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => TextureFrame]),
|
||||||
// register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Luma>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
|
||||||
// register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Luma>, RedGreenBlue]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
|
||||||
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
|
||||||
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]),
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
(
|
(
|
||||||
ProtoNodeIdentifier::new("graphene_std::raster::CombineChannelsNode"),
|
ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)),
|
||||||
|args| {
|
|args| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
use graphene_core::raster::*;
|
let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[0].clone());
|
||||||
use graphene_core::value::*;
|
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(editor_api);
|
||||||
|
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||||
let channel_r: ImageFrameTable<Color> = DowncastBothNode::new(args[0].clone()).eval(()).await;
|
Box::new(any) as TypeErasedBox
|
||||||
let channel_g: ImageFrameTable<Color> = DowncastBothNode::new(args[1].clone()).eval(()).await;
|
|
||||||
let channel_b: ImageFrameTable<Color> = DowncastBothNode::new(args[2].clone()).eval(()).await;
|
|
||||||
let channel_a: ImageFrameTable<Color> = DowncastBothNode::new(args[3].clone()).eval(()).await;
|
|
||||||
|
|
||||||
let insert_r = InsertChannelNode::new(ClonedNode::new(channel_r.clone()), CopiedNode::new(RedGreenBlue::Red));
|
|
||||||
let insert_g = InsertChannelNode::new(ClonedNode::new(channel_g.clone()), CopiedNode::new(RedGreenBlue::Green));
|
|
||||||
let insert_b = InsertChannelNode::new(ClonedNode::new(channel_b.clone()), CopiedNode::new(RedGreenBlue::Blue));
|
|
||||||
let complete_node = insert_r.then(insert_g).then(insert_b);
|
|
||||||
let complete_node = complete_node.then(MaskImageNode::new(ClonedNode::new(channel_a.clone())));
|
|
||||||
|
|
||||||
let channel_r = channel_r.one_item();
|
|
||||||
let channel_g = channel_g.one_item();
|
|
||||||
let channel_b = channel_b.one_item();
|
|
||||||
let channel_a = channel_a.one_item();
|
|
||||||
|
|
||||||
// TODO: Move to FN Node for better performance
|
|
||||||
let (mut transform, mut bounds) = (DAffine2::ZERO, glam::UVec2::ZERO);
|
|
||||||
for image in [channel_a, channel_r, channel_g, channel_b] {
|
|
||||||
if image.image.width() > bounds.x {
|
|
||||||
bounds = glam::UVec2::new(image.image.width(), image.image.height());
|
|
||||||
transform = image.transform;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let empty_image = ImageFrame {
|
|
||||||
image: Image::new(bounds.x, bounds.y, Color::BLACK),
|
|
||||||
transform,
|
|
||||||
alpha_blending: Default::default(),
|
|
||||||
};
|
|
||||||
let empty_image = ImageFrameTable::new(empty_image);
|
|
||||||
let final_image = ClonedNode::new(empty_image).then(complete_node);
|
|
||||||
let final_image = FutureWrapperNode::new(final_image);
|
|
||||||
|
|
||||||
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(final_image);
|
|
||||||
any.into_type_erased()
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(
|
{
|
||||||
concrete!(()),
|
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, dyn_any::DynFuture<'static, &WasmEditorApi>>::new());
|
||||||
concrete!(ImageFrameTable<Color>),
|
let params = vec![fn_type_fut!(Context, &WasmEditorApi)];
|
||||||
vec![
|
let mut node_io = <wgpu_executor::CreateGpuSurfaceNode<_> as NodeIO<'_, Context>>::to_async_node_io(&node, params);
|
||||||
fn_type!(ImageFrameTable<Color>),
|
node_io.call_argument = concrete!(<Context as StaticType>::Static);
|
||||||
fn_type!(ImageFrameTable<Color>),
|
node_io
|
||||||
fn_type!(ImageFrameTable<Color>),
|
},
|
||||||
fn_type!(ImageFrameTable<Color>),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => ImageFrameTable<Color>]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [ImageFrameTable<Color>]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [TextureFrame]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => VectorDataTable]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroupTable]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => GraphicGroupTable]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => GraphicElement]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => Artboard]),
|
|
||||||
#[cfg(feature = "gpu")]
|
|
||||||
register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: (), params: [&WasmEditorApi]),
|
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
(
|
(
|
||||||
ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"),
|
ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"),
|
||||||
|
@ -202,7 +110,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
|
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
|
||||||
let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone());
|
let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone());
|
||||||
// let document_node = ClonedNode::new(document_node.eval(()));
|
|
||||||
let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api);
|
let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api);
|
||||||
let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
|
let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||||
any.into_type_erased()
|
any.into_type_erased()
|
||||||
|
@ -279,27 +186,27 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
// NodeIOTypes::new(concrete!(ImageFrameTable<Luma>), concrete!(ImageFrameTable<Luma>), vec![fn_type!(graphene_core::raster::curve::Curve)]),
|
// NodeIOTypes::new(concrete!(ImageFrameTable<Luma>), concrete!(ImageFrameTable<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.
|
// TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color.
|
||||||
(
|
// (
|
||||||
ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
|
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
|
||||||
|args| {
|
// |args| {
|
||||||
use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
|
// use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
|
||||||
let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
|
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
|
||||||
Box::pin(async move {
|
// Box::pin(async move {
|
||||||
let curve = ClonedNode::new(curve.eval(()).await);
|
// let curve = ValueNode::new(ClonedNode::new(curve.eval(()).await));
|
||||||
|
|
||||||
let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32));
|
// let generate_curves_node = GenerateCurvesNode::new(FutureWrapperNode::new(curve), FutureWrapperNode::new(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 = graphene_std::raster::MapImageNode::new(FutureWrapperNode::new(ValueNode::new(generate_curves_node.eval(()))));
|
||||||
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
|
// let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
|
||||||
let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
|
// let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
|
||||||
any.into_type_erased()
|
// any.into_type_erased()
|
||||||
})
|
// })
|
||||||
},
|
// },
|
||||||
NodeIOTypes::new(
|
// NodeIOTypes::new(
|
||||||
concrete!(ImageFrameTable<Color>),
|
// concrete!(ImageFrameTable<Color>),
|
||||||
concrete!(ImageFrameTable<Color>),
|
// concrete!(ImageFrameTable<Color>),
|
||||||
vec![fn_type!(graphene_core::raster::curve::Curve)],
|
// vec![fn_type!(graphene_core::raster::curve::Curve)],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
// (
|
// (
|
||||||
// ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"),
|
// ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"),
|
||||||
// |args: Vec<graph_craft::proto::SharedNodeContainer>| {
|
// |args: Vec<graph_craft::proto::SharedNodeContainer>| {
|
||||||
|
@ -337,44 +244,31 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
// ],
|
// ],
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Image<Color>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [VectorDataTable]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ImageFrameTable<Color>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Vec<DVec2>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [WindowHandle]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]),
|
||||||
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ShaderInputFrame]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ShaderInputFrame]),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [wgpu_executor::WgpuSurface]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuSurface]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Option<wgpu_executor::WgpuSurface>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<wgpu_executor::WgpuSurface>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [wgpu_executor::WindowHandle]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WindowHandle]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [graphene_std::SurfaceFrame]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => 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 => VectorDataTable]),
|
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ImageFrameTable<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, fn_params: [Footprint => ShaderInputFrame]),
|
|
||||||
#[cfg(feature = "gpu")]
|
|
||||||
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: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => RenderOutput]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
|
||||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]),
|
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
|
||||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroup]),
|
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroup]),
|
||||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]),
|
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
|
||||||
|
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]),
|
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ShaderInputFrame]),
|
||||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => WgpuSurface]),
|
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
|
||||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => Option<WgpuSurface>]),
|
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
|
||||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]),
|
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => TextureFrame]),
|
||||||
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
|
|
||||||
];
|
];
|
||||||
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
||||||
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
|
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use graph_craft::{
|
use graph_craft::concrete;
|
||||||
concrete,
|
use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
||||||
document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork},
|
use graph_craft::generic;
|
||||||
generic,
|
use graph_craft::wasm_application_io::WasmEditorApi;
|
||||||
wasm_application_io::WasmEditorApi,
|
use graph_craft::ProtoNodeIdentifier;
|
||||||
ProtoNodeIdentifier,
|
use graphene_std::uuid::NodeId;
|
||||||
};
|
use graphene_std::Context;
|
||||||
use graphene_std::{transform::Footprint, uuid::NodeId};
|
|
||||||
|
|
||||||
// TODO: this is copy pasta from the editor (and does get out of sync)
|
// TODO: this is copy pasta from the editor (and does get out of sync)
|
||||||
pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEditorApi>) -> NodeNetwork {
|
pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEditorApi>) -> NodeNetwork {
|
||||||
|
@ -27,18 +26,18 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
||||||
|
|
||||||
let render_node = graph_craft::document::DocumentNode {
|
let render_node = graph_craft::document::DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)],
|
||||||
implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
exports: vec![NodeInput::node(NodeId(2), 0)],
|
exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::scope("editor-api")],
|
inputs: vec![NodeInput::scope("editor-api")],
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(())),
|
manual_composition: Some(concrete!(Context)),
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
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()
|
..Default::default()
|
||||||
|
@ -48,7 +47,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
||||||
manual_composition: Some(concrete!(graphene_std::application_io::RenderConfig)),
|
manual_composition: Some(concrete!(graphene_std::application_io::RenderConfig)),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::scope("editor-api"),
|
NodeInput::scope("editor-api"),
|
||||||
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))), 0),
|
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(generic!(T))), 0),
|
||||||
NodeInput::node(NodeId(1), 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")),
|
||||||
|
|
|
@ -37,7 +37,6 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||||
|
|
||||||
let struct_generics: Vec<Ident> = fields.iter().enumerate().map(|(i, _)| format_ident!("Node{}", i)).collect();
|
let struct_generics: Vec<Ident> = fields.iter().enumerate().map(|(i, _)| format_ident!("Node{}", i)).collect();
|
||||||
let input_ident = &input.pat_ident;
|
let input_ident = &input.pat_ident;
|
||||||
let input_type = &input.ty;
|
|
||||||
|
|
||||||
let field_idents: Vec<_> = fields
|
let field_idents: Vec<_> = fields
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -78,12 +77,14 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut future_idents = Vec::new();
|
||||||
|
|
||||||
let field_types: Vec<_> = fields
|
let field_types: Vec<_> = fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|field| match field {
|
.map(|field| match field {
|
||||||
ParsedField::Regular { ty, .. } => ty.clone(),
|
ParsedField::Regular { ty, .. } => ty.clone(),
|
||||||
ParsedField::Node { output_type, input_type, .. } => match parsed.is_async {
|
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>),
|
true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = impl core::future::Future<Output=#output_type>>),
|
||||||
false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>),
|
false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -164,7 +165,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||||
let eval_args = fields.iter().map(|field| match field {
|
let eval_args = fields.iter().map(|field| match field {
|
||||||
ParsedField::Regular { pat_ident, .. } => {
|
ParsedField::Regular { pat_ident, .. } => {
|
||||||
let name = &pat_ident.ident;
|
let name = &pat_ident.ident;
|
||||||
quote! { let #name = self.#name.eval(()); }
|
quote! { let #name = self.#name.eval(__input.clone()).await; }
|
||||||
}
|
}
|
||||||
ParsedField::Node { pat_ident, .. } => {
|
ParsedField::Node { pat_ident, .. } => {
|
||||||
let name = &pat_ident.ident;
|
let name = &pat_ident.ident;
|
||||||
|
@ -181,16 +182,32 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||||
});
|
});
|
||||||
let all_implementation_types = all_implementation_types.chain(input.implementations.iter().cloned());
|
let all_implementation_types = all_implementation_types.chain(input.implementations.iter().cloned());
|
||||||
|
|
||||||
|
let input_type = &parsed.input.ty;
|
||||||
let mut clauses = Vec::new();
|
let mut clauses = Vec::new();
|
||||||
for (field, name) in fields.iter().zip(struct_generics.iter()) {
|
for (field, name) in fields.iter().zip(struct_generics.iter()) {
|
||||||
clauses.push(match (field, *is_async) {
|
clauses.push(match (field, *is_async) {
|
||||||
(ParsedField::Regular { ty, .. }, _) => quote!(#name: #graphene_core::Node<'n, (), Output = #ty> ),
|
(ParsedField::Regular { ty, .. }, _) => {
|
||||||
(ParsedField::Node { input_type, output_type, .. }, false) => {
|
let all_lifetime_ty = substitute_lifetimes(ty.clone(), "all");
|
||||||
quote!(for<'all_input> #name: #graphene_core::Node<'all_input, #input_type, Output = #output_type> + #graphene_core::WasmNotSync)
|
let id = future_idents.len();
|
||||||
|
let fut_ident = format_ident!("F{}", id);
|
||||||
|
future_idents.push(fut_ident.clone());
|
||||||
|
quote!(
|
||||||
|
#fut_ident: core::future::Future<Output = #ty> + #graphene_core::WasmNotSend + 'n,
|
||||||
|
for<'all> #all_lifetime_ty: #graphene_core::WasmNotSend,
|
||||||
|
#name: #graphene_core::Node<'n, #input_type, Output = #fut_ident> + #graphene_core::WasmNotSync
|
||||||
|
)
|
||||||
}
|
}
|
||||||
(ParsedField::Node { input_type, output_type, .. }, true) => {
|
(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 id = future_idents.len();
|
||||||
|
let fut_ident = format_ident!("F{}", id);
|
||||||
|
future_idents.push(fut_ident.clone());
|
||||||
|
|
||||||
|
quote!(
|
||||||
|
#fut_ident: core::future::Future<Output = #output_type> + #graphene_core::WasmNotSend + 'n,
|
||||||
|
#name: #graphene_core::Node<'n, #input_type, Output = #fut_ident > + #graphene_core::WasmNotSync
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
(ParsedField::Node { .. }, false) => unreachable!(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let where_clause = where_clause.clone().unwrap_or(WhereClause {
|
let where_clause = where_clause.clone().unwrap_or(WhereClause {
|
||||||
|
@ -210,24 +227,16 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||||
});
|
});
|
||||||
|
|
||||||
let async_keyword = is_async.then(|| quote!(async));
|
let async_keyword = is_async.then(|| quote!(async));
|
||||||
|
let await_keyword = is_async.then(|| quote!(.await));
|
||||||
|
|
||||||
let eval_impl = if *is_async {
|
let eval_impl = quote! {
|
||||||
quote! {
|
type Output = #graphene_core::registry::DynFuture<'n, #output_type>;
|
||||||
type Output = #graphene_core::registry::DynFuture<'n, #output_type>;
|
#[inline]
|
||||||
#[inline]
|
fn eval(&'n self, __input: #input_type) -> Self::Output {
|
||||||
fn eval(&'n self, __input: #input_type) -> Self::Output {
|
Box::pin(async move {
|
||||||
#(#eval_args)*
|
#(#eval_args)*
|
||||||
Box::pin(self::#fn_name(__input #(, #field_names)*))
|
self::#fn_name(__input #(, #field_names)*) #await_keyword
|
||||||
}
|
})
|
||||||
}
|
|
||||||
} 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 {
|
let path = match parsed.attributes.path {
|
||||||
|
@ -245,10 +254,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||||
/// Underlying implementation for [#struct_name]
|
/// Underlying implementation for [#struct_name]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[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
|
pub(crate) #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
|
||||||
|
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl<'n, #(#fn_generics,)* #(#struct_generics,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*>
|
impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*>
|
||||||
#struct_where_clause
|
#struct_where_clause
|
||||||
{
|
{
|
||||||
#eval_impl
|
#eval_impl
|
||||||
|
@ -260,7 +269,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||||
mod #mod_name {
|
mod #mod_name {
|
||||||
use super::*;
|
use super::*;
|
||||||
use #graphene_core as gcore;
|
use #graphene_core as gcore;
|
||||||
use gcore::{Node, NodeIOTypes, concrete, fn_type, future, ProtoNodeIdentifier, WasmNotSync, NodeIO};
|
use gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO};
|
||||||
use gcore::value::ClonedNode;
|
use gcore::value::ClonedNode;
|
||||||
use gcore::ops::TypeNode;
|
use gcore::ops::TypeNode;
|
||||||
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
|
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
|
||||||
|
@ -323,7 +332,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut constructors = Vec::new();
|
let mut constructors = Vec::new();
|
||||||
let unit = parse_quote!(());
|
let unit = parse_quote!(gcore::Context);
|
||||||
let parameter_types: Vec<_> = parsed
|
let parameter_types: Vec<_> = parsed
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -331,9 +340,9 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
||||||
match field {
|
match field {
|
||||||
ParsedField::Regular { implementations, ty, .. } => {
|
ParsedField::Regular { implementations, ty, .. } => {
|
||||||
if !implementations.is_empty() {
|
if !implementations.is_empty() {
|
||||||
implementations.iter().map(|ty| (&unit, ty, false)).collect()
|
implementations.iter().map(|ty| (&unit, ty)).collect()
|
||||||
} else {
|
} else {
|
||||||
vec![(&unit, ty, false)]
|
vec![(&unit, ty)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParsedField::Node {
|
ParsedField::Node {
|
||||||
|
@ -343,20 +352,19 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if !implementations.is_empty() {
|
if !implementations.is_empty() {
|
||||||
implementations.iter().map(|impl_| (&impl_.input, &impl_.output, true)).collect()
|
implementations.iter().map(|impl_| (&impl_.input, &impl_.output)).collect()
|
||||||
} else {
|
} else {
|
||||||
vec![(input_type, output_type, true)]
|
vec![(input_type, output_type)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(input, out, node)| (substitute_lifetimes(input.clone()), substitute_lifetimes(out.clone()), node))
|
.map(|(input, out)| (substitute_lifetimes(input.clone(), "_"), substitute_lifetimes(out.clone(), "_")))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let max_implementations = parameter_types.iter().map(|x| x.len()).chain([parsed.input.implementations.len().max(1)]).max();
|
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) {
|
for i in 0..max_implementations.unwrap_or(0) {
|
||||||
let mut temp_constructors = Vec::new();
|
let mut temp_constructors = Vec::new();
|
||||||
|
@ -365,38 +373,24 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
||||||
|
|
||||||
for (j, types) in parameter_types.iter().enumerate() {
|
for (j, types) in parameter_types.iter().enumerate() {
|
||||||
let field_name = field_names[j];
|
let field_name = field_names[j];
|
||||||
let (input_type, output_type, impl_node) = &types[i.min(types.len() - 1)];
|
let (input_type, output_type) = &types[i.min(types.len() - 1)];
|
||||||
|
|
||||||
let node = matches!(parsed.fields[j], ParsedField::Node { .. });
|
let node = matches!(parsed.fields[j], ParsedField::Node { .. });
|
||||||
|
|
||||||
let downcast_node = quote!(
|
let downcast_node = quote!(
|
||||||
let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone());
|
let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone());
|
||||||
);
|
);
|
||||||
temp_constructors.push(if node {
|
if node && !parsed.is_async {
|
||||||
if !parsed.is_async {
|
return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters"));
|
||||||
return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters"));
|
}
|
||||||
}
|
temp_constructors.push(downcast_node);
|
||||||
downcast_node
|
temp_node_io.push(quote!(fn_type_fut!(#input_type, #output_type, alias: #output_type)));
|
||||||
} else {
|
panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>));
|
||||||
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() {
|
let input_type = match parsed.input.implementations.is_empty() {
|
||||||
true => parsed.input.ty.clone(),
|
true => parsed.input.ty.clone(),
|
||||||
false => parsed.input.implementations[i.min(parsed.input.implementations.len() - 1)].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!(
|
constructors.push(quote!(
|
||||||
(
|
(
|
||||||
|args| {
|
|args| {
|
||||||
|
@ -404,14 +398,13 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
||||||
#(#temp_constructors;)*
|
#(#temp_constructors;)*
|
||||||
let node = #struct_name::new(#(#field_names,)*);
|
let node = #struct_name::new(#(#field_names,)*);
|
||||||
// try polling futures
|
// try polling futures
|
||||||
#future_node
|
|
||||||
let any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node);
|
let any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node);
|
||||||
Box::new(any) as TypeErasedBox<'_>
|
Box::new(any) as TypeErasedBox<'_>
|
||||||
})
|
})
|
||||||
}, {
|
}, {
|
||||||
let node = #struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*);
|
let node = #struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*);
|
||||||
let params = vec![#(#temp_node_io,)*];
|
let params = vec![#(#temp_node_io,)*];
|
||||||
let mut node_io = NodeIO::<'_, #input_type>::#node_io(&node, params);
|
let mut node_io = NodeIO::<'_, #input_type>::to_async_node_io(&node, params);
|
||||||
node_io
|
node_io
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -443,11 +436,11 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
||||||
|
|
||||||
use syn::{visit_mut::VisitMut, GenericArgument, Lifetime, Type};
|
use syn::{visit_mut::VisitMut, GenericArgument, Lifetime, Type};
|
||||||
|
|
||||||
struct LifetimeReplacer;
|
struct LifetimeReplacer(&'static str);
|
||||||
|
|
||||||
impl VisitMut for LifetimeReplacer {
|
impl VisitMut for LifetimeReplacer {
|
||||||
fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
|
fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
|
||||||
lifetime.ident = syn::Ident::new("_", lifetime.ident.span());
|
lifetime.ident = syn::Ident::new(self.0, lifetime.ident.span());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_type_mut(&mut self, ty: &mut Type) {
|
fn visit_type_mut(&mut self, ty: &mut Type) {
|
||||||
|
@ -472,7 +465,7 @@ impl VisitMut for LifetimeReplacer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn substitute_lifetimes(mut ty: Type) -> Type {
|
fn substitute_lifetimes(mut ty: Type, lifetime: &'static str) -> Type {
|
||||||
LifetimeReplacer.visit_type_mut(&mut ty);
|
LifetimeReplacer(lifetime).visit_type_mut(&mut ty);
|
||||||
ty
|
ty
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,11 @@ use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{format_ident, ToTokens};
|
use quote::{format_ident, ToTokens};
|
||||||
use syn::parse::{Parse, ParseStream, Parser};
|
use syn::parse::{Parse, ParseStream, Parser};
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
use syn::token::{Comma, RArrow};
|
use syn::token::{Comma, RArrow};
|
||||||
use syn::{AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, WhereClause};
|
use syn::{
|
||||||
|
parse_quote, AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, WhereClause,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::codegen::generate_node_code;
|
use crate::codegen::generate_node_code;
|
||||||
|
|
||||||
|
@ -548,10 +551,12 @@ fn extract_attribute<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Attri
|
||||||
// Modify the new_node_fn function to use the code generation
|
// Modify the new_node_fn function to use the code generation
|
||||||
pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
|
pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
|
||||||
let parse_result = parse_node_fn(attr, item.clone());
|
let parse_result = parse_node_fn(attr, item.clone());
|
||||||
let Ok(parsed_node) = parse_result else {
|
let Ok(mut parsed_node) = parse_result else {
|
||||||
let e = parse_result.unwrap_err();
|
let e = parse_result.unwrap_err();
|
||||||
return Error::new(e.span(), format!("Failed to parse node function: {e}")).to_compile_error();
|
return Error::new(e.span(), format!("Failed to parse node function: {e}")).to_compile_error();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
parsed_node.replace_impl_trait_in_input();
|
||||||
if let Err(e) = crate::validation::validate_node_fn(&parsed_node) {
|
if let Err(e) = crate::validation::validate_node_fn(&parsed_node) {
|
||||||
return Error::new(e.span(), format!("Validation Error:\n{e}")).to_compile_error();
|
return Error::new(e.span(), format!("Validation Error:\n{e}")).to_compile_error();
|
||||||
}
|
}
|
||||||
|
@ -564,6 +569,31 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ParsedNodeFn {
|
||||||
|
fn replace_impl_trait_in_input(&mut self) {
|
||||||
|
if let Type::ImplTrait(impl_trait) = self.input.ty.clone() {
|
||||||
|
let ident = Ident::new("_Input", impl_trait.span());
|
||||||
|
let mut bounds = impl_trait.bounds;
|
||||||
|
bounds.push(parse_quote!('n));
|
||||||
|
self.fn_generics.push(GenericParam::Type(TypeParam {
|
||||||
|
attrs: Default::default(),
|
||||||
|
ident: ident.clone(),
|
||||||
|
colon_token: Some(Default::default()),
|
||||||
|
bounds,
|
||||||
|
eq_token: None,
|
||||||
|
default: None,
|
||||||
|
}));
|
||||||
|
self.input.ty = parse_quote!(#ident);
|
||||||
|
if self.input.implementations.is_empty() {
|
||||||
|
self.input.implementations.push(parse_quote!(gcore::Context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.input.pat_ident.ident == "_" {
|
||||||
|
self.input.pat_ident.ident = Ident::new("__ctx", self.input.pat_ident.ident.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -774,7 +804,7 @@ mod tests {
|
||||||
let attr = quote!(category("Vector: Shape"));
|
let attr = quote!(category("Vector: Shape"));
|
||||||
let input = quote!(
|
let input = quote!(
|
||||||
/// Test
|
/// Test
|
||||||
fn circle(_: (), #[default(50.)] radius: f64) -> VectorData {
|
fn circle(_: impl Ctx, #[default(50.)] radius: f64) -> VectorData {
|
||||||
// Implementation details...
|
// Implementation details...
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -795,7 +825,7 @@ mod tests {
|
||||||
where_clause: None,
|
where_clause: None,
|
||||||
input: Input {
|
input: Input {
|
||||||
pat_ident: pat_ident("_"),
|
pat_ident: pat_ident("_"),
|
||||||
ty: parse_quote!(()),
|
ty: parse_quote!(impl Ctx),
|
||||||
implementations: Punctuated::new(),
|
implementations: Punctuated::new(),
|
||||||
},
|
},
|
||||||
output_type: parse_quote!(VectorData),
|
output_type: parse_quote!(VectorData),
|
||||||
|
|
|
@ -6,9 +6,11 @@ pub use executor::GpuExecutor;
|
||||||
|
|
||||||
use dyn_any::{DynAny, StaticType};
|
use dyn_any::{DynAny, StaticType};
|
||||||
use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer};
|
use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer};
|
||||||
use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle};
|
use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle, TextureFrame};
|
||||||
|
use graphene_core::raster::image::ImageFrameTable;
|
||||||
|
use graphene_core::raster::{Image, SRGBA8};
|
||||||
use graphene_core::transform::{Footprint, Transform};
|
use graphene_core::transform::{Footprint, Transform};
|
||||||
use graphene_core::{Color, Cow, Node, SurfaceFrame, Type};
|
use graphene_core::{Color, Cow, Ctx, ExtractFootprint, Node, SurfaceFrame, Type};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
|
@ -823,12 +825,12 @@ impl<T> ShaderInputNode<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn uniform<'a: 'n, T: ToUniformBuffer + Send + 'n>(_: (), #[implementations(f32, DAffine2)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput {
|
async fn uniform<'a: 'n, T: ToUniformBuffer + Send + 'n>(_: impl Ctx, #[implementations(f32, DAffine2)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput {
|
||||||
executor.create_uniform_buffer(data).unwrap()
|
executor.create_uniform_buffer(data).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: (), #[implementations(Vec<u8>)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput {
|
async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: impl Ctx, #[implementations(Vec<u8>)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput {
|
||||||
executor
|
executor
|
||||||
.create_storage_buffer(
|
.create_storage_buffer(
|
||||||
data,
|
data,
|
||||||
|
@ -843,18 +845,18 @@ async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: (), #[implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn create_output_buffer<'a: 'n>(_: (), size: usize, executor: &'a WgpuExecutor, ty: Type) -> Arc<WgpuShaderInput> {
|
async fn create_output_buffer<'a: 'n>(_: impl Ctx + 'a, size: usize, executor: &'a WgpuExecutor, ty: Type) -> Arc<WgpuShaderInput> {
|
||||||
Arc::new(executor.create_output_buffer(size, ty, true).unwrap())
|
Arc::new(executor.create_output_buffer(size, ty, true).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(skip_impl)]
|
#[node_macro::node(skip_impl)]
|
||||||
async fn create_compute_pass<'a: 'n>(_: (), layout: PipelineLayout, executor: &'a WgpuExecutor, output: WgpuShaderInput, instances: ComputePassDimensions) -> CommandBuffer {
|
async fn create_compute_pass<'a: 'n>(_: impl Ctx + 'a, layout: PipelineLayout, executor: &'a WgpuExecutor, output: WgpuShaderInput, instances: ComputePassDimensions) -> CommandBuffer {
|
||||||
executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap()
|
executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug: GPU"))]
|
#[node_macro::node(category("Debug: GPU"))]
|
||||||
async fn create_pipeline_layout(
|
async fn create_pipeline_layout(
|
||||||
_: (),
|
_: impl Ctx,
|
||||||
shader: impl Node<(), Output = ShaderHandle>,
|
shader: impl Node<(), Output = ShaderHandle>,
|
||||||
entry_point: String,
|
entry_point: String,
|
||||||
bind_group: impl Node<(), Output = Bindgroup>,
|
bind_group: impl Node<(), Output = Bindgroup>,
|
||||||
|
@ -869,14 +871,14 @@ async fn create_pipeline_layout(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn read_output_buffer<'a: 'n>(_: (), buffer: Arc<WgpuShaderInput>, executor: &'a WgpuExecutor, _compute_pass: ()) -> Vec<u8> {
|
async fn read_output_buffer<'a: 'n>(_: impl Ctx + 'a, buffer: Arc<WgpuShaderInput>, executor: &'a WgpuExecutor, _compute_pass: ()) -> Vec<u8> {
|
||||||
executor.read_output_buffer(buffer).await.unwrap()
|
executor.read_output_buffer(buffer).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
|
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
|
||||||
|
|
||||||
#[node_macro::node(skip_impl)]
|
#[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> {
|
fn create_gpu_surface<'a: 'n, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + 'a + Send + Sync>(_: impl Ctx + 'a, editor_api: &'a EditorApi<Io>) -> Option<WgpuSurface> {
|
||||||
let canvas = editor_api.application_io.as_ref()?.window()?;
|
let canvas = editor_api.application_io.as_ref()?.window()?;
|
||||||
let executor = editor_api.application_io.as_ref()?.gpu_executor()?;
|
let executor = editor_api.application_io.as_ref()?.gpu_executor()?;
|
||||||
Some(Arc::new(executor.create_surface(canvas).ok()?))
|
Some(Arc::new(executor.create_surface(canvas).ok()?))
|
||||||
|
@ -889,7 +891,13 @@ pub struct ShaderInputFrame {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[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 {
|
async fn render_texture<'a: 'n>(
|
||||||
|
_: impl Ctx + 'a,
|
||||||
|
footprint: Footprint,
|
||||||
|
image: impl Node<Footprint, Output = ShaderInputFrame>,
|
||||||
|
surface: Option<WgpuSurface>,
|
||||||
|
executor: &'a WgpuExecutor,
|
||||||
|
) -> SurfaceFrame {
|
||||||
let surface = surface.unwrap();
|
let surface = surface.unwrap();
|
||||||
let surface_id = surface.window_id;
|
let surface_id = surface.window_id;
|
||||||
let image = image.eval(footprint).await;
|
let image = image.eval(footprint).await;
|
||||||
|
@ -904,34 +912,29 @@ async fn render_texture<'a: 'n>(_: (), footprint: Footprint, image: impl Node<Fo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
// async fn upload_texture<'a: 'n, F: Copy + Send + Sync + 'n>(
|
async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable<Color>, executor: &'a WgpuExecutor) -> TextureFrame {
|
||||||
// #[implementations((), Footprint)] footprint: F,
|
// let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect();
|
||||||
// #[implementations(() -> ImageFrameTable<Color>, Footprint -> ImageFrameTable<Color>)] input: impl Node<F, Output = ImageFrameTable<Color>>,
|
|
||||||
// executor: &'a WgpuExecutor,
|
|
||||||
// ) -> TextureFrame {
|
|
||||||
// // let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect();
|
|
||||||
// let input = input.eval(footprint).await;
|
|
||||||
// let input = input.one_item();
|
|
||||||
|
|
||||||
// let new_data: Vec<SRGBA8> = input.image.data.iter().map(|x| (*x).into()).collect();
|
let input = input.one_item();
|
||||||
// let new_image = Image {
|
let new_data: Vec<SRGBA8> = input.image.data.iter().map(|x| (*x).into()).collect();
|
||||||
// width: input.image.width,
|
let new_image = Image {
|
||||||
// height: input.image.height,
|
width: input.image.width,
|
||||||
// data: new_data,
|
height: input.image.height,
|
||||||
// base64_string: None,
|
data: new_data,
|
||||||
// };
|
base64_string: None,
|
||||||
|
};
|
||||||
|
|
||||||
// let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap();
|
let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap();
|
||||||
// let texture = match shader_input {
|
let texture = match shader_input {
|
||||||
// ShaderInput::TextureBuffer(buffer, _) => buffer,
|
ShaderInput::TextureBuffer(buffer, _) => buffer,
|
||||||
// ShaderInput::StorageTextureBuffer(buffer, _) => buffer,
|
ShaderInput::StorageTextureBuffer(buffer, _) => buffer,
|
||||||
// _ => unreachable!("Unsupported ShaderInput type"),
|
_ => unreachable!("Unsupported ShaderInput type"),
|
||||||
// };
|
};
|
||||||
|
|
||||||
// TextureFrame {
|
TextureFrame {
|
||||||
// texture: texture.into(),
|
texture: texture.into(),
|
||||||
// transform: input.transform,
|
transform: input.transform,
|
||||||
// alpha_blend: Default::default(),
|
alpha_blend: Default::default(),
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ Next, install the dependencies required for development builds:
|
||||||
```sh
|
```sh
|
||||||
cargo install cargo-watch
|
cargo install cargo-watch
|
||||||
cargo install wasm-pack
|
cargo install wasm-pack
|
||||||
cargo install -f wasm-bindgen-cli@0.2.99
|
cargo install -f wasm-bindgen-cli@0.2.100
|
||||||
```
|
```
|
||||||
|
|
||||||
Regarding the last one: you'll likely get faster build times if you manually install that specific version of `wasm-bindgen-cli`. It is supposed to be installed automatically but a version mismatch causes it to reinstall every single recompilation. It may need to be manually updated periodically to match the version of the `wasm-bindgen` dependency in [`Cargo.toml`](https://github.com/GraphiteEditor/Graphite/blob/master/Cargo.toml).
|
Regarding the last one: you'll likely get faster build times if you manually install that specific version of `wasm-bindgen-cli`. It is supposed to be installed automatically but a version mismatch causes it to reinstall every single recompilation. It may need to be manually updated periodically to match the version of the `wasm-bindgen` dependency in [`Cargo.toml`](https://github.com/GraphiteEditor/Graphite/blob/master/Cargo.toml).
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue