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