From 7ac137bfc167d0f7ff78d5fd042d6042ff190455 Mon Sep 17 00:00:00 2001 From: valadaptive <79560998+valadaptive@users.noreply.github.com> Date: Fri, 4 Jul 2025 07:15:48 -0400 Subject: [PATCH] Make the font atlas use a color image (#7298) * [x] I have followed the instructions in the PR template Splitting this out from the Parley work as requested. This removes `FontImage` and makes the font atlas use a `ColorImage`. It converts alpha to coverage at glyph-drawing time, not at delta-upload time. This doesn't do much now, but will allow for color emoji rendering once we start using Parley. I've changed things around so that we pass in `text_alpha_to_coverage` to the `Fonts` the same way we do with `pixels_per_point` and `max_texture_side`, reusing the existing code to check if the setting differs and recreating the font atlas if so. I'm not quite sure why this wasn't done in the first place. I've left `ImageData` as an enum for now, in case we want to add support for more texture pixel formats in the future (which I personally think would be worthwhile). If you'd like, I can just remove that enum entirely. --- crates/egui-wgpu/src/renderer.rs | 13 -- crates/egui/src/context.rs | 63 ++------- crates/egui/src/lib.rs | 2 +- crates/egui_demo_lib/benches/benchmark.rs | 7 +- crates/egui_glow/src/painter.rs | 17 --- crates/epaint/benches/benchmark.rs | 6 +- crates/epaint/src/image.rs | 160 +++++----------------- crates/epaint/src/lib.rs | 2 +- crates/epaint/src/shapes/text_shape.rs | 7 +- crates/epaint/src/text/font.rs | 3 +- crates/epaint/src/text/fonts.rs | 42 ++++-- crates/epaint/src/text/text_layout.rs | 37 ++++- crates/epaint/src/texture_atlas.rs | 29 ++-- crates/epaint/src/textures.rs | 2 +- 14 files changed, 147 insertions(+), 243 deletions(-) diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 473c7302..41a9b3b7 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -564,19 +564,6 @@ impl Renderer { ); Cow::Borrowed(&image.pixels) } - epaint::ImageData::Font(image) => { - assert_eq!( - width as usize * height as usize, - image.pixels.len(), - "Mismatch between texture size and texel count" - ); - profiling::scope!("font -> sRGBA"); - Cow::Owned( - image - .srgba_pixels(Default::default()) - .collect::>(), - ) - } }; let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice()); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index abb03b1c..f3bc73ce 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -78,7 +78,7 @@ impl Default for WrappedTextureManager { // Will be filled in later let font_id = tex_mngr.alloc( "egui_font_texture".into(), - epaint::FontImage::new([0, 0]).into(), + epaint::ColorImage::filled([0, 0], Color32::TRANSPARENT).into(), Default::default(), ); assert_eq!( @@ -610,6 +610,8 @@ impl ContextImpl { log::trace!("Adding new fonts"); } + let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage; + let mut is_new = false; let fonts = self @@ -624,13 +626,14 @@ impl ContextImpl { Fonts::new( pixels_per_point, max_texture_side, + text_alpha_from_coverage, self.font_definitions.clone(), ) }); { profiling::scope!("Fonts::begin_pass"); - fonts.begin_pass(pixels_per_point, max_texture_side); + fonts.begin_pass(pixels_per_point, max_texture_side, text_alpha_from_coverage); } if is_new && self.memory.options.preload_font_glyphs { @@ -1921,16 +1924,6 @@ impl Context { } } - pub(crate) fn reset_font_atlas(&self) { - let pixels_per_point = self.pixels_per_point(); - let fonts = self.read(|ctx| { - ctx.fonts - .get(&pixels_per_point.into()) - .map(|current_fonts| current_fonts.lock().fonts.definitions().clone()) - }); - self.memory_mut(|mem| mem.new_font_definitions = fonts); - } - /// Tell `egui` which fonts to use. /// /// The default `egui` fonts only support latin and cyrillic alphabets, @@ -2066,19 +2059,10 @@ impl Context { /// You can use [`Ui::style_mut`] to change the style of a single [`Ui`]. pub fn set_style_of(&self, theme: Theme, style: impl Into>) { let style = style.into(); - let mut recreate_font_atlas = false; - self.options_mut(|opt| { - let dest = match theme { - Theme::Dark => &mut opt.dark_style, - Theme::Light => &mut opt.light_style, - }; - recreate_font_atlas = - dest.visuals.text_alpha_from_coverage != style.visuals.text_alpha_from_coverage; - *dest = style; + self.options_mut(|opt| match theme { + Theme::Dark => opt.dark_style = style, + Theme::Light => opt.light_style = style, }); - if recreate_font_atlas { - self.reset_font_atlas(); - } } /// The [`crate::Visuals`] used by all subsequent windows, panels etc. @@ -2475,28 +2459,7 @@ impl ContextImpl { } // Inform the backend of all textures that have been updated (including font atlas). - let textures_delta = { - // HACK to get much nicer looking text in light mode. - // This assumes all text is black-on-white in light mode, - // and white-on-black in dark mode, which is not necessarily true, - // but often close enough. - // Of course this fails for cases when there is black-on-white text in dark mode, - // and white-on-black text in light mode. - - let text_alpha_from_coverage = - self.memory.options.style().visuals.text_alpha_from_coverage; - - let mut textures_delta = self.tex_manager.0.write().take_delta(); - - for (_, delta) in &mut textures_delta.set { - if let ImageData::Font(font) = &mut delta.image { - delta.image = - ImageData::Color(font.to_color_image(text_alpha_from_coverage).into()); - } - } - - textures_delta - }; + let textures_delta = self.tex_manager.0.write().take_delta(); let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output); @@ -3094,17 +3057,9 @@ impl Context { options.ui(ui); - let text_alpha_from_coverage_changed = - prev_options.style().visuals.text_alpha_from_coverage - != options.style().visuals.text_alpha_from_coverage; - if options != prev_options { self.options_mut(move |o| *o = options); } - - if text_alpha_from_coverage_changed { - ui.ctx().reset_font_atlas(); - } } fn fonts_tweak_ui(&self, ui: &mut Ui) { diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index b4daa932..7abc5ba4 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -467,7 +467,7 @@ pub use emath::{ remap_clamp, vec2, }; pub use epaint::{ - ClippedPrimitive, ColorImage, CornerRadius, FontImage, ImageData, Margin, Mesh, PaintCallback, + ClippedPrimitive, ColorImage, CornerRadius, ImageData, Margin, Mesh, PaintCallback, PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId, mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta}, diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index e0c86f0d..02f098e6 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -168,6 +168,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let fonts = egui::epaint::text::Fonts::new( pixels_per_point, max_texture_side, + egui::epaint::AlphaFromCoverage::default(), egui::FontDefinitions::default(), ); { @@ -210,7 +211,11 @@ pub fn criterion_benchmark(c: &mut Criterion) { let mut rng = rand::rng(); b.iter(|| { - fonts.begin_pass(pixels_per_point, max_texture_side); + fonts.begin_pass( + pixels_per_point, + max_texture_side, + egui::epaint::AlphaFromCoverage::default(), + ); // Delete a random character, simulating a user making an edit in a long file: let mut new_string = string.clone(); diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index a574cbb7..5833d73e 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -534,23 +534,6 @@ impl Painter { self.upload_texture_srgb(delta.pos, image.size, delta.options, data); } - egui::ImageData::Font(image) => { - assert_eq!( - image.width() * image.height(), - image.pixels.len(), - "Mismatch between texture size and texel count" - ); - - let data: Vec = { - profiling::scope!("font -> sRGBA"); - image - .srgba_pixels(Default::default()) - .flat_map(|a| a.to_array()) - .collect() - }; - - self.upload_texture_srgb(delta.pos, image.size, delta.options, &data); - } }; } diff --git a/crates/epaint/benches/benchmark.rs b/crates/epaint/benches/benchmark.rs index 676e1d0f..d4b10a21 100644 --- a/crates/epaint/benches/benchmark.rs +++ b/crates/epaint/benches/benchmark.rs @@ -1,8 +1,8 @@ use criterion::{Criterion, black_box, criterion_group, criterion_main}; use epaint::{ - ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, TessellationOptions, - Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path, + AlphaFromCoverage, ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, + TessellationOptions, Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path, }; #[global_allocator] @@ -66,7 +66,7 @@ fn tessellate_circles(c: &mut Criterion) { let pixels_per_point = 2.0; let options = TessellationOptions::default(); - let atlas = TextureAtlas::new([4096, 256]); + let atlas = TextureAtlas::new([4096, 256], AlphaFromCoverage::default()); let font_tex_size = atlas.size(); let prepared_discs = atlas.prepared_discs(); diff --git a/crates/epaint/src/image.rs b/crates/epaint/src/image.rs index 6b40714c..e14ea869 100644 --- a/crates/epaint/src/image.rs +++ b/crates/epaint/src/image.rs @@ -7,24 +7,20 @@ use std::sync::Arc; /// /// To load an image file, see [`ColorImage::from_rgba_unmultiplied`]. /// -/// In order to paint the image on screen, you first need to convert it to +/// This is currently an enum with only one variant, but more image types may be added in the future. /// -/// See also: [`ColorImage`], [`FontImage`]. -#[derive(Clone, PartialEq)] +/// See also: [`ColorImage`]. +#[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ImageData { /// RGBA image. Color(Arc), - - /// Used for the font texture. - Font(FontImage), } impl ImageData { pub fn size(&self) -> [usize; 2] { match self { Self::Color(image) => image.size, - Self::Font(image) => image.size, } } @@ -38,7 +34,7 @@ impl ImageData { pub fn bytes_per_pixel(&self) -> usize { match self { - Self::Color(_) | Self::Font(_) => 4, + Self::Color(_) => 4, } } } @@ -271,6 +267,37 @@ impl ColorImage { } Self::new([width, height], output) } + + /// Clone a sub-region as a new image. + pub fn region_by_pixels(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self { + assert!( + x + w <= self.width(), + "x + w should be <= self.width(), but x: {}, w: {}, width: {}", + x, + w, + self.width() + ); + assert!( + y + h <= self.height(), + "y + h should be <= self.height(), but y: {}, h: {}, height: {}", + y, + h, + self.height() + ); + + let mut pixels = Vec::with_capacity(w * h); + for y in y..y + h { + let offset = y * self.width() + x; + pixels.extend(&self.pixels[offset..(offset + w)]); + } + assert_eq!( + pixels.len(), + w * h, + "pixels.len should be w * h, but got {}", + pixels.len() + ); + Self::new([w, h], pixels) + } } impl std::ops::Index<(usize, usize)> for ColorImage { @@ -371,127 +398,12 @@ impl AlphaFromCoverage { } } -/// A single-channel image designed for the font texture. -/// -/// Each value represents "coverage", i.e. how much a texel is covered by a character. -/// -/// This is roughly interpreted as the opacity of a white image. -#[derive(Clone, Default, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct FontImage { - /// width, height - pub size: [usize; 2], - - /// The coverage value. - /// - /// Often you want to use [`Self::srgba_pixels`] instead. - pub pixels: Vec, -} - -impl FontImage { - pub fn new(size: [usize; 2]) -> Self { - Self { - size, - pixels: vec![0.0; size[0] * size[1]], - } - } - - #[inline] - pub fn width(&self) -> usize { - self.size[0] - } - - #[inline] - pub fn height(&self) -> usize { - self.size[1] - } - - /// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom. - #[inline] - pub fn srgba_pixels( - &self, - alpha_from_coverage: AlphaFromCoverage, - ) -> impl ExactSizeIterator + '_ { - self.pixels - .iter() - .map(move |&coverage| alpha_from_coverage.color_from_coverage(coverage)) - } - - /// Convert this coverage image to a [`ColorImage`]. - pub fn to_color_image(&self, alpha_from_coverage: AlphaFromCoverage) -> ColorImage { - profiling::function_scope!(); - let pixels = self.srgba_pixels(alpha_from_coverage).collect(); - ColorImage::new(self.size, pixels) - } - - /// Clone a sub-region as a new image. - pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self { - assert!( - x + w <= self.width(), - "x + w should be <= self.width(), but x: {}, w: {}, width: {}", - x, - w, - self.width() - ); - assert!( - y + h <= self.height(), - "y + h should be <= self.height(), but y: {}, h: {}, height: {}", - y, - h, - self.height() - ); - - let mut pixels = Vec::with_capacity(w * h); - for y in y..y + h { - let offset = y * self.width() + x; - pixels.extend(&self.pixels[offset..(offset + w)]); - } - assert_eq!( - pixels.len(), - w * h, - "pixels.len should be w * h, but got {}", - pixels.len() - ); - Self { - size: [w, h], - pixels, - } - } -} - -impl std::ops::Index<(usize, usize)> for FontImage { - type Output = f32; - - #[inline] - fn index(&self, (x, y): (usize, usize)) -> &f32 { - let [w, h] = self.size; - assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}"); - &self.pixels[y * w + x] - } -} - -impl std::ops::IndexMut<(usize, usize)> for FontImage { - #[inline] - fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut f32 { - let [w, h] = self.size; - assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}"); - &mut self.pixels[y * w + x] - } -} - -impl From for ImageData { - #[inline(always)] - fn from(image: FontImage) -> Self { - Self::Font(image) - } -} - // ---------------------------------------------------------------------------- /// A change to an image. /// /// Either a whole new image, or an update to a rectangular region of it. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[must_use = "The painter must take care of this"] pub struct ImageDelta { diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 7afc7b14..f02889d9 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -50,7 +50,7 @@ pub use self::{ color::ColorMode, corner_radius::CornerRadius, corner_radius_f32::CornerRadiusF32, - image::{AlphaFromCoverage, ColorImage, FontImage, ImageData, ImageDelta}, + image::{AlphaFromCoverage, ColorImage, ImageData, ImageDelta}, margin::Margin, margin_f32::*, mesh::{Mesh, Mesh16, Vertex}, diff --git a/crates/epaint/src/shapes/text_shape.rs b/crates/epaint/src/shapes/text_shape.rs index bf9db964..b366c86c 100644 --- a/crates/epaint/src/shapes/text_shape.rs +++ b/crates/epaint/src/shapes/text_shape.rs @@ -179,7 +179,12 @@ mod tests { #[test] fn text_bounding_box_under_rotation() { - let fonts = Fonts::new(1.0, 1024, FontDefinitions::default()); + let fonts = Fonts::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let font = FontId::monospace(12.0); let mut t = crate::Shape::text( diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 72fffaad..dd095c44 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -279,12 +279,13 @@ impl FontImpl { } else { let glyph_pos = { let atlas = &mut self.atlas.lock(); + let text_alpha_from_coverage = atlas.text_alpha_from_coverage; let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height)); glyph.draw(|x, y, v| { if 0.0 < v { let px = glyph_pos.0 + x as usize; let py = glyph_pos.1 + y as usize; - image[(px, py)] = v; + image[(px, py)] = text_alpha_from_coverage.color_from_coverage(v); } }); glyph_pos diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 6e90b566..5f900691 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, sync::Arc}; use crate::{ - TextureAtlas, + AlphaFromCoverage, TextureAtlas, mutex::{Mutex, MutexGuard}, text::{ Galley, LayoutJob, LayoutSection, @@ -430,36 +430,56 @@ impl Fonts { pub fn new( pixels_per_point: f32, max_texture_side: usize, + text_alpha_from_coverage: AlphaFromCoverage, definitions: FontDefinitions, ) -> Self { let fonts_and_cache = FontsAndCache { - fonts: FontsImpl::new(pixels_per_point, max_texture_side, definitions), + fonts: FontsImpl::new( + pixels_per_point, + max_texture_side, + text_alpha_from_coverage, + definitions, + ), galley_cache: Default::default(), }; Self(Arc::new(Mutex::new(fonts_and_cache))) } /// Call at the start of each frame with the latest known - /// `pixels_per_point` and `max_texture_side`. + /// `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`. /// /// Call after painting the previous frame, but before using [`Fonts`] for the new frame. /// - /// This function will react to changes in `pixels_per_point` and `max_texture_side`, + /// This function will react to changes in `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`, /// as well as notice when the font atlas is getting full, and handle that. - pub fn begin_pass(&self, pixels_per_point: f32, max_texture_side: usize) { + pub fn begin_pass( + &self, + pixels_per_point: f32, + max_texture_side: usize, + text_alpha_from_coverage: AlphaFromCoverage, + ) { let mut fonts_and_cache = self.0.lock(); let pixels_per_point_changed = fonts_and_cache.fonts.pixels_per_point != pixels_per_point; let max_texture_side_changed = fonts_and_cache.fonts.max_texture_side != max_texture_side; + let text_alpha_from_coverage_changed = + fonts_and_cache.fonts.atlas.lock().text_alpha_from_coverage != text_alpha_from_coverage; let font_atlas_almost_full = fonts_and_cache.fonts.atlas.lock().fill_ratio() > 0.8; - let needs_recreate = - pixels_per_point_changed || max_texture_side_changed || font_atlas_almost_full; + let needs_recreate = pixels_per_point_changed + || max_texture_side_changed + || text_alpha_from_coverage_changed + || font_atlas_almost_full; if needs_recreate { let definitions = fonts_and_cache.fonts.definitions.clone(); *fonts_and_cache = FontsAndCache { - fonts: FontsImpl::new(pixels_per_point, max_texture_side, definitions), + fonts: FontsImpl::new( + pixels_per_point, + max_texture_side, + text_alpha_from_coverage, + definitions, + ), galley_cache: Default::default(), }; } @@ -497,7 +517,7 @@ impl Fonts { /// The full font atlas image. #[inline] - pub fn image(&self) -> crate::FontImage { + pub fn image(&self) -> crate::ColorImage { self.lock().fonts.atlas.lock().image().clone() } @@ -642,6 +662,7 @@ impl FontsImpl { pub fn new( pixels_per_point: f32, max_texture_side: usize, + text_alpha_from_coverage: AlphaFromCoverage, definitions: FontDefinitions, ) -> Self { assert!( @@ -651,7 +672,7 @@ impl FontsImpl { let texture_width = max_texture_side.at_most(16 * 1024); let initial_height = 32; // Keep initial font atlas small, so it is fast to upload to GPU. This will expand as needed anyways. - let atlas = TextureAtlas::new([texture_width, initial_height]); + let atlas = TextureAtlas::new([texture_width, initial_height], text_alpha_from_coverage); let atlas = Arc::new(Mutex::new(atlas)); @@ -1120,6 +1141,7 @@ mod tests { let mut fonts = FontsImpl::new( pixels_per_point, max_texture_side, + AlphaFromCoverage::default(), FontDefinitions::default(), ); diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 3777d860..7915bbf6 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -1034,11 +1034,18 @@ fn is_cjk_break_allowed(c: char) -> bool { #[cfg(test)] mod tests { + use crate::AlphaFromCoverage; + use super::{super::*, *}; #[test] fn test_zero_max_width() { - let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default()); + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let mut layout_job = LayoutJob::single_section("W".into(), TextFormat::default()); layout_job.wrap.max_width = 0.0; let galley = layout(&mut fonts, layout_job.into()); @@ -1049,7 +1056,12 @@ mod tests { fn test_truncate_with_newline() { // No matter where we wrap, we should be appending the newline character. - let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default()); + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let text_format = TextFormat { font_id: FontId::monospace(12.0), ..Default::default() @@ -1094,7 +1106,12 @@ mod tests { #[test] fn test_cjk() { - let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default()); + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let mut layout_job = LayoutJob::single_section( "日本語とEnglishの混在した文章".into(), TextFormat::default(), @@ -1109,7 +1126,12 @@ mod tests { #[test] fn test_pre_cjk() { - let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default()); + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let mut layout_job = LayoutJob::single_section( "日本語とEnglishの混在した文章".into(), TextFormat::default(), @@ -1124,7 +1146,12 @@ mod tests { #[test] fn test_truncate_width() { - let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default()); + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let mut layout_job = LayoutJob::single_section("# DNA\nMore text".into(), TextFormat::default()); layout_job.wrap.max_width = f32::INFINITY; diff --git a/crates/epaint/src/texture_atlas.rs b/crates/epaint/src/texture_atlas.rs index 8e174e54..36dd1b48 100644 --- a/crates/epaint/src/texture_atlas.rs +++ b/crates/epaint/src/texture_atlas.rs @@ -1,6 +1,7 @@ +use ecolor::Color32; use emath::{Rect, remap_clamp}; -use crate::{FontImage, ImageDelta}; +use crate::{AlphaFromCoverage, ColorImage, ImageDelta}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] struct Rectu { @@ -57,7 +58,7 @@ pub struct PreparedDisc { /// More characters can be added, possibly expanding the texture. #[derive(Clone)] pub struct TextureAtlas { - image: FontImage, + image: ColorImage, /// What part of the image that is dirty dirty: Rectu, @@ -72,18 +73,22 @@ pub struct TextureAtlas { /// pre-rasterized discs of radii `2^i`, where `i` is the index. discs: Vec, + + /// Controls how to convert glyph coverage to alpha. + pub(crate) text_alpha_from_coverage: AlphaFromCoverage, } impl TextureAtlas { - pub fn new(size: [usize; 2]) -> Self { + pub fn new(size: [usize; 2], text_alpha_from_coverage: AlphaFromCoverage) -> Self { assert!(size[0] >= 1024, "Tiny texture atlas"); let mut atlas = Self { - image: FontImage::new(size), + image: ColorImage::filled(size, Color32::TRANSPARENT), dirty: Rectu::EVERYTHING, cursor: (0, 0), row_height: 0, overflowed: false, discs: vec![], // will be filled in below + text_alpha_from_coverage, }; // Make the top left pixel fully white for `WHITE_UV`, i.e. painting something with solid color: @@ -93,7 +98,7 @@ impl TextureAtlas { (0, 0), "Expected the first allocation to be at (0, 0), but was at {pos:?}" ); - image[pos] = 1.0; + image[pos] = Color32::WHITE; // Allocate a series of anti-aliased discs used to render small filled circles: // TODO(emilk): these circles can be packed A LOT better. @@ -116,7 +121,7 @@ impl TextureAtlas { let coverage = remap_clamp(distance_to_center, (r - 0.5)..=(r + 0.5), 1.0..=0.0); image[((x as i32 + hw + dx) as usize, (y as i32 + hw + dy) as usize)] = - coverage; + text_alpha_from_coverage.color_from_coverage(coverage); } } atlas.discs.push(PrerasterizedDisc { @@ -184,7 +189,7 @@ impl TextureAtlas { /// The full font atlas image. #[inline] - pub fn image(&self) -> &FontImage { + pub fn image(&self) -> &ColorImage { &self.image } @@ -200,14 +205,14 @@ impl TextureAtlas { } 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); + let region = self.image.region_by_pixels(pos, size); Some(ImageDelta::partial(pos, region, texture_options)) } } /// Returns the coordinates of where the rect ended up, /// and invalidates the region. - pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut FontImage) { + pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut ColorImage) { /// On some low-precision GPUs (my old iPad) characters get muddled up /// if we don't add some empty pixels between the characters. /// On modern high-precision GPUs this is not needed. @@ -254,13 +259,15 @@ impl TextureAtlas { } } -fn resize_to_min_height(image: &mut FontImage, required_height: usize) -> bool { +fn resize_to_min_height(image: &mut ColorImage, required_height: usize) -> bool { while required_height >= image.height() { image.size[1] *= 2; // double the height } if image.width() * image.height() > image.pixels.len() { - image.pixels.resize(image.width() * image.height(), 0.0); + image + .pixels + .resize(image.width() * image.height(), Color32::TRANSPARENT); true } else { false diff --git a/crates/epaint/src/textures.rs b/crates/epaint/src/textures.rs index cc191a75..0944a905 100644 --- a/crates/epaint/src/textures.rs +++ b/crates/epaint/src/textures.rs @@ -271,7 +271,7 @@ pub enum TextureWrapMode { /// What has been allocated and freed during the last period. /// /// These are commands given to the integration painter. -#[derive(Clone, Default, PartialEq)] +#[derive(Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[must_use = "The painter must take care of this"] pub struct TexturesDelta {