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
4 changes: 2 additions & 2 deletions crates/egui_demo_app/tests/snapshots/easymarkeditor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions crates/epaint/src/stroke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use std::{fmt::Debug, sync::Arc};

use emath::GuiRounding as _;

use super::{emath, Color32, ColorMode, Pos2, Rect};

/// Describes the width and color of a line.
Expand Down Expand Up @@ -34,6 +36,46 @@ impl Stroke {
pub fn is_empty(&self) -> bool {
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
Expand Down Expand Up @@ -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));
}
56 changes: 3 additions & 53 deletions crates/epaint/src/tessellator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1656,7 +1656,7 @@ impl Tessellator {
if a.x == b.x {
// Vertical line
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;
b.x = x;

Expand All @@ -1677,7 +1677,7 @@ impl Tessellator {
if a.y == b.y {
// Horizontal line
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;
b.y = y;

Expand Down Expand Up @@ -1778,7 +1778,6 @@ impl Tessellator {

let mut corner_radius = CornerRadiusF32::from(corner_radius);
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 {
stroke.color = Color32::TRANSPARENT;
Expand Down Expand Up @@ -1849,17 +1848,7 @@ impl Tessellator {
}
StrokeKind::Middle => {
// On this path we optimize for crisp and symmetric strokes.
// We put odd-width strokes in the center of pixels.
// 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);
}
stroke.round_rect_to_pixel(self.pixels_per_point, &mut rect);
}
StrokeKind::Outside => {
// Put the inside of the stroke on a pixel boundary.
Expand Down Expand Up @@ -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"]
pub fn tessellate_shapes(
pixels_per_point: f32,
Expand Down
3 changes: 2 additions & 1 deletion crates/epaint/src/text/text_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,8 @@ fn add_row_hline(
let mut last_right_x = f32::NAN;

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 {
end_line(line_start.take(), last_right_x);
Expand Down
Loading