Skip to main content

epaint/
image.rs

1use ecolor::linear_f32_from_linear_u8;
2use emath::Vec2;
3
4use crate::{Color32, textures::TextureOptions};
5use std::sync::Arc;
6
7/// An image stored in RAM.
8///
9/// To load an image file, see [`ColorImage::from_rgba_unmultiplied`].
10///
11/// This is currently an enum with only one variant, but more image types may be added in the future.
12///
13/// See also: [`ColorImage`].
14#[derive(Clone, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16pub enum ImageData {
17    /// RGBA image.
18    Color(Arc<ColorImage>),
19}
20
21impl ImageData {
22    pub fn size(&self) -> [usize; 2] {
23        match self {
24            Self::Color(image) => image.size,
25        }
26    }
27
28    pub fn width(&self) -> usize {
29        self.size()[0]
30    }
31
32    pub fn height(&self) -> usize {
33        self.size()[1]
34    }
35
36    pub fn bytes_per_pixel(&self) -> usize {
37        match self {
38            Self::Color(_) => 4,
39        }
40    }
41}
42
43// ----------------------------------------------------------------------------
44
45/// A 2D RGBA color image in RAM.
46#[derive(Clone, Default, PartialEq, Eq)]
47#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
48pub struct ColorImage {
49    /// width, height in texels.
50    pub size: [usize; 2],
51
52    /// Size of the original SVG image (if any), or just the texel size of the image.
53    pub source_size: Vec2,
54
55    /// The pixels, row by row, from top to bottom.
56    pub pixels: Vec<Color32>,
57}
58
59impl ColorImage {
60    /// Create an image filled with the given color.
61    pub fn new(size: [usize; 2], pixels: Vec<Color32>) -> Self {
62        debug_assert!(
63            size[0] * size[1] == pixels.len(),
64            "size: {size:?}, pixels.len(): {}",
65            pixels.len()
66        );
67        Self {
68            size,
69            source_size: Vec2::new(size[0] as f32, size[1] as f32),
70            pixels,
71        }
72    }
73
74    /// Create an image filled with the given color.
75    pub fn filled(size: [usize; 2], color: Color32) -> Self {
76        Self {
77            size,
78            source_size: Vec2::new(size[0] as f32, size[1] as f32),
79            pixels: vec![color; size[0] * size[1]],
80        }
81    }
82
83    /// Create a [`ColorImage`] from flat un-multiplied RGBA data.
84    ///
85    /// This is usually what you want to use after having loaded an image file.
86    ///
87    /// Panics if `size[0] * size[1] * 4 != rgba.len()`.
88    ///
89    /// ## Example using the [`image`](crates.io/crates/image) crate:
90    /// ``` ignore
91    /// fn load_image_from_path(path: &std::path::Path) -> Result<egui::ColorImage, image::ImageError> {
92    ///     let image = image::io::Reader::open(path)?.decode()?;
93    ///     let size = [image.width() as _, image.height() as _];
94    ///     let image_buffer = image.to_rgba8();
95    ///     let pixels = image_buffer.as_flat_samples();
96    ///     Ok(egui::ColorImage::from_rgba_unmultiplied(
97    ///         size,
98    ///         pixels.as_slice(),
99    ///     ))
100    /// }
101    ///
102    /// fn load_image_from_memory(image_data: &[u8]) -> Result<ColorImage, image::ImageError> {
103    ///     let image = image::load_from_memory(image_data)?;
104    ///     let size = [image.width() as _, image.height() as _];
105    ///     let image_buffer = image.to_rgba8();
106    ///     let pixels = image_buffer.as_flat_samples();
107    ///     Ok(ColorImage::from_rgba_unmultiplied(
108    ///         size,
109    ///         pixels.as_slice(),
110    ///     ))
111    /// }
112    /// ```
113    pub fn from_rgba_unmultiplied(size: [usize; 2], rgba: &[u8]) -> Self {
114        assert_eq!(
115            size[0] * size[1] * 4,
116            rgba.len(),
117            "size: {:?}, rgba.len(): {}",
118            size,
119            rgba.len()
120        );
121        let pixels = rgba
122            .chunks_exact(4)
123            .map(|p| Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
124            .collect();
125        Self::new(size, pixels)
126    }
127
128    pub fn from_rgba_premultiplied(size: [usize; 2], rgba: &[u8]) -> Self {
129        assert_eq!(
130            size[0] * size[1] * 4,
131            rgba.len(),
132            "size: {:?}, rgba.len(): {}",
133            size,
134            rgba.len()
135        );
136        let pixels = rgba
137            .chunks_exact(4)
138            .map(|p| Color32::from_rgba_premultiplied(p[0], p[1], p[2], p[3]))
139            .collect();
140        Self::new(size, pixels)
141    }
142
143    /// Create a [`ColorImage`] from flat opaque gray data.
144    ///
145    /// Panics if `size[0] * size[1] != gray.len()`.
146    pub fn from_gray(size: [usize; 2], gray: &[u8]) -> Self {
147        assert_eq!(
148            size[0] * size[1],
149            gray.len(),
150            "size: {:?}, gray.len(): {}",
151            size,
152            gray.len()
153        );
154        let pixels = gray.iter().map(|p| Color32::from_gray(*p)).collect();
155        Self::new(size, pixels)
156    }
157
158    /// Alternative method to `from_gray`.
159    /// Create a [`ColorImage`] from iterator over flat opaque gray data.
160    ///
161    /// Panics if `size[0] * size[1] != gray_iter.len()`.
162    #[doc(alias = "from_grey_iter")]
163    pub fn from_gray_iter(size: [usize; 2], gray_iter: impl Iterator<Item = u8>) -> Self {
164        let pixels: Vec<_> = gray_iter.map(Color32::from_gray).collect();
165        assert_eq!(
166            size[0] * size[1],
167            pixels.len(),
168            "size: {:?}, pixels.len(): {}",
169            size,
170            pixels.len()
171        );
172        Self::new(size, pixels)
173    }
174
175    /// A view of the underlying data as `&[u8]`
176    #[cfg(feature = "bytemuck")]
177    pub fn as_raw(&self) -> &[u8] {
178        bytemuck::cast_slice(&self.pixels)
179    }
180
181    /// A view of the underlying data as `&mut [u8]`
182    #[cfg(feature = "bytemuck")]
183    pub fn as_raw_mut(&mut self) -> &mut [u8] {
184        bytemuck::cast_slice_mut(&mut self.pixels)
185    }
186
187    /// Create a [`ColorImage`] from flat RGB data.
188    ///
189    /// This is what you want to use after having loaded an image file (and if
190    /// you are ignoring the alpha channel - considering it to always be 0xff)
191    ///
192    /// Panics if `size[0] * size[1] * 3 != rgb.len()`.
193    pub fn from_rgb(size: [usize; 2], rgb: &[u8]) -> Self {
194        assert_eq!(
195            size[0] * size[1] * 3,
196            rgb.len(),
197            "size: {:?}, rgb.len(): {}",
198            size,
199            rgb.len()
200        );
201        let pixels = rgb
202            .chunks_exact(3)
203            .map(|p| Color32::from_rgb(p[0], p[1], p[2]))
204            .collect();
205        Self::new(size, pixels)
206    }
207
208    /// An example color image, useful for tests.
209    pub fn example() -> Self {
210        let width = 128;
211        let height = 64;
212        let mut img = Self::filled([width, height], Color32::TRANSPARENT);
213        for y in 0..height {
214            for x in 0..width {
215                let h = x as f32 / width as f32;
216                let s = 1.0;
217                let v = 1.0;
218                let a = y as f32 / height as f32;
219                img[(x, y)] = crate::Hsva { h, s, v, a }.into();
220            }
221        }
222        img
223    }
224
225    /// Set the source size of e.g. the original SVG image.
226    #[inline]
227    pub fn with_source_size(mut self, source_size: Vec2) -> Self {
228        self.source_size = source_size;
229        self
230    }
231
232    #[inline]
233    pub fn width(&self) -> usize {
234        self.size[0]
235    }
236
237    #[inline]
238    pub fn height(&self) -> usize {
239        self.size[1]
240    }
241
242    /// Create a new image from a patch of the current image.
243    ///
244    /// This method is especially convenient for screenshotting a part of the app
245    /// since `region` can be interpreted as screen coordinates of the entire screenshot if `pixels_per_point` is provided for the native application.
246    /// The floats of [`emath::Rect`] are cast to usize, rounding them down in order to interpret them as indices to the image data.
247    ///
248    /// Panics if `region.min.x > region.max.x || region.min.y > region.max.y`, or if a region larger than the image is passed.
249    pub fn region(&self, region: &emath::Rect, pixels_per_point: Option<f32>) -> Self {
250        let pixels_per_point = pixels_per_point.unwrap_or(1.0);
251        let min_x = (region.min.x * pixels_per_point) as usize;
252        let max_x = (region.max.x * pixels_per_point) as usize;
253        let min_y = (region.min.y * pixels_per_point) as usize;
254        let max_y = (region.max.y * pixels_per_point) as usize;
255        assert!(
256            min_x <= max_x && min_y <= max_y,
257            "Screenshot region is invalid: {region:?}"
258        );
259        let width = max_x - min_x;
260        let height = max_y - min_y;
261        let mut output = Vec::with_capacity(width * height);
262        let row_stride = self.size[0];
263
264        for row in min_y..max_y {
265            output.extend_from_slice(
266                &self.pixels[row * row_stride + min_x..row * row_stride + max_x],
267            );
268        }
269        Self::new([width, height], output)
270    }
271
272    /// Clone a sub-region as a new image.
273    pub fn region_by_pixels(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self {
274        assert!(
275            x + w <= self.width(),
276            "x + w should be <= self.width(), but x: {}, w: {}, width: {}",
277            x,
278            w,
279            self.width()
280        );
281        assert!(
282            y + h <= self.height(),
283            "y + h should be <= self.height(), but y: {}, h: {}, height: {}",
284            y,
285            h,
286            self.height()
287        );
288
289        let mut pixels = Vec::with_capacity(w * h);
290        for y in y..y + h {
291            let offset = y * self.width() + x;
292            pixels.extend(&self.pixels[offset..(offset + w)]);
293        }
294        assert_eq!(
295            pixels.len(),
296            w * h,
297            "pixels.len should be w * h, but got {}",
298            pixels.len()
299        );
300        Self::new([w, h], pixels)
301    }
302}
303
304impl core::ops::Index<(usize, usize)> for ColorImage {
305    type Output = Color32;
306
307    #[inline]
308    fn index(&self, (x, y): (usize, usize)) -> &Color32 {
309        let [w, h] = self.size;
310        assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}");
311        &self.pixels[y * w + x]
312    }
313}
314
315impl core::ops::IndexMut<(usize, usize)> for ColorImage {
316    #[inline]
317    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Color32 {
318        let [w, h] = self.size;
319        assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}");
320        &mut self.pixels[y * w + x]
321    }
322}
323
324impl From<ColorImage> for ImageData {
325    #[inline(always)]
326    fn from(image: ColorImage) -> Self {
327        Self::Color(Arc::new(image))
328    }
329}
330
331impl From<Arc<ColorImage>> for ImageData {
332    #[inline]
333    fn from(image: Arc<ColorImage>) -> Self {
334        Self::Color(image)
335    }
336}
337
338impl core::fmt::Debug for ColorImage {
339    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
340        f.debug_struct("ColorImage")
341            .field("size", &self.size)
342            .field("pixel-count", &self.pixels.len())
343            .finish_non_exhaustive()
344    }
345}
346
347// ----------------------------------------------------------------------------
348
349/// How to convert font coverage values into alpha and color values.
350///
351/// epaint stores all glyphs in the font atlas as white (with varying opacity),
352/// so that egui can reuse the same glyph for different text colors
353/// (with a simple color multiplication in the shader).
354///
355/// Because of this simplification, we need to apply a non-linear
356/// ramp to the glyph colors before writing them into the font atlas,
357/// as a way to compensate.
358///
359/// This whole thing is less than rigorous.
360///
361/// It would be better to either render all text colors into the font atlas
362/// (which would require more atlas space, but would allow for more accurate rendering of colored text and emojis),
363/// or do the color compensation in the shader, based on the active text color.
364///
365/// When experimenting, use <https://fonts.google.com/specimen/Ubuntu> to compare to a ground truth.
366///
367/// See <https://hikogui.org/2022/10/24/the-trouble-with-anti-aliasing.html> for related analysis.
368#[derive(Clone, Copy, Debug, Default, PartialEq)]
369#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
370pub enum FontColorTransferFunction {
371    /// Use the raw RGBA values from the font rasterizer, without any conversion.
372    ///
373    /// This is the required mode for colored emojis etc.
374    ///
375    /// This mode looks good for black-on-white text, i.e. light mode.
376    Off,
377
378    /// `alpha = coverage^gamma`.
379    ///
380    /// Gamma=1 looks good for black-on-white text, i.e. light mode.
381    Gamma(f32),
382
383    /// `alpha = 2 * coverage - coverage^2`
384    ///
385    /// This looks good for white-on-black text, i.e. dark mode.
386    ///
387    /// Very similar to a gamma of 0.5, but produces sharper text.
388    /// See <https://www.desmos.com/calculator/w0ndf5blmn> for a comparison to gamma=0.5.
389    #[default]
390    TwoCoverageMinusCoverageSq,
391}
392
393impl FontColorTransferFunction {
394    /// A good-looking default for light mode (black-on-white text).
395    pub const LIGHT_MODE_DEFAULT: Self = Self::Off;
396
397    /// A good-looking default for dark mode (white-on-black text).
398    pub const DARK_MODE_DEFAULT: Self = Self::TwoCoverageMinusCoverageSq;
399
400    /// How to convert a white color written by the font rasterizer
401    /// into a color to be written into the font atlas.
402    #[inline(always)]
403    pub fn to_atlas_color(self, input_color: Color32) -> Color32 {
404        match self {
405            Self::Off | Self::Gamma(1.0) => input_color,
406
407            Self::Gamma(gamma) => {
408                let coverage = linear_f32_from_linear_u8(input_color.a());
409                let alpha = coverage.powf(gamma);
410                Color32::from_white_alpha(ecolor::linear_u8_from_linear_f32(alpha))
411            }
412
413            Self::TwoCoverageMinusCoverageSq => {
414                let coverage = linear_f32_from_linear_u8(input_color.a());
415                let alpha = 2.0 * coverage - coverage * coverage;
416                Color32::from_white_alpha(ecolor::linear_u8_from_linear_f32(alpha))
417            }
418        }
419    }
420
421    /// Convert coverage to alpha.
422    #[inline(always)]
423    pub fn alpha_from_coverage(self, coverage: f32) -> f32 {
424        let coverage = coverage.clamp(0.0, 1.0);
425        match self {
426            Self::Off | Self::Gamma(1.0) => coverage,
427            Self::Gamma(gamma) => coverage.powf(gamma),
428            Self::TwoCoverageMinusCoverageSq => 2.0 * coverage - coverage * coverage,
429        }
430    }
431
432    #[inline(always)]
433    pub fn color_from_coverage(self, coverage: f32) -> Color32 {
434        let alpha = self.alpha_from_coverage(coverage);
435        Color32::from_white_alpha(ecolor::linear_u8_from_linear_f32(alpha))
436    }
437
438    /// Convert this into the closest gamma exponent
439    pub fn to_gamma(self) -> f32 {
440        match self {
441            Self::Off => 1.0,
442            Self::Gamma(gamma) => gamma,
443            Self::TwoCoverageMinusCoverageSq => 0.5, // approximately the same
444        }
445    }
446}
447
448// ----------------------------------------------------------------------------
449
450/// A change to an image.
451///
452/// Either a whole new image, or an update to a rectangular region of it.
453#[derive(Clone, PartialEq, Eq)]
454#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
455#[must_use = "The painter must take care of this"]
456pub struct ImageDelta {
457    /// What to set the texture to.
458    ///
459    /// If [`Self::pos`] is `None`, this describes the whole texture.
460    ///
461    /// If [`Self::pos`] is `Some`, this describes a patch of the whole image starting at [`Self::pos`].
462    pub image: ImageData,
463
464    pub options: TextureOptions,
465
466    /// If `None`, set the whole texture to [`Self::image`].
467    ///
468    /// If `Some(pos)`, update a sub-region of an already allocated texture with the patch in [`Self::image`].
469    pub pos: Option<[usize; 2]>,
470}
471
472impl ImageDelta {
473    /// Update the whole texture.
474    pub fn full(image: impl Into<ImageData>, options: TextureOptions) -> Self {
475        Self {
476            image: image.into(),
477            options,
478            pos: None,
479        }
480    }
481
482    /// Update a sub-region of an existing texture.
483    pub fn partial(pos: [usize; 2], image: impl Into<ImageData>, options: TextureOptions) -> Self {
484        Self {
485            image: image.into(),
486            options,
487            pos: Some(pos),
488        }
489    }
490
491    /// Is this affecting the whole texture?
492    /// If `false`, this is a partial (sub-region) update.
493    pub fn is_whole(&self) -> bool {
494        self.pos.is_none()
495    }
496}