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}