Skip to content

Commit

Permalink
Improve text contrast in bright mode (#1412)
Browse files Browse the repository at this point in the history
* Rename AlphaImage to FontImage to discourage any other use for it
* Encode FontImage as f32 and postpone the alpha correction
* Interpret alpha coverage in a new, making dark text darker, improving contrast in bright mode
  • Loading branch information
emilk authored Mar 23, 2022
1 parent d3c002a commit 8272b08
Show file tree
Hide file tree
Showing 11 changed files with 57 additions and 55 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
### Changed 🔧
* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)).
* Renamed `Frame::margin` to `Frame::inner_margin`.
* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)).

### Fixed 🐛
* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).
* Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)).
* Text is darker and more readable in bright mode ([#1412](https://github.com/emilk/egui/pull/1412)).

### Removed 🔥
* Removed the `single_threaded/multi_threaded` flags - egui is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
Expand Down
2 changes: 1 addition & 1 deletion egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl Default for WrappedTextureManager {
// Will be filled in later
let font_id = tex_mngr.alloc(
"egui_font_texture".into(),
epaint::AlphaImage::new([0, 0]).into(),
epaint::FontImage::new([0, 0]).into(),
);
assert_eq!(font_id, TextureId::default());

Expand Down
2 changes: 1 addition & 1 deletion egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ pub use epaint::{
color, mutex,
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
textures::TexturesDelta,
AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback,
ClippedPrimitive, Color32, ColorImage, FontImage, ImageData, Mesh, PaintCallback,
PaintCallbackInfo, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId,
};

Expand Down
2 changes: 1 addition & 1 deletion egui_glium/src/painter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ impl Painter {
);
image.pixels.iter().map(|color| color.to_tuple()).collect()
}
egui::ImageData::Alpha(image) => {
egui::ImageData::Font(image) => {
let gamma = 1.0;
image
.srgba_pixels(gamma)
Expand Down
2 changes: 1 addition & 1 deletion egui_glow/src/painter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ impl Painter {

self.upload_texture_srgb(delta.pos, image.size, data);
}
egui::ImageData::Alpha(image) => {
egui::ImageData::Font(image) => {
assert_eq!(
image.width() * image.height(),
image.pixels.len(),
Expand Down
2 changes: 2 additions & 0 deletions epaint/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ All notable changes to the epaint crate will be documented in this file.
* Removed the `single_threaded/multi_threaded` flags - epaint is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
* `Tessellator::from_options` is now `Tessellator::new` ([#1408](https://github.com/emilk/egui/pull/1408)).
* Renamed `TessellationOptions::anti_alias` to `feathering` ([#1408](https://github.com/emilk/egui/pull/1408)).
* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)).
* Dark text is darker and more readable on bright backgrounds ([#1412](https://github.com/emilk/egui/pull/1412)).


## 0.17.0 - 2022-02-22
Expand Down
78 changes: 40 additions & 38 deletions epaint/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ use crate::Color32;
///
/// In order to paint the image on screen, you first need to convert it to
///
/// See also: [`ColorImage`], [`AlphaImage`].
/// See also: [`ColorImage`], [`FontImage`].
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ImageData {
/// RGBA image.
Color(ColorImage),
/// Used for the font texture.
Alpha(AlphaImage),
Font(FontImage),
}

impl ImageData {
pub fn size(&self) -> [usize; 2] {
match self {
Self::Color(image) => image.size,
Self::Alpha(image) => image.size,
Self::Font(image) => image.size,
}
}

Expand All @@ -35,7 +35,7 @@ impl ImageData {
pub fn bytes_per_pixel(&self) -> usize {
match self {
Self::Color(_) => 4,
Self::Alpha(_) => 1,
Self::Font(_) => 4,
}
}
}
Expand Down Expand Up @@ -157,25 +157,28 @@ impl From<ColorImage> for ImageData {

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

/// An 8-bit image, representing difference levels of transparent white.
/// A single-channel image designed for the font texture.
///
/// Used for the font texture
#[derive(Clone, Default, Eq, Hash, PartialEq)]
/// 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 AlphaImage {
pub struct FontImage {
/// width, height
pub size: [usize; 2],
/// The alpha (linear space 0-255) of something white.

/// The coverage value.
///
/// One byte per pixel. Often you want to use [`Self::srgba_pixels`] instead.
pub pixels: Vec<u8>,
/// Often you want to use [`Self::srgba_pixels`] instead.
pub pixels: Vec<f32>,
}

impl AlphaImage {
impl FontImage {
pub fn new(size: [usize; 2]) -> Self {
Self {
size,
pixels: vec![0; size[0] * size[1]],
pixels: vec![0.0; size[0] * size[1]],
}
}

Expand All @@ -194,24 +197,19 @@ impl AlphaImage {
/// `gamma` should normally be set to 1.0.
/// If you are having problems with text looking skinny and pixelated, try
/// setting a lower gamma, e.g. `0.5`.
pub fn srgba_pixels(
&'_ self,
gamma: f32,
) -> impl ExactSizeIterator<Item = super::Color32> + '_ {
let srgba_from_alpha_lut: Vec<Color32> = (0..=255)
.map(|a| {
let a = super::color::linear_f32_from_linear_u8(a).powf(gamma);
super::Rgba::from_white_alpha(a).into()
})
.collect();

self.pixels
.iter()
.map(move |&a| srgba_from_alpha_lut[a as usize])
pub fn srgba_pixels(&'_ self, gamma: f32) -> impl ExactSizeIterator<Item = Color32> + '_ {
self.pixels.iter().map(move |coverage| {
// This is arbitrarily chosen to make text look as good as possible.
// In particular, it looks good with gamma=1 and the default eframe backend,
// which uses linear blending.
// See https://github.com/emilk/egui/issues/1410
let a = fast_round(coverage.powf(gamma / 2.2) * 255.0);
Color32::from_rgba_premultiplied(a, a, a, a) // this makes no sense, but works
})
}

/// Clone a sub-region as a new image
pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> AlphaImage {
/// Clone a sub-region as a new image.
pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> FontImage {
assert!(x + w <= self.width());
assert!(y + h <= self.height());

Expand All @@ -221,40 +219,44 @@ impl AlphaImage {
pixels.extend(&self.pixels[offset..(offset + w)]);
}
assert_eq!(pixels.len(), w * h);
AlphaImage {
FontImage {
size: [w, h],
pixels,
}
}
}

impl std::ops::Index<(usize, usize)> for AlphaImage {
type Output = u8;
impl std::ops::Index<(usize, usize)> for FontImage {
type Output = f32;

#[inline]
fn index(&self, (x, y): (usize, usize)) -> &u8 {
fn index(&self, (x, y): (usize, usize)) -> &f32 {
let [w, h] = self.size;
assert!(x < w && y < h);
&self.pixels[y * w + x]
}
}

impl std::ops::IndexMut<(usize, usize)> for AlphaImage {
impl std::ops::IndexMut<(usize, usize)> for FontImage {
#[inline]
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut u8 {
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut f32 {
let [w, h] = self.size;
assert!(x < w && y < h);
&mut self.pixels[y * w + x]
}
}

impl From<AlphaImage> for ImageData {
impl From<FontImage> for ImageData {
#[inline(always)]
fn from(image: AlphaImage) -> Self {
Self::Alpha(image)
fn from(image: FontImage) -> Self {
Self::Font(image)
}
}

fn fast_round(r: f32) -> u8 {
(r + 0.5).floor() as _ // rust does a saturating cast since 1.45
}

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

/// A change to an image.
Expand Down
2 changes: 1 addition & 1 deletion epaint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub mod util;
pub use {
bezier::{CubicBezierShape, QuadraticBezierShape},
color::{Color32, Rgba},
image::{AlphaImage, ColorImage, ImageData, ImageDelta},
image::{ColorImage, FontImage, ImageData, ImageDelta},
mesh::{Mesh, Mesh16, Vertex},
shadow::Shadow,
shape::{
Expand Down
6 changes: 1 addition & 5 deletions epaint/src/text/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ fn allocate_glyph(
if v > 0.0 {
let px = glyph_pos.0 + x as usize;
let py = glyph_pos.1 + y as usize;
image[(px, py)] = fast_round(v * 255.0);
image[(px, py)] = v;
}
});

Expand Down Expand Up @@ -405,7 +405,3 @@ fn allocate_glyph(
uv_rect,
}
}

fn fast_round(r: f32) -> u8 {
(r + 0.5).floor() as _ // rust does a saturating cast since 1.45
}
2 changes: 1 addition & 1 deletion epaint/src/text/fonts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ impl FontsImpl {
// Make the top left pixel fully white:
let (pos, image) = atlas.allocate((1, 1));
assert_eq!(pos, (0, 0));
image[pos] = 255;
image[pos] = 1.0;
}

let atlas = Arc::new(Mutex::new(atlas));
Expand Down
12 changes: 6 additions & 6 deletions epaint/src/texture_atlas.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{AlphaImage, ImageDelta};
use crate::{FontImage, ImageDelta};

#[derive(Clone, Copy, Eq, PartialEq)]
struct Rectu {
Expand Down Expand Up @@ -32,7 +32,7 @@ impl Rectu {
/// More characters can be added, possibly expanding the texture.
#[derive(Clone)]
pub struct TextureAtlas {
image: AlphaImage,
image: FontImage,
/// What part of the image that is dirty
dirty: Rectu,

Expand All @@ -48,7 +48,7 @@ impl TextureAtlas {
pub fn new(size: [usize; 2]) -> Self {
assert!(size[0] >= 1024, "Tiny texture atlas");
Self {
image: AlphaImage::new(size),
image: FontImage::new(size),
dirty: Rectu::EVERYTHING,
cursor: (0, 0),
row_height: 0,
Expand Down Expand Up @@ -91,7 +91,7 @@ impl TextureAtlas {

/// 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 AlphaImage) {
pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut FontImage) {
/// 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.
Expand Down Expand Up @@ -138,13 +138,13 @@ impl TextureAtlas {
}
}

fn resize_to_min_height(image: &mut AlphaImage, required_height: usize) -> bool {
fn resize_to_min_height(image: &mut FontImage, 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);
image.pixels.resize(image.width() * image.height(), 0.0);
true
} else {
false
Expand Down

0 comments on commit 8272b08

Please sign in to comment.