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 new Image from a patch of the current image. This method is especially convenient for screenshotting a part of the app
147    /// since `region` can be interpreted as screen coordinates of the entire screenshot if `pixels_per_point` is provided for the native application.
148    /// The floats of [`emath::Rect`] are cast to usize, rounding them down in order to interpret them as indices to the image data.
149    ///
150    /// Panics if `region.min.x > region.max.x || region.min.y > region.max.y`, or if a region larger than the image is passed.
151    pub fn region(&self, region: &emath::Rect, pixels_per_point: Option<f32>) -> Self {
152        let pixels_per_point = pixels_per_point.unwrap_or(1.0);
153        let min_x = (region.min.x * pixels_per_point) as usize;
154        let max_x = (region.max.x * pixels_per_point) as usize;
155        let min_y = (region.min.y * pixels_per_point) as usize;
156        let max_y = (region.max.y * pixels_per_point) as usize;
157        assert!(
158            min_x <= max_x && min_y <= max_y,
159            "Screenshot region is invalid: {region:?}"
160        );
161        let width = max_x - min_x;
162        let height = max_y - min_y;
163        let mut output = Vec::with_capacity(width * height);
164        let row_stride = self.size[0];
165
166        for row in min_y..max_y {
167            output.extend_from_slice(
168                &self.pixels[row * row_stride + min_x..row * row_stride + max_x],
169            );
170        }
171        Self {
172            size: [width, height],
173            pixels: output,
174        }
175    }
176
177    /// Create a [`ColorImage`] from flat RGB data.
178    ///
179    /// This is what you want to use after having loaded an image file (and if
180    /// you are ignoring the alpha channel - considering it to always be 0xff)
181    ///
182    /// Panics if `size[0] * size[1] * 3 != rgb.len()`.
183    pub fn from_rgb(size: [usize; 2], rgb: &[u8]) -> Self {
184        assert_eq!(size[0] * size[1] * 3, rgb.len());
185        let pixels = rgb
186            .chunks_exact(3)
187            .map(|p| Color32::from_rgb(p[0], p[1], p[2]))
188            .collect();
189        Self { size, pixels }
190    }
191
192    /// An example color image, useful for tests.
193    pub fn example() -> Self {
194        let width = 128;
195        let height = 64;
196        let mut img = Self::new([width, height], Color32::TRANSPARENT);
197        for y in 0..height {
198            for x in 0..width {
199                let h = x as f32 / width as f32;
200                let s = 1.0;
201                let v = 1.0;
202                let a = y as f32 / height as f32;
203                img[(x, y)] = crate::Hsva { h, s, v, a }.into();
204            }
205        }
206        img
207    }
208
209    #[inline]
210    pub fn width(&self) -> usize {
211        self.size[0]
212    }
213
214    #[inline]
215    pub fn height(&self) -> usize {
216        self.size[1]
217    }
218}
219
220impl std::ops::Index<(usize, usize)> for ColorImage {
221    type Output = Color32;
222
223    #[inline]
224    fn index(&self, (x, y): (usize, usize)) -> &Color32 {
225        let [w, h] = self.size;
226        assert!(x < w && y < h);
227        &self.pixels[y * w + x]
228    }
229}
230
231impl std::ops::IndexMut<(usize, usize)> for ColorImage {
232    #[inline]
233    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Color32 {
234        let [w, h] = self.size;
235        assert!(x < w && y < h);
236        &mut self.pixels[y * w + x]
237    }
238}
239
240impl From<ColorImage> for ImageData {
241    #[inline(always)]
242    fn from(image: ColorImage) -> Self {
243        Self::Color(Arc::new(image))
244    }
245}
246
247impl From<Arc<ColorImage>> for ImageData {
248    #[inline]
249    fn from(image: Arc<ColorImage>) -> Self {
250        Self::Color(image)
251    }
252}
253
254impl std::fmt::Debug for ColorImage {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        f.debug_struct("ColorImage")
257            .field("size", &self.size)
258            .field("pixel-count", &self.pixels.len())
259            .finish_non_exhaustive()
260    }
261}
262
263// ----------------------------------------------------------------------------
264
265/// A single-channel image designed for the font texture.
266///
267/// Each value represents "coverage", i.e. how much a texel is covered by a character.
268///
269/// This is roughly interpreted as the opacity of a white image.
270#[derive(Clone, Default, PartialEq)]
271#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
272pub struct FontImage {
273    /// width, height
274    pub size: [usize; 2],
275
276    /// The coverage value.
277    ///
278    /// Often you want to use [`Self::srgba_pixels`] instead.
279    pub pixels: Vec<f32>,
280}
281
282impl FontImage {
283    pub fn new(size: [usize; 2]) -> Self {
284        Self {
285            size,
286            pixels: vec![0.0; size[0] * size[1]],
287        }
288    }
289
290    #[inline]
291    pub fn width(&self) -> usize {
292        self.size[0]
293    }
294
295    #[inline]
296    pub fn height(&self) -> usize {
297        self.size[1]
298    }
299
300    /// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
301    ///
302    /// `gamma` should normally be set to `None`.
303    ///
304    /// If you are having problems with text looking skinny and pixelated, try using a low gamma, e.g. `0.4`.
305    #[inline]
306    pub fn srgba_pixels(&self, gamma: Option<f32>) -> impl ExactSizeIterator<Item = Color32> + '_ {
307        // TODO(emilk): this default coverage gamma is a magic constant, chosen by eye. I don't even know why we need it.
308        // Maybe we need to implement the ideas in https://hikogui.org/2022/10/24/the-trouble-with-anti-aliasing.html
309        let gamma = gamma.unwrap_or(0.55);
310        self.pixels.iter().map(move |coverage| {
311            let alpha = coverage.powf(gamma);
312            // We want to multiply with `vec4(alpha)` in the fragment shader:
313            let a = fast_round(alpha * 255.0);
314            Color32::from_rgba_premultiplied(a, a, a, a)
315        })
316    }
317
318    /// Clone a sub-region as a new image.
319    pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self {
320        assert!(x + w <= self.width());
321        assert!(y + h <= self.height());
322
323        let mut pixels = Vec::with_capacity(w * h);
324        for y in y..y + h {
325            let offset = y * self.width() + x;
326            pixels.extend(&self.pixels[offset..(offset + w)]);
327        }
328        assert_eq!(pixels.len(), w * h);
329        Self {
330            size: [w, h],
331            pixels,
332        }
333    }
334}
335
336impl std::ops::Index<(usize, usize)> for FontImage {
337    type Output = f32;
338
339    #[inline]
340    fn index(&self, (x, y): (usize, usize)) -> &f32 {
341        let [w, h] = self.size;
342        assert!(x < w && y < h);
343        &self.pixels[y * w + x]
344    }
345}
346
347impl std::ops::IndexMut<(usize, usize)> for FontImage {
348    #[inline]
349    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut f32 {
350        let [w, h] = self.size;
351        assert!(x < w && y < h);
352        &mut self.pixels[y * w + x]
353    }
354}
355
356impl From<FontImage> for ImageData {
357    #[inline(always)]
358    fn from(image: FontImage) -> Self {
359        Self::Font(image)
360    }
361}
362
363#[inline]
364fn fast_round(r: f32) -> u8 {
365    (r + 0.5) as _ // rust does a saturating cast since 1.45
366}
367
368// ----------------------------------------------------------------------------
369
370/// A change to an image.
371///
372/// Either a whole new image, or an update to a rectangular region of it.
373#[derive(Clone, PartialEq)]
374#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
375#[must_use = "The painter must take care of this"]
376pub struct ImageDelta {
377    /// What to set the texture to.
378    ///
379    /// If [`Self::pos`] is `None`, this describes the whole texture.
380    ///
381    /// If [`Self::pos`] is `Some`, this describes a patch of the whole image starting at [`Self::pos`].
382    pub image: ImageData,
383
384    pub options: TextureOptions,
385
386    /// If `None`, set the whole texture to [`Self::image`].
387    ///
388    /// If `Some(pos)`, update a sub-region of an already allocated texture with the patch in [`Self::image`].
389    pub pos: Option<[usize; 2]>,
390}
391
392impl ImageDelta {
393    /// Update the whole texture.
394    pub fn full(image: impl Into<ImageData>, options: TextureOptions) -> Self {
395        Self {
396            image: image.into(),
397            options,
398            pos: None,
399        }
400    }
401
402    /// Update a sub-region of an existing texture.
403    pub fn partial(pos: [usize; 2], image: impl Into<ImageData>, options: TextureOptions) -> Self {
404        Self {
405            image: image.into(),
406            options,
407            pos: Some(pos),
408        }
409    }
410
411    /// Is this affecting the whole texture?
412    /// If `false`, this is a partial (sub-region) update.
413    pub fn is_whole(&self) -> bool {
414        self.pos.is_none()
415    }
416}