egui/load.rs
1//! # Image loading
2//!
3//! If you just want to display some images, [`egui_extras`](https://crates.io/crates/egui_extras/)
4//! will get you up and running quickly with its reasonable default implementations of the traits described below.
5//!
6//! 1. Add [`egui_extras`](https://crates.io/crates/egui_extras/) as a dependency with the `all_loaders` feature.
7//! 2. Add a call to [`egui_extras::install_image_loaders`](https://docs.rs/egui_extras/latest/egui_extras/fn.install_image_loaders.html)
8//! in your app's setup code.
9//! 3. Use [`Ui::image`][`crate::ui::Ui::image`] with some [`ImageSource`][`crate::ImageSource`].
10//!
11//! ## Loading process
12//!
13//! There are three kinds of loaders:
14//! - [`BytesLoader`]: load the raw bytes of an image
15//! - [`ImageLoader`]: decode the bytes into an array of colors
16//! - [`TextureLoader`]: ask the backend to put an image onto the GPU
17//!
18//! The different kinds of loaders represent different layers in the loading process:
19//!
20//! ```text,ignore
21//! ui.image("file://image.png")
22//! └► Context::try_load_texture
23//! └► TextureLoader::load
24//! └► Context::try_load_image
25//! └► ImageLoader::load
26//! └► Context::try_load_bytes
27//! └► BytesLoader::load
28//! ```
29//!
30//! As each layer attempts to load the URI, it first asks the layer below it
31//! for the data it needs to do its job. But this is not a strict requirement,
32//! an implementation could instead generate the data it needs!
33//!
34//! Loader trait implementations may be registered on a context with:
35//! - [`Context::add_bytes_loader`]
36//! - [`Context::add_image_loader`]
37//! - [`Context::add_texture_loader`]
38//!
39//! There may be multiple loaders of the same kind registered at the same time.
40//! The `try_load` methods on [`Context`] will attempt to call each loader one by one,
41//! until one of them returns something other than [`LoadError::NotSupported`].
42//!
43//! The loaders are stored in the context. This means they may hold state across frames,
44//! which they can (and _should_) use to cache the results of the operations they perform.
45//!
46//! For example, a [`BytesLoader`] that loads file URIs (`file://image.png`)
47//! would cache each file read. A [`TextureLoader`] would cache each combination
48//! of `(URI, TextureOptions)`, and so on.
49//!
50//! Each URI will be passed through the loaders as a plain `&str`.
51//! The loaders are free to derive as much meaning from the URI as they wish to.
52//! For example, a loader may determine that it doesn't support loading a specific URI
53//! if the protocol does not match what it expects.
54
55mod bytes_loader;
56mod texture_loader;
57
58use std::{
59 borrow::Cow,
60 fmt::{Debug, Display},
61 ops::Deref,
62 sync::Arc,
63};
64
65use ahash::HashMap;
66
67use emath::{Float as _, OrderedFloat};
68use epaint::{ColorImage, TextureHandle, TextureId, Vec2, mutex::Mutex, textures::TextureOptions};
69
70use crate::Context;
71
72pub use self::{bytes_loader::DefaultBytesLoader, texture_loader::DefaultTextureLoader};
73
74/// Represents a failed attempt at loading an image.
75#[derive(Clone, Debug)]
76pub enum LoadError {
77 /// Programmer error: There are no image loaders installed.
78 NoImageLoaders,
79
80 /// A specific loader does not support this scheme or protocol.
81 NotSupported,
82
83 /// A specific loader does not support the format of the image.
84 FormatNotSupported { detected_format: Option<String> },
85
86 /// Programmer error: Failed to find the bytes for this image because
87 /// there was no [`BytesLoader`] supporting the scheme.
88 NoMatchingBytesLoader,
89
90 /// Programmer error: Failed to parse the bytes as an image because
91 /// there was no [`ImageLoader`] supporting the format.
92 NoMatchingImageLoader { detected_format: Option<String> },
93
94 /// Programmer error: no matching [`TextureLoader`].
95 /// Because of the [`DefaultTextureLoader`], this error should never happen.
96 NoMatchingTextureLoader,
97
98 /// Runtime error: Loading was attempted, but failed (e.g. "File not found").
99 Loading(String),
100}
101
102impl LoadError {
103 /// Returns the (approximate) size of the error message in bytes.
104 pub fn byte_size(&self) -> usize {
105 match self {
106 Self::FormatNotSupported { detected_format }
107 | Self::NoMatchingImageLoader { detected_format } => {
108 detected_format.as_ref().map_or(0, |s| s.len())
109 }
110 Self::Loading(message) => message.len(),
111 _ => std::mem::size_of::<Self>(),
112 }
113 }
114}
115
116impl Display for LoadError {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 match self {
119 Self::NoImageLoaders => f.write_str(
120 "No image loaders are installed. If you're trying to load some images \
121 for the first time, follow the steps outlined in https://docs.rs/egui/latest/egui/load/index.html"),
122
123 Self::NoMatchingBytesLoader => f.write_str("No matching BytesLoader. Either you need to call Context::include_bytes, or install some more bytes loaders, e.g. using egui_extras."),
124
125 Self::NoMatchingImageLoader { detected_format: None } => f.write_str("No matching ImageLoader. Either no ImageLoader is installed or the image is corrupted / has an unsupported format."),
126 Self::NoMatchingImageLoader { detected_format: Some(detected_format) } => write!(f, "No matching ImageLoader for format: {detected_format:?}. Make sure you enabled the necessary features on the image crate."),
127
128 Self::NoMatchingTextureLoader => f.write_str("No matching TextureLoader. Did you remove the default one?"),
129
130 Self::NotSupported => f.write_str("Image scheme or URI not supported by this loader"),
131
132 Self::FormatNotSupported { detected_format } => write!(f, "Image format not supported by this loader: {detected_format:?}"),
133
134 Self::Loading(message) => f.write_str(message),
135 }
136 }
137}
138
139impl std::error::Error for LoadError {}
140
141pub type Result<T, E = LoadError> = std::result::Result<T, E>;
142
143/// Given as a hint for image loading requests.
144///
145/// Used mostly for rendering SVG:s to a good size.
146/// The [`SizeHint`] determines at what resolution the image should be rasterized.
147#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
148pub enum SizeHint {
149 /// Scale original size by some factor, keeping the original aspect ratio.
150 ///
151 /// The original size of the image is usually its texel resolution,
152 /// but for an SVG it's the point size of the SVG.
153 ///
154 /// For instance, setting `Scale(2.0)` will rasterize SVG:s to twice their original size,
155 /// which is useful for high-DPI displays.
156 Scale(OrderedFloat<f32>),
157
158 /// Scale to exactly this pixel width, keeping the original aspect ratio.
159 Width(u32),
160
161 /// Scale to exactly this pixel height, keeping the original aspect ratio.
162 Height(u32),
163
164 /// Scale to this pixel size.
165 Size {
166 width: u32,
167 height: u32,
168
169 /// If true, the image will be as large as possible
170 /// while still fitting within the given width/height.
171 maintain_aspect_ratio: bool,
172 },
173}
174
175impl SizeHint {
176 /// Multiply size hint by a factor.
177 pub fn scale_by(self, factor: f32) -> Self {
178 match self {
179 Self::Scale(scale) => Self::Scale(OrderedFloat(factor * scale.0)),
180 Self::Width(width) => Self::Width((factor * width as f32).round() as _),
181 Self::Height(height) => Self::Height((factor * height as f32).round() as _),
182 Self::Size {
183 width,
184 height,
185 maintain_aspect_ratio,
186 } => Self::Size {
187 width: (factor * width as f32).round() as _,
188 height: (factor * height as f32).round() as _,
189 maintain_aspect_ratio,
190 },
191 }
192 }
193}
194
195impl Default for SizeHint {
196 #[inline]
197 fn default() -> Self {
198 Self::Scale(1.0.ord())
199 }
200}
201
202/// Represents a byte buffer.
203///
204/// This is essentially `Cow<'static, [u8]>` but with the `Owned` variant being an `Arc`.
205#[derive(Clone)]
206pub enum Bytes {
207 Static(&'static [u8]),
208 Shared(Arc<[u8]>),
209}
210
211impl Debug for Bytes {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 match self {
214 Self::Static(arg0) => f.debug_tuple("Static").field(&arg0.len()).finish(),
215 Self::Shared(arg0) => f.debug_tuple("Shared").field(&arg0.len()).finish(),
216 }
217 }
218}
219
220impl From<&'static [u8]> for Bytes {
221 #[inline]
222 fn from(value: &'static [u8]) -> Self {
223 Self::Static(value)
224 }
225}
226
227impl<const N: usize> From<&'static [u8; N]> for Bytes {
228 #[inline]
229 fn from(value: &'static [u8; N]) -> Self {
230 Self::Static(value)
231 }
232}
233
234impl From<Arc<[u8]>> for Bytes {
235 #[inline]
236 fn from(value: Arc<[u8]>) -> Self {
237 Self::Shared(value)
238 }
239}
240
241impl From<Vec<u8>> for Bytes {
242 #[inline]
243 fn from(value: Vec<u8>) -> Self {
244 Self::Shared(value.into())
245 }
246}
247
248impl AsRef<[u8]> for Bytes {
249 #[inline]
250 fn as_ref(&self) -> &[u8] {
251 match self {
252 Self::Static(bytes) => bytes,
253 Self::Shared(bytes) => bytes,
254 }
255 }
256}
257
258impl Deref for Bytes {
259 type Target = [u8];
260
261 #[inline]
262 fn deref(&self) -> &Self::Target {
263 self.as_ref()
264 }
265}
266
267/// Represents bytes which are currently being loaded.
268///
269/// This is similar to [`std::task::Poll`], but the `Pending` variant
270/// contains an optional `size`, which may be used during layout to
271/// pre-allocate space the image.
272#[derive(Clone)]
273pub enum BytesPoll {
274 /// Bytes are being loaded.
275 Pending {
276 /// Point size of the image.
277 ///
278 /// Set if known (e.g. from a HTTP header, or by parsing the image file header).
279 size: Option<Vec2>,
280 },
281
282 /// Bytes are loaded.
283 Ready {
284 /// Point size of the image.
285 ///
286 /// Set if known (e.g. from a HTTP header, or by parsing the image file header).
287 size: Option<Vec2>,
288
289 /// File contents, e.g. the contents of a `.png`.
290 bytes: Bytes,
291
292 /// Mime type of the content, e.g. `image/png`.
293 ///
294 /// Set if known (e.g. from `Content-Type` HTTP header).
295 mime: Option<String>,
296 },
297}
298
299/// Used to get a unique ID when implementing one of the loader traits: [`BytesLoader::id`], [`ImageLoader::id`], and [`TextureLoader::id`].
300///
301/// This just expands to `module_path!()` concatenated with the given type name.
302#[macro_export]
303macro_rules! generate_loader_id {
304 ($ty:ident) => {
305 concat!(module_path!(), "::", stringify!($ty))
306 };
307}
308pub use crate::generate_loader_id;
309
310pub type BytesLoadResult = Result<BytesPoll>;
311
312/// Represents a loader capable of loading raw unstructured bytes from somewhere,
313/// e.g. from disk or network.
314///
315/// It should also provide any subsequent loaders a hint for what the bytes may
316/// represent using [`BytesPoll::Ready::mime`], if it can be inferred.
317///
318/// Implementations are expected to cache at least each `URI`.
319pub trait BytesLoader {
320 /// Unique ID of this loader.
321 ///
322 /// To reduce the chance of collisions, use [`generate_loader_id`] for this.
323 fn id(&self) -> &str;
324
325 /// Try loading the bytes from the given uri.
326 ///
327 /// Implementations should call `ctx.request_repaint` to wake up the ui
328 /// once the data is ready.
329 ///
330 /// The implementation should cache any result, so that calling this
331 /// is immediate-mode safe.
332 ///
333 /// # Errors
334 /// This may fail with:
335 /// - [`LoadError::NotSupported`] if the loader does not support loading `uri`.
336 /// - [`LoadError::Loading`] if the loading process failed.
337 fn load(&self, ctx: &Context, uri: &str) -> BytesLoadResult;
338
339 /// Forget the given `uri`.
340 ///
341 /// If `uri` is cached, it should be evicted from cache,
342 /// so that it may be fully reloaded.
343 fn forget(&self, uri: &str);
344
345 /// Forget all URIs ever given to this loader.
346 ///
347 /// If the loader caches any URIs, the entire cache should be cleared,
348 /// so that all of them may be fully reloaded.
349 fn forget_all(&self);
350
351 /// Implementations may use this to perform work at the end of a frame,
352 /// such as evicting unused entries from a cache.
353 fn end_pass(&self, pass_index: u64) {
354 let _ = pass_index;
355 }
356
357 /// If the loader caches any data, this should return the size of that cache.
358 fn byte_size(&self) -> usize;
359
360 /// Returns `true` if some data is currently being loaded.
361 fn has_pending(&self) -> bool {
362 false
363 }
364}
365
366/// Represents an image which is currently being loaded.
367///
368/// This is similar to [`std::task::Poll`], but the `Pending` variant
369/// contains an optional `size`, which may be used during layout to
370/// pre-allocate space the image.
371#[derive(Clone)]
372pub enum ImagePoll {
373 /// Image is loading.
374 Pending {
375 /// Point size of the image.
376 ///
377 /// Set if known (e.g. from a HTTP header, or by parsing the image file header).
378 size: Option<Vec2>,
379 },
380
381 /// Image is loaded.
382 Ready { image: Arc<ColorImage> },
383}
384
385pub type ImageLoadResult = Result<ImagePoll>;
386
387/// An `ImageLoader` decodes raw bytes into a [`ColorImage`].
388///
389/// Implementations are expected to cache at least each `URI`.
390pub trait ImageLoader {
391 /// Unique ID of this loader.
392 ///
393 /// To reduce the chance of collisions, include `module_path!()` as part of this ID.
394 ///
395 /// For example: `concat!(module_path!(), "::MyLoader")`
396 /// for `my_crate::my_loader::MyLoader`.
397 fn id(&self) -> &str;
398
399 /// Try loading the image from the given uri.
400 ///
401 /// Implementations should call `ctx.request_repaint` to wake up the ui
402 /// once the image is ready.
403 ///
404 /// The implementation should cache any result, so that calling this
405 /// is immediate-mode safe.
406 ///
407 /// # Errors
408 /// This may fail with:
409 /// - [`LoadError::NotSupported`] if the loader does not support loading `uri`.
410 /// - [`LoadError::Loading`] if the loading process failed.
411 fn load(&self, ctx: &Context, uri: &str, size_hint: SizeHint) -> ImageLoadResult;
412
413 /// Forget the given `uri`.
414 ///
415 /// If `uri` is cached, it should be evicted from cache,
416 /// so that it may be fully reloaded.
417 fn forget(&self, uri: &str);
418
419 /// Forget all URIs ever given to this loader.
420 ///
421 /// If the loader caches any URIs, the entire cache should be cleared,
422 /// so that all of them may be fully reloaded.
423 fn forget_all(&self);
424
425 /// Implementations may use this to perform work at the end of a pass,
426 /// such as evicting unused entries from a cache.
427 fn end_pass(&self, pass_index: u64) {
428 let _ = pass_index;
429 }
430
431 /// If the loader caches any data, this should return the size of that cache.
432 fn byte_size(&self) -> usize;
433
434 /// Returns `true` if some image is currently being loaded.
435 ///
436 /// NOTE: You probably also want to check [`BytesLoader::has_pending`].
437 fn has_pending(&self) -> bool {
438 false
439 }
440}
441
442/// A texture with a known size.
443#[derive(Clone, Copy, Debug, PartialEq, Eq)]
444pub struct SizedTexture {
445 pub id: TextureId,
446
447 /// Point size of the original SVG, or the size of the image in texels.
448 pub size: Vec2,
449}
450
451impl SizedTexture {
452 /// Create a [`SizedTexture`] from a texture `id` with a specific `size`.
453 pub fn new(id: impl Into<TextureId>, size: impl Into<Vec2>) -> Self {
454 Self {
455 id: id.into(),
456 size: size.into(),
457 }
458 }
459
460 /// Fetch the [id][`SizedTexture::id`] and [size][`SizedTexture::size`] from a [`TextureHandle`].
461 pub fn from_handle(handle: &TextureHandle) -> Self {
462 let size = handle.size();
463 Self {
464 id: handle.id(),
465 size: Vec2::new(size[0] as f32, size[1] as f32),
466 }
467 }
468}
469
470impl From<(TextureId, Vec2)> for SizedTexture {
471 #[inline]
472 fn from((id, size): (TextureId, Vec2)) -> Self {
473 Self { id, size }
474 }
475}
476
477impl<'a> From<&'a TextureHandle> for SizedTexture {
478 #[inline]
479 fn from(handle: &'a TextureHandle) -> Self {
480 Self::from_handle(handle)
481 }
482}
483
484/// Represents a texture is currently being loaded.
485///
486/// This is similar to [`std::task::Poll`], but the `Pending` variant
487/// contains an optional `size`, which may be used during layout to
488/// pre-allocate space the image.
489#[derive(Clone, Copy)]
490pub enum TexturePoll {
491 /// Texture is loading.
492 Pending {
493 /// Point size of the image.
494 ///
495 /// Set if known (e.g. from a HTTP header, or by parsing the image file header).
496 size: Option<Vec2>,
497 },
498
499 /// Texture is loaded.
500 Ready { texture: SizedTexture },
501}
502
503impl TexturePoll {
504 /// Point size of the original SVG, or the size of the image in texels.
505 #[inline]
506 pub fn size(&self) -> Option<Vec2> {
507 match self {
508 Self::Pending { size } => *size,
509 Self::Ready { texture } => Some(texture.size),
510 }
511 }
512
513 #[inline]
514 pub fn texture_id(&self) -> Option<TextureId> {
515 match self {
516 Self::Pending { .. } => None,
517 Self::Ready { texture } => Some(texture.id),
518 }
519 }
520}
521
522pub type TextureLoadResult = Result<TexturePoll>;
523
524/// A `TextureLoader` uploads a [`ColorImage`] to the GPU, returning a [`SizedTexture`].
525///
526/// `egui` comes with an implementation that uses [`Context::load_texture`],
527/// which just asks the egui backend to upload the image to the GPU.
528///
529/// You can implement this trait if you do your own uploading of images to the GPU.
530/// For instance, you can use this to refer to textures in a game engine that egui
531/// doesn't otherwise know about.
532///
533/// Implementations are expected to cache each combination of `(URI, TextureOptions)`.
534pub trait TextureLoader {
535 /// Unique ID of this loader.
536 ///
537 /// To reduce the chance of collisions, include `module_path!()` as part of this ID.
538 ///
539 /// For example: `concat!(module_path!(), "::MyLoader")`
540 /// for `my_crate::my_loader::MyLoader`.
541 fn id(&self) -> &str;
542
543 /// Try loading the texture from the given uri.
544 ///
545 /// Implementations should call `ctx.request_repaint` to wake up the ui
546 /// once the texture is ready.
547 ///
548 /// The implementation should cache any result, so that calling this
549 /// is immediate-mode safe.
550 ///
551 /// # Errors
552 /// This may fail with:
553 /// - [`LoadError::NotSupported`] if the loader does not support loading `uri`.
554 /// - [`LoadError::Loading`] if the loading process failed.
555 fn load(
556 &self,
557 ctx: &Context,
558 uri: &str,
559 texture_options: TextureOptions,
560 size_hint: SizeHint,
561 ) -> TextureLoadResult;
562
563 /// Forget the given `uri`.
564 ///
565 /// If `uri` is cached, it should be evicted from cache,
566 /// so that it may be fully reloaded.
567 fn forget(&self, uri: &str);
568
569 /// Forget all URIs ever given to this loader.
570 ///
571 /// If the loader caches any URIs, the entire cache should be cleared,
572 /// so that all of them may be fully reloaded.
573 fn forget_all(&self);
574
575 /// Implementations may use this to perform work at the end of a pass,
576 /// such as evicting unused entries from a cache.
577 fn end_pass(&self, pass_index: u64) {
578 let _ = pass_index;
579 }
580
581 /// If the loader caches any data, this should return the size of that cache.
582 fn byte_size(&self) -> usize;
583}
584
585type BytesLoaderImpl = Arc<dyn BytesLoader + Send + Sync + 'static>;
586type ImageLoaderImpl = Arc<dyn ImageLoader + Send + Sync + 'static>;
587type TextureLoaderImpl = Arc<dyn TextureLoader + Send + Sync + 'static>;
588
589#[derive(Clone)]
590/// The loaders of bytes, images, and textures.
591pub struct Loaders {
592 pub include: Arc<DefaultBytesLoader>,
593 pub bytes: Mutex<Vec<BytesLoaderImpl>>,
594 pub image: Mutex<Vec<ImageLoaderImpl>>,
595 pub texture: Mutex<Vec<TextureLoaderImpl>>,
596}
597
598impl Default for Loaders {
599 fn default() -> Self {
600 let include = Arc::new(DefaultBytesLoader::default());
601 Self {
602 bytes: Mutex::new(vec![include.clone()]),
603 image: Mutex::new(Vec::new()),
604 // By default we only include `DefaultTextureLoader`.
605 texture: Mutex::new(vec![Arc::new(DefaultTextureLoader::default())]),
606 include,
607 }
608 }
609}
610
611impl Loaders {
612 /// The given pass has just ended.
613 pub fn end_pass(&self, pass_index: u64) {
614 let Self {
615 include,
616 bytes,
617 image,
618 texture,
619 } = self;
620
621 include.end_pass(pass_index);
622 for loader in bytes.lock().iter() {
623 loader.end_pass(pass_index);
624 }
625 for loader in image.lock().iter() {
626 loader.end_pass(pass_index);
627 }
628 for loader in texture.lock().iter() {
629 loader.end_pass(pass_index);
630 }
631 }
632}