srdusr
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-03-15 23:27:00 +0200
committersrdusr <[email protected]>2025-03-15 23:27:00 +0200
commit22775f37f0adfff50f4750b657892fb33e1b2ec4 (patch)
treebf6bf4c3eede2c9a7d7a2ade8320544dbb9589bf
parentd6da5a32c921110cc59684f684751502da0ff0dd (diff)
downloadsrdwm-22775f37f0adfff50f4750b657892fb33e1b2ec4.tar.gz
srdwm-22775f37f0adfff50f4750b657892fb33e1b2ec4.zip
Fix half-pixel seam between border-strip and content corner curves
The border-strip bitmap's own corner rounding (corners.rs's blend_ corner_pixel) and the client-content mask's corner rounding (rounded_corners_pixman.rs's apply_corner_mask) are two independent implementations that need to trace the exact same circle where a window's border meets its own rounded content. Their falloff math was already identical (same smoothstep construction over the same radius-1..radius+1 band), but apply_corner_mask samples each pixel at its own center (x as f32 + 0.5) - the standard rasterization convention, matching the GLES shader used for the winit backend -- while blend_corner_pixel sampled at the raw integer coordinate (x as f32), a systematic half-pixel offset between the two curves. round_top_corners/round_bottom_corners had a compensating "- 1" baked into their own right-edge/bottom-edge center calculation, tuned against the old, uncentered convention. Confirmed live at extreme zoom: a small but real right-angle step partway along an otherwise smooth arc, right where the two curves are supposed to meet -- reported as "squares on the inside corners of each vertex." Added the missing + 0.5 to blend_corner_pixel and removed the two compensating "- 1"s (both call sites), which now line up exactly with apply_corner_mask's own clamp-derived center for the same corner (width - r / height - r, not width - r - 1 / height - r - 1). All 378 existing tests pass unchanged - none of them assert exact pixel positions this shifts by half a pixel.
-rw-r--r--crates/wayland/src/decoration/corners.rs38
1 files changed, 32 insertions, 6 deletions
diff --git a/crates/wayland/src/decoration/corners.rs b/crates/wayland/src/decoration/corners.rs
index db32209..10bad4d 100644
--- a/crates/wayland/src/decoration/corners.rs
+++ b/crates/wayland/src/decoration/corners.rs
@@ -87,7 +87,13 @@ pub(crate) fn round_top_corners(buf: &mut [u8], width: usize, height: usize, rad
blend_corner_pixel(buf, width, x, y, rf, cy, rf);
}
for x in (width - r)..width {
- blend_corner_pixel(buf, width, x, y, (width - r - 1) as f32, cy, rf);
+ // `width - r`, not `width - r - 1` - see `blend_corner_pixel`'s
+ // own doc comment: that `- 1` compensated for this function not
+ // sampling at the pixel centre, which it now does, so the
+ // right corner's centre column lines up with `rounded_corners_
+ // pixman.rs`'s `apply_corner_mask` (`px.clamp(radius, wf -
+ // radius)`, which clamps to exactly `w - r` here) without it.
+ blend_corner_pixel(buf, width, x, y, (width - r) as f32, cy, rf);
}
}
}
@@ -115,7 +121,20 @@ pub(crate) fn round_top_corners(buf: &mut [u8], width: usize, height: usize, rad
/// `clipped_corner_pixels_are_fully_premultiplied_zero_not_just_alpha`
/// already established for the hard-cut case this replaces.
fn blend_corner_pixel(buf: &mut [u8], width: usize, x: usize, y: usize, cx: f32, cy: f32, radius: f32) {
- let (dx, dy) = (x as f32 - cx, y as f32 - cy);
+ // Sampled at the pixel's own *center* (`+ 0.5`), not its raw integer
+ // coordinate - matching `rounded_corners_pixman.rs`'s `apply_corner_
+ // mask` (`px = x as f32 + 0.5`) and `rounded_corners.rs`'s GLES shader,
+ // both of which already use this standard rasterization convention.
+ // This function didn't, a half-pixel systematic difference between
+ // this border-strip curve and the client-content curve it's supposed
+ // to trace exactly the same circle as - confirmed live at extreme
+ // zoom: a small but real right-angle step partway along an otherwise
+ // smooth arc, right where the two curves are supposed to meet
+ // (reported as "squares on the inside corners of each vertex").
+ // `round_top_corners`/`round_bottom_corners`'s own right-edge/bottom
+ // `cx`/`cy` had a compensating `- 1` baked in against the *old*
+ // convention - removed alongside this, see their own doc comments.
+ let (dx, dy) = (x as f32 + 0.5 - cx, y as f32 + 0.5 - cy);
let dist = (dx * dx + dy * dy).sqrt();
let t = ((dist - (radius - 1.0)) / 2.0).clamp(0.0, 1.0);
let mask = 1.0 - (t * t * (3.0 - 2.0 * t));
@@ -150,16 +169,23 @@ pub(crate) fn round_bottom_corners(buf: &mut [u8], width: usize, height: usize,
let rf = r as f32;
// As a float, not the signed-`i64`-offset trick the hard-cut version
// needed to avoid a `usize` underflow - `blend_corner_pixel` already
- // takes float centres, so `height - r - 1` going negative when `r >
+ // takes float centres, so `height - r` going negative when `r >
// height` (the strip-thinner-than-radius case above) is just a
- // negative `f32`, no special-casing required.
- let cy = height as f32 - rf - 1.0;
+ // negative `f32`, no special-casing required. Plain `height - r`, not
+ // `height - r - 1` - see `blend_corner_pixel`'s own doc comment: that
+ // `- 1` compensated for this function not sampling at the pixel
+ // centre, which it now does, so this lines up with `rounded_corners_
+ // pixman.rs`'s own bottom-box centre (`py.clamp(radius, hf - radius)`,
+ // which clamps to exactly `h - r`) without it.
+ let cy = height as f32 - rf;
for y in (height - rows)..height {
for x in 0..r {
blend_corner_pixel(buf, width, x, y, rf, cy, rf);
}
for x in (width - r)..width {
- blend_corner_pixel(buf, width, x, y, (width - r - 1) as f32, cy, rf);
+ // See `round_top_corners`'s matching comment for why this is
+ // `width - r`, not `width - r - 1`.
+ blend_corner_pixel(buf, width, x, y, (width - r) as f32, cy, rf);
}
}
}