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}