egui/widgets/
image.rs

1use std::{borrow::Cow, sync::Arc, time::Duration};
2
3use emath::{Float as _, Rot2};
4use epaint::RectShape;
5
6use crate::{
7    load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll},
8    pos2, Align2, Color32, Context, Id, Mesh, Painter, Rect, Response, Rounding, Sense, Shape,
9    Spinner, Stroke, TextStyle, TextureOptions, Ui, Vec2, Widget,
10};
11
12/// A widget which displays an image.
13///
14/// The task of actually loading the image is deferred to when the `Image` is added to the [`Ui`],
15/// and how it is loaded depends on the provided [`ImageSource`]:
16///
17/// - [`ImageSource::Uri`] will load the image using the [asynchronous loading process][`crate::load`].
18/// - [`ImageSource::Bytes`] will also load the image using the [asynchronous loading process][`crate::load`], but with lower latency.
19/// - [`ImageSource::Texture`] will use the provided texture.
20///
21/// See [`crate::load`] for more information.
22///
23/// ### Examples
24/// // Using it in a layout:
25/// ```
26/// # egui::__run_test_ui(|ui| {
27/// ui.add(
28///     egui::Image::new(egui::include_image!("../../assets/ferris.png"))
29///         .rounding(5.0)
30/// );
31/// # });
32/// ```
33///
34/// // Using it just to paint:
35/// ```
36/// # egui::__run_test_ui(|ui| {
37/// # let rect = egui::Rect::from_min_size(Default::default(), egui::Vec2::splat(100.0));
38/// egui::Image::new(egui::include_image!("../../assets/ferris.png"))
39///     .rounding(5.0)
40///     .tint(egui::Color32::LIGHT_BLUE)
41///     .paint_at(ui, rect);
42/// # });
43/// ```
44///
45#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
46#[derive(Debug, Clone)]
47pub struct Image<'a> {
48    source: ImageSource<'a>,
49    texture_options: TextureOptions,
50    image_options: ImageOptions,
51    sense: Sense,
52    size: ImageSize,
53    pub(crate) show_loading_spinner: Option<bool>,
54}
55
56impl<'a> Image<'a> {
57    /// Load the image from some source.
58    pub fn new(source: impl Into<ImageSource<'a>>) -> Self {
59        fn new_mono(source: ImageSource<'_>) -> Image<'_> {
60            let size = if let ImageSource::Texture(tex) = &source {
61                // User is probably expecting their texture to have
62                // the exact size of the provided `SizedTexture`.
63                ImageSize {
64                    maintain_aspect_ratio: true,
65                    max_size: Vec2::INFINITY,
66                    fit: ImageFit::Exact(tex.size),
67                }
68            } else {
69                Default::default()
70            };
71
72            Image {
73                source,
74                texture_options: Default::default(),
75                image_options: Default::default(),
76                sense: Sense::hover(),
77                size,
78                show_loading_spinner: None,
79            }
80        }
81
82        new_mono(source.into())
83    }
84
85    /// Load the image from a URI.
86    ///
87    /// See [`ImageSource::Uri`].
88    pub fn from_uri(uri: impl Into<Cow<'a, str>>) -> Self {
89        Self::new(ImageSource::Uri(uri.into()))
90    }
91
92    /// Load the image from an existing texture.
93    ///
94    /// See [`ImageSource::Texture`].
95    pub fn from_texture(texture: impl Into<SizedTexture>) -> Self {
96        Self::new(ImageSource::Texture(texture.into()))
97    }
98
99    /// Load the image from some raw bytes.
100    ///
101    /// For better error messages, use the `bytes://` prefix for the URI.
102    ///
103    /// See [`ImageSource::Bytes`].
104    pub fn from_bytes(uri: impl Into<Cow<'static, str>>, bytes: impl Into<Bytes>) -> Self {
105        Self::new(ImageSource::Bytes {
106            uri: uri.into(),
107            bytes: bytes.into(),
108        })
109    }
110
111    /// Texture options used when creating the texture.
112    #[inline]
113    pub fn texture_options(mut self, texture_options: TextureOptions) -> Self {
114        self.texture_options = texture_options;
115        self
116    }
117
118    /// Set the max width of the image.
119    ///
120    /// No matter what the image is scaled to, it will never exceed this limit.
121    #[inline]
122    pub fn max_width(mut self, width: f32) -> Self {
123        self.size.max_size.x = width;
124        self
125    }
126
127    /// Set the max height of the image.
128    ///
129    /// No matter what the image is scaled to, it will never exceed this limit.
130    #[inline]
131    pub fn max_height(mut self, height: f32) -> Self {
132        self.size.max_size.y = height;
133        self
134    }
135
136    /// Set the max size of the image.
137    ///
138    /// No matter what the image is scaled to, it will never exceed this limit.
139    #[inline]
140    pub fn max_size(mut self, size: Vec2) -> Self {
141        self.size.max_size = size;
142        self
143    }
144
145    /// Whether or not the [`ImageFit`] should maintain the image's original aspect ratio.
146    #[inline]
147    pub fn maintain_aspect_ratio(mut self, value: bool) -> Self {
148        self.size.maintain_aspect_ratio = value;
149        self
150    }
151
152    /// Fit the image to its original size with some scaling.
153    ///
154    /// This will cause the image to overflow if it is larger than the available space.
155    ///
156    /// If [`Image::max_size`] is set, this is guaranteed to never exceed that limit.
157    #[inline]
158    pub fn fit_to_original_size(mut self, scale: f32) -> Self {
159        self.size.fit = ImageFit::Original { scale };
160        self
161    }
162
163    /// Fit the image to an exact size.
164    ///
165    /// If [`Image::max_size`] is set, this is guaranteed to never exceed that limit.
166    #[inline]
167    pub fn fit_to_exact_size(mut self, size: Vec2) -> Self {
168        self.size.fit = ImageFit::Exact(size);
169        self
170    }
171
172    /// Fit the image to a fraction of the available space.
173    ///
174    /// If [`Image::max_size`] is set, this is guaranteed to never exceed that limit.
175    #[inline]
176    pub fn fit_to_fraction(mut self, fraction: Vec2) -> Self {
177        self.size.fit = ImageFit::Fraction(fraction);
178        self
179    }
180
181    /// Fit the image to 100% of its available size, shrinking it if necessary.
182    ///
183    /// This is a shorthand for [`Image::fit_to_fraction`] with `1.0` for both width and height.
184    ///
185    /// If [`Image::max_size`] is set, this is guaranteed to never exceed that limit.
186    #[inline]
187    pub fn shrink_to_fit(self) -> Self {
188        self.fit_to_fraction(Vec2::new(1.0, 1.0))
189    }
190
191    /// Make the image respond to clicks and/or drags.
192    #[inline]
193    pub fn sense(mut self, sense: Sense) -> Self {
194        self.sense = sense;
195        self
196    }
197
198    /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right.
199    #[inline]
200    pub fn uv(mut self, uv: impl Into<Rect>) -> Self {
201        self.image_options.uv = uv.into();
202        self
203    }
204
205    /// A solid color to put behind the image. Useful for transparent images.
206    #[inline]
207    pub fn bg_fill(mut self, bg_fill: impl Into<Color32>) -> Self {
208        self.image_options.bg_fill = bg_fill.into();
209        self
210    }
211
212    /// Multiply image color with this. Default is WHITE (no tint).
213    #[inline]
214    pub fn tint(mut self, tint: impl Into<Color32>) -> Self {
215        self.image_options.tint = tint.into();
216        self
217    }
218
219    /// Rotate the image about an origin by some angle
220    ///
221    /// Positive angle is clockwise.
222    /// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right).
223    ///
224    /// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin.
225    ///
226    /// Due to limitations in the current implementation,
227    /// this will turn off rounding of the image.
228    #[inline]
229    pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self {
230        self.image_options.rotation = Some((Rot2::from_angle(angle), origin));
231        self.image_options.rounding = Rounding::ZERO; // incompatible with rotation
232        self
233    }
234
235    /// Round the corners of the image.
236    ///
237    /// The default is no rounding ([`Rounding::ZERO`]).
238    ///
239    /// Due to limitations in the current implementation,
240    /// this will turn off any rotation of the image.
241    #[inline]
242    pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
243        self.image_options.rounding = rounding.into();
244        if self.image_options.rounding != Rounding::ZERO {
245            self.image_options.rotation = None; // incompatible with rounding
246        }
247        self
248    }
249
250    /// Show a spinner when the image is loading.
251    ///
252    /// By default this uses the value of [`crate::Visuals::image_loading_spinners`].
253    #[inline]
254    pub fn show_loading_spinner(mut self, show: bool) -> Self {
255        self.show_loading_spinner = Some(show);
256        self
257    }
258}
259
260impl<'a, T: Into<ImageSource<'a>>> From<T> for Image<'a> {
261    fn from(value: T) -> Self {
262        Image::new(value)
263    }
264}
265
266impl<'a> Image<'a> {
267    /// Returns the size the image will occupy in the final UI.
268    #[inline]
269    pub fn calc_size(&self, available_size: Vec2, original_image_size: Option<Vec2>) -> Vec2 {
270        let original_image_size = original_image_size.unwrap_or(Vec2::splat(24.0)); // Fallback for still-loading textures, or failure to load.
271        self.size.calc_size(available_size, original_image_size)
272    }
273
274    pub fn load_and_calc_size(&self, ui: &Ui, available_size: Vec2) -> Option<Vec2> {
275        let image_size = self.load_for_size(ui.ctx(), available_size).ok()?.size()?;
276        Some(self.size.calc_size(available_size, image_size))
277    }
278
279    #[inline]
280    pub fn size(&self) -> Option<Vec2> {
281        match &self.source {
282            ImageSource::Texture(texture) => Some(texture.size),
283            ImageSource::Uri(_) | ImageSource::Bytes { .. } => None,
284        }
285    }
286
287    /// Returns the URI of the image.
288    ///
289    /// For GIFs, returns the URI without the frame number.
290    #[inline]
291    pub fn uri(&self) -> Option<&str> {
292        let uri = self.source.uri()?;
293
294        if let Ok((gif_uri, _index)) = decode_gif_uri(uri) {
295            Some(gif_uri)
296        } else {
297            Some(uri)
298        }
299    }
300
301    #[inline]
302    pub fn image_options(&self) -> &ImageOptions {
303        &self.image_options
304    }
305
306    #[inline]
307    pub fn source(&'a self, ctx: &Context) -> ImageSource<'a> {
308        match &self.source {
309            ImageSource::Uri(uri) if is_gif_uri(uri) => {
310                let frame_uri = encode_gif_uri(uri, gif_frame_index(ctx, uri));
311                ImageSource::Uri(Cow::Owned(frame_uri))
312            }
313
314            ImageSource::Bytes { uri, bytes } if is_gif_uri(uri) || has_gif_magic_header(bytes) => {
315                let frame_uri = encode_gif_uri(uri, gif_frame_index(ctx, uri));
316                ctx.include_bytes(uri.clone(), bytes.clone());
317                ImageSource::Uri(Cow::Owned(frame_uri))
318            }
319            _ => self.source.clone(),
320        }
321    }
322
323    /// Load the image from its [`Image::source`], returning the resulting [`SizedTexture`].
324    ///
325    /// The `available_size` is used as a hint when e.g. rendering an svg.
326    ///
327    /// # Errors
328    /// May fail if they underlying [`Context::try_load_texture`] call fails.
329    pub fn load_for_size(&self, ctx: &Context, available_size: Vec2) -> TextureLoadResult {
330        let size_hint = self.size.hint(available_size, ctx.pixels_per_point());
331        self.source(ctx)
332            .clone()
333            .load(ctx, self.texture_options, size_hint)
334    }
335
336    /// Paint the image in the given rectangle.
337    ///
338    /// ```
339    /// # egui::__run_test_ui(|ui| {
340    /// # let rect = egui::Rect::from_min_size(Default::default(), egui::Vec2::splat(100.0));
341    /// egui::Image::new(egui::include_image!("../../assets/ferris.png"))
342    ///     .rounding(5.0)
343    ///     .tint(egui::Color32::LIGHT_BLUE)
344    ///     .paint_at(ui, rect);
345    /// # });
346    /// ```
347    #[inline]
348    pub fn paint_at(&self, ui: &Ui, rect: Rect) {
349        paint_texture_load_result(
350            ui,
351            &self.load_for_size(ui.ctx(), rect.size()),
352            rect,
353            self.show_loading_spinner,
354            &self.image_options,
355        );
356    }
357}
358
359impl<'a> Widget for Image<'a> {
360    fn ui(self, ui: &mut Ui) -> Response {
361        let tlr = self.load_for_size(ui.ctx(), ui.available_size());
362        let original_image_size = tlr.as_ref().ok().and_then(|t| t.size());
363        let ui_size = self.calc_size(ui.available_size(), original_image_size);
364
365        let (rect, response) = ui.allocate_exact_size(ui_size, self.sense);
366        if ui.is_rect_visible(rect) {
367            paint_texture_load_result(
368                ui,
369                &tlr,
370                rect,
371                self.show_loading_spinner,
372                &self.image_options,
373            );
374        }
375        texture_load_result_response(&self.source(ui.ctx()), &tlr, response)
376    }
377}
378
379/// This type determines the constraints on how
380/// the size of an image should be calculated.
381#[derive(Debug, Clone, Copy)]
382pub struct ImageSize {
383    /// Whether or not the final size should maintain the original aspect ratio.
384    ///
385    /// This setting is applied last.
386    ///
387    /// This defaults to `true`.
388    pub maintain_aspect_ratio: bool,
389
390    /// Determines the maximum size of the image.
391    ///
392    /// Defaults to `Vec2::INFINITY` (no limit).
393    pub max_size: Vec2,
394
395    /// Determines how the image should shrink/expand/stretch/etc. to fit within its allocated space.
396    ///
397    /// This setting is applied first.
398    ///
399    /// Defaults to `ImageFit::Fraction([1, 1])`
400    pub fit: ImageFit,
401}
402
403/// This type determines how the image should try to fit within the UI.
404///
405/// The final fit will be clamped to [`ImageSize::max_size`].
406#[derive(Debug, Clone, Copy)]
407#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
408pub enum ImageFit {
409    /// Fit the image to its original size, scaled by some factor.
410    ///
411    /// Ignores how much space is actually available in the ui.
412    Original { scale: f32 },
413
414    /// Fit the image to a fraction of the available size.
415    Fraction(Vec2),
416
417    /// Fit the image to an exact size.
418    ///
419    /// Ignores how much space is actually available in the ui.
420    Exact(Vec2),
421}
422
423impl ImageFit {
424    pub fn resolve(self, available_size: Vec2, image_size: Vec2) -> Vec2 {
425        match self {
426            Self::Original { scale } => image_size * scale,
427            Self::Fraction(fract) => available_size * fract,
428            Self::Exact(size) => size,
429        }
430    }
431}
432
433impl ImageSize {
434    /// Size hint for e.g. rasterizing an svg.
435    pub fn hint(&self, available_size: Vec2, pixels_per_point: f32) -> SizeHint {
436        let size = match self.fit {
437            ImageFit::Original { scale } => return SizeHint::Scale(scale.ord()),
438            ImageFit::Fraction(fract) => available_size * fract,
439            ImageFit::Exact(size) => size,
440        };
441        let size = size.min(self.max_size);
442        let size = size * pixels_per_point;
443
444        // `inf` on an axis means "any value"
445        match (size.x.is_finite(), size.y.is_finite()) {
446            (true, true) => SizeHint::Size(size.x.round() as u32, size.y.round() as u32),
447            (true, false) => SizeHint::Width(size.x.round() as u32),
448            (false, true) => SizeHint::Height(size.y.round() as u32),
449            (false, false) => SizeHint::Scale(pixels_per_point.ord()),
450        }
451    }
452
453    /// Calculate the final on-screen size in points.
454    pub fn calc_size(&self, available_size: Vec2, original_image_size: Vec2) -> Vec2 {
455        let Self {
456            maintain_aspect_ratio,
457            max_size,
458            fit,
459        } = *self;
460        match fit {
461            ImageFit::Original { scale } => {
462                let image_size = original_image_size * scale;
463                if image_size.x <= max_size.x && image_size.y <= max_size.y {
464                    image_size
465                } else {
466                    scale_to_fit(image_size, max_size, maintain_aspect_ratio)
467                }
468            }
469            ImageFit::Fraction(fract) => {
470                let scale_to_size = (available_size * fract).min(max_size);
471                scale_to_fit(original_image_size, scale_to_size, maintain_aspect_ratio)
472            }
473            ImageFit::Exact(size) => {
474                let scale_to_size = size.min(max_size);
475                scale_to_fit(original_image_size, scale_to_size, maintain_aspect_ratio)
476            }
477        }
478    }
479}
480
481// TODO(jprochazk): unit-tests
482fn scale_to_fit(image_size: Vec2, available_size: Vec2, maintain_aspect_ratio: bool) -> Vec2 {
483    if maintain_aspect_ratio {
484        let ratio_x = available_size.x / image_size.x;
485        let ratio_y = available_size.y / image_size.y;
486        let ratio = if ratio_x < ratio_y { ratio_x } else { ratio_y };
487        let ratio = if ratio.is_finite() { ratio } else { 1.0 };
488        image_size * ratio
489    } else {
490        available_size
491    }
492}
493
494impl Default for ImageSize {
495    #[inline]
496    fn default() -> Self {
497        Self {
498            max_size: Vec2::INFINITY,
499            fit: ImageFit::Fraction(Vec2::new(1.0, 1.0)),
500            maintain_aspect_ratio: true,
501        }
502    }
503}
504
505/// This type tells the [`Ui`] how to load an image.
506///
507/// This is used by [`Image::new`] and [`Ui::image`].
508#[derive(Clone)]
509pub enum ImageSource<'a> {
510    /// Load the image from a URI, e.g. `https://example.com/image.png`.
511    ///
512    /// This could be a `file://` path, `https://` url, `bytes://` identifier, or some other scheme.
513    ///
514    /// How the URI will be turned into a texture for rendering purposes is
515    /// up to the registered loaders to handle.
516    ///
517    /// See [`crate::load`] for more information.
518    Uri(Cow<'a, str>),
519
520    /// Load the image from an existing texture.
521    ///
522    /// The user is responsible for loading the texture, determining its size,
523    /// and allocating a [`crate::TextureId`] for it.
524    Texture(SizedTexture),
525
526    /// Load the image from some raw bytes.
527    ///
528    /// The [`Bytes`] may be:
529    /// - `'static`, obtained from `include_bytes!` or similar
530    /// - Anything that can be converted to `Arc<[u8]>`
531    ///
532    /// This instructs the [`Ui`] to cache the raw bytes, which are then further processed by any registered loaders.
533    ///
534    /// See also [`crate::include_image`] for an easy way to load and display static images.
535    ///
536    /// See [`crate::load`] for more information.
537    Bytes {
538        /// The unique identifier for this image, e.g. `bytes://my_logo.png`.
539        ///
540        /// You should use a proper extension (`.jpg`, `.png`, `.svg`, etc) for the image to load properly.
541        ///
542        /// Use the `bytes://` scheme for the URI for better error messages.
543        uri: Cow<'static, str>,
544
545        bytes: Bytes,
546    },
547}
548
549impl<'a> std::fmt::Debug for ImageSource<'a> {
550    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
551        match self {
552            ImageSource::Bytes { uri, .. } | ImageSource::Uri(uri) => uri.as_ref().fmt(f),
553            ImageSource::Texture(st) => st.id.fmt(f),
554        }
555    }
556}
557
558impl<'a> ImageSource<'a> {
559    /// Size of the texture, if known.
560    #[inline]
561    pub fn texture_size(&self) -> Option<Vec2> {
562        match self {
563            ImageSource::Texture(texture) => Some(texture.size),
564            ImageSource::Uri(_) | ImageSource::Bytes { .. } => None,
565        }
566    }
567
568    /// # Errors
569    /// Failure to load the texture.
570    pub fn load(
571        self,
572        ctx: &Context,
573        texture_options: TextureOptions,
574        size_hint: SizeHint,
575    ) -> TextureLoadResult {
576        match self {
577            Self::Texture(texture) => Ok(TexturePoll::Ready { texture }),
578            Self::Uri(uri) => ctx.try_load_texture(uri.as_ref(), texture_options, size_hint),
579            Self::Bytes { uri, bytes } => {
580                ctx.include_bytes(uri.clone(), bytes);
581                ctx.try_load_texture(uri.as_ref(), texture_options, size_hint)
582            }
583        }
584    }
585
586    /// Get the `uri` that this image was constructed from.
587    ///
588    /// This will return `None` for [`Self::Texture`].
589    pub fn uri(&self) -> Option<&str> {
590        match self {
591            ImageSource::Bytes { uri, .. } | ImageSource::Uri(uri) => Some(uri),
592            ImageSource::Texture(_) => None,
593        }
594    }
595}
596
597pub fn paint_texture_load_result(
598    ui: &Ui,
599    tlr: &TextureLoadResult,
600    rect: Rect,
601    show_loading_spinner: Option<bool>,
602    options: &ImageOptions,
603) {
604    match tlr {
605        Ok(TexturePoll::Ready { texture }) => {
606            paint_texture_at(ui.painter(), rect, options, texture);
607        }
608        Ok(TexturePoll::Pending { .. }) => {
609            let show_loading_spinner =
610                show_loading_spinner.unwrap_or(ui.visuals().image_loading_spinners);
611            if show_loading_spinner {
612                Spinner::new().paint_at(ui, rect);
613            }
614        }
615        Err(_) => {
616            let font_id = TextStyle::Body.resolve(ui.style());
617            ui.painter().text(
618                rect.center(),
619                Align2::CENTER_CENTER,
620                "⚠",
621                font_id,
622                ui.visuals().error_fg_color,
623            );
624        }
625    }
626}
627
628/// Attach tooltips like "Loading…" or "Failed loading: …".
629pub fn texture_load_result_response(
630    source: &ImageSource<'_>,
631    tlr: &TextureLoadResult,
632    response: Response,
633) -> Response {
634    match tlr {
635        Ok(TexturePoll::Ready { .. }) => response,
636        Ok(TexturePoll::Pending { .. }) => {
637            let uri = source.uri().unwrap_or("image");
638            response.on_hover_text(format!("Loading {uri}…"))
639        }
640        Err(err) => {
641            let uri = source.uri().unwrap_or("image");
642            response.on_hover_text(format!("Failed loading {uri}: {err}"))
643        }
644    }
645}
646
647impl<'a> From<&'a str> for ImageSource<'a> {
648    #[inline]
649    fn from(value: &'a str) -> Self {
650        Self::Uri(value.into())
651    }
652}
653
654impl<'a> From<&'a String> for ImageSource<'a> {
655    #[inline]
656    fn from(value: &'a String) -> Self {
657        Self::Uri(value.as_str().into())
658    }
659}
660
661impl From<String> for ImageSource<'static> {
662    fn from(value: String) -> Self {
663        Self::Uri(value.into())
664    }
665}
666
667impl<'a> From<&'a Cow<'a, str>> for ImageSource<'a> {
668    #[inline]
669    fn from(value: &'a Cow<'a, str>) -> Self {
670        Self::Uri(value.clone())
671    }
672}
673
674impl<'a> From<Cow<'a, str>> for ImageSource<'a> {
675    #[inline]
676    fn from(value: Cow<'a, str>) -> Self {
677        Self::Uri(value)
678    }
679}
680
681impl<T: Into<Bytes>> From<(&'static str, T)> for ImageSource<'static> {
682    #[inline]
683    fn from((uri, bytes): (&'static str, T)) -> Self {
684        Self::Bytes {
685            uri: uri.into(),
686            bytes: bytes.into(),
687        }
688    }
689}
690
691impl<T: Into<Bytes>> From<(Cow<'static, str>, T)> for ImageSource<'static> {
692    #[inline]
693    fn from((uri, bytes): (Cow<'static, str>, T)) -> Self {
694        Self::Bytes {
695            uri,
696            bytes: bytes.into(),
697        }
698    }
699}
700
701impl<T: Into<Bytes>> From<(String, T)> for ImageSource<'static> {
702    #[inline]
703    fn from((uri, bytes): (String, T)) -> Self {
704        Self::Bytes {
705            uri: uri.into(),
706            bytes: bytes.into(),
707        }
708    }
709}
710
711impl<T: Into<SizedTexture>> From<T> for ImageSource<'static> {
712    fn from(value: T) -> Self {
713        Self::Texture(value.into())
714    }
715}
716
717#[derive(Debug, Clone)]
718#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
719pub struct ImageOptions {
720    /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right.
721    pub uv: Rect,
722
723    /// A solid color to put behind the image. Useful for transparent images.
724    pub bg_fill: Color32,
725
726    /// Multiply image color with this. Default is WHITE (no tint).
727    pub tint: Color32,
728
729    /// Rotate the image about an origin by some angle
730    ///
731    /// Positive angle is clockwise.
732    /// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right).
733    ///
734    /// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin.
735    ///
736    /// Due to limitations in the current implementation,
737    /// this will turn off rounding of the image.
738    pub rotation: Option<(Rot2, Vec2)>,
739
740    /// Round the corners of the image.
741    ///
742    /// The default is no rounding ([`Rounding::ZERO`]).
743    ///
744    /// Due to limitations in the current implementation,
745    /// this will turn off any rotation of the image.
746    pub rounding: Rounding,
747}
748
749impl Default for ImageOptions {
750    fn default() -> Self {
751        Self {
752            uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
753            bg_fill: Default::default(),
754            tint: Color32::WHITE,
755            rotation: None,
756            rounding: Rounding::ZERO,
757        }
758    }
759}
760
761pub fn paint_texture_at(
762    painter: &Painter,
763    rect: Rect,
764    options: &ImageOptions,
765    texture: &SizedTexture,
766) {
767    if options.bg_fill != Default::default() {
768        painter.add(RectShape::filled(rect, options.rounding, options.bg_fill));
769    }
770
771    match options.rotation {
772        Some((rot, origin)) => {
773            // TODO(emilk): implement this using `PathShape` (add texture support to it).
774            // This will also give us anti-aliasing of rotated images.
775            debug_assert!(
776                options.rounding == Rounding::ZERO,
777                "Image had both rounding and rotation. Please pick only one"
778            );
779
780            let mut mesh = Mesh::with_texture(texture.id);
781            mesh.add_rect_with_uv(rect, options.uv, options.tint);
782            mesh.rotate(rot, rect.min + origin * rect.size());
783            painter.add(Shape::mesh(mesh));
784        }
785        None => {
786            painter.add(RectShape {
787                rect,
788                rounding: options.rounding,
789                fill: options.tint,
790                stroke: Stroke::NONE,
791                blur_width: 0.0,
792                fill_texture_id: texture.id,
793                uv: options.uv,
794            });
795        }
796    }
797}
798
799/// gif uris contain the uri & the frame that will be displayed
800fn encode_gif_uri(uri: &str, frame_index: usize) -> String {
801    format!("{uri}#{frame_index}")
802}
803
804/// extracts uri and frame index
805/// # Errors
806/// Will return `Err` if `uri` does not match pattern {uri}-{frame_index}
807pub fn decode_gif_uri(uri: &str) -> Result<(&str, usize), String> {
808    let (uri, index) = uri
809        .rsplit_once('#')
810        .ok_or("Failed to find index separator '#'")?;
811    let index: usize = index
812        .parse()
813        .map_err(|_err| format!("Failed to parse gif frame index: {index:?} is not an integer"))?;
814    Ok((uri, index))
815}
816
817/// checks if uri is a gif file
818fn is_gif_uri(uri: &str) -> bool {
819    uri.ends_with(".gif") || uri.contains(".gif#")
820}
821
822/// checks if bytes are gifs
823pub fn has_gif_magic_header(bytes: &[u8]) -> bool {
824    bytes.starts_with(b"GIF87a") || bytes.starts_with(b"GIF89a")
825}
826
827/// calculates at which frame the gif is
828fn gif_frame_index(ctx: &Context, uri: &str) -> usize {
829    let now = ctx.input(|i| Duration::from_secs_f64(i.time));
830
831    let durations: Option<GifFrameDurations> = ctx.data(|data| data.get_temp(Id::new(uri)));
832    if let Some(durations) = durations {
833        let frames: Duration = durations.0.iter().sum();
834        let pos_ms = now.as_millis() % frames.as_millis().max(1);
835        let mut cumulative_ms = 0;
836        for (i, duration) in durations.0.iter().enumerate() {
837            cumulative_ms += duration.as_millis();
838            if pos_ms < cumulative_ms {
839                let ms_until_next_frame = cumulative_ms - pos_ms;
840                ctx.request_repaint_after(Duration::from_millis(ms_until_next_frame as u64));
841                return i;
842            }
843        }
844        0
845    } else {
846        0
847    }
848}
849
850#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
851/// Stores the durations between each frame of a gif
852pub struct GifFrameDurations(pub Arc<Vec<Duration>>);