Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve text contrast in bright mode #1412

Merged
merged 7 commits into from
Mar 23, 2022
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
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