From defc400c21aaf3fc61f4a7530f13fdbe5991fcc1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 9 Feb 2022 08:12:12 +0100 Subject: [PATCH] Add an example of how to display an SVG image in egui (#1228) --- Cargo.lock | 267 ++++++++++++++++++++++- eframe/Cargo.toml | 5 + eframe/examples/rustacean-flat-happy.svg | 33 +++ eframe/examples/svg.rs | 108 +++++++++ eframe/src/lib.rs | 2 +- egui/src/introspection.rs | 2 - egui/src/lib.rs | 2 +- egui_glow/Cargo.toml | 3 - 8 files changed, 411 insertions(+), 11 deletions(-) create mode 100644 eframe/examples/rustacean-flat-happy.svg create mode 100644 eframe/examples/svg.rs diff --git a/Cargo.lock b/Cargo.lock index 286adabcb..6c5cbabfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "ashpd" version = "0.2.2" @@ -831,6 +843,15 @@ dependencies = [ "syn", ] +[[package]] +name = "data-url" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" +dependencies = [ + "matches", +] + [[package]] name = "dconf_rs" version = "0.3.0" @@ -980,7 +1001,10 @@ dependencies = [ "epi", "image", "poll-promise", + "resvg", "rfd", + "tiny-skia", + "usvg", ] [[package]] @@ -1060,7 +1084,6 @@ dependencies = [ "epi", "glow", "glutin", - "image", "memoffset", "tracing", "wasm-bindgen", @@ -1298,12 +1321,29 @@ 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 = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fontdb" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b07f5c05414a0d8caba4c17eef8dc8b5c8955fc7c68d324191c7a56d3f3449" +dependencies = [ + "log", + "memmap2 0.5.2", + "ttf-parser 0.12.3", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -1723,7 +1763,7 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", - "jpeg-decoder", + "jpeg-decoder 0.2.1", "num-iter", "num-rational", "num-traits", @@ -1793,6 +1833,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" + [[package]] name = "jpeg-decoder" version = "0.2.1" @@ -1814,6 +1860,15 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kurbo" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb348d766edbac91ba1eb83020d96f4f8867924d194393083c15a51f185e6a82" +dependencies = [ + "arrayvec", +] + [[package]] name = "lazy-bytes-cast" version = "5.0.1" @@ -1911,6 +1966,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -2274,7 +2338,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef05f2882a8b3e7acc10c153ade2631f7bfc8ce00d2bf3fb8f4e9d2ae6ea5c3" dependencies = [ - "ttf-parser", + "ttf-parser 0.14.0", ] [[package]] @@ -2332,6 +2396,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pico-args" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" + [[package]] name = "pin-project-lite" version = "0.2.8" @@ -2550,6 +2620,12 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rctree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae028b272a6e99d9f8260ceefa3caa09300a8d6c8d2b2001316474bc52122e9" + [[package]] name = "redox_syscall" version = "0.2.10" @@ -2592,6 +2668,23 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "resvg" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d94a32ca845cdda27237a40beba9bd3d3858ac8fc5356eb9442bdeecfe34d9e0" +dependencies = [ + "jpeg-decoder 0.1.22", + "log", + "pico-args", + "png", + "rgb", + "svgfilters", + "svgtypes", + "tiny-skia", + "usvg", +] + [[package]] name = "rfd" version = "0.7.0" @@ -2618,6 +2711,15 @@ dependencies = [ "windows", ] +[[package]] +name = "rgb" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a374af9a0e5fdcdd98c1c7b64f05004f9ea2555b6c75f211daa81268a3c50f1" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.16.20" @@ -2644,6 +2746,15 @@ dependencies = [ "serde", ] +[[package]] +name = "roxmltree" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +dependencies = [ + "xmlparser", +] + [[package]] name = "rust-ini" version = "0.17.0" @@ -2687,12 +2798,37 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustybuzz" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44561062e583c4873162861261f16fd1d85fe927c4904d71329a4fe43dc355ef" +dependencies = [ + "bitflags", + "bytemuck", + "smallvec", + "ttf-parser 0.12.3", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "safe_arch" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +dependencies = [ + "bytemuck", +] + [[package]] name = "safemem" version = "0.3.3" @@ -2828,6 +2964,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" + [[package]] name = "slab" version = "0.4.5" @@ -2860,7 +3011,7 @@ dependencies = [ "dlib", "lazy_static", "log", - "memmap2", + "memmap2 0.3.1", "nix 0.22.3", "pkg-config", "wayland-client", @@ -2931,6 +3082,25 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "svgfilters" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "639abcebc15fdc2df179f37d6f5463d660c1c79cd552c12343a4600827a04bce" +dependencies = [ + "float-cmp", + "rgb", +] + +[[package]] +name = "svgtypes" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dabb3eb59a457c56d5282ab4545609e2cc382b41f6af239bb8d59a7267ef94b3" +dependencies = [ + "siphasher", +] + [[package]] name = "syn" version = "1.0.86" @@ -3051,6 +3221,20 @@ dependencies = [ "num_threads", ] +[[package]] +name = "tiny-skia" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bcfd4339bdd4545eabed74b208f2f1555f2e6540fb58135c01f46c0940aa138" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if 1.0.0", + "png", + "safe_arch", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -3154,6 +3338,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ttf-parser" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" + [[package]] name = "ttf-parser" version = "0.14.0" @@ -3187,6 +3377,24 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-general-category" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07547e3ee45e28326cc23faac56d44f58f16ab23e413db526debce3b0bfd2742" + [[package]] name = "unicode-normalization" version = "0.1.19" @@ -3196,6 +3404,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-script" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dd944fd05f2f0b5c674917aea8a4df6af84f2d8de3fe8d988b95d28fb8fb09" + +[[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.9" @@ -3249,6 +3469,33 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "usvg" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f064d38f79ff69e3160e2fba884e4ede897061c15178041a3976371c68cab1" +dependencies = [ + "base64", + "data-url", + "flate2", + "float-cmp", + "fontdb", + "kurbo", + "log", + "pico-args", + "rctree", + "roxmltree", + "rustybuzz", + "simplecss", + "siphasher", + "svgtypes", + "ttf-parser 0.12.3", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "valuable" version = "0.1.0" @@ -3673,6 +3920,18 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +[[package]] +name = "xmlparser" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index 35265b999..a9ff85c31 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -62,9 +62,14 @@ egui_glow = { version = "0.16.0", path = "../egui_glow", default-features = fals [target.'cfg(target_arch = "wasm32")'.dependencies] egui_web = { version = "0.16.0", path = "../egui_web", default-features = false, features = ["glow"] } + [dev-dependencies] ehttp = "0.2" image = { version = "0.24", default-features = false, features = ["jpeg", "png"] } poll-promise = "0.1" rfd = "0.7" +# svg.rs example: +resvg = "0.20" +tiny-skia = "0.6" +usvg = "0.20" diff --git a/eframe/examples/rustacean-flat-happy.svg b/eframe/examples/rustacean-flat-happy.svg new file mode 100644 index 000000000..c7f240dd9 --- /dev/null +++ b/eframe/examples/rustacean-flat-happy.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eframe/examples/svg.rs b/eframe/examples/svg.rs new file mode 100644 index 000000000..e4aff0cfa --- /dev/null +++ b/eframe/examples/svg.rs @@ -0,0 +1,108 @@ +//! A good way of displaying an SVG image in egui. +//! +//! Requires the dependencies `resvg`, `tiny-skia`, `usvg` +use eframe::{egui, epi}; + +/// Load an SVG and rasterize it into an egui image. +fn load_svg_data(svg_data: &[u8]) -> Result { + let mut opt = usvg::Options::default(); + opt.fontdb.load_system_fonts(); + + let rtree = usvg::Tree::from_data(svg_data, &opt.to_ref()).map_err(|err| err.to_string())?; + + let pixmap_size = rtree.svg_node().size.to_screen_size(); + let [w, h] = [pixmap_size.width(), pixmap_size.height()]; + + let mut pixmap = tiny_skia::Pixmap::new(w, h) + .ok_or_else(|| format!("Failed to create SVG Pixmap of size {}x{}", w, h))?; + + resvg::render( + &rtree, + usvg::FitTo::Original, + tiny_skia::Transform::default(), + pixmap.as_mut(), + ) + .ok_or_else(|| "Failed to render SVG".to_owned())?; + + let image = egui::ColorImage::from_rgba_unmultiplied( + [pixmap.width() as _, pixmap.height() as _], + pixmap.data(), + ); + + Ok(image) +} + +// ---------------------------------------------------------------------------- + +/// An SVG image to be shown in egui +struct SvgImage { + image: egui::ColorImage, + texture: Option, +} + +impl SvgImage { + /// Pass itn the bytes of an SVG that you've loaded from disk + pub fn from_svg_data(bytes: &[u8]) -> Result { + Ok(Self { + image: load_svg_data(bytes)?, + texture: None, + }) + } + + pub fn show_max_size(&mut self, ui: &mut egui::Ui, max_size: egui::Vec2) -> egui::Response { + let mut desired_size = egui::vec2(self.image.width() as _, self.image.height() as _); + desired_size *= (max_size.x / desired_size.x).min(1.0); + desired_size *= (max_size.y / desired_size.y).min(1.0); + self.show_size(ui, desired_size) + } + + pub fn show_size(&mut self, ui: &mut egui::Ui, desired_size: egui::Vec2) -> egui::Response { + // We need to convert the SVG to a texture to display it: + // Future improvement: tell backend to do mip-mapping of the image to + // make it look smoother when downsized. + let svg_texture = self + .texture + .get_or_insert_with(|| ui.ctx().load_texture("svg", self.image.clone())); + ui.image(svg_texture, desired_size) + } +} + +// ---------------------------------------------------------------------------- + +struct MyApp { + svg_image: SvgImage, +} + +impl Default for MyApp { + fn default() -> Self { + Self { + svg_image: SvgImage::from_svg_data(include_bytes!("rustacean-flat-happy.svg")).unwrap(), + } + } +} + +impl epi::App for MyApp { + fn name(&self) -> &str { + "svg example" + } + + fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("SVG example"); + ui.label("The SVG is rasterized and displayed as a texture."); + + ui.separator(); + + let max_size = ui.available_size(); + self.svg_image.show_max_size(ui, max_size); + }); + } +} + +fn main() { + let options = eframe::NativeOptions { + initial_window_size: Some(egui::vec2(1000.0, 700.0)), + ..Default::default() + }; + eframe::run_native(Box::new(MyApp::default()), options); +} diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index d0ba574db..31e9643ec 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -65,7 +65,7 @@ )] #![allow(clippy::needless_doctest_main)] -pub use {egui, epi}; +pub use {egui, egui::emath, egui::epaint, epi}; #[cfg(not(target_arch = "wasm32"))] pub use epi::NativeOptions; diff --git a/egui/src/introspection.rs b/egui/src/introspection.rs index d0b346380..3726b8081 100644 --- a/egui/src/introspection.rs +++ b/egui/src/introspection.rs @@ -24,8 +24,6 @@ pub fn font_id_ui(ui: &mut Ui, font_id: &mut FontId) { // Show font texture in demo Ui pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Response { - use epaint::Mesh; - ui.vertical(|ui| { let color = if ui.visuals().dark_mode { Color32::WHITE diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 646780f39..fdf37174a 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -387,7 +387,7 @@ pub use epaint::{ color, mutex, text::{FontData, FontDefinitions, FontFamily, FontId}, textures::TexturesDelta, - AlphaImage, ClippedMesh, Color32, ColorImage, ImageData, Rgba, Rounding, Shape, Stroke, + AlphaImage, ClippedMesh, Color32, ColorImage, ImageData, Mesh, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId, }; diff --git a/egui_glow/Cargo.toml b/egui_glow/Cargo.toml index fe6870eec..30c680e49 100644 --- a/egui_glow/Cargo.toml +++ b/egui_glow/Cargo.toml @@ -73,6 +73,3 @@ glutin = { version = "0.28.0", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { version = "0.3", features=["console"] } wasm-bindgen = { version = "0.2" } - -[dev-dependencies] -image = { version = "0.24", default-features = false, features = ["png"] }