Improve text contrast in bright mode (#1412)
* 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
This commit is contained in:
parent
d3c002a4e5
commit
8272b08742
11 changed files with 57 additions and 55 deletions
|
@ -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)).
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ impl ImageData {
|
|||
pub fn bytes_per_pixel(&self) -> usize {
|
||||
match self {
|
||||
Self::Color(_) => 4,
|
||||
Self::Alpha(_) => 1,
|
||||
Self::Font(_) => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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.
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{AlphaImage, ImageDelta};
|
||||
use crate::{FontImage, ImageDelta};
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
struct Rectu {
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue