Skip to content

Commit d20f93e

Browse files
authored
Make all lines and rectangles crisp (emilk#5518)
* Merge this first: emilk#5517 This aligns all rectangles and (horizontal or vertical) line segments to the physical pixel grid in the `epaint::Tessellator`, making these shapes appear crisp everywhere. * Closes emilk#5164 * Closes emilk#3667 This undoes a lot of the explicit, egui-side aligning added in: * emilk#4943 The new approach has several benefits over the old one: * It is done automatically by epaint, so it is applied to everything (no longer opt-in) * It is applied after any layer transforms (so it always works) * It makes line segments crisper on high-DPI screens * All filled rectangles now has sides that end on pixel boundaries
1 parent dfcc679 commit d20f93e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+314
-251
lines changed

crates/egui/src/containers/panel.rs

+18-12
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ impl Side {
7878
Self::Right => rect.right(),
7979
}
8080
}
81+
82+
fn sign(self) -> f32 {
83+
match self {
84+
Self::Left => -1.0,
85+
Self::Right => 1.0,
86+
}
87+
}
8188
}
8289

8390
/// A panel that covers the entire left or right side of a [`Ui`] or screen.
@@ -349,12 +356,8 @@ impl SidePanel {
349356
// TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done
350357
let resize_x = side.opposite().side_x(rect);
351358

352-
// This makes it pixel-perfect for odd-sized strokes (width=1.0, width=3.0, etc)
353-
let resize_x = ui.painter().round_to_pixel_center(resize_x);
354-
355-
// We want the line exactly on the last pixel but rust rounds away from zero so we bring it back a bit for
356-
// left-side panels
357-
let resize_x = resize_x - if side == Side::Left { 1.0 } else { 0.0 };
359+
// Make sure the line is on the inside of the panel:
360+
let resize_x = resize_x + 0.5 * side.sign() * stroke.width;
358361
ui.painter().vline(resize_x, panel_rect.y_range(), stroke);
359362
}
360363

@@ -562,6 +565,13 @@ impl TopBottomSide {
562565
Self::Bottom => rect.bottom(),
563566
}
564567
}
568+
569+
fn sign(self) -> f32 {
570+
match self {
571+
Self::Top => -1.0,
572+
Self::Bottom => 1.0,
573+
}
574+
}
565575
}
566576

567577
/// A panel that covers the entire top or bottom of a [`Ui`] or screen.
@@ -843,12 +853,8 @@ impl TopBottomPanel {
843853
// TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done
844854
let resize_y = side.opposite().side_y(rect);
845855

846-
// This makes it pixel-perfect for odd-sized strokes (width=1.0, width=3.0, etc)
847-
let resize_y = ui.painter().round_to_pixel_center(resize_y);
848-
849-
// We want the line exactly on the last pixel but rust rounds away from zero so we bring it back a bit for
850-
// top-side panels
851-
let resize_y = resize_y - if side == TopBottomSide::Top { 1.0 } else { 0.0 };
856+
// Make sure the line is on the inside of the panel:
857+
let resize_y = resize_y + 0.5 * side.sign() * stroke.width;
852858
ui.painter().hline(panel_rect.x_range(), resize_y, stroke);
853859
}
854860

crates/egui/src/containers/resize.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,9 @@ pub fn paint_resize_corner_with_style(
400400
corner: Align2,
401401
) {
402402
let painter = ui.painter();
403-
let cp = painter.round_pos_to_pixels(corner.pos_in_rect(rect));
403+
let cp = corner
404+
.pos_in_rect(rect)
405+
.round_to_pixels(ui.pixels_per_point());
404406
let mut w = 2.0;
405407
let stroke = Stroke {
406408
width: 1.0, // Set width to 1.0 to prevent overlapping

crates/egui/src/containers/window.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ impl<'open> Window<'open> {
596596
},
597597
);
598598

599-
title_rect = area_content_ui.painter().round_rect_to_pixels(title_rect);
599+
title_rect = title_rect.round_to_pixels(area_content_ui.pixels_per_point());
600600

601601
if on_top && area_content_ui.visuals().window_highlight_topmost {
602602
let mut round = window_frame.rounding;

crates/egui/src/context.rs

-45
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use emath::GuiRounding as _;
77
use epaint::{
88
emath::{self, TSTransform},
99
mutex::RwLock,
10-
pos2,
1110
stats::PaintStats,
1211
tessellator,
1312
text::{FontInsert, FontPriority, Fonts},
@@ -2004,50 +2003,6 @@ impl Context {
20042003
});
20052004
}
20062005

2007-
/// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels).
2008-
#[inline]
2009-
pub(crate) fn round_to_pixel_center(&self, point: f32) -> f32 {
2010-
let pixels_per_point = self.pixels_per_point();
2011-
((point * pixels_per_point - 0.5).round() + 0.5) / pixels_per_point
2012-
}
2013-
2014-
/// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels).
2015-
#[inline]
2016-
pub(crate) fn round_pos_to_pixel_center(&self, point: Pos2) -> Pos2 {
2017-
pos2(
2018-
self.round_to_pixel_center(point.x),
2019-
self.round_to_pixel_center(point.y),
2020-
)
2021-
}
2022-
2023-
/// Useful for pixel-perfect rendering of filled shapes
2024-
#[inline]
2025-
pub(crate) fn round_to_pixel(&self, point: f32) -> f32 {
2026-
let pixels_per_point = self.pixels_per_point();
2027-
(point * pixels_per_point).round() / pixels_per_point
2028-
}
2029-
2030-
/// Useful for pixel-perfect rendering of filled shapes
2031-
#[inline]
2032-
pub(crate) fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
2033-
pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y))
2034-
}
2035-
2036-
/// Useful for pixel-perfect rendering of filled shapes
2037-
#[inline]
2038-
pub(crate) fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
2039-
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
2040-
}
2041-
2042-
/// Useful for pixel-perfect rendering of filled shapes
2043-
#[inline]
2044-
pub(crate) fn round_rect_to_pixels(&self, rect: Rect) -> Rect {
2045-
Rect {
2046-
min: self.round_pos_to_pixels(rect.min),
2047-
max: self.round_pos_to_pixels(rect.max),
2048-
}
2049-
}
2050-
20512006
/// Allocate a texture.
20522007
///
20532008
/// This is for advanced users.

crates/egui/src/introspection.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ impl Widget for &mut epaint::TessellationOptions {
144144
coarse_tessellation_culling,
145145
prerasterized_discs,
146146
round_text_to_pixels,
147+
round_line_segments_to_pixels,
148+
round_rects_to_pixels,
147149
debug_paint_clip_rects,
148150
debug_paint_text_rects,
149151
debug_ignore_clip_rects,
@@ -179,13 +181,22 @@ impl Widget for &mut epaint::TessellationOptions {
179181

180182
ui.checkbox(validate_meshes, "Validate meshes").on_hover_text("Check that incoming meshes are valid, i.e. that all indices are in range, etc.");
181183

184+
ui.collapsing("Align to pixel grid", |ui| {
185+
ui.checkbox(round_text_to_pixels, "Text")
186+
.on_hover_text("Most text already is, so don't expect to see a large change.");
187+
188+
ui.checkbox(round_line_segments_to_pixels, "Line segments")
189+
.on_hover_text("Makes line segments appear crisp on any display.");
190+
191+
ui.checkbox(round_rects_to_pixels, "Rectangles")
192+
.on_hover_text("Makes line segments appear crisp on any display.");
193+
});
194+
182195
ui.collapsing("Debug", |ui| {
183196
ui.checkbox(
184197
coarse_tessellation_culling,
185198
"Do coarse culling in the tessellator",
186199
);
187-
ui.checkbox(round_text_to_pixels, "Align text positions to pixel grid")
188-
.on_hover_text("Most text already is, so don't expect to see a large change.");
189200

190201
ui.checkbox(debug_ignore_clip_rects, "Ignore clip rectangles");
191202
ui.checkbox(debug_paint_clip_rects, "Paint clip rectangles");

crates/egui/src/painter.rs

+47-36
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
use std::sync::Arc;
22

3+
use emath::GuiRounding as _;
4+
use epaint::{
5+
text::{Fonts, Galley, LayoutJob},
6+
CircleShape, ClippedShape, PathStroke, RectShape, Rounding, Shape, Stroke,
7+
};
8+
39
use crate::{
410
emath::{Align2, Pos2, Rangef, Rect, Vec2},
511
layers::{LayerId, PaintList, ShapeIdx},
612
Color32, Context, FontId,
713
};
8-
use epaint::{
9-
text::{Fonts, Galley, LayoutJob},
10-
CircleShape, ClippedShape, PathStroke, RectShape, Rounding, Shape, Stroke,
11-
};
1214

1315
/// Helper to paint shapes and text to a specific region on a specific layer.
1416
///
1517
/// All coordinates are screen coordinates in the unit points (one point can consist of many physical pixels).
18+
///
19+
/// A [`Painter`] never outlive a single frame/pass.
1620
#[derive(Clone)]
1721
pub struct Painter {
1822
/// Source of fonts and destination of shapes
1923
ctx: Context,
2024

25+
/// For quick access, without having to go via [`Context`].
26+
pixels_per_point: f32,
27+
2128
/// Where we paint
2229
layer_id: LayerId,
2330

@@ -38,8 +45,10 @@ pub struct Painter {
3845
impl Painter {
3946
/// Create a painter to a specific layer within a certain clip rectangle.
4047
pub fn new(ctx: Context, layer_id: LayerId, clip_rect: Rect) -> Self {
48+
let pixels_per_point = ctx.pixels_per_point();
4149
Self {
4250
ctx,
51+
pixels_per_point,
4352
layer_id,
4453
clip_rect,
4554
fade_to_color: None,
@@ -49,28 +58,20 @@ impl Painter {
4958

5059
/// Redirect where you are painting.
5160
#[must_use]
52-
pub fn with_layer_id(self, layer_id: LayerId) -> Self {
53-
Self {
54-
ctx: self.ctx,
55-
layer_id,
56-
clip_rect: self.clip_rect,
57-
fade_to_color: None,
58-
opacity_factor: 1.0,
59-
}
61+
#[inline]
62+
pub fn with_layer_id(mut self, layer_id: LayerId) -> Self {
63+
self.layer_id = layer_id;
64+
self
6065
}
6166

6267
/// Create a painter for a sub-region of this [`Painter`].
6368
///
6469
/// The clip-rect of the returned [`Painter`] will be the intersection
6570
/// of the given rectangle and the `clip_rect()` of the parent [`Painter`].
6671
pub fn with_clip_rect(&self, rect: Rect) -> Self {
67-
Self {
68-
ctx: self.ctx.clone(),
69-
layer_id: self.layer_id,
70-
clip_rect: rect.intersect(self.clip_rect),
71-
fade_to_color: self.fade_to_color,
72-
opacity_factor: self.opacity_factor,
73-
}
72+
let mut new_self = self.clone();
73+
new_self.clip_rect = rect.intersect(self.clip_rect);
74+
new_self
7475
}
7576

7677
/// Redirect where you are painting.
@@ -82,7 +83,7 @@ impl Painter {
8283
}
8384

8485
/// If set, colors will be modified to look like this
85-
pub(crate) fn set_fade_to_color(&mut self, fade_to_color: Option<Color32>) {
86+
pub fn set_fade_to_color(&mut self, fade_to_color: Option<Color32>) {
8687
self.fade_to_color = fade_to_color;
8788
}
8889

@@ -118,24 +119,27 @@ impl Painter {
118119
/// If `false`, nothing you paint will show up.
119120
///
120121
/// Also checks [`Context::will_discard`].
121-
pub(crate) fn is_visible(&self) -> bool {
122+
pub fn is_visible(&self) -> bool {
122123
self.fade_to_color != Some(Color32::TRANSPARENT) && !self.ctx.will_discard()
123124
}
124125

125126
/// If `false`, nothing added to the painter will be visible
126-
pub(crate) fn set_invisible(&mut self) {
127+
pub fn set_invisible(&mut self) {
127128
self.fade_to_color = Some(Color32::TRANSPARENT);
128129
}
129-
}
130130

131-
/// ## Accessors etc
132-
impl Painter {
133131
/// Get a reference to the parent [`Context`].
134132
#[inline]
135133
pub fn ctx(&self) -> &Context {
136134
&self.ctx
137135
}
138136

137+
/// Number of physical pixels for each logical UI point.
138+
#[inline]
139+
pub fn pixels_per_point(&self) -> f32 {
140+
self.pixels_per_point
141+
}
142+
139143
/// Read-only access to the shared [`Fonts`].
140144
///
141145
/// See [`Context`] documentation for how locks work.
@@ -180,37 +184,42 @@ impl Painter {
180184
/// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels).
181185
#[inline]
182186
pub fn round_to_pixel_center(&self, point: f32) -> f32 {
183-
self.ctx().round_to_pixel_center(point)
187+
point.round_to_pixel_center(self.pixels_per_point())
184188
}
185189

186190
/// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels).
191+
#[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"]
187192
#[inline]
188193
pub fn round_pos_to_pixel_center(&self, pos: Pos2) -> Pos2 {
189-
self.ctx().round_pos_to_pixel_center(pos)
194+
pos.round_to_pixel_center(self.pixels_per_point())
190195
}
191196

192197
/// Useful for pixel-perfect rendering of filled shapes.
198+
#[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"]
193199
#[inline]
194200
pub fn round_to_pixel(&self, point: f32) -> f32 {
195-
self.ctx().round_to_pixel(point)
201+
point.round_to_pixels(self.pixels_per_point())
196202
}
197203

198204
/// Useful for pixel-perfect rendering.
205+
#[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"]
199206
#[inline]
200207
pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
201-
self.ctx().round_vec_to_pixels(vec)
208+
vec.round_to_pixels(self.pixels_per_point())
202209
}
203210

204211
/// Useful for pixel-perfect rendering.
212+
#[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"]
205213
#[inline]
206214
pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
207-
self.ctx().round_pos_to_pixels(pos)
215+
pos.round_to_pixels(self.pixels_per_point())
208216
}
209217

210218
/// Useful for pixel-perfect rendering.
219+
#[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"]
211220
#[inline]
212221
pub fn round_rect_to_pixels(&self, rect: Rect) -> Rect {
213-
self.ctx().round_rect_to_pixels(rect)
222+
rect.round_to_pixels(self.pixels_per_point())
214223
}
215224
}
216225

@@ -337,7 +346,7 @@ impl Painter {
337346
/// # Paint different primitives
338347
impl Painter {
339348
/// Paints a line from the first point to the second.
340-
pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<PathStroke>) -> ShapeIdx {
349+
pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<Stroke>) -> ShapeIdx {
341350
self.add(Shape::LineSegment {
342351
points,
343352
stroke: stroke.into(),
@@ -351,13 +360,13 @@ impl Painter {
351360
}
352361

353362
/// Paints a horizontal line.
354-
pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<PathStroke>) -> ShapeIdx {
355-
self.add(Shape::hline(x, y, stroke.into()))
363+
pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> ShapeIdx {
364+
self.add(Shape::hline(x, y, stroke))
356365
}
357366

358367
/// Paints a vertical line.
359-
pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<PathStroke>) -> ShapeIdx {
360-
self.add(Shape::vline(x, y, stroke.into()))
368+
pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> ShapeIdx {
369+
self.add(Shape::vline(x, y, stroke))
361370
}
362371

363372
pub fn circle(
@@ -398,6 +407,7 @@ impl Painter {
398407
})
399408
}
400409

410+
/// The stroke extends _outside_ the [`Rect`].
401411
pub fn rect(
402412
&self,
403413
rect: Rect,
@@ -417,6 +427,7 @@ impl Painter {
417427
self.add(RectShape::filled(rect, rounding, fill_color))
418428
}
419429

430+
/// The stroke extends _outside_ the [`Rect`].
420431
pub fn rect_stroke(
421432
&self,
422433
rect: Rect,

0 commit comments

Comments
 (0)