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": {}
|
||||
},
|
||||
"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": {
|
||||
"vscode": {
|
||||
// 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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
|
@ -2400,6 +2406,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"log",
|
||||
"num-traits",
|
||||
"pretty_assertions",
|
||||
"reqwest 0.12.12",
|
||||
"rustc-hash 2.1.0",
|
||||
"serde",
|
||||
|
@ -3505,9 +3512,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
|
|||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.76"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
|
@ -4907,6 +4914,16 @@ version = "0.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
|
@ -7502,20 +7519,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.99"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.99"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
|
@ -7527,9 +7545,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.49"
|
||||
version = "0.4.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
|
||||
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
|
@ -7540,9 +7558,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.99"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -7550,9 +7568,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.99"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -7563,9 +7581,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.99"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
|
@ -7691,9 +7712,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.76"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
|
||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
@ -8824,6 +8845,12 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.5"
|
||||
|
|
|
@ -63,10 +63,10 @@ spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git" }
|
|||
wgpu-types = "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
|
||||
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"
|
||||
js-sys = "=0.3.76"
|
||||
web-sys = "=0.3.76"
|
||||
js-sys = "=0.3.77"
|
||||
web-sys = "=0.3.77"
|
||||
winit = "0.29"
|
||||
url = "2.5"
|
||||
tokio = { version = "1.29", features = ["fs", "io-std"] }
|
||||
|
@ -80,6 +80,7 @@ base64 = "0.22"
|
|||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
rustybuzz = "0.20"
|
||||
spirv = "0.3"
|
||||
pretty_assertions = "1.4.1"
|
||||
fern = { version = "0.7", features = ["colored"] }
|
||||
num_enum = "0.7"
|
||||
num-derive = "0.4"
|
||||
|
|
|
@ -293,9 +293,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
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::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||
],
|
||||
|
@ -390,7 +390,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Load Image",
|
||||
category: "Raster: Generator",
|
||||
category: "Network",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
|
@ -398,20 +398,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
nodes: [
|
||||
DocumentNode {
|
||||
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")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
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")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")),
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
|
@ -484,7 +484,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
|
@ -554,7 +554,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
|
@ -638,20 +638,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
DocumentNode {
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
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")),
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
|
@ -724,7 +724,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
category: "Raster",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::raster::NoisePatternNode")),
|
||||
inputs: vec![
|
||||
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!(BrushCache), 3),
|
||||
],
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::brush::BrushNode")),
|
||||
..Default::default()
|
||||
}]
|
||||
|
@ -1062,7 +1062,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"),
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
|
@ -1081,7 +1081,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"),
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
|
@ -1103,7 +1103,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
nodes: vec![DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")),
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
}]
|
||||
.into_iter()
|
||||
|
@ -1158,12 +1158,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
DocumentNode {
|
||||
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")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
|
@ -1242,7 +1242,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
|
@ -1321,7 +1321,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
|
@ -1406,7 +1406,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
|
@ -1515,7 +1515,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
|
@ -1595,7 +1595,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
|
@ -1665,13 +1665,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")),
|
||||
..Default::default()
|
||||
|
@ -1736,7 +1736,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(ShaderInputFrame), 0),
|
||||
NodeInput::network(concrete!(Arc<wgpu_executor::Surface>), 1),
|
||||
|
@ -1947,35 +1947,36 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
// Aims for interoperable compatibility with:
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Max%20input%20range-,Curves,-Curves%20settings%20files
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Curves",
|
||||
category: "Raster: Adjustment",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Curve(Default::default()), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec!["Image".into(), "Curve".into()],
|
||||
output_names: vec!["Image".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("TODO"),
|
||||
properties: None,
|
||||
},
|
||||
(*IMAGINATE_NODE).clone(),
|
||||
// TODO: Fix this, it's currently broken
|
||||
// DocumentNodeDefinition {
|
||||
// identifier: "Curves",
|
||||
// category: "Raster: Adjustment",
|
||||
// node_template: NodeTemplate {
|
||||
// document_node: DocumentNode {
|
||||
// implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
|
||||
// inputs: vec![
|
||||
// NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
|
||||
// NodeInput::value(TaggedValue::Curve(Default::default()), false),
|
||||
// ],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
// input_properties: vec!["Image".into(), "Curve".into()],
|
||||
// output_names: vec!["Image".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// },
|
||||
// description: Cow::Borrowed("TODO"),
|
||||
// properties: None,
|
||||
// },
|
||||
// (*IMAGINATE_NODE).clone(),
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Line",
|
||||
category: "Vector: Shape",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::LineNode"),
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::None, false),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::new(0., -50.)), false),
|
||||
|
@ -2089,7 +2090,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"),
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![
|
||||
NodeInput::scope("editor-api"),
|
||||
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), 5),
|
||||
],
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::TransformNode")),
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -2350,7 +2351,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
NodeInput::value(TaggedValue::U32(0), false),
|
||||
],
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
|
@ -2699,7 +2700,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
.enumerate()
|
||||
.map(|(index, (field, 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 {
|
||||
RegistryValueSource::None => {}
|
||||
|
@ -2753,7 +2754,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
|
||||
pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentNodeDefinition {
|
||||
identifier: "Imaginate",
|
||||
category: "Raster: Generator",
|
||||
category: "Raster",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
|
@ -2762,7 +2763,7 @@ pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentN
|
|||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
skip_deduplication: true,
|
||||
..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::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);
|
||||
|
|
|
@ -575,6 +575,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
};
|
||||
|
||||
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
|
||||
log::error!("could not get reference in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -584,6 +585,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
};
|
||||
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
|
||||
if reference == "Fill" && inputs_count == 8 {
|
||||
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::transform::Footprint;
|
||||
use graphene_core::vector::style::ViewMode;
|
||||
use graphene_core::Context;
|
||||
use graphene_std::renderer::{format_transform_matrix, RenderMetadata};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||
|
@ -286,17 +287,17 @@ impl NodeRuntime {
|
|||
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)
|
||||
} 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)
|
||||
} 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)
|
||||
} 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)
|
||||
}
|
||||
// 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());
|
||||
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<(), VectorDataTable>>() {
|
||||
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;
|
||||
#[cfg(feature = "log-bad-types")]
|
||||
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 {
|
||||
|
@ -68,6 +75,20 @@ impl<'a, T: StaticType + 'a> DynAny<'a> for T {
|
|||
fn type_name(&self) -> &'static str {
|
||||
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> {
|
||||
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 crate::Node;
|
||||
#[derive(Clone)]
|
||||
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> {
|
||||
|
|
|
@ -2,10 +2,10 @@ use crate::application_io::{TextureFrame, TextureFrameTable};
|
|||
use crate::instances::Instances;
|
||||
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use crate::raster::BlendMode;
|
||||
use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut};
|
||||
use crate::transform::{Transform, TransformMut};
|
||||
use crate::uuid::NodeId;
|
||||
use crate::vector::{VectorData, VectorDataTable};
|
||||
use crate::Color;
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||
|
||||
use dyn_any::DynAny;
|
||||
|
||||
|
@ -280,28 +280,8 @@ impl ArtboardGroup {
|
|||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn layer<F: 'n + Send + Copy>(
|
||||
#[implementations(
|
||||
(),
|
||||
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();
|
||||
async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
|
||||
let mut stack = stack.one_item().clone();
|
||||
|
||||
if stack.transform.matrix2.determinant() != 0. {
|
||||
*element.transform_mut() = stack.transform.inverse() * element.transform();
|
||||
|
@ -318,72 +298,36 @@ async fn layer<F: 'n + Send + Copy>(
|
|||
}
|
||||
|
||||
#[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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
ImageFrameTable<Color>,
|
||||
TextureFrameTable,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> TextureFrameTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> TextureFrameTable,
|
||||
)]
|
||||
data: impl Node<F, Output = Data>,
|
||||
data: Data,
|
||||
) -> GraphicElement {
|
||||
data.eval(footprint).await.into()
|
||||
data.into()
|
||||
}
|
||||
|
||||
#[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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
ImageFrameTable<Color>,
|
||||
TextureFrameTable,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> TextureFrameTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> TextureFrameTable,
|
||||
)]
|
||||
element: impl Node<F, Output = Data>,
|
||||
element: Data,
|
||||
) -> GraphicGroupTable {
|
||||
element.eval(footprint).await.into()
|
||||
element.into()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("General"))]
|
||||
async fn flatten_group<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
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();
|
||||
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
|
||||
let nested_group = group.one_item().clone();
|
||||
|
||||
let mut flat_group = GraphicGroup::default();
|
||||
|
||||
|
@ -420,34 +364,28 @@ async fn flatten_group<F: 'n + Send>(
|
|||
}
|
||||
|
||||
#[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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> ImageFrameTable<Color>,
|
||||
Context -> TextureFrameTable,
|
||||
)]
|
||||
mut footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> TextureFrame,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> TextureFrame,
|
||||
)]
|
||||
contents: impl Node<F, Output = Data>,
|
||||
contents: impl Node<Context<'static>, Output = Data>,
|
||||
label: String,
|
||||
location: IVec2,
|
||||
dimensions: IVec2,
|
||||
background: Color,
|
||||
clip: bool,
|
||||
) -> Artboard {
|
||||
footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2()));
|
||||
let graphic_group = contents.eval(footprint).await;
|
||||
let footprint = ctx.try_footprint().copied();
|
||||
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 {
|
||||
graphic_group: graphic_group.into(),
|
||||
|
@ -460,28 +398,16 @@ async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroupTable
|
|||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn append_artboard<F: 'n + Send + Copy>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> ArtboardGroup,
|
||||
Footprint -> ArtboardGroup,
|
||||
)]
|
||||
artboards: impl Node<F, Output = ArtboardGroup>,
|
||||
#[implementations(
|
||||
() -> Artboard,
|
||||
Footprint -> Artboard,
|
||||
)]
|
||||
artboard: impl Node<F, Output = Artboard>,
|
||||
node_path: Vec<NodeId>,
|
||||
) -> ArtboardGroup {
|
||||
let artboard = artboard.eval(footprint).await;
|
||||
let mut artboards = artboards.eval(footprint).await;
|
||||
|
||||
async fn append_artboard(ctx: impl Ctx, mut artboards: ArtboardGroup, artboard: Artboard, node_path: Vec<NodeId>) -> ArtboardGroup {
|
||||
// let mut artboards = artboards.eval(ctx.clone()).await;
|
||||
// let artboard = artboard.eval(ctx).await;
|
||||
// let foot = ctx.footprint();
|
||||
// log::debug!("{:?}", foot);
|
||||
// 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();
|
||||
artboards.append_artboard(artboard, encapsulating_node_id);
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ pub use crate as graphene_core;
|
|||
pub use ctor;
|
||||
|
||||
pub mod consts;
|
||||
pub mod context;
|
||||
pub mod generic;
|
||||
pub mod instances;
|
||||
pub mod logic;
|
||||
|
@ -48,6 +49,7 @@ pub mod application_io;
|
|||
#[cfg(feature = "reflections")]
|
||||
pub mod registry;
|
||||
|
||||
pub use context::*;
|
||||
use core::any::TypeId;
|
||||
pub use memo::MemoHash;
|
||||
pub use raster::Color;
|
||||
|
@ -56,7 +58,7 @@ pub use types::Cow;
|
|||
// 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.
|
||||
/// 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;
|
||||
/// Evaluates the node with the single specified input.
|
||||
fn eval(&'i self, input: Input) -> Self::Output;
|
||||
|
@ -79,10 +81,10 @@ mod types;
|
|||
#[cfg(feature = "alloc")]
|
||||
pub use types::*;
|
||||
|
||||
pub trait NodeIO<'i, Input: 'i>: 'i + Node<'i, Input>
|
||||
pub trait NodeIO<'i, Input>: Node<'i, Input>
|
||||
where
|
||||
Self::Output: 'i + StaticTypeSized,
|
||||
Input: 'i + StaticTypeSized,
|
||||
Input: StaticTypeSized,
|
||||
{
|
||||
fn input_type(&self) -> TypeId {
|
||||
TypeId::of::<Input::Static>()
|
||||
|
@ -112,8 +114,7 @@ where
|
|||
{
|
||||
NodeIOTypes {
|
||||
call_argument: concrete!(<Input as StaticTypeSized>::Static),
|
||||
// TODO return actual future type
|
||||
return_value: concrete!(<<Self::Output as Future>::Output as StaticTypeSized>::Static),
|
||||
return_value: future!(<<Self::Output as Future>::Output as StaticTypeSized>::Static),
|
||||
inputs,
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +123,7 @@ where
|
|||
impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N
|
||||
where
|
||||
N::Output: 'i + StaticTypeSized,
|
||||
I: 'i + StaticTypeSized,
|
||||
I: StaticTypeSized,
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -152,13 +153,13 @@ use dyn_any::StaticTypeSized;
|
|||
use core::pin::Pin;
|
||||
|
||||
#[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;
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**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;
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
|
|
|
@ -1,57 +1,40 @@
|
|||
use crate::transform::Footprint;
|
||||
use crate::vector::VectorDataTable;
|
||||
|
||||
use crate::Context;
|
||||
use crate::Ctx;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn log_to_console<T: core::fmt::Debug, F: Send + 'n>(
|
||||
#[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 {
|
||||
fn log_to_console<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> T {
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
let value = value.eval(footprint).await;
|
||||
debug!("{:#?}", value);
|
||||
value
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn to_string<T: core::fmt::Debug + 'n, F: Send + 'n>(
|
||||
#[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;
|
||||
#[node_macro::node(category("Debug"), skip_impl)]
|
||||
fn to_string<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
|
||||
format!("{:?}", value)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn switch<T, F: Send + 'n>(
|
||||
#[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F,
|
||||
async fn switch<T, C: Send + 'n + Clone>(
|
||||
#[implementations(Context)] ctx: C,
|
||||
condition: bool,
|
||||
#[expose]
|
||||
#[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
|
||||
Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2,
|
||||
)]
|
||||
if_true: impl Node<F, Output = T>,
|
||||
if_true: impl Node<C, Output = T>,
|
||||
#[expose]
|
||||
#[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
|
||||
Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2,
|
||||
)]
|
||||
if_false: impl Node<F, Output = T>,
|
||||
if_false: impl Node<C, Output = T>,
|
||||
) -> T {
|
||||
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 {
|
||||
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::registry::types::Percentage;
|
||||
use crate::vector::style::GradientStops;
|
||||
use crate::Ctx;
|
||||
use crate::{Color, Node};
|
||||
|
||||
use math_parser::ast;
|
||||
|
@ -39,7 +40,7 @@ impl ValueProvider for MathNodeContext {
|
|||
/// Calculates a mathematical expression with input values "A" and "B"
|
||||
#[node_macro::node(category("General"), properties("math_properties"))]
|
||||
fn math<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
/// The value of "A" when calculating the expression
|
||||
#[implementations(f64, f32)]
|
||||
operand_a: U,
|
||||
|
@ -84,7 +85,7 @@ fn math<U: num_traits::float::Float>(
|
|||
/// The addition operation (+) calculates the sum of two numbers.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
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, DVec2, f64)] addend: T,
|
||||
) -> <U as Add<T>>::Output {
|
||||
|
@ -94,7 +95,7 @@ fn add<U: Add<T>, T>(
|
|||
/// The subtraction operation (-) calculates the difference between two numbers.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
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, DVec2, f64)] subtrahend: T,
|
||||
) -> <U as Sub<T>>::Output {
|
||||
|
@ -104,7 +105,7 @@ fn subtract<U: Sub<T>, T>(
|
|||
/// The multiplication operation (×) calculates the product of two numbers.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
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,
|
||||
#[default(1.)]
|
||||
#[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.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
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,
|
||||
#[default(1.)]
|
||||
#[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.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
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,
|
||||
#[default(2.)]
|
||||
#[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.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn exponent<U: Pow<T>, T>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U,
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
|
||||
|
@ -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.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn root<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
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".
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn logarithm<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, f32)] value: U,
|
||||
#[default(2.)]
|
||||
#[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.
|
||||
#[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 {
|
||||
theta.sin()
|
||||
} 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.
|
||||
#[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 {
|
||||
theta.cos()
|
||||
} 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.
|
||||
#[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 {
|
||||
theta.tan()
|
||||
} 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.
|
||||
#[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 {
|
||||
value.asin()
|
||||
} 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.
|
||||
#[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 {
|
||||
value.acos()
|
||||
} 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.
|
||||
#[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 {
|
||||
value.atan()
|
||||
} 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.
|
||||
#[node_macro::node(name("Tangent Inverse 2-Argument"), category("Math: Trig"))]
|
||||
fn tangent_inverse_2_argument<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, f32)] y: U,
|
||||
#[expose]
|
||||
#[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.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn random<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
seed: u64,
|
||||
#[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.
|
||||
#[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());
|
||||
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.
|
||||
#[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());
|
||||
value.to_u64().unwrap()
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
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()
|
||||
}
|
||||
|
||||
/// The absolute value function (abs) removes the negative sign from an input value, if present.
|
||||
#[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()
|
||||
}
|
||||
|
||||
/// The minimum function (min) picks the smaller of two numbers.
|
||||
#[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 {
|
||||
true => 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.
|
||||
#[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 {
|
||||
true => 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.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
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)] min: 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.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
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)]
|
||||
#[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.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
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)]
|
||||
#[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.
|
||||
#[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
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
fn logical_and(_: (), value: bool, other_value: bool) -> bool {
|
||||
fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool {
|
||||
value && other_value
|
||||
}
|
||||
|
||||
/// The logical not operation (!) reverses true and false value of the input.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_not(_: (), input: bool) -> bool {
|
||||
fn logical_not(_: impl Ctx, input: bool) -> bool {
|
||||
!input
|
||||
}
|
||||
|
||||
/// Constructs a bool value which may be set to true or false.
|
||||
#[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
|
||||
}
|
||||
|
||||
/// Constructs a number value which may be set to any real number.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn number_value(_: (), _primary: (), number: f64) -> f64 {
|
||||
fn number_value(_: impl Ctx, _primary: (), number: f64) -> f64 {
|
||||
number
|
||||
}
|
||||
|
||||
/// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn percentage_value(_: (), _primary: (), percentage: Percentage) -> f64 {
|
||||
fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 {
|
||||
percentage
|
||||
}
|
||||
|
||||
/// Constructs a two-dimensional vector value which may be set to any XY coordinate.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> DVec2 {
|
||||
#[node_macro::node(name("Vector2 Value"), category("Value"))]
|
||||
fn vector2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 {
|
||||
DVec2::new(x, y)
|
||||
}
|
||||
|
||||
/// Constructs a color value which may be set to any color, or no color.
|
||||
#[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
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
fn gradient_value(_: (), _primary: (), gradient: GradientStops) -> GradientStops {
|
||||
fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> GradientStops {
|
||||
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.
|
||||
#[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
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
|
||||
#[cfg(feature = "std")]
|
||||
#[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()
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option.
|
||||
#[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)
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
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()
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Clones the input value.
|
||||
#[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()
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
|
||||
// TODO: Rename to "Passthrough"
|
||||
/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes.
|
||||
#[node_macro::node(skip_impl)]
|
||||
fn identity<'i, T: 'i>(value: T) -> T {
|
||||
fn identity<'i, T: 'i + Send>(value: T) -> T {
|
||||
value
|
||||
}
|
||||
|
||||
|
@ -537,13 +538,13 @@ where
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{generic::*, structural::*, value::*};
|
||||
use crate::generic::*;
|
||||
|
||||
#[test]
|
||||
pub fn dot_product_function() {
|
||||
let vector_a = glam::DVec2::new(1., 2.);
|
||||
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]
|
||||
|
@ -572,8 +573,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
pub fn identity_node() {
|
||||
let value = ValueNode(4u32).then(IdentityNode::new());
|
||||
assert_eq!(value.eval(()), &4);
|
||||
assert_eq!(identity(&4), &4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
pub use self::color::{Color, Luma, SRGBA8};
|
||||
use crate::raster::image::ImageFrameTable;
|
||||
use crate::registry::types::Percentage;
|
||||
use crate::transform::Footprint;
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::Ctx;
|
||||
use crate::GraphicGroupTable;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
@ -182,6 +182,9 @@ pub trait Alpha {
|
|||
}
|
||||
fn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self;
|
||||
}
|
||||
pub trait AlphaMut: Alpha {
|
||||
fn set_alpha(&mut self, value: Self::AlphaChannel);
|
||||
}
|
||||
|
||||
pub trait Depth {
|
||||
type DepthChannel: Channel;
|
||||
|
@ -228,6 +231,12 @@ pub trait Bitmap {
|
|||
type Pixel: Pixel;
|
||||
fn width(&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>;
|
||||
}
|
||||
|
||||
|
@ -316,51 +325,31 @@ impl SetBlendMode for ImageFrameTable<Color> {
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Style"))]
|
||||
async fn blend_mode<F: 'n + Send, T: SetBlendMode>(
|
||||
fn blend_mode<T: SetBlendMode>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
ImageFrameTable<Color>,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
)]
|
||||
value: impl Node<F, Output = T>,
|
||||
mut value: T,
|
||||
blend_mode: BlendMode,
|
||||
) -> T {
|
||||
let mut value = value.eval(footprint).await;
|
||||
value.set_blend_mode(blend_mode);
|
||||
value
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Style"))]
|
||||
async fn opacity<F: 'n + Send, T: MultiplyAlpha>(
|
||||
fn opacity<T: MultiplyAlpha>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
ImageFrameTable<Color>,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
)]
|
||||
value: impl Node<F, Output = T>,
|
||||
mut value: T,
|
||||
#[default(100.)] factor: Percentage,
|
||||
) -> T {
|
||||
let mut value = value.eval(footprint).await;
|
||||
let opacity_multiplier = factor / 100.;
|
||||
value.multiply_alpha(opacity_multiplier);
|
||||
value
|
||||
|
|
|
@ -6,9 +6,9 @@ use crate::raster::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
|
|||
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use crate::raster::{Channel, Color, Pixel};
|
||||
use crate::registry::types::{Angle, Percentage, SignedPercentage};
|
||||
use crate::transform::Footprint;
|
||||
use crate::vector::style::GradientStops;
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{Ctx, Node};
|
||||
use crate::{GraphicElement, GraphicGroupTable};
|
||||
|
||||
use dyn_any::DynAny;
|
||||
|
@ -284,26 +284,16 @@ impl From<BlendMode> for vello::peniko::Mix {
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn luminance<F: 'n + Send, T: Adjust<Color>>(
|
||||
fn luminance<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
luminance_calc: LuminanceCalculation,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let luminance = match luminance_calc {
|
||||
LuminanceCalculation::SRGB => color.luminance_srgb(),
|
||||
|
@ -318,26 +308,16 @@ async fn luminance<F: 'n + Send, T: Adjust<Color>>(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
|
||||
fn extract_channel<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
channel: RedGreenBlueAlpha,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let extracted_value = match channel {
|
||||
RedGreenBlueAlpha::Red => color.r(),
|
||||
|
@ -351,25 +331,15 @@ async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
|
||||
fn make_opaque<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
if color.a() == 0. {
|
||||
return color.with_alpha(1.);
|
||||
|
@ -385,31 +355,21 @@ async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
|
|||
// Algorithm from:
|
||||
// https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn levels<F: 'n + Send, T: Adjust<Color>>(
|
||||
fn levels<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
#[default(0.)] shadows: Percentage,
|
||||
#[default(50.)] midtones: Percentage,
|
||||
#[default(100.)] highlights: Percentage,
|
||||
#[default(0.)] output_minimums: Percentage,
|
||||
#[default(100.)] output_maximums: Percentage,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
// Input Range (Range: 0-1)
|
||||
|
@ -451,7 +411,7 @@ async fn levels<F: 'n + Send, T: Adjust<Color>>(
|
|||
|
||||
color.to_linear_srgb()
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
// Aims for interoperable compatibility with:
|
||||
|
@ -462,23 +422,14 @@ async fn levels<F: 'n + Send, T: Adjust<Color>>(
|
|||
// https://stackoverflow.com/a/55233732/775283
|
||||
// Works the same for gamma and linear color
|
||||
#[node_macro::node(name("Black & White"), category("Raster: Adjustment"))]
|
||||
async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn black_and_white<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
#[default(Color::BLACK)] tint: Color,
|
||||
#[default(40.)]
|
||||
#[range((-200., 300.))]
|
||||
|
@ -499,8 +450,7 @@ async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
|
|||
#[range((-200., 300.))]
|
||||
magentas: Percentage,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
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()
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
// Aims for interoperable compatibility with:
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings
|
||||
#[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))]
|
||||
async fn hue_saturation<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn hue_saturation<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
hue_shift: Angle,
|
||||
saturation_shift: SignedPercentage,
|
||||
lightness_shift: SignedPercentage,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
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:
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn invert<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn invert<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
|
@ -620,29 +550,19 @@ async fn invert<F: 'n + Send, T: Adjust<Color>>(
|
|||
// Aims for interoperable compatibility with:
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn threshold<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn threshold<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
#[default(50.)] min_luminance: Percentage,
|
||||
#[default(100.)] max_luminance: Percentage,
|
||||
luminance_calc: LuminanceCalculation,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let min_luminance = Color::srgb_to_linear(min_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
|
||||
}
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
trait Blend<P: Pixel> {
|
||||
|
@ -723,44 +643,35 @@ impl Blend<Color> for GradientStops {
|
|||
}
|
||||
|
||||
#[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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
over: impl Node<F, Output = T>,
|
||||
over: T,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
under: impl Node<F, Output = T>,
|
||||
under: T,
|
||||
blend_mode: BlendMode,
|
||||
#[default(100.)] opacity: Percentage,
|
||||
) -> 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.))
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn blend_color_pair(input: (Color, Color), blend_mode: BlendMode, opacity: Percentage) -> Color {
|
||||
#[node_macro::node(category(""), skip_impl)]
|
||||
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.)
|
||||
}
|
||||
|
||||
|
@ -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=Gradient%20settings%20(Photoshop%206.0)
|
||||
#[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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
gradient: GradientStops,
|
||||
reverse: bool,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let intensity = color.luminance_srgb();
|
||||
let intensity = if reverse { 1. - intensity } else { intensity };
|
||||
gradient.evalute(intensity as f64)
|
||||
});
|
||||
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
// 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
|
||||
// The results of this implementation are very close to correct, but not quite perfect
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn vibrance<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
vibrance: SignedPercentage,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let vibrance = vibrance as f32 / 100.;
|
||||
// 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%.
|
||||
|
@ -963,7 +853,7 @@ async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
|
|||
altered_color.map_rgb(|c| c * (1. - factor) + luminance * factor)
|
||||
}
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
#[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=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr
|
||||
#[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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
|
||||
monochrome: bool,
|
||||
#[default(40.)]
|
||||
|
@ -1270,8 +1151,7 @@ async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
|
|||
// Display-only properties (not used within the node)
|
||||
_output_channel: RedGreenBlue,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
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()
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
@ -1356,24 +1236,15 @@ impl core::fmt::Display for SelectiveColorChoice {
|
|||
//
|
||||
// Algorithm based on:
|
||||
// https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
|
||||
#[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"))]
|
||||
async fn selective_color<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
mode: RelativeAbsolute,
|
||||
#[name("(Reds) Cyan")] r_c: 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,
|
||||
_colors: SelectiveColorChoice,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
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()
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
pub(super) trait MultiplyAlpha {
|
||||
|
@ -1534,28 +1404,18 @@ where
|
|||
// https://www.axiomx.com/posterize.htm
|
||||
// This algorithm produces fully accurate output in relation to the industry standard.
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn posterize<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn posterize<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
#[default(4)]
|
||||
#[min(2.)]
|
||||
levels: u32,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
|
@ -1577,30 +1437,20 @@ async fn posterize<F: 'n + Send, T: Adjust<Color>>(
|
|||
// Algorithm based on:
|
||||
// https://geraldbakker.nl/psnumbers/exposure.html
|
||||
#[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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
exposure: f64,
|
||||
offset: f64,
|
||||
#[default(1.)]
|
||||
#[range((0.01, 10.))]
|
||||
gamma_correction: f64,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let adjusted = color
|
||||
// Exposure
|
||||
|
@ -1619,7 +1469,7 @@ const WINDOW_SIZE: usize = 1024;
|
|||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[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};
|
||||
|
||||
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")]
|
||||
#[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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
#[default(Color::BLACK)] color: Color,
|
||||
blend_mode: BlendMode,
|
||||
#[default(100.)] opacity: Percentage,
|
||||
) -> T {
|
||||
let opacity = (opacity as f32 / 100.).clamp(0., 1.);
|
||||
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|pixel| {
|
||||
image.adjust(|pixel| {
|
||||
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.
|
||||
|
@ -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())
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
// #[cfg(feature = "alloc")]
|
||||
|
@ -1702,10 +1542,11 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
|
|||
// #[cfg(feature = "alloc")]
|
||||
// mod index_node {
|
||||
// use crate::raster::{Color, ImageFrame};
|
||||
// use crate::Ctx;
|
||||
|
||||
// #[node_macro::node(category(""))]
|
||||
// pub fn index<T: Default + Clone>(
|
||||
// _: (),
|
||||
// _: impl Ctx,
|
||||
// #[implementations(Vec<ImageFrame<Color>>, Vec<Color>)]
|
||||
// #[widget(ParsedWidgetOverride::Hidden)]
|
||||
// input: Vec<T>,
|
||||
|
@ -1752,7 +1593,7 @@ mod test {
|
|||
// 100% of the output should come from the multiplied value
|
||||
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();
|
||||
|
||||
// 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::{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;
|
||||
#[cfg(feature = "serde")]
|
||||
|
@ -257,6 +257,11 @@ impl RGBMut for Color {
|
|||
self.blue = blue;
|
||||
}
|
||||
}
|
||||
impl AlphaMut for Color {
|
||||
fn set_alpha(&mut self, value: Self::AlphaChannel) {
|
||||
self.alpha = value;
|
||||
}
|
||||
}
|
||||
|
||||
impl Pixel for Color {
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
|
|
|
@ -132,14 +132,15 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode<I, Root> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{ops::IdentityNode, value::ValueNode};
|
||||
use crate::generic::FnNode;
|
||||
use crate::value::ValueNode;
|
||||
|
||||
#[test]
|
||||
fn compose() {
|
||||
let value = ValueNode::new(4u32);
|
||||
let compose = value.then(IdentityNode::new());
|
||||
let compose = value.then(FnNode::new(|x| x));
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -148,7 +149,7 @@ mod test {
|
|||
let value = ValueNode::new(5);
|
||||
|
||||
assert_eq!(value.eval(()), &5);
|
||||
let id = IdentityNode::new();
|
||||
let id = FnNode::new(|x| x);
|
||||
|
||||
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::image::{ImageFrame, ImageFrameTable};
|
||||
use crate::raster::Pixel;
|
||||
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};
|
||||
|
||||
|
@ -244,6 +244,12 @@ pub struct Footprint {
|
|||
|
||||
impl Default for Footprint {
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Footprint {
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
transform: DAffine2::IDENTITY,
|
||||
resolution: glam::UVec2::new(1920, 1080),
|
||||
|
@ -251,9 +257,6 @@ impl Default for Footprint {
|
|||
ignore_modifications: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Footprint {
|
||||
pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox {
|
||||
let inverse = self.transform.inverse();
|
||||
let start = inverse.transform_point2((0., 0.).into());
|
||||
|
@ -277,7 +280,7 @@ impl From<()> for Footprint {
|
|||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
|
@ -301,26 +304,15 @@ impl ApplyTransform for () {
|
|||
}
|
||||
|
||||
#[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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> ImageFrameTable<Color>,
|
||||
Context -> TextureFrameTable,
|
||||
)]
|
||||
mut input: I,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> GraphicGroupTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> TextureFrame,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> TextureFrame,
|
||||
)]
|
||||
transform_target: impl Node<I, Output = T>,
|
||||
transform_target: impl Node<Context<'static>, Output = T>,
|
||||
translate: DVec2,
|
||||
rotate: f64,
|
||||
scale: DVec2,
|
||||
|
@ -328,22 +320,27 @@ async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Syn
|
|||
_pivot: DVec2,
|
||||
) -> T {
|
||||
let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
||||
let footprint = input.clone().into();
|
||||
if !footprint.ignore_modifications {
|
||||
input.apply_transform(&modification);
|
||||
let footprint = ctx.try_footprint().copied();
|
||||
|
||||
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_target
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn replace_transform<Data: TransformMut, TransformInput: Transform>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(VectorDataTable, ImageFrameTable<Color>, GraphicGroupTable)] mut data: Data,
|
||||
#[implementations(DAffine2)] transform: TransformInput,
|
||||
) -> Data {
|
||||
|
|
|
@ -53,6 +53,9 @@ macro_rules! future {
|
|||
($type:ty) => {{
|
||||
$crate::Type::Future(Box::new(concrete!($type)))
|
||||
}};
|
||||
($type:ty, $name:ty) => {
|
||||
$crate::Type::Future(Box::new(concrete!($type, $name)))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
@ -67,6 +70,18 @@ macro_rules! fn_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)]
|
||||
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 = match name.as_str() {
|
||||
"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::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(),
|
||||
|
@ -189,7 +205,7 @@ pub enum Type {
|
|||
/// 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.
|
||||
Fn(Box<Type>, Box<Type>),
|
||||
/// Not used at the moment.
|
||||
/// Represents a future which promises to return the inner type.
|
||||
Future(Box<Type>),
|
||||
}
|
||||
|
||||
|
@ -280,7 +296,7 @@ impl Type {
|
|||
Self::Generic(_) => self,
|
||||
Self::Concrete(_) => self,
|
||||
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)]
|
||||
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;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
N
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,10 @@ impl<'i, const N: u32> Node<'i, ()> for IntNode<N> {
|
|||
#[derive(Default, Debug, Clone, Copy)]
|
||||
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;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
@ -77,10 +77,10 @@ impl<T> RefCellMutNode<T> {
|
|||
#[derive(Default)]
|
||||
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;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
self.0.replace(T::default())
|
||||
}
|
||||
}
|
||||
|
@ -94,10 +94,10 @@ impl<T> OnceCellNode<T> {
|
|||
#[derive(Clone, Copy)]
|
||||
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;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
@ -140,10 +140,10 @@ impl<T: Clone> DebugClonedNode<T> {
|
|||
#[derive(Clone, Copy)]
|
||||
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;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
@ -157,9 +157,9 @@ impl<T: Copy> CopiedNode<T> {
|
|||
#[derive(Default)]
|
||||
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;
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
T::default()
|
||||
}
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_default_node() {
|
||||
let node = DefaultNode::<u32>::new();
|
||||
assert_eq!(node.eval(()), 0);
|
||||
assert_eq!(node.eval(42), 0);
|
||||
}
|
||||
#[test]
|
||||
#[allow(clippy::unit_cmp)]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::transform::Footprint;
|
||||
use crate::vector::{HandleId, PointId, VectorData, VectorDataTable};
|
||||
use crate::Ctx;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
|
@ -35,12 +35,12 @@ impl CornerRadius for [f64; 4] {
|
|||
}
|
||||
|
||||
#[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))))
|
||||
}
|
||||
|
||||
#[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 corner1 = -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"))]
|
||||
fn rectangle<F: 'n + Send, T: CornerRadius>(
|
||||
#[implementations((), Footprint)] _footprint: F,
|
||||
fn rectangle<T: CornerRadius>(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[default(100)] width: f64,
|
||||
#[default(100)] height: f64,
|
||||
|
@ -71,8 +71,8 @@ fn rectangle<F: 'n + Send, T: CornerRadius>(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn regular_polygon<F: 'n + Send>(
|
||||
#[implementations((), Footprint)] _footprint: F,
|
||||
fn regular_polygon(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[default(6)]
|
||||
#[min(3.)]
|
||||
|
@ -85,8 +85,8 @@ fn regular_polygon<F: 'n + Send>(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn star<F: 'n + Send>(
|
||||
#[implementations((), Footprint)] _footprint: F,
|
||||
fn star(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[default(5)]
|
||||
#[min(2.)]
|
||||
|
@ -102,15 +102,14 @@ fn star<F: 'n + Send>(
|
|||
}
|
||||
|
||||
#[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)))
|
||||
}
|
||||
|
||||
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
||||
#[node_macro::node(category(""))]
|
||||
fn path<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);
|
||||
|
||||
vector_data.colinear_manipulators = colinear_manipulators
|
||||
.iter()
|
||||
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::*;
|
||||
use crate::transform::Footprint;
|
||||
use crate::uuid::generate_uuid;
|
||||
use crate::Ctx;
|
||||
|
||||
use bezier_rs::BezierHandles;
|
||||
use dyn_any::DynAny;
|
||||
|
@ -424,20 +424,7 @@ impl core::hash::Hash for VectorModification {
|
|||
|
||||
/// A node that applies a procedural modification to some [`VectorData`].
|
||||
#[node_macro::node(category(""))]
|
||||
async fn path_modify<F: 'n + Send + Sync + Clone>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
input: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
modification: Box<VectorModification>,
|
||||
) -> VectorDataTable {
|
||||
let mut vector_data = vector_data.eval(input).await;
|
||||
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item_mut();
|
||||
|
||||
modification.apply(vector_data);
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::renderer::GraphicElementRendered;
|
|||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
use crate::vector::style::LineJoin;
|
||||
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 glam::{DAffine2, DVec2};
|
||||
|
@ -43,21 +43,11 @@ impl VectorIterMut for VectorDataTable {
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||
async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
async fn assign_colors<T: VectorIterMut>(
|
||||
_: impl Ctx,
|
||||
#[implementations(GraphicGroupTable, VectorDataTable)]
|
||||
#[widget(ParsedWidgetOverride::Hidden)]
|
||||
vector_group: impl Node<F, Output = T>,
|
||||
mut vector_group: T,
|
||||
#[default(true)] fill: bool,
|
||||
stroke: bool,
|
||||
#[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_repeat_every")] repeat_every: u32,
|
||||
) -> T {
|
||||
let mut vector_group = vector_group.eval(footprint).await;
|
||||
|
||||
let length = vector_group.vector_iter_mut().count();
|
||||
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"))]
|
||||
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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
VectorDataTable,
|
||||
VectorDataTable,
|
||||
VectorDataTable,
|
||||
VectorDataTable,
|
||||
GraphicGroupTable,
|
||||
GraphicGroupTable,
|
||||
GraphicGroupTable,
|
||||
GraphicGroupTable
|
||||
)]
|
||||
footprint: F,
|
||||
mut vector_data: TargetTy,
|
||||
#[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,
|
||||
Option<Color>,
|
||||
Color,
|
||||
|
@ -161,44 +115,19 @@ async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIter
|
|||
_backup_color: Option<Color>,
|
||||
_backup_gradient: Gradient,
|
||||
) -> TargetTy {
|
||||
let mut target = vector_data.eval(footprint).await;
|
||||
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
|
||||
vector_data
|
||||
}
|
||||
|
||||
#[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(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
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>,
|
||||
Color,
|
||||
Option<Color>,
|
||||
|
@ -213,7 +142,6 @@ async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy
|
|||
line_join: LineJoin,
|
||||
#[default(4.)] miter_limit: f64,
|
||||
) -> TargetTy {
|
||||
let mut target = vector_data.eval(footprint).await;
|
||||
let stroke = Stroke {
|
||||
color: color.into(),
|
||||
weight,
|
||||
|
@ -224,37 +152,24 @@ async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy
|
|||
line_join_miter_limit: miter_limit,
|
||||
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
|
||||
vector_data
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
async fn repeat<I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||
_: impl Ctx,
|
||||
// TODO: Implement other GraphicElementRendered types.
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
instance: impl Node<F, Output = I>,
|
||||
#[implementations(VectorDataTable, GraphicGroupTable)] instance: I,
|
||||
#[default(100., 100.)]
|
||||
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
||||
direction: DVec2,
|
||||
angle: Angle,
|
||||
#[default(4)] instances: IntegerCount,
|
||||
) -> GraphicGroupTable {
|
||||
let instance = instance.eval(footprint).await;
|
||||
let first_vector_transform = instance.transform();
|
||||
|
||||
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))]
|
||||
async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
async fn circular_repeat<I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||
_: impl Ctx,
|
||||
// TODO: Implement other GraphicElementRendered types.
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
instance: impl Node<F, Output = I>,
|
||||
#[implementations(VectorDataTable, GraphicGroupTable)] instance: I,
|
||||
angle_offset: Angle,
|
||||
#[default(5)] radius: f64,
|
||||
#[default(5)] instances: IntegerCount,
|
||||
) -> GraphicGroupTable {
|
||||
let instance = instance.eval(footprint).await;
|
||||
let first_vector_transform = instance.transform();
|
||||
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))]
|
||||
async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatElement + TransformMut + Send + 'n>(
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
points: impl Node<F, Output = VectorDataTable>,
|
||||
async fn copy_to_points<I: GraphicElementRendered + TransformMut + Send + 'n>(
|
||||
_: impl Ctx,
|
||||
points: VectorDataTable,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
instance: impl Node<F, Output = I>,
|
||||
#[implementations(VectorDataTable, GraphicGroupTable)]
|
||||
instance: I,
|
||||
#[default(1)] random_scale_min: f64,
|
||||
#[default(1)] random_scale_max: 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_seed: SeedValue,
|
||||
) -> GraphicGroupTable {
|
||||
let points = points.eval(footprint).await;
|
||||
let points = points.one_item();
|
||||
|
||||
let instance = instance.eval(footprint).await;
|
||||
|
||||
let instance_transform = instance.transform();
|
||||
|
||||
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))]
|
||||
async fn bounding_box<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
) -> VectorDataTable {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
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"))]
|
||||
async fn offset_path<F: 'n + Send>(
|
||||
#[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;
|
||||
async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
let subpaths = vector_data.stroke_bezier_paths();
|
||||
let mut result = VectorData::empty();
|
||||
result.style = vector_data.style.clone();
|
||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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))]
|
||||
async fn solidify_stroke<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
) -> VectorDataTable {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
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))]
|
||||
async fn flatten_vector_elements<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
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();
|
||||
async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupTable) -> VectorDataTable {
|
||||
let graphic_group_input = graphic_group_input.one_item();
|
||||
|
||||
// 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
|
||||
|
@ -587,7 +421,7 @@ async fn flatten_vector_elements<F: 'n + Send>(
|
|||
}
|
||||
|
||||
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.
|
||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
|
@ -614,34 +448,10 @@ impl ConcatElement for GraphicGroupTable {
|
|||
}
|
||||
|
||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||
async fn sample_points<F: 'n + Send + Copy>(
|
||||
#[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 {
|
||||
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 {
|
||||
// Limit the smallest spacing to something sensible to avoid freezing the application.
|
||||
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 subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await;
|
||||
|
||||
// Create an iterator over the bezier segments with enumeration and peeking capability.
|
||||
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))]
|
||||
async fn poisson_disk_points<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
async fn poisson_disk_points(
|
||||
_: impl Ctx,
|
||||
vector_data: VectorDataTable,
|
||||
#[default(10.)]
|
||||
#[min(0.01)]
|
||||
separation_disk_diameter: f64,
|
||||
seed: SeedValue,
|
||||
) -> VectorDataTable {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
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))]
|
||||
async fn subpath_segment_lengths<F: 'n + Send>(
|
||||
#[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;
|
||||
async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec<f64> {
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
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))]
|
||||
async fn spline<F: 'n + Send>(
|
||||
#[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;
|
||||
async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item_mut();
|
||||
|
||||
// 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))]
|
||||
async fn jitter_points<F: 'n + Send>(
|
||||
#[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;
|
||||
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
|
||||
let mut vector_data = vector_data.one_item().clone();
|
||||
|
||||
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))]
|
||||
async fn morph<F: 'n + Send + Copy>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
source: impl Node<F, Output = VectorDataTable>,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
target: impl Node<F, Output = VectorDataTable>,
|
||||
async fn morph(
|
||||
_: impl Ctx,
|
||||
source: VectorDataTable,
|
||||
#[expose] target: VectorDataTable,
|
||||
#[range((0., 1.))]
|
||||
#[default(0.5)]
|
||||
time: Fraction,
|
||||
#[min(0.)] start_index: IntegerCount,
|
||||
) -> VectorDataTable {
|
||||
let source = source.eval(footprint).await;
|
||||
let source = source.one_item();
|
||||
let target = target.eval(footprint).await;
|
||||
let target = target.one_item();
|
||||
|
||||
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))]
|
||||
async fn bevel<F: 'n + Send + Copy>(
|
||||
#[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;
|
||||
fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
|
||||
let source = source.one_item();
|
||||
|
||||
let result = bevel_algorithm(source.clone(), distance);
|
||||
|
||||
VectorDataTable::new(result)
|
||||
VectorDataTable::new(bevel_algorithm(source.clone(), distance))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>) -> f64 {
|
||||
let vector_data = vector_data.eval(Footprint::default()).await;
|
||||
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 {
|
||||
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 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))]
|
||||
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
|
||||
let vector_data = vector_data.eval(Footprint::default()).await;
|
||||
async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
|
||||
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();
|
||||
|
||||
if centroid_type == CentroidType::Area {
|
||||
|
@ -1303,16 +1037,16 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorDataTable> {
|
||||
FutureWrapperNode(VectorDataTable::new(VectorData::from_subpath(data)))
|
||||
fn vector_node(data: Subpath<PointId>) -> VectorDataTable {
|
||||
VectorDataTable::new(VectorData::from_subpath(data))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repeat() {
|
||||
let direction = DVec2::X * 1.5;
|
||||
let instances = 3;
|
||||
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 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(), repeated).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 3);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
|
@ -1323,8 +1057,8 @@ mod test {
|
|||
async fn repeat_transform_position() {
|
||||
let direction = DVec2::new(12., 10.);
|
||||
let instances = 8;
|
||||
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 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(), repeated).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
|
@ -1333,8 +1067,8 @@ mod test {
|
|||
}
|
||||
#[tokio::test]
|
||||
async fn circle_repeat() {
|
||||
let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).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(), repeated).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
|
@ -1346,10 +1080,7 @@ mod test {
|
|||
}
|
||||
#[tokio::test]
|
||||
async fn bounding_box() {
|
||||
let bounding_box = BoundingBoxNode {
|
||||
vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)),
|
||||
};
|
||||
let bounding_box = bounding_box.eval(Footprint::default()).await;
|
||||
let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await;
|
||||
let bounding_box = bounding_box.one_item();
|
||||
assert_eq!(bounding_box.region_bezier_paths().count(), 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 instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE);
|
||||
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 flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(copy_to_points)).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(), copy_to_points).await;
|
||||
let flattened_copy_to_points = flattened_copy_to_points.one_item();
|
||||
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() {
|
||||
|
@ -1390,7 +1121,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn sample_points() {
|
||||
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();
|
||||
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.]) {
|
||||
|
@ -1400,7 +1131,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn adaptive_spacing() {
|
||||
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();
|
||||
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.]) {
|
||||
|
@ -1411,7 +1142,7 @@ mod test {
|
|||
async fn poisson() {
|
||||
let sample_points = super::poisson_disk_points(
|
||||
Footprint::default(),
|
||||
&vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
|
||||
vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
|
||||
10. * std::f64::consts::SQRT_2,
|
||||
0,
|
||||
)
|
||||
|
@ -1429,12 +1160,12 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn lengths() {
|
||||
let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let lengths = 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.]);
|
||||
}
|
||||
#[tokio::test]
|
||||
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();
|
||||
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.)]);
|
||||
|
@ -1443,7 +1174,7 @@ mod test {
|
|||
async fn morph() {
|
||||
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
||||
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
|
||||
let sample_points = super::morph(Footprint::default(), &vector_node(source), &vector_node(target), 0.5, 0).await;
|
||||
let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await;
|
||||
let sample_points = sample_points.one_item();
|
||||
assert_eq!(
|
||||
&sample_points.point_domain.positions()[..4],
|
||||
|
@ -1452,7 +1183,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[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 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<_>>());
|
||||
|
@ -1461,42 +1192,42 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn bevel_rect() {
|
||||
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();
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 8);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 8);
|
||||
|
||||
// Segments
|
||||
contains_segment(&beveled, 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, 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(5., 0.), DVec2::new(95., 0.)));
|
||||
contains_segment(beveled.clone(), 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(0., 5.), DVec2::new(0., 95.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 5.), DVec2::new(100., 95.)));
|
||||
|
||||
// Joins
|
||||
contains_segment(&beveled, 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, 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., 0.), DVec2::new(0., 5.)));
|
||||
contains_segment(beveled.clone(), 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(100., 95.), DVec2::new(95., 100.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(0., 95.)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
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 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();
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 3);
|
||||
|
||||
// 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.));
|
||||
contains_segment(&beveled, trimmed);
|
||||
contains_segment(beveled.clone(), trimmed);
|
||||
|
||||
// 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]
|
||||
|
@ -1506,7 +1237,7 @@ mod test {
|
|||
let mut vector_data = VectorData::from_subpath(source);
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
|
||||
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();
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||
|
@ -1514,31 +1245,31 @@ mod test {
|
|||
assert_eq!(beveled.transform, transform);
|
||||
|
||||
// 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.));
|
||||
contains_segment(&beveled, trimmed);
|
||||
contains_segment(beveled.clone(), trimmed);
|
||||
|
||||
// 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]
|
||||
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 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();
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||
|
||||
// Segments
|
||||
contains_segment(&beveled, 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, 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(0., 0.), DVec2::new(50., 0.)));
|
||||
contains_segment(beveled.clone(), 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(50., 100.)));
|
||||
|
||||
// Joins
|
||||
contains_segment(&beveled, 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(50., 0.), DVec2::new(100., 50.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.)));
|
||||
}
|
||||
|
||||
#[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 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 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();
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||
|
||||
// Segments
|
||||
contains_segment(&beveled, 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, point);
|
||||
contains_segment(beveled.clone(), 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(-5., 0.), DVec2::new(0., 0.)));
|
||||
contains_segment(beveled.clone(), point);
|
||||
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, end);
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(start.start, start.end));
|
||||
contains_segment(beveled.clone(), end);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ winit = { workspace = true }
|
|||
[dev-dependencies]
|
||||
# Workspace dependencies
|
||||
graph-craft = { workspace = true, features = ["loading"] }
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
# Required dependencies
|
||||
criterion = { version = "0.5", features = ["html_reports"]}
|
||||
|
|
|
@ -304,7 +304,7 @@ impl DocumentNode {
|
|||
match first {
|
||||
NodeInput::Value { tagged_value, .. } => {
|
||||
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 } => {
|
||||
assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node");
|
||||
|
@ -1567,7 +1567,7 @@ mod test {
|
|||
NodeId(14),
|
||||
ProtoNode {
|
||||
identifier: "graphene_core::value::ClonedNode".into(),
|
||||
input: ProtoNodeInput::None,
|
||||
input: ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context)),
|
||||
construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(vec![NodeId(1), NodeId(4)]),
|
||||
|
@ -1589,7 +1589,7 @@ mod test {
|
|||
|
||||
println!("{:#?}", resolved_network[0]);
|
||||
println!("{construction_network:#?}");
|
||||
assert_eq!(resolved_network[0], construction_network);
|
||||
pretty_assertions::assert_eq!(resolved_network[0], construction_network);
|
||||
}
|
||||
|
||||
fn flat_network() -> NodeNetwork {
|
||||
|
|
|
@ -102,8 +102,8 @@ macro_rules! tagged_value {
|
|||
})
|
||||
}
|
||||
Type::Fn(_, output) => TaggedValue::from_type(output),
|
||||
Type::Future(_) => {
|
||||
None
|
||||
Type::Future(output) => {
|
||||
TaggedValue::from_type(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ impl TaggedValue {
|
|||
Some(ty)
|
||||
}
|
||||
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)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
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,
|
||||
/// 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.
|
||||
|
@ -203,6 +203,7 @@ impl ProtoNode {
|
|||
if self.skip_deduplication {
|
||||
self.original_location.path.hash(&mut hasher);
|
||||
}
|
||||
|
||||
std::mem::discriminant(&self.input).hash(&mut hasher);
|
||||
match self.input {
|
||||
ProtoNodeInput::None => (),
|
||||
|
@ -212,6 +213,7 @@ impl ProtoNode {
|
|||
ProtoNodeInput::Node(id) => (id, false).hash(&mut hasher),
|
||||
ProtoNodeInput::NodeLambda(id) => (id, true).hash(&mut hasher),
|
||||
};
|
||||
|
||||
Some(NodeId(hasher.finish()))
|
||||
}
|
||||
|
||||
|
@ -224,7 +226,7 @@ impl ProtoNode {
|
|||
Self {
|
||||
identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"),
|
||||
construction_args: value,
|
||||
input: ProtoNodeInput::None,
|
||||
input: ProtoNodeInput::ManualComposition(concrete!(Context)),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(path),
|
||||
inputs_exposed: vec![false; inputs_exposed],
|
||||
|
@ -552,10 +554,11 @@ impl core::fmt::Debug for GraphErrorType {
|
|||
GraphErrorType::NoImplementations => write!(f, "No implementations found"),
|
||||
GraphErrorType::NoConstructor => write!(f, "No construct found for node"),
|
||||
GraphErrorType::InvalidImplementations { inputs, error_inputs } => {
|
||||
let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}", index + 1);
|
||||
let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::<Vec<_>>().join("\n");
|
||||
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").replace("Option<Arc<OwnedContextImpl>>", "Context");
|
||||
let mut errors = error_inputs.iter().map(format_error_list).collect::<Vec<_>>();
|
||||
errors.sort();
|
||||
let inputs = inputs.replace("Option<Arc<OwnedContextImpl>>", "Context");
|
||||
write!(
|
||||
f,
|
||||
"This node isn't compatible with the com-\n\
|
||||
|
@ -651,9 +654,9 @@ impl TypingContext {
|
|||
let inputs = match node.construction_args {
|
||||
// If the node has a value input we can infer the return type from it
|
||||
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
|
||||
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());
|
||||
return Ok(types);
|
||||
}
|
||||
|
@ -696,6 +699,8 @@ impl TypingContext {
|
|||
match (from, to) {
|
||||
// Direct comparison of two concrete types.
|
||||
(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.
|
||||
// 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.
|
||||
|
|
|
@ -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::bbox::{AxisAlignedBbox, Bbox};
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_core::raster::{Alpha, BlendColorPairNode, Color, Image, Pixel, Sample};
|
||||
use graphene_core::transform::{Footprint, Transform, TransformMut};
|
||||
use graphene_core::raster::{Alpha, Color, Image, Pixel, Sample};
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
|
||||
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::Node;
|
||||
use graphene_core::{Ctx, Node};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[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();
|
||||
|
||||
vector_data.point_domain.positions().to_vec()
|
||||
|
@ -131,14 +133,21 @@ where
|
|||
target
|
||||
}
|
||||
|
||||
pub fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
|
||||
let stamp = BrushStampGeneratorNode::new(CopiedNode::new(brush_style.color), CopiedNode::new(brush_style.hardness), CopiedNode::new(brush_style.flow));
|
||||
let stamp = stamp.eval(brush_style.diameter);
|
||||
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
|
||||
let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);
|
||||
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(());
|
||||
let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
|
||||
let blend_executor = BlendImageTupleNode::new(ValueNode::new(normal_blend));
|
||||
blend_executor.eval((blank_texture, stamp)).image
|
||||
use crate::raster::empty_image;
|
||||
let blank_texture = empty_image((), transform, Color::TRANSPARENT);
|
||||
// let normal_blend = BlendColorPairNode::new(
|
||||
// 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 {
|
||||
|
@ -202,7 +211,7 @@ pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Col
|
|||
}
|
||||
|
||||
#[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 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() {
|
||||
// Create brush texture.
|
||||
// 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 tex = create_brush_texture(&stroke.style);
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
if brush_texture.is_none() {
|
||||
let tex = create_brush_texture(&stroke.style).await;
|
||||
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.
|
||||
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_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 blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(normal_blend));
|
||||
// let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.)));
|
||||
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 target = core::mem::take(&mut brush_plan.first_stroke_texture);
|
||||
ExtendImageToBoundsNode::new(CopiedNode::new(stroke_to_layer)).eval(target)
|
||||
} 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.
|
||||
|
@ -277,34 +295,44 @@ fn brush(_: Footprint, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Co
|
|||
let mut erase_restore_mask = opaque_image;
|
||||
|
||||
for stroke in erase_restore_strokes {
|
||||
let brush_texture = cache.get_cached_brush(&stroke.style).unwrap_or_else(|| {
|
||||
let tex = create_brush_texture(&stroke.style);
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
if brush_texture.is_none() {
|
||||
let tex = create_brush_texture(&stroke.style).await;
|
||||
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();
|
||||
|
||||
match stroke.style.blend_mode {
|
||||
BlendMode::Erase => {
|
||||
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Erase), CopiedNode::new(100.));
|
||||
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask);
|
||||
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Erase, 1.));
|
||||
let blit_node = BlitNode::new(
|
||||
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.
|
||||
BlendMode::Restore => {
|
||||
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Restore), CopiedNode::new(100.));
|
||||
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask);
|
||||
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 1.));
|
||||
let blit_node = BlitNode::new(
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
|
||||
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.));
|
||||
let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params));
|
||||
actual_image = blend_executor.eval((actual_image, erase_restore_mask));
|
||||
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));
|
||||
let blend_executor = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(blend_params)));
|
||||
actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await;
|
||||
}
|
||||
|
||||
ImageFrameTable::new(actual_image)
|
||||
|
@ -315,15 +343,13 @@ mod test {
|
|||
use super::*;
|
||||
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::value::ClonedNode;
|
||||
|
||||
use glam::DAffine2;
|
||||
|
||||
#[test]
|
||||
fn test_brush_texture() {
|
||||
let brush_texture_node = BrushStampGeneratorNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(100.), ClonedNode::new(100.));
|
||||
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.)));
|
||||
// center pixel should be BLACK
|
||||
assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));
|
||||
|
|
|
@ -1,28 +1,14 @@
|
|||
use graph_craft::proto::types::Percentage;
|
||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::Color;
|
||||
use graphene_core::{Color, Ctx};
|
||||
|
||||
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
|
||||
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
|
||||
use std::cmp::{max, min};
|
||||
|
||||
#[node_macro::node(category("Raster: Filter"))]
|
||||
async fn dehaze<F: 'n + Send + Sync>(
|
||||
#[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;
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Percentage) -> ImageFrameTable<Color> {
|
||||
let image_frame = image_frame.one_item();
|
||||
|
||||
// Prepare the image data for processing
|
||||
|
|
|
@ -19,7 +19,7 @@ use crate::wasm_application_io::WasmApplicationIo;
|
|||
|
||||
// TODO: Move to graph-craft
|
||||
#[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 compiler = graph_craft::graphene_compiler::Compiler {};
|
||||
let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() };
|
||||
|
@ -279,7 +279,7 @@ where
|
|||
}
|
||||
|
||||
#[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 background = background.one_item();
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use graphene_core::Ctx;
|
||||
|
||||
#[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()
|
||||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
use graphene_core::raster::image::ImageFrameTable;
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::Color;
|
||||
use graphene_core::{Color, Ctx};
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn image_color_palette<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> ImageFrameTable<Color>,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
)]
|
||||
image: impl Node<F, Output = ImageFrameTable<Color>>,
|
||||
async fn image_color_palette(
|
||||
_: impl Ctx,
|
||||
image: ImageFrameTable<Color>,
|
||||
#[min(1.)]
|
||||
#[max(28.)]
|
||||
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 colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
|
||||
|
||||
let image = image.eval(footprint).await;
|
||||
let image = image.one_item();
|
||||
|
||||
for pixel in image.image.data.iter() {
|
||||
|
@ -75,30 +65,24 @@ async fn image_color_palette<F: 'n + Send>(
|
|||
mod test {
|
||||
use super::*;
|
||||
|
||||
use graph_craft::generic::FnNode;
|
||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::value::CopiedNode;
|
||||
use graphene_core::Node;
|
||||
|
||||
#[test]
|
||||
fn test_image_color_palette() {
|
||||
let node = ImageColorPaletteNode {
|
||||
max_size: CopiedNode(1u32),
|
||||
image: FnNode::new(|_| {
|
||||
Box::pin(async move {
|
||||
ImageFrameTable::new(ImageFrame {
|
||||
image: Image {
|
||||
width: 100,
|
||||
height: 100,
|
||||
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
|
||||
base64_string: None,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
let result = image_color_palette(
|
||||
(),
|
||||
ImageFrameTable::new(ImageFrame {
|
||||
image: Image {
|
||||
width: 100,
|
||||
height: 100,
|
||||
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
|
||||
base64_string: None,
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
};
|
||||
assert_eq!(futures::executor::block_on(node.eval(())), [Color::from_rgbaf32(0., 0., 0., 1.).unwrap()]);
|
||||
1,
|
||||
);
|
||||
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::image::{ImageFrame, ImageFrameTable};
|
||||
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::{AlphaBlending, Color, Node};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node};
|
||||
|
||||
use fastnoise_lite;
|
||||
use glam::{DAffine2, DVec2, Vec2};
|
||||
|
@ -28,13 +29,14 @@ impl From<std::io::Error> for Error {
|
|||
}
|
||||
|
||||
#[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();
|
||||
|
||||
// Resize the image using the image crate
|
||||
let image = &image_frame.image;
|
||||
let data = bytemuck::cast_vec(image.data.clone());
|
||||
|
||||
let footprint = ctx.footprint();
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox();
|
||||
let intersection = viewport_bounds.intersect(&image_bounds);
|
||||
|
@ -107,15 +109,7 @@ where
|
|||
image
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
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>)]
|
||||
#[node_macro::node]
|
||||
fn insert_channel<
|
||||
// _P is the color of the input image.
|
||||
_P: RGBMut,
|
||||
|
@ -124,8 +118,9 @@ fn insert_channel<
|
|||
Input: BitmapMut<Pixel = _P>,
|
||||
Insertion: Bitmap<Pixel = _S>,
|
||||
>(
|
||||
mut image: Input,
|
||||
insertion: Insertion,
|
||||
_: impl Ctx,
|
||||
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||
#[implementations(ImageFrameTable<Color>)] insertion: Insertion,
|
||||
target_channel: RedGreenBlue,
|
||||
) -> Input
|
||||
where
|
||||
|
@ -154,15 +149,60 @@ where
|
|||
|
||||
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)]
|
||||
pub struct MaskImageNode<P, S, Stencil> {
|
||||
stencil: Stencil,
|
||||
_p: PhantomData<P>,
|
||||
_s: PhantomData<S>,
|
||||
if dimensions.iter().any(|&(x, y)| x != image.width() || y != image.height()) {
|
||||
log::warn!("Stencil and image have different sizes. This is not supported.");
|
||||
return image;
|
||||
}
|
||||
|
||||
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<
|
||||
// _P is the color of the input image. It must have an alpha channel because that is going to
|
||||
// be modified by the mask
|
||||
|
@ -175,8 +215,9 @@ fn mask_image<
|
|||
// Stencil
|
||||
Stencil: Transform + Sample<Pixel = _S>,
|
||||
>(
|
||||
mut image: Input,
|
||||
stencil: Stencil,
|
||||
_: impl Ctx,
|
||||
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||
#[implementations(ImageFrameTable<Color>)] stencil: Stencil,
|
||||
) -> Input {
|
||||
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
|
||||
let mask_size = stencil.transform().decompose_scale();
|
||||
|
@ -207,17 +248,17 @@ fn mask_image<
|
|||
image
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlendImageTupleNode<P, Fg, MapFn> {
|
||||
map_fn: MapFn,
|
||||
_p: PhantomData<P>,
|
||||
_fg: PhantomData<Fg>,
|
||||
}
|
||||
// #[derive(Debug, Clone, Copy)]
|
||||
// pub struct BlendImageTupleNode<P, Fg, MapFn> {
|
||||
// map_fn: MapFn,
|
||||
// _p: PhantomData<P>,
|
||||
// _fg: PhantomData<Fg>,
|
||||
// }
|
||||
|
||||
#[node_macro::old_node_fn(BlendImageTupleNode<_P, _Fg>)]
|
||||
fn blend_image_tuple<_P: Alpha + Pixel + Debug, MapFn, _Fg: Sample<Pixel = _P> + Transform>(images: (ImageFrame<_P>, _Fg), map_fn: &'input MapFn) -> ImageFrame<_P>
|
||||
#[node_macro::node(skip_impl)]
|
||||
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
|
||||
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;
|
||||
|
||||
|
@ -319,7 +360,7 @@ fn extend_image_to_bounds(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFr
|
|||
}
|
||||
|
||||
#[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 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,
|
||||
// }
|
||||
|
||||
#[node_macro::node(category("Raster: Generator"))]
|
||||
#[node_macro::node(category("Raster"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn noise_pattern(
|
||||
footprint: Footprint,
|
||||
ctx: impl ExtractFootprint + Ctx,
|
||||
_primary: (),
|
||||
clip: bool,
|
||||
seed: u32,
|
||||
|
@ -452,6 +493,7 @@ fn noise_pattern(
|
|||
cellular_return_type: CellularReturnType,
|
||||
cellular_jitter: f64,
|
||||
) -> ImageFrameTable<Color> {
|
||||
let footprint = ctx.footprint();
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
|
||||
let mut size = viewport_bounds.size();
|
||||
|
@ -585,8 +627,9 @@ fn noise_pattern(
|
|||
ImageFrameTable::new(result)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Generator"))]
|
||||
fn mandelbrot(footprint: Footprint) -> ImageFrameTable<Color> {
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
|
||||
let footprint = ctx.footprint();
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
|
||||
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 graphene_core::text::TypesettingConfig;
|
||||
pub use graphene_core::text::{bounding_box, load_face, to_path, Font, FontCache};
|
||||
use graphene_core::Ctx;
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn text<'i: 'n>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
editor: &'i WasmEditorApi,
|
||||
text: String,
|
||||
font_name: Font,
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use crate::transform::Footprint;
|
||||
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use graphene_core::vector::misc::BooleanOperation;
|
||||
use graphene_core::vector::style::Fill;
|
||||
pub use graphene_core::vector::*;
|
||||
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;
|
||||
use path_bool::{FillRule, PathBooleanOperation};
|
||||
|
||||
|
@ -13,22 +11,7 @@ use glam::{DAffine2, DVec2};
|
|||
use std::ops::Mul;
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn boolean_operation<F: 'n + Send>(
|
||||
#[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();
|
||||
|
||||
async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable {
|
||||
fn vector_from_image<T: Transform>(image_frame: T) -> VectorData {
|
||||
let corner1 = DVec2::ZERO;
|
||||
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
|
||||
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::transform::Footprint;
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::GraphicGroupTable;
|
||||
use graphene_core::{Color, WasmNotSend};
|
||||
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use base64::Engine;
|
||||
|
@ -27,7 +26,7 @@ use wasm_bindgen::JsCast;
|
|||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
||||
|
||||
#[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())
|
||||
}
|
||||
|
||||
|
@ -37,7 +36,7 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc<WasmSur
|
|||
// #[node_macro::node(category("Debug: GPU"))]
|
||||
// #[cfg(target_arch = "wasm32")]
|
||||
// async fn draw_image_frame(
|
||||
// _: (),
|
||||
// _: impl Ctx,
|
||||
// image: ImageFrameTable<graphene_core::raster::SRGBA8>,
|
||||
// surface_handle: Arc<WasmSurfaceHandle>,
|
||||
// ) -> 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"))]
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrameTable<Color> {
|
||||
#[node_macro::node(category("Network"))]
|
||||
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
|
||||
let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
|
||||
return ImageFrameTable::default();
|
||||
};
|
||||
|
@ -156,13 +155,13 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
|
|||
#[node_macro::node(category(""))]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
ImageFrameTable<Color>,
|
||||
GraphicGroupTable,
|
||||
)]
|
||||
data: impl Node<Footprint, Output = T>,
|
||||
mut data: T,
|
||||
footprint: Footprint,
|
||||
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
|
||||
) -> ImageFrameTable<Color> {
|
||||
|
@ -171,7 +170,6 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
|
|||
return ImageFrameTable::default();
|
||||
}
|
||||
|
||||
let mut data = data.eval(footprint).await;
|
||||
let mut render = SvgRender::new();
|
||||
let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox();
|
||||
let size = aabb.size();
|
||||
|
@ -218,31 +216,34 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
|
|||
#[node_macro::node(category(""))]
|
||||
async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
|
||||
render_config: RenderConfig,
|
||||
editor_api: &'a WasmEditorApi,
|
||||
editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>,
|
||||
#[implementations(
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> graphene_core::Artboard,
|
||||
Footprint -> graphene_core::ArtboardGroup,
|
||||
Footprint -> Option<Color>,
|
||||
Footprint -> Vec<Color>,
|
||||
Footprint -> bool,
|
||||
Footprint -> f32,
|
||||
Footprint -> f64,
|
||||
Footprint -> String,
|
||||
Context -> VectorDataTable,
|
||||
Context -> ImageFrameTable<Color>,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> graphene_core::Artboard,
|
||||
Context -> graphene_core::ArtboardGroup,
|
||||
Context -> Option<Color>,
|
||||
Context -> Vec<Color>,
|
||||
Context -> bool,
|
||||
Context -> f32,
|
||||
Context -> f64,
|
||||
Context -> String,
|
||||
)]
|
||||
data: impl Node<Footprint, Output = T>,
|
||||
_surface_handle: impl Node<(), Output = Option<wgpu_executor::WgpuSurface>>,
|
||||
data: impl Node<Context<'static>, Output = T>,
|
||||
_surface_handle: impl Node<Context<'static>, Output = Option<wgpu_executor::WgpuSurface>>,
|
||||
) -> RenderOutput {
|
||||
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 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"))]
|
||||
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();
|
||||
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
|
||||
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::proto::{NodeConstructor, TypeErasedBox};
|
||||
use graphene_core::fn_type;
|
||||
use graphene_core::ops::IdentityNode;
|
||||
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::structural::Then;
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::value::{ClonedNode, ValueNode};
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::{concrete, generic, Artboard, GraphicGroupTable};
|
||||
use graphene_core::{fn_type_fut, future};
|
||||
use graphene_core::{Cow, ProtoNodeIdentifier, Type};
|
||||
use graphene_core::{Node, NodeIO, NodeIOTypes};
|
||||
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
|
||||
use graphene_std::application_io::TextureFrame;
|
||||
use graphene_std::raster::*;
|
||||
use graphene_std::wasm_application_io::*;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::{GraphicElement, GraphicGroup};
|
||||
#[cfg(feature = "gpu")]
|
||||
use wgpu_executor::{ShaderInputFrame, WgpuExecutor};
|
||||
use wgpu_executor::{WgpuSurface, WindowHandle};
|
||||
|
||||
use glam::{DAffine2, DVec2, UVec2};
|
||||
use glam::{DVec2, UVec2};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
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 {
|
||||
// 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.
|
||||
|
@ -95,7 +50,7 @@ macro_rules! async_node {
|
|||
),*);
|
||||
// TODO: Propagate the future type through the node graph
|
||||
// let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(Type::Future(Box::new(concrete!($type)))))),*];
|
||||
let params = vec![$(fn_type!($arg, $type)),*];
|
||||
let params = vec![$(fn_type_fut!($arg, $type)),*];
|
||||
let mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params);
|
||||
node_io.call_argument = concrete!(<$input as StaticType>::Static);
|
||||
node_io
|
||||
|
@ -107,11 +62,11 @@ macro_rules! async_node {
|
|||
// TODO: turn into hashmap
|
||||
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
|
||||
let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
|
||||
(
|
||||
ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
|
||||
|_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
|
||||
NodeIOTypes::new(generic!(I), generic!(I), vec![]),
|
||||
),
|
||||
// (
|
||||
// ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
|
||||
// |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
|
||||
// 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<Color>>, input: ImageFrameTable<SRGBA8>, 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<GraphicGroupTable>, input: VectorDataTable, params: []),
|
||||
async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: ImageFrameTable<Color>, params: []),
|
||||
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Color>]),
|
||||
register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Color>, RedGreenBlue]),
|
||||
// register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Luma>]),
|
||||
// register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Luma>, RedGreenBlue]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => TextureFrame]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
|
||||
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| {
|
||||
Box::pin(async move {
|
||||
use graphene_core::raster::*;
|
||||
use graphene_core::value::*;
|
||||
|
||||
let channel_r: ImageFrameTable<Color> = DowncastBothNode::new(args[0].clone()).eval(()).await;
|
||||
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()
|
||||
let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[0].clone());
|
||||
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(editor_api);
|
||||
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
Box::new(any) as TypeErasedBox
|
||||
})
|
||||
},
|
||||
NodeIOTypes::new(
|
||||
concrete!(()),
|
||||
concrete!(ImageFrameTable<Color>),
|
||||
vec![
|
||||
fn_type!(ImageFrameTable<Color>),
|
||||
fn_type!(ImageFrameTable<Color>),
|
||||
fn_type!(ImageFrameTable<Color>),
|
||||
fn_type!(ImageFrameTable<Color>),
|
||||
],
|
||||
),
|
||||
{
|
||||
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, dyn_any::DynFuture<'static, &WasmEditorApi>>::new());
|
||||
let params = vec![fn_type_fut!(Context, &WasmEditorApi)];
|
||||
let mut node_io = <wgpu_executor::CreateGpuSurfaceNode<_> as NodeIO<'_, Context>>::to_async_node_io(&node, params);
|
||||
node_io.call_argument = concrete!(<Context as StaticType>::Static);
|
||||
node_io
|
||||
},
|
||||
),
|
||||
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")]
|
||||
(
|
||||
ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"),
|
||||
|
@ -202,7 +110,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||
Box::pin(async move {
|
||||
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].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 any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
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)]),
|
||||
// ),
|
||||
// TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color.
|
||||
(
|
||||
ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
|
||||
|args| {
|
||||
use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
|
||||
let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
|
||||
Box::pin(async move {
|
||||
let curve = ClonedNode::new(curve.eval(()).await);
|
||||
// (
|
||||
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
|
||||
// |args| {
|
||||
// use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
|
||||
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
|
||||
// Box::pin(async move {
|
||||
// let curve = ValueNode::new(ClonedNode::new(curve.eval(()).await));
|
||||
|
||||
let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32));
|
||||
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
|
||||
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
|
||||
let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
|
||||
any.into_type_erased()
|
||||
})
|
||||
},
|
||||
NodeIOTypes::new(
|
||||
concrete!(ImageFrameTable<Color>),
|
||||
concrete!(ImageFrameTable<Color>),
|
||||
vec![fn_type!(graphene_core::raster::curve::Curve)],
|
||||
),
|
||||
),
|
||||
// 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(FutureWrapperNode::new(ValueNode::new(generate_curves_node.eval(()))));
|
||||
// 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);
|
||||
// any.into_type_erased()
|
||||
// })
|
||||
// },
|
||||
// NodeIOTypes::new(
|
||||
// concrete!(ImageFrameTable<Color>),
|
||||
// concrete!(ImageFrameTable<Color>),
|
||||
// vec![fn_type!(graphene_core::raster::curve::Curve)],
|
||||
// ),
|
||||
// ),
|
||||
// (
|
||||
// ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"),
|
||||
// |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: (), params: [VectorDataTable]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ImageFrameTable<Color>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Vec<DVec2>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [WindowHandle]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
|
||||
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")]
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ShaderInputFrame]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ShaderInputFrame]),
|
||||
#[cfg(feature = "gpu")]
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [wgpu_executor::WgpuSurface]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Option<wgpu_executor::WgpuSurface>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [wgpu_executor::WindowHandle]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [graphene_std::SurfaceFrame]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [RenderOutput]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Image<Color>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => 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: Context, fn_params: [Context => 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: Context, fn_params: [Context => wgpu_executor::WindowHandle]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::SurfaceFrame]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => RenderOutput]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroup]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroup]),
|
||||
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")]
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => WgpuSurface]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]),
|
||||
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ShaderInputFrame]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => TextureFrame]),
|
||||
];
|
||||
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
||||
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use graph_craft::{
|
||||
concrete,
|
||||
document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork},
|
||||
generic,
|
||||
wasm_application_io::WasmEditorApi,
|
||||
ProtoNodeIdentifier,
|
||||
};
|
||||
use graphene_std::{transform::Footprint, uuid::NodeId};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
||||
use graph_craft::generic;
|
||||
use graph_craft::wasm_application_io::WasmEditorApi;
|
||||
use graph_craft::ProtoNodeIdentifier;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::Context;
|
||||
|
||||
// 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 {
|
||||
|
@ -27,18 +26,18 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
|||
|
||||
let render_node = graph_craft::document::DocumentNode {
|
||||
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)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(())),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..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)),
|
||||
inputs: vec![
|
||||
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),
|
||||
],
|
||||
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 input_ident = &input.pat_ident;
|
||||
let input_type = &input.ty;
|
||||
|
||||
let field_idents: Vec<_> = fields
|
||||
.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
|
||||
.iter()
|
||||
.map(|field| match field {
|
||||
ParsedField::Regular { ty, .. } => ty.clone(),
|
||||
ParsedField::Node { output_type, input_type, .. } => match parsed.is_async {
|
||||
true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output: core::future::Future<Output=#output_type> + #graphene_core::WasmNotSend>),
|
||||
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>),
|
||||
},
|
||||
})
|
||||
|
@ -164,7 +165,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
let eval_args = fields.iter().map(|field| match field {
|
||||
ParsedField::Regular { pat_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, .. } => {
|
||||
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 input_type = &parsed.input.ty;
|
||||
let mut clauses = Vec::new();
|
||||
for (field, name) in fields.iter().zip(struct_generics.iter()) {
|
||||
clauses.push(match (field, *is_async) {
|
||||
(ParsedField::Regular { ty, .. }, _) => quote!(#name: #graphene_core::Node<'n, (), Output = #ty> ),
|
||||
(ParsedField::Node { input_type, output_type, .. }, false) => {
|
||||
quote!(for<'all_input> #name: #graphene_core::Node<'all_input, #input_type, Output = #output_type> + #graphene_core::WasmNotSync)
|
||||
(ParsedField::Regular { ty, .. }, _) => {
|
||||
let all_lifetime_ty = substitute_lifetimes(ty.clone(), "all");
|
||||
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) => {
|
||||
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 {
|
||||
|
@ -210,24 +227,16 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
});
|
||||
|
||||
let async_keyword = is_async.then(|| quote!(async));
|
||||
let await_keyword = is_async.then(|| quote!(.await));
|
||||
|
||||
let eval_impl = if *is_async {
|
||||
quote! {
|
||||
type Output = #graphene_core::registry::DynFuture<'n, #output_type>;
|
||||
#[inline]
|
||||
fn eval(&'n self, __input: #input_type) -> Self::Output {
|
||||
let eval_impl = quote! {
|
||||
type Output = #graphene_core::registry::DynFuture<'n, #output_type>;
|
||||
#[inline]
|
||||
fn eval(&'n self, __input: #input_type) -> Self::Output {
|
||||
Box::pin(async move {
|
||||
#(#eval_args)*
|
||||
Box::pin(self::#fn_name(__input #(, #field_names)*))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
type Output = #output_type;
|
||||
#[inline]
|
||||
fn eval(&'n self, __input: #input_type) -> Self::Output {
|
||||
#(#eval_args)*
|
||||
self::#fn_name(__input #(, #field_names)*)
|
||||
}
|
||||
self::#fn_name(__input #(, #field_names)*) #await_keyword
|
||||
})
|
||||
}
|
||||
};
|
||||
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]
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
|
||||
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]
|
||||
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
|
||||
{
|
||||
#eval_impl
|
||||
|
@ -260,7 +269,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
mod #mod_name {
|
||||
use super::*;
|
||||
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::ops::TypeNode;
|
||||
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 unit = parse_quote!(());
|
||||
let unit = parse_quote!(gcore::Context);
|
||||
let parameter_types: Vec<_> = parsed
|
||||
.fields
|
||||
.iter()
|
||||
|
@ -331,9 +340,9 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
|||
match field {
|
||||
ParsedField::Regular { implementations, ty, .. } => {
|
||||
if !implementations.is_empty() {
|
||||
implementations.iter().map(|ty| (&unit, ty, false)).collect()
|
||||
implementations.iter().map(|ty| (&unit, ty)).collect()
|
||||
} else {
|
||||
vec![(&unit, ty, false)]
|
||||
vec![(&unit, ty)]
|
||||
}
|
||||
}
|
||||
ParsedField::Node {
|
||||
|
@ -343,20 +352,19 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
|||
..
|
||||
} => {
|
||||
if !implementations.is_empty() {
|
||||
implementations.iter().map(|impl_| (&impl_.input, &impl_.output, true)).collect()
|
||||
implementations.iter().map(|impl_| (&impl_.input, &impl_.output)).collect()
|
||||
} else {
|
||||
vec![(input_type, output_type, true)]
|
||||
vec![(input_type, output_type)]
|
||||
}
|
||||
}
|
||||
}
|
||||
.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();
|
||||
|
||||
let max_implementations = parameter_types.iter().map(|x| x.len()).chain([parsed.input.implementations.len().max(1)]).max();
|
||||
let future_node = (!parsed.is_async).then(|| quote!(let node = gcore::registry::FutureWrapperNode::new(node);));
|
||||
|
||||
for i in 0..max_implementations.unwrap_or(0) {
|
||||
let mut temp_constructors = Vec::new();
|
||||
|
@ -365,38 +373,24 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
|||
|
||||
for (j, types) in parameter_types.iter().enumerate() {
|
||||
let field_name = field_names[j];
|
||||
let (input_type, output_type, impl_node) = &types[i.min(types.len() - 1)];
|
||||
let (input_type, output_type) = &types[i.min(types.len() - 1)];
|
||||
|
||||
let node = matches!(parsed.fields[j], ParsedField::Node { .. });
|
||||
|
||||
let downcast_node = quote!(
|
||||
let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone());
|
||||
);
|
||||
temp_constructors.push(if node {
|
||||
if !parsed.is_async {
|
||||
return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters"));
|
||||
}
|
||||
downcast_node
|
||||
} else {
|
||||
quote!(
|
||||
#downcast_node
|
||||
let #field_name = #field_name.eval(()).await;
|
||||
let #field_name = ClonedNode::new(#field_name);
|
||||
let #field_name: TypeNode<_, #input_type, #output_type> = TypeNode::new(#field_name);
|
||||
// try polling futures
|
||||
)
|
||||
});
|
||||
temp_node_io.push(quote!(fn_type!(#input_type, #output_type, alias: #output_type)));
|
||||
match parsed.is_async && *impl_node {
|
||||
true => panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>)),
|
||||
false => panic_node_types.push(quote!(#input_type, #output_type)),
|
||||
};
|
||||
let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone());
|
||||
);
|
||||
if node && !parsed.is_async {
|
||||
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);
|
||||
temp_node_io.push(quote!(fn_type_fut!(#input_type, #output_type, alias: #output_type)));
|
||||
panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>));
|
||||
}
|
||||
let input_type = match parsed.input.implementations.is_empty() {
|
||||
true => parsed.input.ty.clone(),
|
||||
false => parsed.input.implementations[i.min(parsed.input.implementations.len() - 1)].clone(),
|
||||
};
|
||||
let node_io = if parsed.is_async { quote!(to_async_node_io) } else { quote!(to_node_io) };
|
||||
constructors.push(quote!(
|
||||
(
|
||||
|args| {
|
||||
|
@ -404,14 +398,13 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
|||
#(#temp_constructors;)*
|
||||
let node = #struct_name::new(#(#field_names,)*);
|
||||
// try polling futures
|
||||
#future_node
|
||||
let any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node);
|
||||
Box::new(any) as TypeErasedBox<'_>
|
||||
})
|
||||
}, {
|
||||
let node = #struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*);
|
||||
let params = vec![#(#temp_node_io,)*];
|
||||
let mut node_io = NodeIO::<'_, #input_type>::#node_io(&node, params);
|
||||
let mut node_io = NodeIO::<'_, #input_type>::to_async_node_io(&node, params);
|
||||
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};
|
||||
|
||||
struct LifetimeReplacer;
|
||||
struct LifetimeReplacer(&'static str);
|
||||
|
||||
impl VisitMut for LifetimeReplacer {
|
||||
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) {
|
||||
|
@ -472,7 +465,7 @@ impl VisitMut for LifetimeReplacer {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
fn substitute_lifetimes(mut ty: Type) -> Type {
|
||||
LifetimeReplacer.visit_type_mut(&mut ty);
|
||||
fn substitute_lifetimes(mut ty: Type, lifetime: &'static str) -> Type {
|
||||
LifetimeReplacer(lifetime).visit_type_mut(&mut ty);
|
||||
ty
|
||||
}
|
||||
|
|
|
@ -4,8 +4,11 @@ use proc_macro2::TokenStream as TokenStream2;
|
|||
use quote::{format_ident, ToTokens};
|
||||
use syn::parse::{Parse, ParseStream, Parser};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
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;
|
||||
|
||||
|
@ -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
|
||||
pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
|
||||
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();
|
||||
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) {
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -774,7 +804,7 @@ mod tests {
|
|||
let attr = quote!(category("Vector: Shape"));
|
||||
let input = quote!(
|
||||
/// Test
|
||||
fn circle(_: (), #[default(50.)] radius: f64) -> VectorData {
|
||||
fn circle(_: impl Ctx, #[default(50.)] radius: f64) -> VectorData {
|
||||
// Implementation details...
|
||||
}
|
||||
);
|
||||
|
@ -795,7 +825,7 @@ mod tests {
|
|||
where_clause: None,
|
||||
input: Input {
|
||||
pat_ident: pat_ident("_"),
|
||||
ty: parse_quote!(()),
|
||||
ty: parse_quote!(impl Ctx),
|
||||
implementations: Punctuated::new(),
|
||||
},
|
||||
output_type: parse_quote!(VectorData),
|
||||
|
|
|
@ -6,9 +6,11 @@ pub use executor::GpuExecutor;
|
|||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
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::{Color, Cow, Node, SurfaceFrame, Type};
|
||||
use graphene_core::{Color, Cow, Ctx, ExtractFootprint, Node, SurfaceFrame, Type};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use futures::Future;
|
||||
|
@ -823,12 +825,12 @@ impl<T> ShaderInputNode<T> {
|
|||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
|
||||
#[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
|
||||
.create_storage_buffer(
|
||||
data,
|
||||
|
@ -843,18 +845,18 @@ async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: (), #[implementation
|
|||
}
|
||||
|
||||
#[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())
|
||||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug: GPU"))]
|
||||
async fn create_pipeline_layout(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
shader: impl Node<(), Output = ShaderHandle>,
|
||||
entry_point: String,
|
||||
bind_group: impl Node<(), Output = Bindgroup>,
|
||||
|
@ -869,14 +871,14 @@ async fn create_pipeline_layout(
|
|||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
|
||||
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
|
||||
|
||||
#[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 executor = editor_api.application_io.as_ref()?.gpu_executor()?;
|
||||
Some(Arc::new(executor.create_surface(canvas).ok()?))
|
||||
|
@ -889,7 +891,13 @@ pub struct ShaderInputFrame {
|
|||
}
|
||||
|
||||
#[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_id = surface.window_id;
|
||||
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(""))]
|
||||
// async fn upload_texture<'a: 'n, F: Copy + Send + Sync + 'n>(
|
||||
// #[implementations((), Footprint)] footprint: F,
|
||||
// #[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();
|
||||
#[node_macro::node(category(""))]
|
||||
async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable<Color>, executor: &'a WgpuExecutor) -> TextureFrame {
|
||||
// let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect();
|
||||
|
||||
// let new_data: Vec<SRGBA8> = input.image.data.iter().map(|x| (*x).into()).collect();
|
||||
// let new_image = Image {
|
||||
// width: input.image.width,
|
||||
// height: input.image.height,
|
||||
// data: new_data,
|
||||
// base64_string: None,
|
||||
// };
|
||||
let input = input.one_item();
|
||||
let new_data: Vec<SRGBA8> = input.image.data.iter().map(|x| (*x).into()).collect();
|
||||
let new_image = Image {
|
||||
width: input.image.width,
|
||||
height: input.image.height,
|
||||
data: new_data,
|
||||
base64_string: None,
|
||||
};
|
||||
|
||||
// let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap();
|
||||
// let texture = match shader_input {
|
||||
// ShaderInput::TextureBuffer(buffer, _) => buffer,
|
||||
// ShaderInput::StorageTextureBuffer(buffer, _) => buffer,
|
||||
// _ => unreachable!("Unsupported ShaderInput type"),
|
||||
// };
|
||||
let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap();
|
||||
let texture = match shader_input {
|
||||
ShaderInput::TextureBuffer(buffer, _) => buffer,
|
||||
ShaderInput::StorageTextureBuffer(buffer, _) => buffer,
|
||||
_ => unreachable!("Unsupported ShaderInput type"),
|
||||
};
|
||||
|
||||
// TextureFrame {
|
||||
// texture: texture.into(),
|
||||
// transform: input.transform,
|
||||
// alpha_blend: Default::default(),
|
||||
// }
|
||||
// }
|
||||
TextureFrame {
|
||||
texture: texture.into(),
|
||||
transform: input.transform,
|
||||
alpha_blend: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ Next, install the dependencies required for development builds:
|
|||
```sh
|
||||
cargo install cargo-watch
|
||||
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).
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue