egui_extras/
image.rs

1#![allow(deprecated)]
2
3use egui::{mutex::Mutex, TextureOptions};
4
5#[cfg(feature = "svg")]
6use egui::SizeHint;
7
8/// An image to be shown in egui.
9///
10/// Load once, and save somewhere in your app state.
11///
12/// Use the `svg` and `image` features to enable more constructors.
13///
14/// ⚠ This type is deprecated: Consider using [`egui::Image`] instead.
15#[deprecated = "consider using `egui::Image` instead"]
16pub struct RetainedImage {
17    debug_name: String,
18
19    size: [usize; 2],
20
21    /// Cleared once [`Self::texture`] has been loaded.
22    image: Mutex<egui::ColorImage>,
23
24    /// Lazily loaded when we have an egui context.
25    texture: Mutex<Option<egui::TextureHandle>>,
26
27    options: TextureOptions,
28}
29
30impl RetainedImage {
31    pub fn from_color_image(debug_name: impl Into<String>, image: egui::ColorImage) -> Self {
32        Self {
33            debug_name: debug_name.into(),
34            size: image.size,
35            image: Mutex::new(image),
36            texture: Default::default(),
37            options: Default::default(),
38        }
39    }
40
41    /// Load a (non-svg) image.
42    ///
43    /// `image_bytes` should be the raw contents of an image file (`.png`, `.jpg`, …).
44    ///
45    /// Requires the "image" feature. You must also opt-in to the image formats you need
46    /// with e.g. `image = { version = "0.25", features = ["jpeg", "png"] }`.
47    ///
48    /// # Errors
49    /// On invalid image or unsupported image format.
50    #[cfg(feature = "image")]
51    pub fn from_image_bytes(
52        debug_name: impl Into<String>,
53        image_bytes: &[u8],
54    ) -> Result<Self, String> {
55        Ok(Self::from_color_image(
56            debug_name,
57            load_image_bytes(image_bytes).map_err(|err| err.to_string())?,
58        ))
59    }
60
61    /// Pass in the bytes of an SVG that you've loaded.
62    ///
63    /// # Errors
64    /// On invalid image
65    #[cfg(feature = "svg")]
66    pub fn from_svg_bytes(debug_name: impl Into<String>, svg_bytes: &[u8]) -> Result<Self, String> {
67        Self::from_svg_bytes_with_size(debug_name, svg_bytes, None)
68    }
69
70    /// Pass in the str of an SVG that you've loaded.
71    ///
72    /// # Errors
73    /// On invalid image
74    #[cfg(feature = "svg")]
75    pub fn from_svg_str(debug_name: impl Into<String>, svg_str: &str) -> Result<Self, String> {
76        Self::from_svg_bytes(debug_name, svg_str.as_bytes())
77    }
78
79    /// Pass in the bytes of an SVG that you've loaded
80    /// and the scaling option to resize the SVG with
81    ///
82    /// # Errors
83    /// On invalid image
84    #[cfg(feature = "svg")]
85    pub fn from_svg_bytes_with_size(
86        debug_name: impl Into<String>,
87        svg_bytes: &[u8],
88        size_hint: Option<SizeHint>,
89    ) -> Result<Self, String> {
90        Ok(Self::from_color_image(
91            debug_name,
92            load_svg_bytes_with_size(svg_bytes, size_hint)?,
93        ))
94    }
95
96    /// Set the texture filters to use for the image.
97    ///
98    /// **Note:** If the texture has already been uploaded to the GPU, this will require
99    /// re-uploading the texture with the updated filter.
100    ///
101    /// # Example
102    /// ```rust
103    /// # use egui_extras::RetainedImage;
104    /// # use egui::{Color32, epaint::{ColorImage, textures::TextureOptions}};
105    /// # let pixels = vec![Color32::BLACK];
106    /// # let color_image = ColorImage {
107    /// #   size: [1, 1],
108    /// #   pixels,
109    /// # };
110    /// #
111    /// // Upload a pixel art image without it getting blurry when resized
112    /// let image = RetainedImage::from_color_image("my_image", color_image)
113    ///     .with_options(TextureOptions::NEAREST);
114    /// ```
115    #[inline]
116    pub fn with_options(mut self, options: TextureOptions) -> Self {
117        self.options = options;
118
119        // If the texture has already been uploaded, this will force it to be re-uploaded with the
120        // updated filter.
121        *self.texture.lock() = None;
122
123        self
124    }
125
126    /// The size of the image data (number of pixels wide/high).
127    pub fn size(&self) -> [usize; 2] {
128        self.size
129    }
130
131    /// The width of the image.
132    pub fn width(&self) -> usize {
133        self.size[0]
134    }
135
136    /// The height of the image.
137    pub fn height(&self) -> usize {
138        self.size[1]
139    }
140
141    /// The size of the image data (number of pixels wide/high).
142    pub fn size_vec2(&self) -> egui::Vec2 {
143        let [w, h] = self.size();
144        egui::vec2(w as f32, h as f32)
145    }
146
147    /// The debug name of the image, e.g. the file name.
148    pub fn debug_name(&self) -> &str {
149        &self.debug_name
150    }
151
152    /// The texture id for this image.
153    pub fn texture_id(&self, ctx: &egui::Context) -> egui::TextureId {
154        self.texture
155            .lock()
156            .get_or_insert_with(|| {
157                let image: &mut egui::ColorImage = &mut self.image.lock();
158                let image = std::mem::take(image);
159                ctx.load_texture(&self.debug_name, image, self.options)
160            })
161            .id()
162    }
163
164    /// Show the image with the given maximum size.
165    pub fn show_max_size(&self, ui: &mut egui::Ui, max_size: egui::Vec2) -> egui::Response {
166        let mut desired_size = self.size_vec2();
167        desired_size *= (max_size.x / desired_size.x).min(1.0);
168        desired_size *= (max_size.y / desired_size.y).min(1.0);
169        self.show_size(ui, desired_size)
170    }
171
172    /// Show the image with the original size (one image pixel = one gui point).
173    pub fn show(&self, ui: &mut egui::Ui) -> egui::Response {
174        self.show_size(ui, self.size_vec2())
175    }
176
177    /// Show the image with the given scale factor (1.0 = original size).
178    pub fn show_scaled(&self, ui: &mut egui::Ui, scale: f32) -> egui::Response {
179        self.show_size(ui, self.size_vec2() * scale)
180    }
181
182    /// Show the image with the given size.
183    pub fn show_size(&self, ui: &mut egui::Ui, desired_size: egui::Vec2) -> egui::Response {
184        // We need to convert the SVG to a texture to display it:
185        // Future improvement: tell backend to do mip-mapping of the image to
186        // make it look smoother when downsized.
187        ui.image((self.texture_id(ui.ctx()), desired_size))
188    }
189}
190
191// ----------------------------------------------------------------------------
192
193/// Load a (non-svg) image.
194///
195/// Requires the "image" feature. You must also opt-in to the image formats you need
196/// with e.g. `image = { version = "0.25", features = ["jpeg", "png"] }`.
197///
198/// # Errors
199/// On invalid image or unsupported image format.
200#[cfg(feature = "image")]
201pub fn load_image_bytes(image_bytes: &[u8]) -> Result<egui::ColorImage, egui::load::LoadError> {
202    profiling::function_scope!();
203    let image = image::load_from_memory(image_bytes).map_err(|err| match err {
204        image::ImageError::Unsupported(err) => match err.kind() {
205            image::error::UnsupportedErrorKind::Format(format) => {
206                egui::load::LoadError::FormatNotSupported {
207                    detected_format: Some(format.to_string()),
208                }
209            }
210            _ => egui::load::LoadError::Loading(err.to_string()),
211        },
212        err => egui::load::LoadError::Loading(err.to_string()),
213    })?;
214    let size = [image.width() as _, image.height() as _];
215    let image_buffer = image.to_rgba8();
216    let pixels = image_buffer.as_flat_samples();
217    Ok(egui::ColorImage::from_rgba_unmultiplied(
218        size,
219        pixels.as_slice(),
220    ))
221}
222
223/// Load an SVG and rasterize it into an egui image.
224///
225/// Requires the "svg" feature.
226///
227/// # Errors
228/// On invalid image
229#[cfg(feature = "svg")]
230pub fn load_svg_bytes(svg_bytes: &[u8]) -> Result<egui::ColorImage, String> {
231    load_svg_bytes_with_size(svg_bytes, None)
232}
233
234/// Load an SVG and rasterize it into an egui image with a scaling parameter.
235///
236/// Requires the "svg" feature.
237///
238/// # Errors
239/// On invalid image
240#[cfg(feature = "svg")]
241pub fn load_svg_bytes_with_size(
242    svg_bytes: &[u8],
243    size_hint: Option<SizeHint>,
244) -> Result<egui::ColorImage, String> {
245    use resvg::tiny_skia::{IntSize, Pixmap};
246    use resvg::usvg::{Options, Tree, TreeParsing};
247
248    profiling::function_scope!();
249
250    let opt = Options::default();
251
252    let mut rtree = Tree::from_data(svg_bytes, &opt).map_err(|err| err.to_string())?;
253
254    let mut size = rtree.size.to_int_size();
255    match size_hint {
256        None => (),
257        Some(SizeHint::Size(w, h)) => {
258            size = size.scale_to(
259                IntSize::from_wh(w, h).ok_or_else(|| format!("Failed to scale SVG to {w}x{h}"))?,
260            );
261        }
262        Some(SizeHint::Height(h)) => {
263            size = size
264                .scale_to_height(h)
265                .ok_or_else(|| format!("Failed to scale SVG to height {h}"))?;
266        }
267        Some(SizeHint::Width(w)) => {
268            size = size
269                .scale_to_width(w)
270                .ok_or_else(|| format!("Failed to scale SVG to width {w}"))?;
271        }
272        Some(SizeHint::Scale(z)) => {
273            let z_inner = z.into_inner();
274            size = size
275                .scale_by(z_inner)
276                .ok_or_else(|| format!("Failed to scale SVG by {z_inner}"))?;
277        }
278    };
279    let (w, h) = (size.width(), size.height());
280
281    let mut pixmap =
282        Pixmap::new(w, h).ok_or_else(|| format!("Failed to create SVG Pixmap of size {w}x{h}"))?;
283
284    rtree.size = size.to_size();
285    resvg::Tree::from_usvg(&rtree).render(Default::default(), &mut pixmap.as_mut());
286
287    let image = egui::ColorImage::from_rgba_unmultiplied([w as _, h as _], pixmap.data());
288
289    Ok(image)
290}