diff options
| author | srdusr <[email protected]> | 2025-03-15 23:27:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2025-03-15 23:27:00 +0200 |
| commit | 22775f37f0adfff50f4750b657892fb33e1b2ec4 (patch) | |
| tree | bf6bf4c3eede2c9a7d7a2ade8320544dbb9589bf /crates/wayland | |
| parent | d6da5a32c921110cc59684f684751502da0ff0dd (diff) | |
| download | srdwm-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.
Diffstat (limited to 'crates/wayland')
| -rw-r--r-- | crates/wayland/src/decoration/corners.rs | 38 |
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); } } } |