Skip to content

Commit

Permalink
Introduce egui_extras with RetainedImage for loading svg,png,jpeg,… (e…
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk authored Feb 21, 2022
1 parent 713917e commit c3fc899
Show file tree
Hide file tree
Showing 18 changed files with 449 additions and 172 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ jobs:
toolchain: 1.56.0
override: true
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
- run: cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui-winit -p egui_glium -p egui_glow --lib --no-deps --all-features
- run: cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui-winit -p egui_extras -p egui_glium -p egui_glow --lib --no-deps --all-features

doc_web:
name: cargo doc web
Expand Down
5 changes: 4 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Also see [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUT


## Crate overview
The crates in this repository are: `egui, emath, epaint, egui, epi, egui-winit, egui_web, egui_glium, egui_glow, egui_demo_lib, egui_demo_app`.
The crates in this repository are: `egui, emath, epaint, egui_extras, epi, egui-winit, egui_web, egui_glium, egui_glow, egui_demo_lib, egui_demo_app`.

### `egui`: The main GUI library.
Example code: `if ui.button("Click me").clicked() { … }`
Expand All @@ -21,6 +21,9 @@ Example: `Shape::Circle { center, radius, fill, stroke }`

Depends on `emath`, [`ab_glyph`](https://crates.io/crates/ab_glyph), [`atomic_refcell`](https://crates.io/crates/atomic_refcell), [`ahash`](https://crates.io/crates/ahash).

### `egui_extras`
This adds additional features on top of `egui`.

### `epi`
Depends only on `egui`.
Adds a thin application level wrapper around `egui` for hosting an `egui` app inside of `eframe`.
Expand Down
88 changes: 60 additions & 28 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"
members = [
"egui_demo_app",
"egui_demo_lib",
"egui_extras",
"egui_glium",
"egui_glow",
"egui_web",
Expand Down
7 changes: 2 additions & 5 deletions eframe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,9 @@ egui_web = { version = "0.16.0", path = "../egui_web", default-features = false,


[dev-dependencies]
# For examples:
egui_extras = { path = "../egui_extras", features = ["image", "svg"] }
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"
34 changes: 8 additions & 26 deletions eframe/examples/download_image.rs
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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 {
Expand All @@ -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
});
Expand All @@ -43,39 +43,21 @@ 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 {:?}",
content_type
))
}
}

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(),
))
}
39 changes: 19 additions & 20 deletions eframe/examples/image.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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(),
));
});
}
}
Expand All @@ -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(),
))
}
Loading

0 comments on commit c3fc899

Please sign in to comment.