Make text underline and strikethrough pixel perfect crisp (#5857)
Small visual fix: pixel-align any text underline or strikethrough. Before they could be often be blurry.
This commit is contained in:
parent
884be3491d
commit
7ea3f762b8
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:fb4ac08fb40dd1413feee549ba977906160c82d0aba427d6d79d2e56080aa04e
|
oid sha256:3e6a383dca7e91d07df4bf501e2de13d046f04546a08d026efe3f82fc96b6e29
|
||||||
size 178975
|
size 178887
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
use std::{fmt::Debug, sync::Arc};
|
use std::{fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
|
use emath::GuiRounding as _;
|
||||||
|
|
||||||
use super::{emath, Color32, ColorMode, Pos2, Rect};
|
use super::{emath, Color32, ColorMode, Pos2, Rect};
|
||||||
|
|
||||||
/// Describes the width and color of a line.
|
/// Describes the width and color of a line.
|
||||||
|
|
@ -34,6 +36,46 @@ impl Stroke {
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.width <= 0.0 || self.color == Color32::TRANSPARENT
|
self.width <= 0.0 || self.color == Color32::TRANSPARENT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For vertical or horizontal lines:
|
||||||
|
/// round the stroke center to produce a sharp, pixel-aligned line.
|
||||||
|
pub fn round_center_to_pixel(&self, pixels_per_point: f32, coord: &mut f32) {
|
||||||
|
// If the stroke is an odd number of pixels wide,
|
||||||
|
// we want to round the center of it to the center of a pixel.
|
||||||
|
//
|
||||||
|
// If however it is an even number of pixels wide,
|
||||||
|
// we want to round the center to be between two pixels.
|
||||||
|
//
|
||||||
|
// We also want to treat strokes that are _almost_ odd as it it was odd,
|
||||||
|
// to make it symmetric. Same for strokes that are _almost_ even.
|
||||||
|
//
|
||||||
|
// For strokes less than a pixel wide we also round to the center,
|
||||||
|
// because it will rendered as a single row of pixels by the tessellator.
|
||||||
|
|
||||||
|
let pixel_size = 1.0 / pixels_per_point;
|
||||||
|
|
||||||
|
if self.width <= pixel_size || is_nearest_integer_odd(pixels_per_point * self.width) {
|
||||||
|
*coord = coord.round_to_pixel_center(pixels_per_point);
|
||||||
|
} else {
|
||||||
|
*coord = coord.round_to_pixels(pixels_per_point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn round_rect_to_pixel(&self, pixels_per_point: f32, rect: &mut Rect) {
|
||||||
|
// We put odd-width strokes in the center of pixels.
|
||||||
|
// To understand why, see `fn round_center_to_pixel`.
|
||||||
|
|
||||||
|
let pixel_size = 1.0 / pixels_per_point;
|
||||||
|
|
||||||
|
let width = self.width;
|
||||||
|
if width <= 0.0 {
|
||||||
|
*rect = rect.round_to_pixels(pixels_per_point);
|
||||||
|
} else if width <= pixel_size || is_nearest_integer_odd(pixels_per_point * width) {
|
||||||
|
*rect = rect.round_to_pixel_center(pixels_per_point);
|
||||||
|
} else {
|
||||||
|
*rect = rect.round_to_pixels(pixels_per_point);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Color> From<(f32, Color)> for Stroke
|
impl<Color> From<(f32, Color)> for Stroke
|
||||||
|
|
@ -182,3 +224,21 @@ impl From<Stroke> for PathStroke {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the nearest integer is odd.
|
||||||
|
fn is_nearest_integer_odd(x: f32) -> bool {
|
||||||
|
(x * 0.5 + 0.25).fract() > 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_nearest_integer_odd() {
|
||||||
|
assert!(is_nearest_integer_odd(0.6));
|
||||||
|
assert!(is_nearest_integer_odd(1.0));
|
||||||
|
assert!(is_nearest_integer_odd(1.4));
|
||||||
|
assert!(!is_nearest_integer_odd(1.6));
|
||||||
|
assert!(!is_nearest_integer_odd(2.0));
|
||||||
|
assert!(!is_nearest_integer_odd(2.4));
|
||||||
|
assert!(is_nearest_integer_odd(2.6));
|
||||||
|
assert!(is_nearest_integer_odd(3.0));
|
||||||
|
assert!(is_nearest_integer_odd(3.4));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1656,7 +1656,7 @@ impl Tessellator {
|
||||||
if a.x == b.x {
|
if a.x == b.x {
|
||||||
// Vertical line
|
// Vertical line
|
||||||
let mut x = a.x;
|
let mut x = a.x;
|
||||||
round_line_segment(&mut x, &stroke, self.pixels_per_point);
|
stroke.round_center_to_pixel(self.pixels_per_point, &mut x);
|
||||||
a.x = x;
|
a.x = x;
|
||||||
b.x = x;
|
b.x = x;
|
||||||
|
|
||||||
|
|
@ -1677,7 +1677,7 @@ impl Tessellator {
|
||||||
if a.y == b.y {
|
if a.y == b.y {
|
||||||
// Horizontal line
|
// Horizontal line
|
||||||
let mut y = a.y;
|
let mut y = a.y;
|
||||||
round_line_segment(&mut y, &stroke, self.pixels_per_point);
|
stroke.round_center_to_pixel(self.pixels_per_point, &mut y);
|
||||||
a.y = y;
|
a.y = y;
|
||||||
b.y = y;
|
b.y = y;
|
||||||
|
|
||||||
|
|
@ -1778,7 +1778,6 @@ impl Tessellator {
|
||||||
|
|
||||||
let mut corner_radius = CornerRadiusF32::from(corner_radius);
|
let mut corner_radius = CornerRadiusF32::from(corner_radius);
|
||||||
let round_to_pixels = round_to_pixels.unwrap_or(self.options.round_rects_to_pixels);
|
let round_to_pixels = round_to_pixels.unwrap_or(self.options.round_rects_to_pixels);
|
||||||
let pixel_size = 1.0 / self.pixels_per_point;
|
|
||||||
|
|
||||||
if stroke.width == 0.0 {
|
if stroke.width == 0.0 {
|
||||||
stroke.color = Color32::TRANSPARENT;
|
stroke.color = Color32::TRANSPARENT;
|
||||||
|
|
@ -1849,17 +1848,7 @@ impl Tessellator {
|
||||||
}
|
}
|
||||||
StrokeKind::Middle => {
|
StrokeKind::Middle => {
|
||||||
// On this path we optimize for crisp and symmetric strokes.
|
// On this path we optimize for crisp and symmetric strokes.
|
||||||
// We put odd-width strokes in the center of pixels.
|
stroke.round_rect_to_pixel(self.pixels_per_point, &mut rect);
|
||||||
// To understand why, see `fn round_line_segment`.
|
|
||||||
if stroke.width <= 0.0 {
|
|
||||||
rect = rect.round_to_pixels(self.pixels_per_point);
|
|
||||||
} else if stroke.width <= pixel_size
|
|
||||||
|| is_nearest_integer_odd(self.pixels_per_point * stroke.width)
|
|
||||||
{
|
|
||||||
rect = rect.round_to_pixel_center(self.pixels_per_point);
|
|
||||||
} else {
|
|
||||||
rect = rect.round_to_pixels(self.pixels_per_point);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
StrokeKind::Outside => {
|
StrokeKind::Outside => {
|
||||||
// Put the inside of the stroke on a pixel boundary.
|
// Put the inside of the stroke on a pixel boundary.
|
||||||
|
|
@ -2203,45 +2192,6 @@ impl Tessellator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn round_line_segment(coord: &mut f32, stroke: &Stroke, pixels_per_point: f32) {
|
|
||||||
// If the stroke is an odd number of pixels wide,
|
|
||||||
// we want to round the center of it to the center of a pixel.
|
|
||||||
//
|
|
||||||
// If however it is an even number of pixels wide,
|
|
||||||
// we want to round the center to be between two pixels.
|
|
||||||
//
|
|
||||||
// We also want to treat strokes that are _almost_ odd as it it was odd,
|
|
||||||
// to make it symmetric. Same for strokes that are _almost_ even.
|
|
||||||
//
|
|
||||||
// For strokes less than a pixel wide we also round to the center,
|
|
||||||
// because it will rendered as a single row of pixels by the tessellator.
|
|
||||||
|
|
||||||
let pixel_size = 1.0 / pixels_per_point;
|
|
||||||
|
|
||||||
if stroke.width <= pixel_size || is_nearest_integer_odd(pixels_per_point * stroke.width) {
|
|
||||||
*coord = coord.round_to_pixel_center(pixels_per_point);
|
|
||||||
} else {
|
|
||||||
*coord = coord.round_to_pixels(pixels_per_point);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_nearest_integer_odd(width: f32) -> bool {
|
|
||||||
(width * 0.5 + 0.25).fract() > 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_nearest_integer_odd() {
|
|
||||||
assert!(is_nearest_integer_odd(0.6));
|
|
||||||
assert!(is_nearest_integer_odd(1.0));
|
|
||||||
assert!(is_nearest_integer_odd(1.4));
|
|
||||||
assert!(!is_nearest_integer_odd(1.6));
|
|
||||||
assert!(!is_nearest_integer_odd(2.0));
|
|
||||||
assert!(!is_nearest_integer_odd(2.4));
|
|
||||||
assert!(is_nearest_integer_odd(2.6));
|
|
||||||
assert!(is_nearest_integer_odd(3.0));
|
|
||||||
assert!(is_nearest_integer_odd(3.4));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deprecated = "Use `Tessellator::new(…).tessellate_shapes(…)` instead"]
|
#[deprecated = "Use `Tessellator::new(…).tessellate_shapes(…)` instead"]
|
||||||
pub fn tessellate_shapes(
|
pub fn tessellate_shapes(
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
|
|
|
||||||
|
|
@ -866,7 +866,8 @@ fn add_row_hline(
|
||||||
let mut last_right_x = f32::NAN;
|
let mut last_right_x = f32::NAN;
|
||||||
|
|
||||||
for glyph in &row.glyphs {
|
for glyph in &row.glyphs {
|
||||||
let (stroke, y) = stroke_and_y(glyph);
|
let (stroke, mut y) = stroke_and_y(glyph);
|
||||||
|
stroke.round_center_to_pixel(point_scale.pixels_per_point, &mut y);
|
||||||
|
|
||||||
if stroke == Stroke::NONE {
|
if stroke == Stroke::NONE {
|
||||||
end_line(line_start.take(), last_right_x);
|
end_line(line_start.take(), last_right_x);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue