mirror of
https://github.com/emilk/egui.git
synced 2026-06-28 07:23:13 -04:00
Introduce egui_extras with RetainedImage for loading svg,png,jpeg,… (#1282)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
use egui_extras::RetainedImage;
|
||||
use poll_promise::Promise;
|
||||
|
||||
fn main() {
|
||||
@@ -11,7 +12,7 @@ fn main() {
|
||||
#[derive(Default)]
|
||||
struct MyApp {
|
||||
/// `None` when download hasn't started yet.
|
||||
promise: Option<Promise<ehttp::Result<egui::TextureHandle>>>,
|
||||
promise: Option<Promise<ehttp::Result<RetainedImage>>>,
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
@@ -24,14 +25,13 @@ impl epi::App for MyApp {
|
||||
// Begin download.
|
||||
// We download the image using `ehttp`, a library that works both in WASM and on native.
|
||||
// We use the `poll-promise` library to communicate with the UI thread.
|
||||
let ctx = ctx.clone();
|
||||
let frame = frame.clone();
|
||||
let (sender, promise) = Promise::new();
|
||||
let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024");
|
||||
ehttp::fetch(request, move |response| {
|
||||
let image = response.and_then(parse_response);
|
||||
sender.send(image); // send the results back to the UI thread.
|
||||
frame.request_repaint(); // wake up UI thread
|
||||
let texture = response.and_then(|response| parse_response(&ctx, response));
|
||||
sender.send(texture); // send the results back to the UI thread.
|
||||
});
|
||||
promise
|
||||
});
|
||||
@@ -43,24 +43,17 @@ impl epi::App for MyApp {
|
||||
Some(Err(err)) => {
|
||||
ui.colored_label(egui::Color32::RED, err); // something went wrong
|
||||
}
|
||||
Some(Ok(texture)) => {
|
||||
let mut size = texture.size_vec2();
|
||||
size *= (ui.available_width() / size.x).min(1.0);
|
||||
size *= (ui.available_height() / size.y).min(1.0);
|
||||
ui.image(texture, size);
|
||||
Some(Ok(image)) => {
|
||||
image.show_max_size(ui, ui.available_size());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_response(
|
||||
ctx: &egui::Context,
|
||||
response: ehttp::Response,
|
||||
) -> Result<egui::TextureHandle, String> {
|
||||
fn parse_response(response: ehttp::Response) -> Result<RetainedImage, String> {
|
||||
let content_type = response.content_type().unwrap_or_default();
|
||||
if content_type.starts_with("image/") {
|
||||
let image = load_image(&response.bytes).map_err(|err| err.to_string())?;
|
||||
Ok(ctx.load_texture("my-image", image))
|
||||
RetainedImage::from_image_bytes(&response.url, &response.bytes)
|
||||
} else {
|
||||
Err(format!(
|
||||
"Expected image, found content-type {:?}",
|
||||
@@ -68,14 +61,3 @@ fn parse_response(
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn load_image(image_data: &[u8]) -> Result<egui::ColorImage, image::ImageError> {
|
||||
let image = image::load_from_memory(image_data)?;
|
||||
let size = [image.width() as _, image.height() as _];
|
||||
let image_buffer = image.to_rgba8();
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
Ok(egui::ColorImage::from_rgba_unmultiplied(
|
||||
size,
|
||||
pixels.as_slice(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
use egui_extras::RetainedImage;
|
||||
|
||||
#[derive(Default)]
|
||||
struct MyApp {
|
||||
texture: Option<egui::TextureHandle>,
|
||||
image: RetainedImage,
|
||||
}
|
||||
|
||||
impl Default for MyApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
image: RetainedImage::from_image_bytes(
|
||||
"rust-logo-256x256.png",
|
||||
include_bytes!("rust-logo-256x256.png"),
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
@@ -13,17 +25,15 @@ impl epi::App for MyApp {
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
let image = load_image(include_bytes!("rust-logo-256x256.png")).unwrap();
|
||||
ctx.load_texture("rust-logo", image)
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("This is an image:");
|
||||
ui.image(texture, texture.size_vec2());
|
||||
self.image.show(ui);
|
||||
|
||||
ui.heading("This is an image you can click:");
|
||||
ui.add(egui::ImageButton::new(texture, texture.size_vec2()));
|
||||
ui.add(egui::ImageButton::new(
|
||||
self.image.texture_id(ctx),
|
||||
self.image.size_vec2(),
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -32,14 +42,3 @@ fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
||||
|
||||
fn load_image(image_data: &[u8]) -> Result<egui::ColorImage, image::ImageError> {
|
||||
let image = image::load_from_memory(image_data)?;
|
||||
let size = [image.width() as _, image.height() as _];
|
||||
let image_buffer = image.to_rgba8();
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
Ok(egui::ColorImage::from_rgba_unmultiplied(
|
||||
size,
|
||||
pixels.as_slice(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,82 +1,23 @@
|
||||
//! A good way of displaying an SVG image in egui.
|
||||
//!
|
||||
//! Requires the dependencies `resvg`, `tiny-skia`, `usvg`
|
||||
//! Requires the dependency `egui_extras` with the `svg` feature.
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
|
||||
/// Load an SVG and rasterize it into an egui image.
|
||||
fn load_svg_data(svg_data: &[u8]) -> Result<egui::ColorImage, String> {
|
||||
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<egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl SvgImage {
|
||||
/// Pass itn the bytes of an SVG that you've loaded from disk
|
||||
pub fn from_svg_data(bytes: &[u8]) -> Result<Self, String> {
|
||||
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,
|
||||
svg_image: egui_extras::RetainedImage,
|
||||
}
|
||||
|
||||
impl Default for MyApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
svg_image: SvgImage::from_svg_data(include_bytes!("rustacean-flat-happy.svg")).unwrap(),
|
||||
svg_image: egui_extras::RetainedImage::from_svg_bytes(
|
||||
"rustacean-flat-happy.svg",
|
||||
include_bytes!("rustacean-flat-happy.svg"),
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user