From e1cdb2242df0fe3894ce2732844623498f81d297 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sat, 30 Sep 2023 11:43:24 +0200 Subject: [PATCH] Lay Groundwork for Rust-based SVG rasterization (#1422) * Add functions for constructing a usvg tree * Actually encode the image in the usvg tree * Implement path translation * Render document using resvg --- Cargo.lock | 443 +++++++++++++++++- editor/src/node_graph_executor.rs | 4 +- libraries/bezier-rs/src/bezier/mod.rs | 8 +- libraries/bezier-rs/src/subpath/mod.rs | 2 +- node-graph/gcore/Cargo.toml | 20 +- node-graph/gcore/src/graphic_element.rs | 109 ++++- .../gcore/src/graphic_element/renderer.rs | 7 +- node-graph/gcore/src/raster/image.rs | 15 +- node-graph/gstd/Cargo.toml | 5 + node-graph/gstd/src/wasm_application_io.rs | 18 +- 10 files changed, 585 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7f06573..85a5fe26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1111,6 +1111,12 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "data-url" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" + [[package]] name = "deranged" version = "0.3.8" @@ -1346,6 +1352,15 @@ dependencies = [ "libc", ] +[[package]] +name = "euclid" +version = "0.22.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" +dependencies = [ + "num-traits", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1392,6 +1407,14 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fello" +version = "0.1.0" +source = "git+https://github.com/dfrg/fount?rev=dadbcf75695f035ca46766bfd60555d05bd421b1#dadbcf75695f035ca46766bfd60555d05bd421b1" +dependencies = [ + "read-fonts", +] + [[package]] name = "fern" version = "0.6.2" @@ -1440,6 +1463,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "flume" version = "0.10.14" @@ -1459,6 +1488,35 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "font-types" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6978d65d61022aa249fefdd914dc8215757f617f1a697c496ef6b42013366567" + +[[package]] +name = "fontconfig-parser" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "674e258f4b5d2dcd63888c01c68413c51f565e8af99d2f7701c7b81d79ef41c4" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8d8cbea8f21307d7e84bca254772981296f058a1d36b461bf4d83a7499fc9e" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2 0.6.2", + "slotmap", + "tinyvec", + "ttf-parser 0.19.1", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -2077,17 +2135,18 @@ dependencies = [ "glam", "image", "js-sys", - "kurbo", + "kurbo 0.9.5 (git+https://github.com/linebender/kurbo.git)", "log", "node-macro", "num-derive", "num-traits", "rand_chacha 0.3.1", - "rustybuzz", + "rustybuzz 0.8.0", "serde", "specta", "spin 0.9.8", "spirv-std", + "usvg 0.35.0", "wasm-bindgen", "web-sys", ] @@ -2117,12 +2176,15 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "reqwest", + "resvg", "rustc-hash", "serde", "serde_json", "tempfile", "tokio", "url", + "vello", + "vello_svg", "vulkan-executor", "wasm-bindgen", "wasm-bindgen-futures", @@ -2162,9 +2224,9 @@ dependencies = [ "graphene-core", "graphene-std", "image", - "kurbo", + "kurbo 0.9.5 (git+https://github.com/linebender/kurbo.git)", "log", - "rustybuzz", + "rustybuzz 0.8.0", "serde", "specta", ] @@ -2289,6 +2351,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + [[package]] name = "h2" version = "0.3.21" @@ -2604,6 +2676,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "indexmap" version = "1.9.3" @@ -2827,6 +2905,15 @@ dependencies = [ "selectors", ] +[[package]] +name = "kurbo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +dependencies = [ + "arrayvec", +] + [[package]] name = "kurbo" version = "0.9.5" @@ -3027,6 +3114,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -3683,7 +3779,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" dependencies = [ - "ttf-parser", + "ttf-parser 0.19.1", ] [[package]] @@ -3752,6 +3848,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "peniko" +version = "0.1.0" +source = "git+https://github.com/linebender/peniko?rev=cafdac9a211a0fb2fec5656bd663d1ac770bcc81#cafdac9a211a0fb2fec5656bd663d1ac770bcc81" +dependencies = [ + "kurbo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -3866,6 +3971,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.3" @@ -4183,6 +4294,21 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rctree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" + +[[package]] +name = "read-fonts" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87d08214643b2df95b0b3955cd9f264bcfab22b73470b83df4992df523b4d6eb" +dependencies = [ + "font-types", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -4317,6 +4443,23 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "resvg" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6554f47c38eca56827eea7f285c2a3018b4e12e0e195cc105833c008be338f1" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "png", + "rgb", + "svgtypes", + "tiny-skia 0.10.0", + "usvg 0.35.0", +] + [[package]] name = "rfd" version = "0.10.0" @@ -4341,6 +4484,15 @@ dependencies = [ "windows 0.37.0", ] +[[package]] +name = "rgb" +version = "0.8.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.16.20" @@ -4368,6 +4520,28 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rosvgtree" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad747e7384940e7bf33b15ba433b7bad9f44c0c6d5287a67c2cb22cd1743d497" +dependencies = [ + "log", + "roxmltree", + "simplecss", + "siphasher", + "svgtypes", +] + +[[package]] +name = "roxmltree" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f595a457b6b8c6cda66a48503e92ee8d19342f905948f29c383200ec9eb1d8" +dependencies = [ + "xmlparser", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -4453,6 +4627,22 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rustybuzz" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162bdf42e261bee271b3957691018634488084ef577dddeb6420a9684cab2a6a" +dependencies = [ + "bitflags 1.3.2", + "bytemuck", + "smallvec", + "ttf-parser 0.18.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + [[package]] name = "rustybuzz" version = "0.8.0" @@ -4462,7 +4652,7 @@ dependencies = [ "bitflags 1.3.2", "bytemuck", "smallvec", - "ttf-parser", + "ttf-parser 0.19.1", "unicode-bidi-mirroring", "unicode-ccc", "unicode-general-category", @@ -4538,9 +4728,9 @@ checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" dependencies = [ "ab_glyph", "log", - "memmap2", + "memmap2 0.5.10", "smithay-client-toolkit", - "tiny-skia", + "tiny-skia 0.8.4", ] [[package]] @@ -4819,6 +5009,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -4903,7 +5102,7 @@ dependencies = [ "dlib", "lazy_static", "log", - "memmap2", + "memmap2 0.5.10", "nix 0.24.3", "pkg-config", "wayland-client", @@ -5070,6 +5269,9 @@ name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] [[package]] name = "string_cache" @@ -5103,6 +5305,22 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "svg_fmt" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2" + +[[package]] +name = "svgtypes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4b0611e7f3277f68c0fa18e385d9e2d26923691379690039548f867cef02a7" +dependencies = [ + "kurbo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "siphasher", +] + [[package]] name = "syn" version = "1.0.109" @@ -5631,7 +5849,22 @@ dependencies = [ "bytemuck", "cfg-if", "png", - "tiny-skia-path", + "tiny-skia-path 0.8.4", +] + +[[package]] +name = "tiny-skia" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db11798945fa5c3e5490c794ccca7c6de86d3afdd54b4eb324109939c6f37bc" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path 0.10.0", ] [[package]] @@ -5645,6 +5878,17 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tiny-skia-path" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f60aa35c89ac2687ace1a2556eaaea68e8c0d47408a2e3e7f5c98a489e7281c" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -5891,6 +6135,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "ttf-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" + [[package]] name = "ttf-parser" version = "0.19.1" @@ -5970,6 +6220,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unicode-width" version = "0.1.10" @@ -6000,6 +6256,127 @@ dependencies = [ "serde", ] +[[package]] +name = "usvg" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae32eb823aab35fc343b19c4d354f70e713b442ce34cdfa8497bf6c39af8a342" +dependencies = [ + "base64 0.21.2", + "log", + "pico-args", + "usvg-parser 0.33.0", + "usvg-text-layout 0.33.0", + "usvg-tree 0.33.0", + "xmlwriter", +] + +[[package]] +name = "usvg" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d09ddfb0d93bf84824c09336d32e42f80961a9d1680832eb24fdf249ce11e6" +dependencies = [ + "base64 0.21.2", + "log", + "pico-args", + "usvg-parser 0.35.0", + "usvg-text-layout 0.35.0", + "usvg-tree 0.35.0", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7529174e721c8078d62b08399258469b1d68b4e5f2983b347d6a9d39779366c" +dependencies = [ + "data-url", + "flate2", + "imagesize", + "kurbo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "rosvgtree", + "strict-num", + "svgtypes", + "usvg-tree 0.33.0", +] + +[[package]] +name = "usvg-parser" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19bf93d230813599927d88557014e0908ecc3531666d47c634c6838bc8db408" +dependencies = [ + "data-url", + "flate2", + "imagesize", + "kurbo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "roxmltree", + "simplecss", + "siphasher", + "svgtypes", + "usvg-tree 0.35.0", +] + +[[package]] +name = "usvg-text-layout" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e672fbc19261c6553113cc04ff2ff38ae52fadbd90f2d814040857795fb5c50" +dependencies = [ + "fontdb", + "kurbo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "rustybuzz 0.7.0", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "usvg-tree 0.33.0", +] + +[[package]] +name = "usvg-text-layout" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "035044604e89652c0a2959b8b356946997a52649ba6cade45928c2842376feb4" +dependencies = [ + "fontdb", + "kurbo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "rustybuzz 0.7.0", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "usvg-tree 0.35.0", +] + +[[package]] +name = "usvg-tree" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a56e9cd3be5eb6d6744477e95b82d52d393fc1dba4b5b090912c33af337c20b" +dependencies = [ + "kurbo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rctree", + "strict-num", + "svgtypes", +] + +[[package]] +name = "usvg-tree" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7939a7e4ed21cadb5d311d6339730681c3e24c3e81d60065be80e485d3fc8b92" +dependencies = [ + "rctree", + "strict-num", + "svgtypes", + "tiny-skia-path 0.10.0", +] + [[package]] name = "utf-8" version = "0.7.6" @@ -6033,6 +6410,40 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "vello" +version = "0.0.1" +source = "git+https://github.com/linebender/vello#0d5a926056c06f0fc005fde5e321507758994dbb" +dependencies = [ + "bytemuck", + "fello", + "futures-intrusive", + "peniko", + "raw-window-handle", + "vello_encoding", + "wgpu", +] + +[[package]] +name = "vello_encoding" +version = "0.1.0" +source = "git+https://github.com/linebender/vello#0d5a926056c06f0fc005fde5e321507758994dbb" +dependencies = [ + "bytemuck", + "fello", + "guillotiere", + "peniko", +] + +[[package]] +name = "vello_svg" +version = "0.0.1" +source = "git+https://github.com/linebender/vello#0d5a926056c06f0fc005fde5e321507758994dbb" +dependencies = [ + "usvg 0.33.0", + "vello", +] + [[package]] name = "version-compare" version = "0.0.11" @@ -7074,6 +7485,18 @@ version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" +[[package]] +name = "xmlparser" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "zbus" version = "3.14.1" diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index cd9036c1..d785f235 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -184,7 +184,7 @@ impl NodeRuntime { resolution: viewport_resolution, ..Default::default() }, - export_format: graphene_core::application_io::ExportFormat::Svg, + export_format: graphene_core::application_io::ExportFormat::Canvas, }, image_frame: None, }; @@ -402,7 +402,7 @@ impl NodeGraphExecutor { use image::{ImageBuffer, Rgba}; use std::io::Cursor; - let (result_bytes, width, height) = image.into_flat_u8(); + let (result_bytes, width, height) = image.to_flat_u8(); let mut output: ImageBuffer, _> = image::ImageBuffer::from_raw(width, height, result_bytes).ok_or_else(|| "Invalid image size".to_string())?; if let Some(size) = resize { diff --git a/libraries/bezier-rs/src/bezier/mod.rs b/libraries/bezier-rs/src/bezier/mod.rs index 86914240..286ccd18 100644 --- a/libraries/bezier-rs/src/bezier/mod.rs +++ b/libraries/bezier-rs/src/bezier/mod.rs @@ -16,7 +16,7 @@ use std::fmt::{Debug, Formatter, Result}; /// Representation of the handle point(s) in a bezier segment. #[derive(Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -enum BezierHandles { +pub enum BezierHandles { Linear, /// Handles for a quadratic curve. Quadratic { @@ -42,11 +42,11 @@ unsafe impl dyn_any::StaticType for BezierHandles { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Bezier { /// Start point of the bezier curve. - start: DVec2, + pub start: DVec2, /// Start point of the bezier curve. - end: DVec2, + pub end: DVec2, /// Handles of the bezier curve. - handles: BezierHandles, + pub handles: BezierHandles, } impl Debug for Bezier { diff --git a/libraries/bezier-rs/src/subpath/mod.rs b/libraries/bezier-rs/src/subpath/mod.rs index dfb8f12d..6cba7732 100644 --- a/libraries/bezier-rs/src/subpath/mod.rs +++ b/libraries/bezier-rs/src/subpath/mod.rs @@ -16,7 +16,7 @@ use std::ops::{Index, IndexMut}; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Subpath { manipulator_groups: Vec>, - closed: bool, + pub closed: bool, } #[cfg(feature = "dyn-any")] diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index a44bc56d..d5067ba3 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -9,25 +9,10 @@ license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -std = [ - "dyn-any", - "dyn-any/std", - "alloc", - "glam/std", - "specta", - "num-traits/std", - "rustybuzz", - "image", -] +std = ["dyn-any", "dyn-any/std", "alloc", "glam/std", "specta", "num-traits/std", "rustybuzz", "image"] default = ["async", "serde", "kurbo", "log", "std", "rand_chacha", "wasm"] log = ["dep:log"] -serde = [ - "dep:serde", - "glam/serde", - "bezier-rs/serde", - "bezier-rs/serde", - "base64", -] +serde = ["dep:serde", "glam/serde", "bezier-rs/serde", "bezier-rs/serde", "base64"] gpu = ["spirv-std", "glam/bytemuck", "dyn-any", "glam/libm"] async = ["async-trait", "alloc"] nightly = [] @@ -77,6 +62,7 @@ num-traits = { version = "0.2.15", default-features = false, features = [ wasm-bindgen = { workspace = true, optional = true } js-sys = { version = "0.3.55", optional = true } +usvg = "0.35.0" [dependencies.web-sys] version = "0.3.4" diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 4200397e..a31348b9 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -1,13 +1,14 @@ use crate::raster::{BlendMode, ImageFrame}; -use crate::vector::VectorData; +use crate::vector::{subpath, VectorData}; use crate::{Color, Node}; +use bezier_rs::BezierHandles; use dyn_any::{DynAny, StaticType}; use node_macro::node_fn; use core::future::Future; use core::ops::{Deref, DerefMut}; -use glam::IVec2; +use glam::{DAffine2, DVec2, IVec2, UVec2}; pub mod renderer; @@ -198,6 +199,110 @@ where impl GraphicGroup { pub const EMPTY: Self = Self(Vec::new()); + + pub fn to_usvg_tree(&self, resolution: UVec2, viewbox: [DVec2; 2]) -> usvg::Tree { + let root_node = usvg::Node::new(usvg::NodeKind::Group(usvg::Group::default())); + let tree = usvg::Tree { + size: usvg::Size::from_wh(resolution.x as f32, resolution.y as f32).unwrap(), + view_box: usvg::ViewBox { + rect: usvg::NonZeroRect::from_ltrb(viewbox[0].x as f32, viewbox[0].y as f32, viewbox[1].x as f32, viewbox[1].y as f32).unwrap(), + aspect: usvg::AspectRatio::default(), + }, + root: root_node.clone(), + }; + + for element in self.0.iter() { + root_node.append(element.to_usvg_node()); + } + tree + } +} + +impl GraphicElement { + fn to_usvg_node(&self) -> usvg::Node { + fn to_transform(transform: DAffine2) -> usvg::Transform { + let cols = transform.to_cols_array(); + usvg::Transform::from_row(cols[0] as f32, cols[1] as f32, cols[2] as f32, cols[3] as f32, cols[4] as f32, cols[5] as f32) + } + + match &self.graphic_element_data { + GraphicElementData::VectorShape(vector_data) => { + use usvg::tiny_skia_path::PathBuilder; + let mut builder = PathBuilder::new(); + + let transform = to_transform(vector_data.transform); + let style = &vector_data.style; + for subpath in vector_data.subpaths.iter() { + let start = vector_data.transform.transform_point2(subpath[0].anchor); + builder.move_to(start.x as f32, start.y as f32); + for bezier in subpath.iter() { + bezier.apply_transformation(|pos| vector_data.transform.transform_point2(pos)); + let end = bezier.end; + match bezier.handles { + BezierHandles::Linear => builder.line_to(end.x as f32, end.y as f32), + BezierHandles::Quadratic { handle } => builder.quad_to(handle.x as f32, handle.y as f32, end.x as f32, end.y as f32), + BezierHandles::Cubic { handle_start, handle_end } => { + builder.cubic_to(handle_start.x as f32, handle_start.y as f32, handle_end.x as f32, handle_end.y as f32, end.x as f32, end.y as f32) + } + } + } + if subpath.closed { + builder.close() + } + } + let path = builder.finish().unwrap(); + let mut path = usvg::Path::new(path.into()); + path.transform = transform; + // TODO: use proper style + path.fill = None; + path.stroke = Some(usvg::Stroke::default()); + usvg::Node::new(usvg::NodeKind::Path(path)) + } + GraphicElementData::ImageFrame(image_frame) => { + if image_frame.image.width * image_frame.image.height == 0 { + return usvg::Node::new(usvg::NodeKind::Group(usvg::Group::default())); + } + let png = image_frame.image.to_png(); + usvg::Node::new(usvg::NodeKind::Image(usvg::Image { + id: String::new(), + transform: to_transform(image_frame.transform), + visibility: usvg::Visibility::Visible, + view_box: usvg::ViewBox { + rect: usvg::NonZeroRect::from_xywh(0., 0., 1., 1.).unwrap(), + aspect: usvg::AspectRatio::default(), + }, + rendering_mode: usvg::ImageRendering::OptimizeSpeed, + kind: usvg::ImageKind::PNG(png.into()), + })) + } + GraphicElementData::Text(text) => usvg::Node::new(usvg::NodeKind::Text(usvg::Text { + id: String::new(), + transform: usvg::Transform::identity(), + rendering_mode: usvg::TextRendering::OptimizeSpeed, + positions: Vec::new(), + rotate: Vec::new(), + writing_mode: usvg::WritingMode::LeftToRight, + chunks: vec![usvg::TextChunk { + text: text.clone(), + x: None, + y: None, + anchor: usvg::TextAnchor::Start, + spans: vec![], + text_flow: usvg::TextFlow::Linear, + }], + })), + GraphicElementData::GraphicGroup(group) => { + let group_element = usvg::Node::new(usvg::NodeKind::Group(usvg::Group::default())); + + for element in group.0.iter() { + group_element.append(element.to_usvg_node()); + } + group_element + } + // TODO + GraphicElementData::Artboard(board) => usvg::Node::new(usvg::NodeKind::Group(usvg::Group::default())), + } + } } impl core::hash::Hash for GraphicElement { diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index fbb877ca..ebaa094d 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -297,12 +297,7 @@ impl GraphicElementRendered for ImageFrame { if image.data.is_empty() { return; } - let (flat_data, _, _) = image.clone().into_flat_u8(); - let mut output = Vec::new(); - let encoder = image::codecs::png::PngEncoder::new(&mut output); - encoder - .write_image(&flat_data, image.width, image.height, image::ColorType::Rgba8) - .expect("failed to encode image as png"); + let output = image.to_png(); let preamble = "data:image/png;base64,"; let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4); base64_string.push_str(preamble); diff --git a/node-graph/gcore/src/raster/image.rs b/node-graph/gcore/src/raster/image.rs index ad6bf458..7dcd4ea1 100644 --- a/node-graph/gcore/src/raster/image.rs +++ b/node-graph/gcore/src/raster/image.rs @@ -134,6 +134,15 @@ impl Image { let data = image_data.chunks_exact(4).map(|v| Color::from_rgba8_srgb(v[0], v[1], v[2], v[3])).collect(); Image { width, height, data } } + + pub fn to_png(&self) -> Vec { + use ::image::ImageEncoder; + let (data, width, height) = self.to_flat_u8(); + let mut png = Vec::new(); + let encoder = ::image::codecs::png::PngEncoder::new(&mut png); + encoder.write_image(&data, width, height, ::image::ColorType::Rgba8).expect("failed to encode image as png"); + png + } } use super::*; @@ -143,9 +152,9 @@ where

::AlphaChannel: Linear, { /// Flattens each channel cast to a u8 - pub fn into_flat_u8(self) -> (Vec, u32, u32) { + pub fn to_flat_u8(&self) -> (Vec, u32, u32) { let Image { width, height, data } = self; - assert_eq!(data.len(), width as usize * height as usize); + assert_eq!(data.len(), *width as usize * *height as usize); // Cache the last sRGB value we computed, speeds up fills. let mut last_r = 0.; @@ -190,7 +199,7 @@ where i += 4; } - (result, width, height) + (result, *width, *height) } } diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index 37b945f1..76f6ddc7 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -22,6 +22,8 @@ quantization = ["autoquant"] wasm = ["wasm-bindgen", "web-sys", "js-sys"] imaginate = ["image/png", "base64", "js-sys", "web-sys", "wasm-bindgen-futures"] image-compare = ["dep:image-compare"] +vello = ["dep:vello", "resvg", "gpu", "dep:vello_svg"] +resvg = ["dep:resvg"] wayland = [] [dependencies] @@ -72,6 +74,9 @@ winit = "0.28.6" url = "2.4.0" tokio = { version = "1.29.0", optional = true, features = ["fs", "io-std"] } image-compare = { version = "0.3.0", optional = true } +vello = { git = "https://github.com/linebender/vello", version = "0.0.1", optional = true } +vello_svg = { git = "https://github.com/linebender/vello", version = "0.0.1", optional = true } +resvg = { version = "0.35.0", optional = true } [dependencies.serde] version = "1.0" diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 8c9f0976..fc379fe6 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -315,6 +315,7 @@ async fn render_node<'a: 'input, F: Future>( render.format_svg(min, max); RenderOutput::Svg(render.svg.to_string()) } + #[cfg(any(feature = "resvg", feature = "vello"))] ExportFormat::Canvas => { data.render_svg(&mut render, &render_params); // TODO: reenable once we switch to full node graph @@ -326,7 +327,22 @@ async fn render_node<'a: 'input, F: Future>( let canvas = &surface_handle.surface; canvas.set_width(resolution.x); canvas.set_height(resolution.y); + let usvg_tree = data.to_usvg_tree(resolution, [min, max]); + if let Some(exec) = editor.application_io.gpu_executor() { + todo!() + } else { + let rtree = resvg::Tree::from_usvg(&usvg_tree); + + let pixmap_size = rtree.size.to_int_size(); + let mut pixmap = resvg::tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap(); + rtree.render(resvg::tiny_skia::Transform::default(), &mut pixmap.as_mut()); + let array: Clamped<&[u8]> = Clamped(pixmap.data()); + let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::().unwrap(); + let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, pixmap_size.width(), pixmap_size.height()).expect("Failed to construct ImageData"); + context.put_image_data(&image_data, 0.0, 0.0).unwrap(); + } + /* let preamble = "data:image/svg+xml;base64,"; let mut base64_string = String::with_capacity(preamble.len() + array.len() * 4); base64_string.push_str(preamble); @@ -334,9 +350,9 @@ async fn render_node<'a: 'input, F: Future>( let image_data = web_sys::HtmlImageElement::new().unwrap(); image_data.set_src(base64_string.as_str()); - let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::().unwrap(); wasm_bindgen_futures::JsFuture::from(image_data.decode()).await.unwrap(); context.draw_image_with_html_image_element(&image_data, 0.0, 0.0).unwrap(); + */ let frame = SurfaceHandleFrame { surface_handle, transform: DAffine2::IDENTITY,