epaint/
image.rs

1use crate::{textures::TextureOptions, Color32};
2use std::sync::Arc;
3
4/// An image stored in RAM.
5///
6/// To load an image file, see [`ColorImage::from_rgba_unmultiplied`].
7///
8/// In order to paint the image on screen, you first need to convert it to
9///
10/// See also: [`ColorImage`], [`FontImage`].
11#[derive(Clone, PartialEq)]
12#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
13pub enum ImageData {
14    /// RGBA image.
15    Color(Arc<ColorImage>),
16
17    /// Used for the font texture.
18    Font(FontImage),
19}
20
21impl ImageData {
22    pub fn size(&self) -> [usize; 2] {
23        match self {
24            Self::Color(image) => image.size,
25            Self::Font(image) => image.size,
26        }
27    }
28
29    pub fn width(&self) -> usize {
30        self.size()[0]
31    }
32
33    pub fn height(&self) -> usize {
34        self.size()[1]
35    }
36
37    pub fn bytes_per_pixel(&self) -> usize {
38        match self {
39            Self::Color(_) | Self::Font(_) => 4,
40        }
41    }
42}
43
44// ----------------------------------------------------------------------------
45
46/// A 2D RGBA color image in RAM.
47#[derive(Clone, Default, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
49pub struct ColorImage {
50    /// width, height.
51    pub size: [usize; 2],
52
53    /// The pixels, row by row, from top to bottom.
54    pub pixels: Vec<Color32>,
55}
56
57impl ColorImage {
58    /// Create an image filled with the given color.
59    pub fn new(size: [usize; 2], color: Color32) -> Self {
60        Self {
61            size,
62            pixels: vec![color; size[0] * size[1]],
63        }
64    }
65
66    /// Create a [`ColorImage`] from flat un-multiplied RGBA data.
67    ///
68    /// This is usually what you want to use after having loaded an image file.
69    ///
70    /// Panics if `size[0] * size[1] * 4 != rgba.len()`.
71    ///
72    /// ## Example using the [`image`](crates.io/crates/image) crate:
73    /// ``` ignore
74    /// fn load_image_from_path(path: &std::path::Path) -> Result<egui::ColorImage, image::ImageError> {
75    ///     let image = image::io::Reader::open(path)?.decode()?;
76    ///     let size = [image.width() as _, image.height() as _];
77    ///     let image_buffer = image.to_rgba8();
78    ///     let pixels = image_buffer.as_flat_samples();
79    ///     Ok(egui::ColorImage::from_rgba_unmultiplied(
80    ///         size,
81    ///         pixels.as_slice(),
82    ///     ))
83    /// }
84    ///
85    /// fn load_image_from_memory(image_data: &[u8]) -> Result<ColorImage, image::ImageError> {
86    ///     let image = image::load_from_memory(image_data)?;
87    ///     let size = [image.width() as _, image.height() as _];
88    ///     let image_buffer = image.to_rgba8();
89    ///     let pixels = image_buffer.as_flat_samples();
90    ///     Ok(ColorImage::from_rgba_unmultiplied(
91    ///         size,
92    ///         pixels.as_slice(),
93    ///     ))
94    /// }
95    /// ```
96    pub fn from_rgba_unmultiplied(size: [usize; 2], rgba: &[u8]) -> Self {
97        assert_eq!(size[0] * size[1] * 4, rgba.len());
98        let pixels = rgba
99            .chunks_exact(4)
100            .map(|p| Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
101            .collect();
102        Self { size, pixels }
103    }
104
105    pub fn from_rgba_premultiplied(size: [usize; 2], rgba: &[u8]) -> Self {
106        assert_eq!(size[0] * size[1] * 4, rgba.len());
107        let pixels = rgba
108            .chunks_exact(4)
109            .map(|p| Color32::from_rgba_premultiplied(p[0], p[1], p[2], p[3]))
110            .collect();
111        Self { size, pixels }
112    }
113
114    /// Create a [`ColorImage`] from flat opaque gray data.
115    ///
116    /// Panics if `size[0] * size[1] != gray.len()`.
117    pub fn from_gray(size: [usize; 2], gray: &[u8]) -> Self {
118        assert_eq!(size[0] * size[1], gray.len());
119        let pixels = gray.iter().map(|p| Color32::from_gray(*p)).collect();
120        Self { size, pixels }
121    }
122
123    /// Alternative method to `from_gray`.
124    /// Create a [`ColorImage`] from iterator over flat opaque gray data.
125    ///
126    /// Panics if `size[0] * size[1] != gray_iter.len()`.
127    #[doc(alias = "from_grey_iter")]
128    pub fn from_gray_iter(size: [usize; 2], gray_iter: impl Iterator<Item = u8>) -> Self {
129        let pixels: Vec<_> = gray_iter.map(Color32::from_gray).collect();
130        assert_eq!(size[0] * size[1], pixels.len());
131        Self { size, pixels }
132    }
133
134    /// A view of the underlying data as `&[u8]`
135    #[cfg(feature = "bytemuck")]
136    pub fn as_raw(&self) -> &[u8] {
137        bytemuck::cast_slice(&self.pixels)
138    }
139
140    /// A view of the underlying data as `&mut [u8]`
141    #[cfg(feature = "bytemuck")]
142    pub fn as_raw_mut(&mut self) -> &mut [u8] {
143        bytemuck::cast_slice_mut(&mut self.pixels)
144    }
145
146    /// Create a [`ColorImage`] from flat RGB data.
147    ///
148    /// This is what you want to use after having loaded an image file (and if
149    /// you are ignoring the alpha channel - considering it to always be 0xff)
150    ///
151    /// Panics if `size[0] * size[1] * 3 != rgb.len()`.
152    pub fn from_rgb(size: [usize; 2], rgb: &[u8]) -> Self {
153        assert_eq!(size[0] * size[1] * 3, rgb.len());
154        let pixels = rgb
155            .chunks_exact(3)
156            .map(|p| Color32::from_rgb(p[0], p[1], p[2]))
157            .collect();
158        Self { size, pixels }
159    }
160
161    /// An example color image, useful for tests.
162    pub fn example() -> Self {
163        let width = 128;
164        let height = 64;
165        let mut img = Self::new([width, height], Color32::TRANSPARENT);
166        for y in 0..height {
167            for x in 0..width {
168                let h = x as f32 / width as f32;
169                let s = 1.0;
170                let v = 1.0;
171                let a = y as f32 / height as f32;
172                img[(x, y)] = crate::Hsva { h, s, v, a }.into();
173            }
174        }
175        img
176    }
177
178    #[inline]
179    pub fn width(&self) -> usize {
180        self.size[0]
181    }
182
183    #[inline]
184    pub fn height(&self) -> usize {
185        self.size[1]
186    }
187
188    /// Create a new image from a patch of the current image.
189    ///
190    /// This method is especially convenient for screenshotting a part of the app
191    /// since `region` can be interpreted as screen coordinates of the entire screenshot if `pixels_per_point` is provided for the native application.
192    /// The floats of [`emath::Rect`] are cast to usize, rounding them down in order to interpret them as indices to the image data.
193    ///
194    /// Panics if `region.min.x > region.max.x || region.min.y > region.max.y`, or if a region larger than the image is passed.
195    pub fn region(&self, region: &emath::Rect, pixels_per_point: Option<f32>) -> Self {
196        let pixels_per_point = pixels_per_point.unwrap_or(1.0);
197        let min_x = (region.min.x * pixels_per_point) as usize;
198        let max_x = (region.max.x * pixels_per_point) as usize;
199        let min_y = (region.min.y * pixels_per_point) as usize;
200        let max_y = (region.max.y * pixels_per_point) as usize;
201        assert!(
202            min_x <= max_x && min_y <= max_y,
203            "Screenshot region is invalid: {region:?}"
204        );
205        let width = max_x - min_x;
206        let height = max_y - min_y;
207        let mut output = Vec::with_capacity(width * height);
208        let row_stride = self.size[0];
209
210        for row in min_y..max_y {
211            output.extend_from_slice(
212                &self.pixels[row * row_stride + min_x..row * row_stride + max_x],
213            );
214        }
215        Self {
216            size: [width, height],
217            pixels: output,
218        }
219    }
220}
221
222impl std::ops::Index<(usize, usize)> for ColorImage {
223    type Output = Color32;
224
225    #[inline]
226    fn index(&self, (x, y): (usize, usize)) -> &Color32 {
227        let [w, h] = self.size;
228        assert!(x < w && y < h);
229        &self.pixels[y * w + x]
230    }
231}
232
233impl std::ops::IndexMut<(usize, usize)> for ColorImage {
234    #[inline]
235    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Color32 {
236        let [w, h] = self.size;
237        assert!(x < w && y < h);
238        &mut self.pixels[y * w + x]
239    }
240}
241
242impl From<ColorImage> for ImageData {
243    #[inline(always)]
244    fn from(image: ColorImage) -> Self {
245        Self::Color(Arc::new(image))
246    }
247}
248
249impl From<Arc<ColorImage>> for ImageData {
250    #[inline]
251    fn from(image: Arc<ColorImage>) -> Self {
252        Self::Color(image)
253    }
254}
255
256impl std::fmt::Debug for ColorImage {
257    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258        f.debug_struct("ColorImage")
259            .field("size", &self.size)
260            .field("pixel-count", &self.pixels.len())
261            .finish_non_exhaustive()
262    }
263}
264
265// ----------------------------------------------------------------------------
266
267/// A single-channel image designed for the font texture.
268///
269/// Each value represents "coverage", i.e. how much a texel is covered by a character.
270///
271/// This is roughly interpreted as the opacity of a white image.
272#[derive(Clone, Default, PartialEq)]
273#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
274pub struct FontImage {
275    /// width, height
276    pub size: [usize; 2],
277
278    /// The coverage value.
279    ///
280    /// Often you want to use [`Self::srgba_pixels`] instead.
281    pub pixels: Vec<f32>,
282}
283
284impl FontImage {
285    pub fn new(size: [usize; 2]) -> Self {
286        Self {
287            size,
288            pixels: vec![0.0; size[0] * size[1]],
289        }
290    }
291
292    #[inline]
293    pub fn width(&self) -> usize {
294        self.size[0]
295    }
296
297    #[inline]
298    pub fn height(&self) -> usize {
299        self.size[1]
300    }
301
302    /// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
303    ///
304    /// `gamma` should normally be set to `None`.
305    ///
306    /// If you are having problems with text looking skinny and pixelated, try using a low gamma, e.g. `0.4`.
307    #[inline]
308    pub fn srgba_pixels(&self, gamma: Option<f32>) -> impl ExactSizeIterator<Item = Color32> + '_ {
309        // TODO(emilk): this default coverage gamma is a magic constant, chosen by eye. I don't even know why we need it.
310        // Maybe we need to implement the ideas in https://hikogui.org/2022/10/24/the-trouble-with-anti-aliasing.html
311        let gamma = gamma.unwrap_or(0.55);
312        self.pixels.iter().map(move |coverage| {
313            let alpha = coverage.powf(gamma);
314            // We want to multiply with `vec4(alpha)` in the fragment shader:
315            let a = fast_round(alpha * 255.0);
316            Color32::from_rgba_premultiplied(a, a, a, a)
317        })
318    }
319
320    /// Clone a sub-region as a new image.
321    pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self {
322        assert!(x + w <= self.width());
323        assert!(y + h <= self.height());
324
325        let mut pixels = Vec::with_capacity(w * h);
326        for y in y..y + h {
327            let offset = y * self.width() + x;
328            pixels.extend(&self.pixels[offset..(offset + w)]);
329        }
330        assert_eq!(pixels.len(), w * h);
331        Self {
332            size: [w, h],
333            pixels,
334        }
335    }
336}
337
338impl std::ops::Index<(usize, usize)> for FontImage {
339    type Output = f32;
340
341    #[inline]
342    fn index(&self, (x, y): (usize, usize)) -> &f32 {
343        let [w, h] = self.size;
344        assert!(x < w && y < h);
345        &self.pixels[y * w + x]
346    }
347}
348
349impl std::ops::IndexMut<(usize, usize)> for FontImage {
350    #[inline]
351    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut f32 {
352        let [w, h] = self.size;
353        assert!(x < w && y < h);
354        &mut self.pixels[y * w + x]
355    }
356}
357
358impl From<FontImage> for ImageData {
359    #[inline(always)]
360    fn from(image: FontImage) -> Self {
361        Self::Font(image)
362    }
363}
364
365#[inline]
366fn fast_round(r: f32) -> u8 {
367    (r + 0.5) as _ // rust does a saturating cast since 1.45
368}
369
370// ----------------------------------------------------------------------------
371
372/// A change to an image.
373///
374/// Either a whole new image, or an update to a rectangular region of it.
375#[derive(Clone, PartialEq)]
376#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
377#[must_use = "The painter must take care of this"]
378pub struct ImageDelta {
379    /// What to set the texture to.
380    ///
381    /// If [`Self::pos`] is `None`, this describes the whole texture.
382    ///
383    /// If [`Self::pos`] is `Some`, this describes a patch of the whole image starting at [`Self::pos`].
384    pub image: ImageData,
385
386    pub options: TextureOptions,
387
388    /// If `None`, set the whole texture to [`Self::image`].
389    ///
390    /// If `Some(pos)`, update a sub-region of an already allocated texture with the patch in [`Self::image`].
391    pub pos: Option<[usize; 2]>,
392}
393
394impl ImageDelta {
395    /// Update the whole texture.
396    pub fn full(image: impl Into<ImageData>, options: TextureOptions) -> Self {
397        Self {
398            image: image.into(),
399            options,
400            pos: None,
401        }
402    }
403
404    /// Update a sub-region of an existing texture.
405    pub fn partial(pos: [usize; 2], image: impl Into<ImageData>, options: TextureOptions) -> Self {
406        Self {
407            image: image.into(),
408            options,
409            pos: Some(pos),
410        }
411    }
412
413    /// Is this affecting the whole texture?
414    /// If `false`, this is a partial (sub-region) update.
415    pub fn is_whole(&self) -> bool {
416        self.pos.is_none()
417    }
418}