diff --git a/egui/src/context.rs b/egui/src/context.rs index 9ea3759860e..08656de3c93 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -5,7 +5,7 @@ use crate::{ animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState, input_state::*, layers::GraphicLayers, memory::Options, output::FullOutput, TextureHandle, *, }; -use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *}; +use epaint::{mutex::*, stats::*, text::Fonts, textures::TextureFilter, TessellationOptions, *}; // ---------------------------------------------------------------------------- @@ -19,6 +19,7 @@ impl Default for WrappedTextureManager { let font_id = tex_mngr.alloc( "egui_font_texture".into(), epaint::FontImage::new([0, 0]).into(), + Default::default(), ); assert_eq!(font_id, TextureId::default()); @@ -707,6 +708,7 @@ impl Context { &self, name: impl Into, image: impl Into, + filter: TextureFilter, ) -> TextureHandle { let name = name.into(); let image = image.into(); @@ -720,7 +722,7 @@ impl Context { max_texture_side ); let tex_mngr = self.tex_manager(); - let tex_id = tex_mngr.write().alloc(name, image); + let tex_id = tex_mngr.write().alloc(name, image, filter); TextureHandle::new(tex_mngr, tex_id) } diff --git a/egui_demo_lib/src/color_test.rs b/egui_demo_lib/src/color_test.rs index bef78c39392..09936b90be9 100644 --- a/egui_demo_lib/src/color_test.rs +++ b/egui_demo_lib/src/color_test.rs @@ -1,4 +1,4 @@ -use egui::{color::*, widgets::color_picker::show_color, *}; +use egui::{color::*, epaint::textures::TextureFilter, widgets::color_picker::show_color, *}; use std::collections::HashMap; const GRADIENT_SIZE: Vec2 = vec2(256.0, 24.0); @@ -334,6 +334,7 @@ impl TextureManager { size: [width, height], pixels, }, + TextureFilter::Linear, ) }) } diff --git a/egui_demo_lib/src/demo/plot_demo.rs b/egui_demo_lib/src/demo/plot_demo.rs index 2b81427ad7d..adff2d9bf9b 100644 --- a/egui_demo_lib/src/demo/plot_demo.rs +++ b/egui_demo_lib/src/demo/plot_demo.rs @@ -549,8 +549,11 @@ impl ItemsDemo { }; let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| { - ui.ctx() - .load_texture("plot_demo", egui::ColorImage::example()) + ui.ctx().load_texture( + "plot_demo", + egui::ColorImage::example(), + egui::epaint::textures::TextureFilter::Linear, + ) }); let image = PlotImage::new( texture, diff --git a/egui_demo_lib/src/demo/widget_gallery.rs b/egui_demo_lib/src/demo/widget_gallery.rs index 9a25d8c87ac..858ca6d16c6 100644 --- a/egui_demo_lib/src/demo/widget_gallery.rs +++ b/egui_demo_lib/src/demo/widget_gallery.rs @@ -115,8 +115,11 @@ impl WidgetGallery { } = self; let texture: &egui::TextureHandle = texture.get_or_insert_with(|| { - ui.ctx() - .load_texture("example", egui::ColorImage::example()) + ui.ctx().load_texture( + "example", + egui::ColorImage::example(), + egui::epaint::textures::TextureFilter::Linear, + ) }); ui.add(doc_link_label("Label", "label,heading")); diff --git a/egui_extras/src/image.rs b/egui_extras/src/image.rs index b9001f201e5..807ed98b59d 100644 --- a/egui_extras/src/image.rs +++ b/egui_extras/src/image.rs @@ -1,3 +1,4 @@ +use egui::epaint::textures::TextureFilter; use egui::mutex::Mutex; /// An image to be shown in egui. @@ -12,8 +13,8 @@ pub struct RetainedImage { image: Mutex, /// Lazily loaded when we have an egui context. texture: Mutex>, + filter: TextureFilter, } - impl RetainedImage { pub fn from_color_image(debug_name: impl Into, image: ColorImage) -> Self { Self { @@ -21,9 +22,39 @@ impl RetainedImage { size: image.size, image: Mutex::new(image), texture: Default::default(), + filter: Default::default(), } } + /// Set the texture filter to use for the image. + /// + /// **Note:** If the texture has already been uploaded to the GPU, this will require + /// re-uploading the texture with the updated filter. + /// + /// # Example + /// ```rust + /// # use egui_extras::RetainedImage; + /// # use egui::{Color32, epaint::{ColorImage, textures::TextureFilter}}; + /// # let pixels = vec![Color32::BLACK]; + /// # let color_image = ColorImage { + /// # size: [1, 1], + /// # pixels, + /// # }; + /// # + /// // Upload a pixel art image without it getting blurry when resized + /// let image = RetainedImage::from_color_image("my_image", color_image) + /// .with_texture_filter(TextureFilter::Nearest); + /// ``` + pub fn with_texture_filter(mut self, filter: TextureFilter) -> Self { + self.filter = filter; + + // If the texture has already been uploaded, this will force it to be re-uploaded with the + // updated filter. + *self.texture.lock() = None; + + self + } + /// Load a (non-svg) image. /// /// Requires the "image" feature. You must also opt-in to the image formats you need @@ -86,7 +117,7 @@ impl RetainedImage { .get_or_insert_with(|| { let image: &mut ColorImage = &mut self.image.lock(); let image = std::mem::take(image); - ctx.load_texture(&self.debug_name, image) + ctx.load_texture(&self.debug_name, image, self.filter) }) .id() } diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index f05e41a903b..2ae43e5e194 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -20,20 +20,13 @@ pub use glow::Context; const VERT_SRC: &str = include_str!("shader/vertex.glsl"); const FRAG_SRC: &str = include_str!("shader/fragment.glsl"); -#[derive(Copy, Clone)] -pub enum TextureFilter { - Linear, - Nearest, -} +pub type TextureFilter = egui::epaint::textures::TextureFilter; -impl Default for TextureFilter { - fn default() -> Self { - TextureFilter::Linear - } +trait TextureFilterExt { + fn glow_code(&self) -> u32; } - -impl TextureFilter { - pub(crate) fn glow_code(&self) -> u32 { +impl TextureFilterExt for TextureFilter { + fn glow_code(&self) -> u32 { match self { TextureFilter::Linear => glow::LINEAR, TextureFilter::Nearest => glow::NEAREST, @@ -60,8 +53,6 @@ pub struct Painter { is_embedded: bool, vao: crate::vao::VertexArrayObject, srgb_support: bool, - /// The filter used for subsequent textures. - texture_filter: TextureFilter, post_process: Option, vbo: glow::Buffer, element_array_buffer: glow::Buffer, @@ -216,7 +207,6 @@ impl Painter { is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300), vao, srgb_support, - texture_filter: Default::default(), post_process, vbo, element_array_buffer, @@ -450,12 +440,6 @@ impl Painter { } } - // Set the filter to be used for any subsequent textures loaded via - // [`Self::set_texture`]. - pub fn set_texture_filter(&mut self, texture_filter: TextureFilter) { - self.texture_filter = texture_filter; - } - // ------------------------------------------------------------------------ pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) { @@ -481,7 +465,7 @@ impl Painter { let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref()); - self.upload_texture_srgb(delta.pos, image.size, data); + self.upload_texture_srgb(delta.pos, image.size, delta.filter.into(), data); } egui::ImageData::Font(image) => { assert_eq!( @@ -500,12 +484,18 @@ impl Painter { .flat_map(|a| a.to_array()) .collect(); - self.upload_texture_srgb(delta.pos, image.size, &data); + self.upload_texture_srgb(delta.pos, image.size, delta.filter.into(), &data); } }; } - fn upload_texture_srgb(&mut self, pos: Option<[usize; 2]>, [w, h]: [usize; 2], data: &[u8]) { + fn upload_texture_srgb( + &mut self, + pos: Option<[usize; 2]>, + [w, h]: [usize; 2], + texture_filter: TextureFilter, + data: &[u8], + ) { assert_eq!(data.len(), w * h * 4); assert!( w >= 1 && h >= 1, @@ -525,12 +515,12 @@ impl Painter { self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, - self.texture_filter.glow_code() as i32, + texture_filter.glow_code() as i32, ); self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, - self.texture_filter.glow_code() as i32, + texture_filter.glow_code() as i32, ); self.gl.tex_parameter_i32( diff --git a/epaint/src/image.rs b/epaint/src/image.rs index 1a4f449d9fc..7fe1a1acb06 100644 --- a/epaint/src/image.rs +++ b/epaint/src/image.rs @@ -1,4 +1,4 @@ -use crate::Color32; +use crate::{textures::TextureFilter, Color32}; /// An image stored in RAM. /// @@ -272,6 +272,8 @@ pub struct ImageDelta { /// If [`Self::pos`] is `Some`, this describes a patch of the whole image starting at [`Self::pos`]. pub image: ImageData, + pub filter: TextureFilter, + /// If `None`, set the whole texture to [`Self::image`]. /// /// If `Some(pos)`, update a sub-region of an already allocated texture with the patch in [`Self::image`]. @@ -280,17 +282,19 @@ pub struct ImageDelta { impl ImageDelta { /// Update the whole texture. - pub fn full(image: impl Into) -> Self { + pub fn full(image: impl Into, filter: TextureFilter) -> Self { Self { image: image.into(), + filter, pos: None, } } /// Update a sub-region of an existing texture. - pub fn partial(pos: [usize; 2], image: impl Into) -> Self { + pub fn partial(pos: [usize; 2], image: impl Into, filter: TextureFilter) -> Self { Self { image: image.into(), + filter, pos: Some(pos), } } diff --git a/epaint/src/texture_atlas.rs b/epaint/src/texture_atlas.rs index 96d1f8b34e7..a6eae96549e 100644 --- a/epaint/src/texture_atlas.rs +++ b/epaint/src/texture_atlas.rs @@ -1,6 +1,6 @@ use emath::{remap_clamp, Rect}; -use crate::{FontImage, ImageDelta}; +use crate::{textures::TextureFilter, FontImage, ImageDelta}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] struct Rectu { @@ -176,12 +176,12 @@ impl TextureAtlas { if dirty == Rectu::NOTHING { None } else if dirty == Rectu::EVERYTHING { - Some(ImageDelta::full(self.image.clone())) + Some(ImageDelta::full(self.image.clone(), TextureFilter::Linear)) } else { let pos = [dirty.min_x, dirty.min_y]; let size = [dirty.max_x - dirty.min_x, dirty.max_y - dirty.min_y]; let region = self.image.region(pos, size); - Some(ImageDelta::partial(pos, region)) + Some(ImageDelta::partial(pos, region, TextureFilter::Linear)) } } diff --git a/epaint/src/texture_handle.rs b/epaint/src/texture_handle.rs index 288df3c1c37..728a87c081c 100644 --- a/epaint/src/texture_handle.rs +++ b/epaint/src/texture_handle.rs @@ -1,6 +1,9 @@ use std::sync::Arc; -use crate::{emath::NumExt, mutex::RwLock, ImageData, ImageDelta, TextureId, TextureManager}; +use crate::{ + emath::NumExt, mutex::RwLock, textures::TextureFilter, ImageData, ImageDelta, TextureId, + TextureManager, +}; /// Used to paint images. /// @@ -63,17 +66,22 @@ impl TextureHandle { } /// Assign a new image to an existing texture. - pub fn set(&mut self, image: impl Into) { + pub fn set(&mut self, image: impl Into, filter: TextureFilter) { self.tex_mngr .write() - .set(self.id, ImageDelta::full(image.into())); + .set(self.id, ImageDelta::full(image.into(), filter)); } /// Assign a new image to a subregion of the whole texture. - pub fn set_partial(&mut self, pos: [usize; 2], image: impl Into) { + pub fn set_partial( + &mut self, + pos: [usize; 2], + image: impl Into, + filter: TextureFilter, + ) { self.tex_mngr .write() - .set(self.id, ImageDelta::partial(pos, image.into())); + .set(self.id, ImageDelta::partial(pos, image.into(), filter)); } /// width x height diff --git a/epaint/src/textures.rs b/epaint/src/textures.rs index 205b25c9464..de1b08a0c6e 100644 --- a/epaint/src/textures.rs +++ b/epaint/src/textures.rs @@ -27,7 +27,7 @@ impl TextureManager { /// MUST have a white pixel at (0,0) ([`crate::WHITE_UV`]). /// /// The texture is given a retain-count of `1`, requiring one call to [`Self::free`] to free it. - pub fn alloc(&mut self, name: String, image: ImageData) -> TextureId { + pub fn alloc(&mut self, name: String, image: ImageData, filter: TextureFilter) -> TextureId { let id = TextureId::Managed(self.next_id); self.next_id += 1; @@ -36,9 +36,10 @@ impl TextureManager { size: image.size(), bytes_per_pixel: image.bytes_per_pixel(), retain_count: 1, + filter, }); - self.delta.set.insert(id, ImageDelta::full(image)); + self.delta.set.insert(id, ImageDelta::full(image, filter)); id } @@ -130,6 +131,22 @@ pub struct TextureMeta { /// Free when this reaches zero. pub retain_count: usize, + + /// The texture filtering mode to use when rendering + pub filter: TextureFilter, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum TextureFilter { + Linear, + Nearest, +} + +impl Default for TextureFilter { + fn default() -> Self { + Self::Linear + } } impl TextureMeta {