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, OrderedFloat};
68use epaint::{mutex::Mutex, textures::TextureOptions, ColorImage, TextureHandle, TextureId, Vec2};
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, protocol or image format.
81    NotSupported,
82
83    /// Programmer error: Failed to find the bytes for this image because
84    /// there was no [`BytesLoader`] supporting the scheme.
85    NoMatchingBytesLoader,
86
87    /// Programmer error: Failed to parse the bytes as an image because
88    /// there was no [`ImageLoader`] supporting the scheme.
89    NoMatchingImageLoader,
90
91    /// Programmer error: no matching [`TextureLoader`].
92    /// Because of the [`DefaultTextureLoader`], this error should never happen.
93    NoMatchingTextureLoader,
94
95    /// Runtime error: Loading was attempted, but failed (e.g. "File not found").
96    Loading(String),
97}
98
99impl Display for LoadError {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        match self {
102            Self::NoImageLoaders => f.write_str(
103                "No image loaders are installed. If you're trying to load some images \
104                for the first time, follow the steps outlined in https://docs.rs/egui/latest/egui/load/index.html"),
105
106            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."),
107
108            Self::NoMatchingImageLoader => f.write_str("No matching ImageLoader. Either you need to call Context::include_bytes, or install some more bytes loaders, e.g. using egui_extras."),
109
110            Self::NoMatchingTextureLoader => f.write_str("No matching TextureLoader. Did you remove the default one?"),
111
112            Self::NotSupported => f.write_str("Image scheme or URI not supported by this loader"),
113
114            Self::Loading(message) => f.write_str(message),
115        }
116    }
117}
118
119impl std::error::Error for LoadError {}
120
121pub type Result<T, E = LoadError> = std::result::Result<T, E>;
122
123/// Given as a hint for image loading requests.
124///
125/// Used mostly for rendering SVG:s to a good size.
126///
127/// All variants will preserve the original aspect ratio.
128#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
129pub enum SizeHint {
130    /// Scale original size by some factor.
131    Scale(OrderedFloat<f32>),
132
133    /// Scale to width.
134    Width(u32),
135
136    /// Scale to height.
137    Height(u32),
138
139    /// Scale to size.
140    Size(u32, u32),
141}
142
143impl Default for SizeHint {
144    #[inline]
145    fn default() -> Self {
146        Self::Scale(1.0.ord())
147    }
148}
149
150impl From<Vec2> for SizeHint {
151    #[inline]
152    fn from(value: Vec2) -> Self {
153        Self::Size(value.x.round() as u32, value.y.round() as u32)
154    }
155}
156
157/// Represents a byte buffer.
158///
159/// This is essentially `Cow<'static, [u8]>` but with the `Owned` variant being an `Arc`.
160#[derive(Clone)]
161pub enum Bytes {
162    Static(&'static [u8]),
163    Shared(Arc<[u8]>),
164}
165
166impl Debug for Bytes {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        match self {
169            Self::Static(arg0) => f.debug_tuple("Static").field(&arg0.len()).finish(),
170            Self::Shared(arg0) => f.debug_tuple("Shared").field(&arg0.len()).finish(),
171        }
172    }
173}
174
175impl From<&'static [u8]> for Bytes {
176    #[inline]
177    fn from(value: &'static [u8]) -> Self {
178        Self::Static(value)
179    }
180}
181
182impl<const N: usize> From<&'static [u8; N]> for Bytes {
183    #[inline]
184    fn from(value: &'static [u8; N]) -> Self {
185        Self::Static(value)
186    }
187}
188
189impl From<Arc<[u8]>> for Bytes {
190    #[inline]
191    fn from(value: Arc<[u8]>) -> Self {
192        Self::Shared(value)
193    }
194}
195
196impl From<Vec<u8>> for Bytes {
197    #[inline]
198    fn from(value: Vec<u8>) -> Self {
199        Self::Shared(value.into())
200    }
201}
202
203impl AsRef<[u8]> for Bytes {
204    #[inline]
205    fn as_ref(&self) -> &[u8] {
206        match self {
207            Self::Static(bytes) => bytes,
208            Self::Shared(bytes) => bytes,
209        }
210    }
211}
212
213impl Deref for Bytes {
214    type Target = [u8];
215
216    #[inline]
217    fn deref(&self) -> &Self::Target {
218        self.as_ref()
219    }
220}
221
222/// Represents bytes which are currently being loaded.
223///
224/// This is similar to [`std::task::Poll`], but the `Pending` variant
225/// contains an optional `size`, which may be used during layout to
226/// pre-allocate space the image.
227#[derive(Clone)]
228pub enum BytesPoll {
229    /// Bytes are being loaded.
230    Pending {
231        /// Set if known (e.g. from a HTTP header, or by parsing the image file header).
232        size: Option<Vec2>,
233    },
234
235    /// Bytes are loaded.
236    Ready {
237        /// Set if known (e.g. from a HTTP header, or by parsing the image file header).
238        size: Option<Vec2>,
239
240        /// File contents, e.g. the contents of a `.png`.
241        bytes: Bytes,
242
243        /// Mime type of the content, e.g. `image/png`.
244        ///
245        /// Set if known (e.g. from `Content-Type` HTTP header).
246        mime: Option<String>,
247    },
248}
249
250/// Used to get a unique ID when implementing one of the loader traits: [`BytesLoader::id`], [`ImageLoader::id`], and [`TextureLoader::id`].
251///
252/// This just expands to `module_path!()` concatenated with the given type name.
253#[macro_export]
254macro_rules! generate_loader_id {
255    ($ty:ident) => {
256        concat!(module_path!(), "::", stringify!($ty))
257    };
258}
259pub use crate::generate_loader_id;
260
261pub type BytesLoadResult = Result<BytesPoll>;
262
263/// Represents a loader capable of loading raw unstructured bytes from somewhere,
264/// e.g. from disk or network.
265///
266/// It should also provide any subsequent loaders a hint for what the bytes may
267/// represent using [`BytesPoll::Ready::mime`], if it can be inferred.
268///
269/// Implementations are expected to cache at least each `URI`.
270pub trait BytesLoader {
271    /// Unique ID of this loader.
272    ///
273    /// To reduce the chance of collisions, use [`generate_loader_id`] for this.
274    fn id(&self) -> &str;
275
276    /// Try loading the bytes from the given uri.
277    ///
278    /// Implementations should call `ctx.request_repaint` to wake up the ui
279    /// once the data is ready.
280    ///
281    /// The implementation should cache any result, so that calling this
282    /// is immediate-mode safe.
283    ///
284    /// # Errors
285    /// This may fail with:
286    /// - [`LoadError::NotSupported`] if the loader does not support loading `uri`.
287    /// - [`LoadError::Loading`] if the loading process failed.
288    fn load(&self, ctx: &Context, uri: &str) -> BytesLoadResult;
289
290    /// Forget the given `uri`.
291    ///
292    /// If `uri` is cached, it should be evicted from cache,
293    /// so that it may be fully reloaded.
294    fn forget(&self, uri: &str);
295
296    /// Forget all URIs ever given to this loader.
297    ///
298    /// If the loader caches any URIs, the entire cache should be cleared,
299    /// so that all of them may be fully reloaded.
300    fn forget_all(&self);
301
302    /// Implementations may use this to perform work at the end of a frame,
303    /// such as evicting unused entries from a cache.
304    fn end_frame(&self, frame_index: usize) {
305        let _ = frame_index;
306    }
307
308    /// If the loader caches any data, this should return the size of that cache.
309    fn byte_size(&self) -> usize;
310}
311
312/// Represents an image which is currently being loaded.
313///
314/// This is similar to [`std::task::Poll`], but the `Pending` variant
315/// contains an optional `size`, which may be used during layout to
316/// pre-allocate space the image.
317#[derive(Clone)]
318pub enum ImagePoll {
319    /// Image is loading.
320    Pending {
321        /// Set if known (e.g. from a HTTP header, or by parsing the image file header).
322        size: Option<Vec2>,
323    },
324
325    /// Image is loaded.
326    Ready { image: Arc<ColorImage> },
327}
328
329pub type ImageLoadResult = Result<ImagePoll>;
330
331/// An `ImageLoader` decodes raw bytes into a [`ColorImage`].
332///
333/// Implementations are expected to cache at least each `URI`.
334pub trait ImageLoader {
335    /// Unique ID of this loader.
336    ///
337    /// To reduce the chance of collisions, include `module_path!()` as part of this ID.
338    ///
339    /// For example: `concat!(module_path!(), "::MyLoader")`
340    /// for `my_crate::my_loader::MyLoader`.
341    fn id(&self) -> &str;
342
343    /// Try loading the image from the given uri.
344    ///
345    /// Implementations should call `ctx.request_repaint` to wake up the ui
346    /// once the image is ready.
347    ///
348    /// The implementation should cache any result, so that calling this
349    /// is immediate-mode safe.
350    ///
351    /// # Errors
352    /// This may fail with:
353    /// - [`LoadError::NotSupported`] if the loader does not support loading `uri`.
354    /// - [`LoadError::Loading`] if the loading process failed.
355    fn load(&self, ctx: &Context, uri: &str, size_hint: SizeHint) -> ImageLoadResult;
356
357    /// Forget the given `uri`.
358    ///
359    /// If `uri` is cached, it should be evicted from cache,
360    /// so that it may be fully reloaded.
361    fn forget(&self, uri: &str);
362
363    /// Forget all URIs ever given to this loader.
364    ///
365    /// If the loader caches any URIs, the entire cache should be cleared,
366    /// so that all of them may be fully reloaded.
367    fn forget_all(&self);
368
369    /// Implementations may use this to perform work at the end of a frame,
370    /// such as evicting unused entries from a cache.
371    fn end_frame(&self, frame_index: usize) {
372        let _ = frame_index;
373    }
374
375    /// If the loader caches any data, this should return the size of that cache.
376    fn byte_size(&self) -> usize;
377}
378
379/// A texture with a known size.
380#[derive(Clone, Copy, Debug, PartialEq, Eq)]
381pub struct SizedTexture {
382    pub id: TextureId,
383    pub size: Vec2,
384}
385
386impl SizedTexture {
387    /// Create a [`SizedTexture`] from a texture `id` with a specific `size`.
388    pub fn new(id: impl Into<TextureId>, size: impl Into<Vec2>) -> Self {
389        Self {
390            id: id.into(),
391            size: size.into(),
392        }
393    }
394
395    /// Fetch the [id][`SizedTexture::id`] and [size][`SizedTexture::size`] from a [`TextureHandle`].
396    pub fn from_handle(handle: &TextureHandle) -> Self {
397        let size = handle.size();
398        Self {
399            id: handle.id(),
400            size: Vec2::new(size[0] as f32, size[1] as f32),
401        }
402    }
403}
404
405impl From<(TextureId, Vec2)> for SizedTexture {
406    #[inline]
407    fn from((id, size): (TextureId, Vec2)) -> Self {
408        Self { id, size }
409    }
410}
411
412impl<'a> From<&'a TextureHandle> for SizedTexture {
413    #[inline]
414    fn from(handle: &'a TextureHandle) -> Self {
415        Self::from_handle(handle)
416    }
417}
418
419/// Represents a texture is currently being loaded.
420///
421/// This is similar to [`std::task::Poll`], but the `Pending` variant
422/// contains an optional `size`, which may be used during layout to
423/// pre-allocate space the image.
424#[derive(Clone, Copy)]
425pub enum TexturePoll {
426    /// Texture is loading.
427    Pending {
428        /// Set if known (e.g. from a HTTP header, or by parsing the image file header).
429        size: Option<Vec2>,
430    },
431
432    /// Texture is loaded.
433    Ready { texture: SizedTexture },
434}
435
436impl TexturePoll {
437    #[inline]
438    pub fn size(&self) -> Option<Vec2> {
439        match self {
440            Self::Pending { size } => *size,
441            Self::Ready { texture } => Some(texture.size),
442        }
443    }
444
445    #[inline]
446    pub fn texture_id(&self) -> Option<TextureId> {
447        match self {
448            Self::Pending { .. } => None,
449            Self::Ready { texture } => Some(texture.id),
450        }
451    }
452}
453
454pub type TextureLoadResult = Result<TexturePoll>;
455
456/// A `TextureLoader` uploads a [`ColorImage`] to the GPU, returning a [`SizedTexture`].
457///
458/// `egui` comes with an implementation that uses [`Context::load_texture`],
459/// which just asks the egui backend to upload the image to the GPU.
460///
461/// You can implement this trait if you do your own uploading of images to the GPU.
462/// For instance, you can use this to refer to textures in a game engine that egui
463/// doesn't otherwise know about.
464///
465/// Implementations are expected to cache each combination of `(URI, TextureOptions)`.
466pub trait TextureLoader {
467    /// Unique ID of this loader.
468    ///
469    /// To reduce the chance of collisions, include `module_path!()` as part of this ID.
470    ///
471    /// For example: `concat!(module_path!(), "::MyLoader")`
472    /// for `my_crate::my_loader::MyLoader`.
473    fn id(&self) -> &str;
474
475    /// Try loading the texture from the given uri.
476    ///
477    /// Implementations should call `ctx.request_repaint` to wake up the ui
478    /// once the texture is ready.
479    ///
480    /// The implementation should cache any result, so that calling this
481    /// is immediate-mode safe.
482    ///
483    /// # Errors
484    /// This may fail with:
485    /// - [`LoadError::NotSupported`] if the loader does not support loading `uri`.
486    /// - [`LoadError::Loading`] if the loading process failed.
487    fn load(
488        &self,
489        ctx: &Context,
490        uri: &str,
491        texture_options: TextureOptions,
492        size_hint: SizeHint,
493    ) -> TextureLoadResult;
494
495    /// Forget the given `uri`.
496    ///
497    /// If `uri` is cached, it should be evicted from cache,
498    /// so that it may be fully reloaded.
499    fn forget(&self, uri: &str);
500
501    /// Forget all URIs ever given to this loader.
502    ///
503    /// If the loader caches any URIs, the entire cache should be cleared,
504    /// so that all of them may be fully reloaded.
505    fn forget_all(&self);
506
507    /// Implementations may use this to perform work at the end of a frame,
508    /// such as evicting unused entries from a cache.
509    fn end_frame(&self, frame_index: usize) {
510        let _ = frame_index;
511    }
512
513    /// If the loader caches any data, this should return the size of that cache.
514    fn byte_size(&self) -> usize;
515}
516
517type BytesLoaderImpl = Arc<dyn BytesLoader + Send + Sync + 'static>;
518type ImageLoaderImpl = Arc<dyn ImageLoader + Send + Sync + 'static>;
519type TextureLoaderImpl = Arc<dyn TextureLoader + Send + Sync + 'static>;
520
521#[derive(Clone)]
522/// The loaders of bytes, images, and textures.
523pub struct Loaders {
524    pub include: Arc<DefaultBytesLoader>,
525    pub bytes: Mutex<Vec<BytesLoaderImpl>>,
526    pub image: Mutex<Vec<ImageLoaderImpl>>,
527    pub texture: Mutex<Vec<TextureLoaderImpl>>,
528}
529
530impl Default for Loaders {
531    fn default() -> Self {
532        let include = Arc::new(DefaultBytesLoader::default());
533        Self {
534            bytes: Mutex::new(vec![include.clone()]),
535            image: Mutex::new(Vec::new()),
536            // By default we only include `DefaultTextureLoader`.
537            texture: Mutex::new(vec![Arc::new(DefaultTextureLoader::default())]),
538            include,
539        }
540    }
541}