diff --git a/Cargo.lock b/Cargo.lock index 3105ea07e4..44494cbcae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,7 +92,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "thiserror 1.0.69", ] @@ -914,6 +914,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -927,8 +937,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", "foreign-types 0.5.0", "libc", ] @@ -940,7 +950,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.3", + "core-foundation 0.10.1", "libc", ] @@ -1072,9 +1093,9 @@ checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" dependencies = [ "powerfmt", ] @@ -1414,6 +1435,26 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -2108,7 +2149,7 @@ dependencies = [ "bytemuck", "cef", "cef-dll-sys", - "core-foundation", + "core-foundation 0.9.4", "derivative", "dirs", "futures", @@ -2598,9 +2639,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.6" +version = "0.25.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +checksum = "1c6a3ce16143778e24df6f95365f12ed105425b22abefd289dd88a64bab59605" dependencies = [ "bytemuck", "byteorder-lite", @@ -2608,8 +2649,9 @@ dependencies = [ "exr", "gif", "image-webp 0.2.4", + "moxcms", "num-traits", - "png", + "png 0.18.0", "qoi", "ravif", "rayon", @@ -2861,12 +2903,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jpeg-decoder" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" - [[package]] name = "js-sys" version = "0.3.77" @@ -3115,13 +3151,13 @@ dependencies = [ [[package]] name = "metal" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ "bitflags 2.9.3", "block", - "core-graphics-types", + "core-graphics-types 0.2.0", "foreign-types 0.5.0", "log", "objc", @@ -3161,28 +3197,39 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "moxcms" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "naga" -version = "25.0.1" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +checksum = "916cbc7cb27db60be930a4e2da243cf4bc39569195f22fd8ee419cd31d5b662c" dependencies = [ "arrayvec", "bit-set", "bitflags 2.9.3", + "cfg-if", "cfg_aliases", "codespan-reporting", "half", "hashbrown", "hexf-parse", "indexmap", + "libm", "log", "num-traits", "once_cell", "petgraph 0.8.2", "rustc-hash 1.1.0", "spirv", - "strum 0.26.3", "thiserror 2.0.16", "unicode-ident", ] @@ -3228,7 +3275,7 @@ dependencies = [ "bitflags 2.9.3", "jni-sys", "log", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "raw-window-handle", "thiserror 1.0.69", @@ -3240,15 +3287,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys", -] - [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -3288,7 +3326,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "strum 0.27.2", + "strum", "syn 2.0.106", ] @@ -3974,7 +4012,6 @@ dependencies = [ "fixedbitset", "hashbrown", "indexmap", - "serde", ] [[package]] @@ -4073,6 +4110,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.9.3", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.10.0" @@ -4226,6 +4276,15 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "pxfm" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e790881194f6f6e86945f0a42a6981977323669aeb6c40e9c7ec253133b96f8" +dependencies = [ + "num-traits", +] + [[package]] name = "qoi" version = "0.4.1" @@ -4873,7 +4932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.3", - "core-foundation", + "core-foundation 0.9.4", "core-foundation-sys", "libc", "security-framework-sys", @@ -5208,35 +5267,13 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", -] - [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros 0.27.2", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.106", + "strum_macros", ] [[package]] @@ -5339,7 +5376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.3", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5456,23 +5493,25 @@ dependencies = [ [[package]] name = "tiff" -version = "0.9.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" dependencies = [ + "fax", "flate2", - "jpeg-decoder", + "half", + "quick-error", "weezl", + "zune-jpeg", ] [[package]] name = "time" -version = "0.3.41" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "8ca967379f9d8eb8058d86ed467d81d03e81acd45757e4ca341c24affbe8e8e3" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde", @@ -5482,15 +5521,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "a9108bb380861b07264b950ded55a44a14a4adc68b9f5efd85aafc3aa4d40a68" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "7182799245a7264ce590b349d90338f1c1affad93d2639aed5f8f69c090b334c" dependencies = [ "num-conv", "time-core", @@ -5507,7 +5546,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", - "png", + "png 0.17.16", "tiny-skia-path", ] @@ -6043,14 +6082,14 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vello" version = "0.5.0" -source = "git+https://github.com/linebender/vello.git?rev=87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b#87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" +source = "git+https://github.com/DJMcNab/vello.git?branch=luminance-mask-layer#2a6e445d9de397c1b552024cddfd8cb0d5881eaa" dependencies = [ "bytemuck", "futures-intrusive", "log", "peniko", - "png", - "skrifa 0.31.3", + "png 0.17.16", + "skrifa 0.36.0", "static_assertions", "thiserror 2.0.16", "vello_encoding", @@ -6061,19 +6100,19 @@ dependencies = [ [[package]] name = "vello_encoding" version = "0.5.0" -source = "git+https://github.com/linebender/vello.git?rev=87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b#87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" +source = "git+https://github.com/DJMcNab/vello.git?branch=luminance-mask-layer#2a6e445d9de397c1b552024cddfd8cb0d5881eaa" dependencies = [ "bytemuck", "guillotiere", "peniko", - "skrifa 0.31.3", + "skrifa 0.36.0", "smallvec", ] [[package]] name = "vello_shaders" version = "0.5.0" -source = "git+https://github.com/linebender/vello.git?rev=87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b#87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" +source = "git+https://github.com/DJMcNab/vello.git?branch=luminance-mask-layer#2a6e445d9de397c1b552024cddfd8cb0d5881eaa" dependencies = [ "bytemuck", "log", @@ -6345,12 +6384,13 @@ checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" -version = "25.0.2" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9" +checksum = "70b6ff82bbf6e9206828e1a3178e851f8c20f1c9028e74dd3a8090741ccd5798" dependencies = [ "arrayvec", "bitflags 2.9.3", + "cfg-if", "cfg_aliases", "document-features", "hashbrown", @@ -6373,9 +6413,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "25.0.2" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500" +checksum = "d5f62f1053bd28c2268f42916f31588f81f64796e2ff91b81293515017ca8bd9" dependencies = [ "arrayvec", "bit-set", @@ -6405,27 +6445,27 @@ dependencies = [ [[package]] name = "wgpu-core-deps-apple" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d" +checksum = "18ae5fbde6a4cbebae38358aa73fcd6e0f15c6144b67ef5dc91ded0db125dbdf" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-emscripten" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445" +checksum = "d7670e390f416006f746b4600fdd9136455e3627f5bd763abf9a65daa216dd2d" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46" +checksum = "720a5cb9d12b3d337c15ff0e24d3e97ed11490ff3f7506e7f3d98c68fa5d6f14" dependencies = [ "wgpu-hal", ] @@ -6451,9 +6491,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "25.0.2" +version = "26.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17" +checksum = "7df2c64ac282a91ad7662c90bc4a77d4a2135bc0b2a2da5a4d4e267afc034b9e" dependencies = [ "android_system_properties", "arrayvec", @@ -6464,7 +6504,7 @@ dependencies = [ "bytemuck", "cfg-if", "cfg_aliases", - "core-graphics-types", + "core-graphics-types 0.2.0", "glow", "glutin_wgl_sys", "gpu-alloc", @@ -6478,11 +6518,12 @@ dependencies = [ "log", "metal", "naga", - "ndk-sys 0.5.0+25.2.9519653", + "ndk-sys", "objc", "ordered-float", "parking_lot", "portable-atomic", + "portable-atomic-util", "profiling", "range-alloc", "raw-window-handle", @@ -6498,9 +6539,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" +checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2" dependencies = [ "bitflags 2.9.3", "bytemuck", @@ -6912,7 +6953,7 @@ dependencies = [ "calloop", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", diff --git a/Cargo.toml b/Cargo.toml index 94671aec4c..6a8db38de7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ quote = "1.0" chrono = "0.4" ron = "0.11" fastnoise-lite = "1.1" -wgpu = { version = "25.0", features = [ +wgpu = { version = "26.0.1", features = [ # We don't have wgpu on multiple threads (yet) https://github.com/gfx-rs/wgpu/blob/trunk/CHANGELOG.md#wgpu-types-now-send-sync-on-wasm "fragile-send-sync-non-atomic-wasm", "spirv", @@ -132,7 +132,7 @@ web-sys = { version = "=0.3.77", features = [ winit = { version = "0.30", features = ["wayland", "rwh_06"] } url = "2.5" tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] } -vello = { git = "https://github.com/linebender/vello.git", rev = "87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" } # TODO switch back to stable when a release is made +vello = { git = "https://github.com/DJMcNab/vello.git", branch = "luminance-mask-layer" } # TODO switch back to stable when a release is made resvg = "0.45" usvg = "0.45" rand = { version = "0.9", default-features = false, features = ["std_rng"] } diff --git a/node-graph/gcore/src/artboard.rs b/node-graph/gcore/src/artboard.rs index a2fb0a779a..168259d99f 100644 --- a/node-graph/gcore/src/artboard.rs +++ b/node-graph/gcore/src/artboard.rs @@ -79,6 +79,7 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re for (artboard, source_node_id) in artboard_group.artboards { table.push(TableRow { element: artboard, + mask: None, transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::default(), source_node_id, diff --git a/node-graph/gcore/src/graphic.rs b/node-graph/gcore/src/graphic.rs index f902826c99..a2a945b150 100644 --- a/node-graph/gcore/src/graphic.rs +++ b/node-graph/gcore/src/graphic.rs @@ -378,6 +378,7 @@ async fn flatten_graphic(_: impl Ctx, content: Table, fully_flatten: bo _ => { output_graphic_table.push(TableRow { element: current_element, + mask: current_row.mask.clone(), transform: *current_row.transform, alpha_blending: *current_row.alpha_blending, source_node_id: reference, @@ -416,6 +417,7 @@ async fn flatten_vector(_: impl Ctx, content: Table) -> Table { for current_vector_row in vector_table.iter() { output_vector_table.push(TableRow { element: current_vector_row.element.clone(), + mask: current_vector_row.mask.clone(), transform: *current_graphic_row.transform * *current_vector_row.transform, alpha_blending: AlphaBlending { blend_mode: current_vector_row.alpha_blending.blend_mode, @@ -520,6 +522,7 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res for (graphic, source_node_id) in old.elements { graphic_table.push(TableRow { element: graphic, + mask: None, transform: old.transform, alpha_blending: old.alpha_blending, source_node_id, @@ -535,6 +538,7 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res for (graphic, source_node_id) in &row.element.elements { graphic_table.push(TableRow { element: graphic.clone(), + mask: None, transform: *row.transform, alpha_blending: *row.alpha_blending, source_node_id: *source_node_id, diff --git a/node-graph/gcore/src/raster/image.rs b/node-graph/gcore/src/raster/image.rs index c58d493af8..d6c7e10200 100644 --- a/node-graph/gcore/src/raster/image.rs +++ b/node-graph/gcore/src/raster/image.rs @@ -393,6 +393,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D }, FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => TableRow { element: Raster::new_cpu(image_frame_with_transform_and_blending.image), + mask: None, transform: image_frame_with_transform_and_blending.transform, alpha_blending: image_frame_with_transform_and_blending.alpha_blending, source_node_id: None, diff --git a/node-graph/gcore/src/table.rs b/node-graph/gcore/src/table.rs index 761d708748..95e00d3b23 100644 --- a/node-graph/gcore/src/table.rs +++ b/node-graph/gcore/src/table.rs @@ -1,3 +1,4 @@ +use crate::Graphic; use crate::bounds::{BoundingBox, RenderBoundingBox}; use crate::transform::ApplyTransform; use crate::uuid::NodeId; @@ -6,10 +7,13 @@ use dyn_any::StaticType; use glam::DAffine2; use std::hash::Hash; +pub type Mask = Option; + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct Table { #[serde(alias = "instances", alias = "instance")] element: Vec, + mask: Vec, transform: Vec, alpha_blending: Vec, source_node_id: Vec>, @@ -23,6 +27,7 @@ impl Table { pub fn with_capacity(capacity: usize) -> Self { Self { element: Vec::with_capacity(capacity), + mask: Vec::with_capacity(capacity), transform: Vec::with_capacity(capacity), alpha_blending: Vec::with_capacity(capacity), source_node_id: Vec::with_capacity(capacity), @@ -32,6 +37,7 @@ impl Table { pub fn new_from_element(element: T) -> Self { Self { element: vec![element], + mask: vec![None], transform: vec![DAffine2::IDENTITY], alpha_blending: vec![AlphaBlending::default()], source_node_id: vec![None], @@ -41,6 +47,7 @@ impl Table { pub fn new_from_row(row: TableRow) -> Self { Self { element: vec![row.element], + mask: vec![row.mask], transform: vec![row.transform], alpha_blending: vec![row.alpha_blending], source_node_id: vec![row.source_node_id], @@ -49,6 +56,7 @@ impl Table { pub fn push(&mut self, row: TableRow) { self.element.push(row.element); + self.mask.push(row.mask); self.transform.push(row.transform); self.alpha_blending.push(row.alpha_blending); self.source_node_id.push(row.source_node_id); @@ -56,6 +64,7 @@ impl Table { pub fn extend(&mut self, table: Table) { self.element.extend(table.element); + self.mask.extend(table.mask); self.transform.extend(table.transform); self.alpha_blending.extend(table.alpha_blending); self.source_node_id.extend(table.source_node_id); @@ -68,6 +77,7 @@ impl Table { Some(TableRowRef { element: &self.element[index], + mask: &self.mask[index], transform: &self.transform[index], alpha_blending: &self.alpha_blending[index], source_node_id: &self.source_node_id[index], @@ -81,6 +91,7 @@ impl Table { Some(TableRowMut { element: &mut self.element[index], + mask: &mut self.mask[index], transform: &mut self.transform[index], alpha_blending: &mut self.alpha_blending[index], source_node_id: &mut self.source_node_id[index], @@ -99,11 +110,13 @@ impl Table { pub fn iter(&self) -> impl DoubleEndedIterator> + Clone { self.element .iter() + .zip(self.mask.iter()) .zip(self.transform.iter()) .zip(self.alpha_blending.iter()) .zip(self.source_node_id.iter()) - .map(|(((element, transform), alpha_blending), source_node_id)| TableRowRef { + .map(|((((element, mask), transform), alpha_blending), source_node_id)| TableRowRef { element, + mask, transform, alpha_blending, source_node_id, @@ -114,11 +127,13 @@ impl Table { pub fn iter_mut(&mut self) -> impl DoubleEndedIterator> { self.element .iter_mut() + .zip(self.mask.iter_mut()) .zip(self.transform.iter_mut()) .zip(self.alpha_blending.iter_mut()) .zip(self.source_node_id.iter_mut()) - .map(|(((element, transform), alpha_blending), source_node_id)| TableRowMut { + .map(|((((element, mask), transform), alpha_blending), source_node_id)| TableRowMut { element, + mask, transform, alpha_blending, source_node_id, @@ -156,6 +171,7 @@ impl IntoIterator for Table { fn into_iter(self) -> Self::IntoIter { TableRowIter { element: self.element.into_iter(), + mask: self.mask.into_iter(), transform: self.transform.into_iter(), alpha_blending: self.alpha_blending.into_iter(), source_node_id: self.source_node_id.into_iter(), @@ -165,6 +181,7 @@ impl IntoIterator for Table { pub struct TableRowIter { element: std::vec::IntoIter, + mask: std::vec::IntoIter, transform: std::vec::IntoIter, alpha_blending: std::vec::IntoIter, source_node_id: std::vec::IntoIter>, @@ -174,12 +191,14 @@ impl Iterator for TableRowIter { fn next(&mut self) -> Option { let element = self.element.next()?; + let mask = self.mask.next()?; let transform = self.transform.next()?; let alpha_blending = self.alpha_blending.next()?; let source_node_id = self.source_node_id.next()?; Some(TableRow { element, + mask, transform, alpha_blending, source_node_id, @@ -191,6 +210,7 @@ impl Default for Table { fn default() -> Self { Self { element: Vec::new(), + mask: Vec::new(), transform: Vec::new(), alpha_blending: Vec::new(), source_node_id: Vec::new(), @@ -242,10 +262,11 @@ impl FromIterator> for Table { } } -#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TableRow { #[serde(alias = "instance")] pub element: T, + pub mask: Mask, pub transform: DAffine2, pub alpha_blending: AlphaBlending, pub source_node_id: Option, @@ -255,6 +276,7 @@ impl TableRow { pub fn new_from_element(element: T) -> Self { Self { element, + mask: None, transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::default(), source_node_id: None, @@ -264,6 +286,7 @@ impl TableRow { pub fn as_ref(&self) -> TableRowRef<'_, T> { TableRowRef { element: &self.element, + mask: &self.mask, transform: &self.transform, alpha_blending: &self.alpha_blending, source_node_id: &self.source_node_id, @@ -273,6 +296,7 @@ impl TableRow { pub fn as_mut(&mut self) -> TableRowMut<'_, T> { TableRowMut { element: &mut self.element, + mask: &mut self.mask, transform: &mut self.transform, alpha_blending: &mut self.alpha_blending, source_node_id: &mut self.source_node_id, @@ -283,6 +307,7 @@ impl TableRow { #[derive(Copy, Clone, Debug, PartialEq)] pub struct TableRowRef<'a, T> { pub element: &'a T, + pub mask: &'a Mask, pub transform: &'a DAffine2, pub alpha_blending: &'a AlphaBlending, pub source_node_id: &'a Option, @@ -295,6 +320,7 @@ impl TableRowRef<'_, T> { { TableRow { element: self.element.clone(), + mask: self.mask.clone(), transform: *self.transform, alpha_blending: *self.alpha_blending, source_node_id: *self.source_node_id, @@ -305,6 +331,7 @@ impl TableRowRef<'_, T> { #[derive(Debug)] pub struct TableRowMut<'a, T> { pub element: &'a mut T, + pub mask: &'a mut Mask, pub transform: &'a mut DAffine2, pub alpha_blending: &'a mut AlphaBlending, pub source_node_id: &'a mut Option, diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index f577340769..2a6e68889e 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -431,6 +431,7 @@ async fn round_corners( .map(|source| { let source_transform = *source.transform; let source_transform_inverse = source_transform.inverse(); + let source_mask = source.mask.clone(); let source_node_id = source.source_node_id; let source = source.element; @@ -523,6 +524,7 @@ async fn round_corners( TableRow { element: result, + mask: source_mask.clone(), transform: source_transform, alpha_blending: Default::default(), source_node_id: *source_node_id, @@ -664,6 +666,7 @@ async fn auto_tangents( let transform = *source.transform; let alpha_blending = *source.alpha_blending; let source_node_id = *source.source_node_id; + let mask = source.mask.clone(); let source = source.element; let mut result = Vector { @@ -760,6 +763,7 @@ async fn auto_tangents( TableRow { element: result, + mask, transform, alpha_blending, source_node_id, @@ -949,6 +953,7 @@ async fn separate_subpaths(_: impl Ctx, content: Table) -> Table .flat_map(|row| { let style = row.element.style.clone(); let transform = row.transform; + let mask = row.mask; let alpha_blending = row.alpha_blending; let source_node_id = row.source_node_id; @@ -961,6 +966,7 @@ async fn separate_subpaths(_: impl Ctx, content: Table) -> Table TableRow { element: vector, + mask: mask.clone(), transform, alpha_blending, source_node_id, diff --git a/node-graph/gpath-bool/src/lib.rs b/node-graph/gpath-bool/src/lib.rs index df3a089414..b20b93d1ba 100644 --- a/node-graph/gpath-bool/src/lib.rs +++ b/node-graph/gpath-bool/src/lib.rs @@ -200,6 +200,7 @@ fn difference<'a>(vector: impl DoubleEndedIterator) -> Table { TableRow { element, + mask: row.mask.clone(), transform: row.transform, alpha_blending: row.alpha_blending, source_node_id: row.source_node_id, @@ -312,6 +314,7 @@ fn flatten_vector(graphic_table: &Table) -> Table { TableRow { element, + mask: row.mask.clone(), transform: row.transform, alpha_blending: row.alpha_blending, source_node_id: row.source_node_id, diff --git a/node-graph/graster-nodes/src/std_nodes.rs b/node-graph/graster-nodes/src/std_nodes.rs index 490652246f..7aa4953b31 100644 --- a/node-graph/graster-nodes/src/std_nodes.rs +++ b/node-graph/graster-nodes/src/std_nodes.rs @@ -2,9 +2,10 @@ use crate::adjustments::{CellularDistanceFunction, CellularReturnType, DomainWar use dyn_any::DynAny; use fastnoise_lite; use glam::{DAffine2, DVec2, Vec2}; +use graphene_core::Graphic; use graphene_core::blending::AlphaBlending; use graphene_core::color::Color; -use graphene_core::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut}; +use graphene_core::color::{AlphaMut, Channel, LinearChannel, Luminance, RGBMut}; use graphene_core::context::{Ctx, ExtractFootprint}; use graphene_core::math::bbox::Bbox; use graphene_core::raster::image::Image; @@ -12,6 +13,7 @@ use graphene_core::raster::{Bitmap, BitmapMut}; use graphene_core::raster_types::{CPU, Raster}; use graphene_core::table::{Table, TableRow}; use graphene_core::transform::Transform; +use graphene_core::vector::Vector; use rand::prelude::*; use rand_chacha::ChaCha8Rng; use std::fmt::Debug; @@ -175,6 +177,7 @@ pub fn combine_channels( Some(TableRow { element: Raster::new_cpu(image), + mask: None, transform, alpha_blending, source_node_id, @@ -184,54 +187,47 @@ pub fn combine_channels( } #[node_macro::node(category("Raster"))] -pub fn mask( +pub fn mask( _: impl Ctx, /// The image to be masked. - image: Table>, - /// The stencil to be used for masking. - #[expose] - stencil: Table>, -) -> Table> { - // TODO: Figure out what it means to support multiple stencil rows? - let Some(stencil) = stencil.into_iter().next() else { - // No stencil provided so we return the original image - return image; - }; - let stencil_size = DVec2::new(stencil.element.width as f64, stencil.element.height as f64); + #[implementations( + Table, + Table, + Table>, + Table, + Table, + Table>, + Table, + Table, + Table>, + )] + mut image: Table, + #[expose] + #[implementations( + Table, + Table, + Table, + Table, + Table, + Table, + Table>, + Table>, + Table>, + )] + stencil: Table, +) -> Table +where + Table: Into + Clone, +{ + for instance in image.iter_mut() { + *instance.mask = Some(stencil.clone().into()); + } image - .into_iter() - .filter_map(|mut row| { - let image_size = DVec2::new(row.element.width as f64, row.element.height as f64); - let mask_size = stencil.transform.decompose_scale(); - - if mask_size == DVec2::ZERO { - return None; - } - - // Transforms a point from the background image to the foreground image - let bg_to_fg = row.transform * DAffine2::from_scale(1. / image_size); - let stencil_transform_inverse = stencil.transform.inverse(); - - for y in 0..row.element.height { - for x in 0..row.element.width { - let image_point = DVec2::new(x as f64, y as f64); - let mask_point = bg_to_fg.transform_point2(image_point); - let local_mask_point = stencil_transform_inverse.transform_point2(mask_point); - let mask_point = stencil.transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE)); - let mask_point = (DAffine2::from_scale(stencil_size) * stencil.transform.inverse()).transform_point2(mask_point); - - let image_pixel = row.element.data_mut().get_pixel_mut(x, y).unwrap(); - let mask_pixel = stencil.element.sample(mask_point); - *image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().cast_linear_channel()); - } - } - - Some(row) - }) - .collect() } +// TODO: Use as in-place raster modifier + #[node_macro::node(category(""))] pub fn extend_image_to_bounds(_: impl Ctx, image: Table>, bounds: DAffine2) -> Table> { image diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index 3917a14c3c..480fb9194c 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -536,6 +536,10 @@ impl Render for Table { attributes.push(mask_type.to_attribute(), selector); } + + if let Some(mask) = row.mask { + attributes.push_mask(mask, render_params.for_clipper()); + } }, |render| { row.element.render_svg(render, render_params); @@ -576,6 +580,19 @@ impl Render for Table { } } + let mut masked = false; + if let Some(mask) = row.mask { + let bounds = mask.bounding_box(transform, true); + + if let RenderBoundingBox::Rectangle(bounds) = bounds { + masked = true; + let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); + + scene.push_luminance_mask_layer(1., kurbo::Affine::IDENTITY, &rect); + mask.render_to_vello(scene, transform, context, &render_params.for_clipper()); + } + } + let next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled()); if next_clips && mask_element_and_transform.is_none() { mask_element_and_transform = Some((row.element, transform)); @@ -607,6 +624,10 @@ impl Render for Table { row.element.render_to_vello(scene, transform, context, render_params); } + if masked { + scene.pop_layer(); + } + if layer { scene.pop_layer(); } @@ -734,6 +755,7 @@ impl Render for Table { let vector_row = Table::new_from_row(TableRow { element, + mask: None, alpha_blending: *row.alpha_blending, transform: *row.transform, source_node_id: None, @@ -789,6 +811,11 @@ impl Render for Table { let selector = format!("url(#{id})"); attributes.push(mask_type.to_attribute(), selector); } + + if let Some(mask) = row.mask { + attributes.push_mask(mask, render_params.for_clipper()); + } + attributes.push_val(fill_and_stroke); let opacity = row.alpha_blending.opacity(render_params.for_mask); @@ -872,6 +899,19 @@ impl Render for Table { ); } + let mut masked = false; + if let Some(mask) = row.mask { + let bounds = mask.bounding_box(element_transform, true); + + if let RenderBoundingBox::Rectangle(bounds) = bounds { + masked = true; + let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); + + scene.push_luminance_mask_layer(1., kurbo::Affine::IDENTITY, &rect); + mask.render_to_vello(scene, element_transform, _context, &render_params.for_clipper()); + } + } + let can_draw_aligned_stroke = row.element.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && row.element.stroke_bezier_paths().all(|path| path.closed()); @@ -997,6 +1037,7 @@ impl Render for Table { let vector_table = Table::new_from_row(TableRow { element, + mask: None, alpha_blending: *row.alpha_blending, transform: *row.transform, source_node_id: None, @@ -1060,6 +1101,10 @@ impl Render for Table { } } + if masked { + scene.pop_layer(); + } + // If we pushed a layer for opacity or a blend mode, we need to pop it if layer { scene.pop_layer(); @@ -1218,30 +1263,38 @@ impl Render for Table> { base64_string }); - render.leaf_tag("image", |attributes| { - attributes.push("width", "1"); - attributes.push("height", "1"); - attributes.push("preserveAspectRatio", "none"); - attributes.push("href", base64_string); - let matrix = format_transform_matrix(transform); - if !matrix.is_empty() { - attributes.push("transform", matrix); - } + let render_image = |render: &mut SvgRender| { + render.leaf_tag("image", |attributes| { + attributes.push("width", "1"); + attributes.push("height", "1"); + attributes.push("preserveAspectRatio", "none"); + attributes.push("href", base64_string); + let matrix = format_transform_matrix(transform); + if !matrix.is_empty() { + attributes.push("transform", matrix); + } - let opacity = row.alpha_blending.opacity(render_params.for_mask); - if opacity < 1. { - attributes.push("opacity", opacity.to_string()); - } - if row.alpha_blending.blend_mode != BlendMode::default() { - attributes.push("style", row.alpha_blending.blend_mode.render()); - } - }); + let opacity = row.alpha_blending.opacity(render_params.for_mask); + if opacity < 1. { + attributes.push("opacity", opacity.to_string()); + } + if row.alpha_blending.blend_mode != BlendMode::default() { + attributes.push("style", row.alpha_blending.blend_mode.render()); + } + }); + }; + + if let Some(mask) = row.mask { + render.parent_tag("g", |attributes| attributes.push_mask(mask, render_params.for_clipper()), render_image); + } else { + render_image(render) + } } } } #[cfg(feature = "vello")] - fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) { + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { use vello::peniko; for row in self.iter() { @@ -1268,8 +1321,25 @@ impl Render for Table> { let image = peniko::Image::new(image.to_flat_u8().0.into(), peniko::ImageFormat::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat); let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); + let mut masked = false; + if let Some(mask) = row.mask { + let bounds = mask.bounding_box(transform, true); + + if let RenderBoundingBox::Rectangle(bounds) = bounds { + masked = true; + let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); + + scene.push_luminance_mask_layer(1., kurbo::Affine::IDENTITY, &rect); + mask.render_to_vello(scene, transform, _context, &render_params.for_clipper()); + } + } + scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array())); + if masked { + scene.pop_layer(); + } + if layer { scene.pop_layer(); } @@ -1561,10 +1631,26 @@ impl SvgRenderAttrs<'_> { value(self.0); self.0.svg.push("\"".into()); } + pub fn push(&mut self, name: impl Into, value: impl Into) { self.push_complex(name, move |renderer| renderer.svg.push(value.into())); } + pub fn push_val(&mut self, value: impl Into) { self.0.svg.push(value.into()); } + + fn push_mask(&mut self, mask: &Graphic, render_params: RenderParams) { + let uuid = generate_uuid(); + let mut svg = SvgRender::new(); + mask.render_svg(&mut svg, &render_params); + + let id = format!("mask-{}", uuid); + write!(&mut self.0.svg_defs, r##"{}"##, svg.svg_defs).unwrap(); + write!(&mut self.0.svg_defs, r##"{}"##, svg.svg.to_svg_string()).unwrap(); + + let selector = format!("url(#{id})"); + + self.push("mask", selector); + } } diff --git a/node-graph/wgpu-executor/src/texture_upload.rs b/node-graph/wgpu-executor/src/texture_upload.rs index 9fdae68ea7..e3cd9bcba9 100644 --- a/node-graph/wgpu-executor/src/texture_upload.rs +++ b/node-graph/wgpu-executor/src/texture_upload.rs @@ -39,6 +39,7 @@ pub async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: Table TableRow { element: Raster::new_gpu(texture), + mask: row.mask.clone(), transform: *row.transform, alpha_blending: *row.alpha_blending, source_node_id: *row.source_node_id,