Better rounding of rectangles with thin outlines (#5571)

Better positioning of rectangle outline when the stroke width is less
than one pixel
This commit is contained in:
Emil Ernerfeldt 2025-01-02 23:50:40 +01:00 committed by GitHub
parent 46b58e5bcc
commit 4784136fee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 18 additions and 38 deletions

View File

@ -102,7 +102,7 @@ pub enum SnapshotError {
}
const HOW_TO_UPDATE_SCREENSHOTS: &str =
"Run `UPDATE_SNAPSHOTS=1 cargo test` to update the snapshots.";
"Run `UPDATE_SNAPSHOTS=1 cargo test --all-features` to update the snapshots.";
impl Display for SnapshotError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View File

@ -1291,29 +1291,6 @@ impl Tessellator {
self.clip_rect = clip_rect;
}
#[inline(always)]
pub fn round_to_pixel(&self, point: f32) -> f32 {
(point * self.pixels_per_point).round() / self.pixels_per_point
}
#[inline(always)]
pub fn round_to_pixel_center(&self, point: f32) -> f32 {
((point * self.pixels_per_point - 0.5).round() + 0.5) / self.pixels_per_point
}
#[inline(always)]
pub fn round_pos_to_pixel(&self, pos: Pos2) -> Pos2 {
pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y))
}
#[inline(always)]
pub fn round_pos_to_pixel_center(&self, pos: Pos2) -> Pos2 {
pos2(
self.round_to_pixel_center(pos.x),
self.round_to_pixel_center(pos.y),
)
}
/// Tessellate a clipped shape into a list of primitives.
pub fn tessellate_clipped_shape(
&mut self,
@ -1716,8 +1693,16 @@ impl Tessellator {
// Since the stroke extends outside of the rectangle,
// we can round the rectangle sides to the physical pixel edges,
// and the filled rect will appear crisp, as will the inside of the stroke.
let Stroke { .. } = stroke; // Make sure we remember to update this if we change `stroke` to `PathStroke`
rect = rect.round_to_pixels(self.pixels_per_point);
let Stroke { width, .. } = stroke; // Make sure we remember to update this if we change `stroke` to `PathStroke`
if width <= self.feathering && !stroke.is_empty() {
// If the stroke is thin, make sure its center is in the center of the pixel:
rect = rect
.expand(width / 2.0)
.round_to_pixel_center(self.pixels_per_point)
.shrink(width / 2.0);
} else {
rect = rect.round_to_pixels(self.pixels_per_point);
}
}
// It is common to (sometimes accidentally) create an infinitely sized rectangle.
@ -1727,7 +1712,7 @@ impl Tessellator {
let old_feathering = self.feathering;
if old_feathering < blur_width {
if self.feathering < blur_width {
// We accomplish the blur by using a larger-than-normal feathering.
// Feathering is usually used to make the edges of a shape softer for anti-aliasing.
@ -1836,10 +1821,7 @@ impl Tessellator {
// The contents of the galley are already snapped to pixel coordinates,
// but we need to make sure the galley ends up on the start of a physical pixel:
let galley_pos = if self.options.round_text_to_pixels {
pos2(
self.round_to_pixel(galley_pos.x),
self.round_to_pixel(galley_pos.y),
)
galley_pos.round_to_pixels(self.pixels_per_point)
} else {
*galley_pos
};
@ -1917,13 +1899,11 @@ impl Tessellator {
);
if *underline != Stroke::NONE {
self.scratchpad_path.clear();
self.scratchpad_path.add_line_segment([
self.round_pos_to_pixel_center(row_rect.left_bottom()),
self.round_pos_to_pixel_center(row_rect.right_bottom()),
]);
self.scratchpad_path
.stroke_open(0.0, &PathStroke::from(*underline), out);
self.tessellate_line_segment(
[row_rect.left_bottom(), row_rect.right_bottom()],
*underline,
out,
);
}
}
}