Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion crates/egui-wgpu/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,11 @@ impl Renderer {
"Mismatch between texture size and texel count"
);
profiling::scope!("font -> sRGBA");
Cow::Owned(image.srgba_pixels(None).collect::<Vec<epaint::Color32>>())
Cow::Owned(
image
.srgba_pixels(Default::default())
.collect::<Vec<epaint::Color32>>(),
)
}
};
let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
Expand Down
56 changes: 52 additions & 4 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1876,6 +1876,16 @@ 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,
Expand Down Expand Up @@ -2011,10 +2021,19 @@ 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<Arc<Style>>) {
let style = style.into();
self.options_mut(|opt| match theme {
Theme::Dark => opt.dark_style = style,
Theme::Light => opt.light_style = style,
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;
});
if recreate_font_atlas {
self.reset_font_atlas();
}
}

/// The [`crate::Visuals`] used by all subsequent windows, panels etc.
Expand Down Expand Up @@ -2411,7 +2430,28 @@ impl ContextImpl {
}

// Inform the backend of all textures that have been updated (including font atlas).
let textures_delta = self.tex_manager.0.write().take_delta();
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 mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output);

Expand Down Expand Up @@ -3009,9 +3049,17 @@ 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) {
Expand Down
6 changes: 3 additions & 3 deletions crates/egui/src/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,11 +408,11 @@ impl Options {
.show(ui, |ui| {
theme_preference.radio_buttons(ui);

std::sync::Arc::make_mut(match theme {
let style = std::sync::Arc::make_mut(match theme {
Theme::Dark => dark_style,
Theme::Light => light_style,
})
.ui(ui);
});
style.ui(ui);
});

CollapsingHeader::new("✒ Painting")
Expand Down
46 changes: 45 additions & 1 deletion crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#![allow(clippy::if_same_then_else)]

use emath::Align;
use epaint::{CornerRadius, Shadow, Stroke, text::FontTweak};
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, text::FontTweak};
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};

use crate::{
Expand Down Expand Up @@ -921,6 +921,9 @@ pub struct Visuals {
/// this is more to provide a convenient summary of the rest of the settings.
pub dark_mode: bool,

/// ADVANCED: Controls how we render text.
pub text_alpha_from_coverage: AlphaFromCoverage,

/// Override default text color for all text.
///
/// This is great for setting the color of text for any widget.
Expand Down Expand Up @@ -1374,6 +1377,7 @@ impl Visuals {
pub fn dark() -> Self {
Self {
dark_mode: true,
text_alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
override_text_color: None,
weak_text_alpha: 0.6,
weak_text_color: None,
Expand Down Expand Up @@ -1436,6 +1440,7 @@ impl Visuals {
pub fn light() -> Self {
Self {
dark_mode: false,
text_alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
widgets: Widgets::light(),
selection: Selection::light(),
hyperlink_color: Color32::from_rgb(0, 155, 255),
Expand Down Expand Up @@ -2068,6 +2073,7 @@ impl Visuals {
pub fn ui(&mut self, ui: &mut crate::Ui) {
let Self {
dark_mode,
text_alpha_from_coverage,
override_text_color: _,
weak_text_alpha,
weak_text_color,
Expand Down Expand Up @@ -2216,6 +2222,10 @@ impl Visuals {
"Weak text color",
);
});

ui.add_space(4.0);

text_alpha_from_coverage_ui(ui, text_alpha_from_coverage);
});

ui.collapsing("Text cursor", |ui| {
Expand Down Expand Up @@ -2326,6 +2336,40 @@ impl Visuals {
}
}

fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut AlphaFromCoverage) {
let mut dark_mode_special =
*text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;

ui.horizontal(|ui| {
ui.label("Text rendering:");

ui.checkbox(&mut dark_mode_special, "Dark-mode special");

if dark_mode_special {
*text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq;
} else {
let mut gamma = match text_alpha_from_coverage {
AlphaFromCoverage::Linear => 1.0,
AlphaFromCoverage::Gamma(gamma) => *gamma,
AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same
};

ui.add(
DragValue::new(&mut gamma)
.speed(0.01)
.range(0.1..=4.0)
.prefix("Gamma: "),
);

if gamma == 1.0 {
*text_alpha_from_coverage = AlphaFromCoverage::Linear;
} else {
*text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
}
}
});
}

impl TextCursorStyle {
fn ui(&mut self, ui: &mut Ui) {
let Self {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion crates/egui_glow/src/painter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ impl Painter {
let data: Vec<u8> = {
profiling::scope!("font -> sRGBA");
image
.srgba_pixels(None)
.srgba_pixels(Default::default())
.flat_map(|a| a.to_array())
.collect()
};
Expand Down
90 changes: 67 additions & 23 deletions crates/epaint/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,59 @@ impl std::fmt::Debug for ColorImage {

// ----------------------------------------------------------------------------

/// How to convert font coverage values into alpha and color values.
//
// This whole thing is less than rigorous.
// Ideally we should do this in a shader instead, and use different computations
// for different text colors.
// See https://hikogui.org/2022/10/24/the-trouble-with-anti-aliasing.html for an in-depth analysis.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum AlphaFromCoverage {
/// `alpha = coverage`.
///
/// Looks good for black-on-white text, i.e. light mode.
///
/// Same as [`Self::Gamma`]`(1.0)`, but more efficient.
Linear,

/// `alpha = coverage^gamma`.
Gamma(f32),

/// `alpha = 2 * coverage - coverage^2`
///
/// This looks good for white-on-black text, i.e. dark mode.
///
/// Very similar to a gamma of 0.5, but produces sharper text.
/// See <https://www.desmos.com/calculator/w0ndf5blmn> for a comparison to gamma=0.5.
#[default]
TwoCoverageMinusCoverageSq,
}

impl AlphaFromCoverage {
/// A good-looking default for light mode (black-on-white text).
pub const LIGHT_MODE_DEFAULT: Self = Self::Linear;

/// A good-looking default for dark mode (white-on-black text).
pub const DARK_MODE_DEFAULT: Self = Self::TwoCoverageMinusCoverageSq;

/// Convert coverage to alpha.
#[inline(always)]
pub fn alpha_from_coverage(&self, coverage: f32) -> f32 {
match self {
Self::Linear => coverage,
Self::Gamma(gamma) => coverage.powf(*gamma),
Self::TwoCoverageMinusCoverageSq => 2.0 * coverage - coverage * coverage,
}
}

#[inline(always)]
pub fn color_from_coverage(&self, coverage: f32) -> Color32 {
let alpha = self.alpha_from_coverage(coverage);
Color32::from_white_alpha(ecolor::linear_u8_from_linear_f32(alpha))
}
}

/// A single-channel image designed for the font texture.
///
/// Each value represents "coverage", i.e. how much a texel is covered by a character.
Expand Down Expand Up @@ -354,30 +407,21 @@ impl FontImage {
}

/// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
///
/// `gamma` should normally be set to `None`.
///
/// If you are having problems with text looking skinny and pixelated, try using a low gamma, e.g. `0.4`.
#[inline]
pub fn srgba_pixels(&self, gamma: Option<f32>) -> impl ExactSizeIterator<Item = Color32> + '_ {
// This whole function is less than rigorous.
// Ideally we should do this in a shader instead, and use different computations
// for different text colors.
// See https://hikogui.org/2022/10/24/the-trouble-with-anti-aliasing.html for an in-depth analysis.
self.pixels.iter().map(move |coverage| {
let alpha = if let Some(gamma) = gamma {
coverage.powf(gamma)
} else {
// alpha = coverage * coverage; // recommended by the article for WHITE text (using linear blending)

// The following is recommended by the article for BLACK text (using linear blending).
// Very similar to a gamma of 0.5, but produces sharper text.
// In practice it works well for all text colors (better than a gamma of 0.5, for instance).
// See https://www.desmos.com/calculator/w0ndf5blmn for a visual comparison.
2.0 * coverage - coverage * coverage
};
Color32::from_white_alpha(ecolor::linear_u8_from_linear_f32(alpha))
})
pub fn srgba_pixels(
&self,
alpha_from_coverage: AlphaFromCoverage,
) -> impl ExactSizeIterator<Item = Color32> + '_ {
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.
Expand Down
2 changes: 1 addition & 1 deletion crates/epaint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub use self::{
color::ColorMode,
corner_radius::CornerRadius,
corner_radius_f32::CornerRadiusF32,
image::{ColorImage, FontImage, ImageData, ImageDelta},
image::{AlphaFromCoverage, ColorImage, FontImage, ImageData, ImageDelta},
margin::Margin,
margin_f32::*,
mesh::{Mesh, Mesh16, Vertex},
Expand Down