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}