1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
|
//! Rounding a rasterized titlebar/border bitmap's own top or bottom
//! corners to a quarter-circle, by fading the pixels outside it to fully
//! transparent - the CPU-bitmap equivalent of `rounded_corners.rs`'s GLES
//! fragment shader, for the software-only udev/Pixman render path. Shared
//! by `titlebar.rs` and `border.rs`, which both cut corners out of their
//! own otherwise-independent buffers and need the two curves to agree
//! exactly where they meet.
/// Clips the top-left and top-right corners of a titlebar buffer to a
/// quarter-circle by making the pixels outside it fully transparent, so
/// whatever's behind (the desktop, on every top-level window) shows through
/// instead of a hard square corner.
///
/// Only the *top* corners: the titlebar's bottom edge meets the window's
/// content, which this compositor has no way to clip (content is rendered
/// entirely by the client) - rounding that seam too would need a
/// compositor-wide clip mask over arbitrary client buffers, a much larger
/// change than this cosmetic pass. Real desktops mostly round this the same
/// way: only the outermost corners of a window, not every internal seam.
///
/// Hard cutoff rather than an anti-aliased edge, matching this codebase's
/// existing pixel-art aesthetic elsewhere (the cursor bitmaps) rather than
/// mixing rendering styles for one corner treatment.
///
/// Zeroes all four BGRA bytes for a cut pixel, not just alpha: this buffer
/// is `Fourcc::Argb8888`, which both Wayland/`wl_shm` and Pixman treat as
/// premultiplied - a genuinely transparent premultiplied pixel is `(0, 0,
/// 0, 0)` in every channel, not just alpha, since the stored colour already
/// carries the alpha multiplied in. Leaving the opaque titlebar-background
/// RGB behind while zeroing only alpha produced a byte pattern Pixman's own
/// `OVER` compositing (`result = src + dst * (1 - src_alpha)`) does not
/// actually treat as "nothing here": with `src_alpha = 0` the formula still
/// adds the stale, un-premultiplied `src` RGB straight through, so the
/// "cut" pixel came out opaque and the corner still read as square --
/// confirmed live, pixel-by-pixel, no visible transparency anywhere in a
/// window's real top corner despite this function running and a nonzero
/// radius. `rounded_corners_pixman.rs`'s `apply_corner_mask` - the
/// equivalent mask for client *content* - already gets this right (scales
/// all four bytes together); this was the one corner-rounding path in the
/// codebase that didn't match it.
/// `center_row` is which row of *this* buffer's own local coordinates the
/// corner circle's centre sits on - not always `radius` itself. A plain
/// titlebar with nothing above it passes `radius as i32` (the ordinary
/// case: the circle's top tip is this buffer's own row 0, same as this
/// function always assumed before `center_row` existed). A border-top
/// strip sitting `thickness` rows *above* the titlebar it visually
/// continues into needs the *same* radius and the *same* circle - not a
/// same-centre-different-radius circle of its own, which is what passing
/// `radius + thickness` here used to do (see the doc comment on
/// `render_border_top`'s call site for why that was tried first). Two
/// concentric circles of different radii do not meet smoothly at any
/// boundary between them: at the exact seam, one buffer's mask is
/// computed against one radius and the other buffer's mask is computed
/// one pixel later against a different radius, producing a visible jump
/// rather than a continuous curve - confirmed live, screenshotted at
/// actual render resolution, not just reasoned about: the titlebar-to-
/// border seam showed a hard stepped notch, not a curve. Since a border
/// strip's own row 0 already sits at the *true* top of the combined
/// shape, it passes `radius as i32` too (unshifted) - it's the titlebar,
/// starting `thickness` rows *into* the circle instead of at its top,
/// that needs to shift, by passing `radius as i32 - border_width as
/// i32` (see `render_titlebar`'s call site).
///
/// `center_col` is the exact same idea, horizontally: which *column* of
/// this buffer's own local coordinates the left corner's circle centre
/// sits on (the right corner mirrors it, `width - r` outward from the
/// right edge by the same amount `center_col` is inward from the left).
/// A border strip's own column 0 is the *true* left edge, so it passes
/// `radius as i32`, same as its unshifted `center_row`. A titlebar's own
/// column 0 sits `border_width` columns *inside* that same true edge --
/// its buffer is only as wide as the content it sits above, not the
/// wider border strip around it - so it needs the identical `radius as
/// i32 - border_width as i32` shift horizontally too, or its own circle
/// centre ends up `border_width` columns to the right of the border
/// strip's, two different circles again despite `center_row` already
/// lining up the vertical one. Confirmed live at a real corner, zoomed:
/// the border's own curve covered most of the shared corner correctly,
/// but a `border_width`-wide sliver of the titlebar's own (wrongly
/// centred) curve poked through right where the two should have met
/// exactly, reading as a small square notch bitten out of an otherwise
/// smooth arc - reported as "squares on the inside corners of each
/// vertex/border corner." Every existing caller before this parameter
/// existed passed the unshifted, no-op case (`radius as i32`, same as
/// `center_row`'s own default), so this is additive, not a behaviour
/// change for border's own corner.
///
/// `inner`, when `Some`, also carves this corner into a proper ring - see
/// [`carve_inner_corner_pixel`]'s own doc comment for why that's needed at
/// all, and [`InnerRing`]'s own doc comment for why it needs a centre
/// that's independent of `center_row`/`center_col`, not just a smaller
/// radius at the same one. Every caller that isn't `render_border_top`/
/// `render_border_bottom` (a titlebar's own corner, the lock-screen box)
/// passes `None` and keeps today's solid-disk-past-the-nominal-edge
/// behaviour, which is correct for a single flat-coloured panel with
/// nothing of a *different* colour underneath it needing to show through.
pub(crate) fn round_top_corners(buf: &mut [u8], width: usize, height: usize, radius: u32, center_row: i32, center_col: i32, inner: Option<InnerRing>) {
let r = (radius as usize).min(width / 2);
if r == 0 {
return;
}
let rf = r as f32;
let cy = center_row as f32;
// How far a given centre column sits from the unshifted default
// (`radius`) - the right corner's own centre needs shifting by the
// same amount, in the opposite direction (further *into* the buffer
// from the right edge, mirroring how the left corner shifts further
// *into* it from the left), since the buffer's own right edge is the
// mirror image of its left one, not an independent second true edge.
// A closure, not a one-off `let`, since the inner ring's own centre
// (potentially different from `center_col`) needs the identical
// mirroring, not just the outer cut's.
let mirror_col = |col: i32| (width - r) as f32 + (radius as i32 - col) as f32;
// `> 0`, not just `.is_some()`: a ring whose own radius would be zero
// or negative (an unusually thick border relative to its corner
// radius, for the titlebar-aligned case) has no ring to carve at all
// - the whole disk out to `radius` already *is* the intended
// thickness, and a zero/negative inner radius would carve away the
// entire corner instead of nothing.
let inner = inner.filter(|ring| ring.radius > 0);
// Only rows that could plausibly need blending at all: below
// `center_row` (this buffer's slice of the circle, whatever portion
// of it falls within `[0, height)`) is where the actual curve lives;
// rows above `center_row - r` or at/below `center_row` are either
// already past the transparent tip or already fully inside the
// shape, and calling `blend_corner_pixel` there would either be a
// wasted no-op (large `dist`, `mask >= 1`) or - critically, for a
// *tall* buffer whose straight edge extends far past the corner --
// wrongly compute a huge `dist` from being far below the centre and
// clip an ordinary straight-edge pixel to transparent. The original
// unshifted version of this function avoided that the same way, by
// simply never iterating past row `r`; this is that same bound,
// generalised to an arbitrary `center_row`.
let y_lo = (center_row - r as i32).max(0) as usize;
let y_hi = (center_row.max(0) as usize).min(height);
for y in y_lo..y_hi {
for x in 0..r {
blend_corner_pixel(buf, width, x, y, center_col as f32, cy, rf);
if let Some(ring) = &inner {
carve_inner_corner_pixel(buf, width, x, y, ring.center_col as f32, ring.center_row as f32, ring.radius as f32);
}
}
for x in (width - r)..width {
// `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.
let cx = mirror_col(center_col);
blend_corner_pixel(buf, width, x, y, cx, cy, rf);
if let Some(ring) = &inner {
carve_inner_corner_pixel(buf, width, x, y, mirror_col(ring.center_col), ring.center_row as f32, ring.radius as f32);
}
}
}
}
/// The inner ring [`round_top_corners`]/[`round_bottom_corners`] carve into
/// their own outer disk - see [`carve_inner_corner_pixel`]'s own doc
/// comment for why a ring, not a filled disk, is what a border strip's
/// corner actually needs to look like.
///
/// A *separate* centre from the outer cut's `center_row`/`center_col`, not
/// just a smaller radius at the same one - because what the inner cut
/// needs to reveal differs by what's actually drawn underneath this
/// specific strip's own "extra" rows:
///
/// - A **titlebar** band underneath (`render_border_top`, `decorated`):
/// the titlebar draws its *own* independently rounded corner, sharing
/// the exact same circle as the border's own outer cut (`center_row`/
/// `center_col`'s own doc comment on the `radius - border_width` shift
/// that lines the two buffers' circles up). The inner ring here should
/// match *that* circle exactly - same centre as the outer cut, radius
/// `radius - border_width`.
/// - **Client content** underneath (`render_border_top` when undecorated,
/// `render_border_bottom` always - there is no "bottom titlebar" in
/// this compositor's design): content's own rounded-corner mask
/// (`rounded_corners_pixman.rs`'s `apply_corner_mask`) is centred
/// `radius` from *its own* buffer's edges, and that buffer's own origin
/// sits `border_width` rows/columns inside this strip's - a genuinely
/// *different* circle from the border's own outer one, offset by
/// `(border_width, border_width)` diagonally, not just a smaller
/// concentric one. Reusing the titlebar-style "same centre, smaller
/// radius" ring here left a real, visible gap along part of the seam
/// and a thin sliver of double coverage along the rest - both curves
/// are radius-`radius` circles, but centred `border_width` apart, so no
/// single concentric ring traces both correctly. Confirmed live at
/// extreme zoom against a solid-colour wallpaper (easier to spot a
/// sub-pixel-scale gap against than the usual desktop image): a very
/// thin wedge of wallpaper visible right at the point the two circles'
/// radii diverge most. Matching content's own circle exactly --
/// `radius` unchanged, centre shifted `border_width` further into the
/// buffer on both axes - traces the *same* curve content's own mask
/// already cuts to, so the two meet with no gap and no overlap.
pub(crate) struct InnerRing {
pub(crate) center_row: i32,
pub(crate) center_col: i32,
pub(crate) radius: u32,
}
/// Multiplies the pixel at `(x, y)` by a smoothed 0..1 mask based on its
/// distance from `(cx, cy)` versus `radius` - `1` (unchanged) well inside
/// the circle, `0` (fully transparent) well outside it, blended over a ~2px
/// band at the boundary. Same anti-aliasing technique `rounded_corners.rs`'s
/// GLES fragment shader already uses for content rounding
/// (`smoothstep(radius - 1.0, radius + 1.0, dist)`), applied here to a CPU
/// bitmap pixel by pixel instead of a per-fragment shader.
///
/// The previous version of both callers did a hard binary cut instead --
/// fully opaque or fully transparent, nothing between - which read as a
/// jagged single-pixel "break" in the border line rather than a curve,
/// especially in a border strip only a couple of rows tall (the common
/// case: `border_width` is usually 2-3px) where there's no room for the
/// eye to average a staircase into something that looks round. Reported
/// live as "line breaks" right where a window's border met its curved
/// corner.
///
/// `buf` is premultiplied BGRA (`color::rgb_to_bgra`'s own convention), so
/// scaling all four bytes by the same factor is the correct way to reduce a
/// pixel's effective alpha - same reasoning
/// `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 keep = corner_keep_mask(x, y, cx, cy, radius);
scale_pixel(buf, width, x, y, keep);
}
/// `1.0` (fully kept) within `radius - 1` of `(cx, cy)`, `0.0` (fully cut)
/// beyond `radius + 1`, smoothstepped between - the shared falloff both
/// [`blend_corner_pixel`] (an *outer* cut: keep near the centre, cut far
/// from it) and [`carve_inner_corner_pixel`] (an *inner* cut: the same
/// falloff, inverted, so it cuts *near* the centre instead) are built from.
/// 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, once - 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.
fn corner_keep_mask(x: usize, y: usize, cx: f32, cy: f32, radius: f32) -> f32 {
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);
1.0 - (t * t * (3.0 - 2.0 * t))
}
/// Multiplies every BGRA byte of the pixel at `(x, y)` by `mask` - `buf` is
/// premultiplied BGRA (`color::rgb_to_bgra`'s own convention), so scaling
/// all four bytes by the same factor is the correct way to reduce a
/// pixel's effective alpha (zeroing all four, not just alpha, for a fully
/// cut pixel - a genuinely transparent premultiplied pixel is `(0, 0, 0,
/// 0)` in every channel, not just alpha, since the stored colour already
/// carries the alpha multiplied in; leaving stale opaque RGB behind while
/// zeroing only alpha produced a byte pattern Pixman's own `OVER`
/// compositing does not actually treat as "nothing here" - confirmed
/// live, pixel-by-pixel, no visible transparency despite alpha already
/// being zero).
fn scale_pixel(buf: &mut [u8], width: usize, x: usize, y: usize, mask: f32) {
if mask >= 1.0 {
return;
}
let idx = (y * width + x) * 4;
if mask <= 0.0 {
buf[idx..idx + 4].fill(0);
return;
}
for c in &mut buf[idx..idx + 4] {
*c = (*c as f32 * mask).round() as u8;
}
}
/// The border strip's own missing half of a proper rounded-corner *ring*:
/// [`blend_corner_pixel`] already cuts everything *outside* `radius` of the
/// shared corner centre (the true rounded silhouette), but nothing used to
/// cut anything *inside* it - so the strip's own "extra" rows (past its
/// nominal `border_width`, present whenever `corner_radius > border_width`
/// - see `render_border_top`'s own doc comment) stayed a solid *filled*
/// quarter-disk out to the centre column/row, then hit `clip_middle_
/// beyond_thickness`'s hard, unblended rectangular cut at exactly column/
/// row `radius` - which is essentially the disk's own *most opaque*
/// point (dead centre, mask ~1.0), not somewhere the curve had already
/// faded out. The result: a solid wedge of border colour with two straight
/// inner edges meeting the titlebar/content at a right angle, not a
/// uniform-width curved ring - confirmed live, zoomed: a clean rectangular
/// step, not a blend, reported as "squares on the inside corners."
///
/// The fix: cut *this* pixel wherever it falls within `border_width` of
/// the *same* shared centre `blend_corner_pixel` already cut around --
/// i.e. within `radius - border_width` of it, same smoothstep falloff,
/// inverted. Combined with the existing outer cut, the strip's corner
/// becomes a genuine ring of ~`border_width` visible thickness tapering
/// smoothly to nothing by the point `clip_middle_beyond_thickness`'s own
/// (already-transparent-by-then) hard cut takes over, instead of jumping
/// from opaque to transparent in one pixel. Only ever called with `radius
/// > border_width` (`round_top_corners`/`round_bottom_corners` skip it
/// otherwise, since there is no ring to speak of - see their own call
/// sites) - `inner_radius` would otherwise be zero or negative, cutting
/// the entire disk including the visible outer sliver that's supposed to
/// remain `border_width` px thick.
fn carve_inner_corner_pixel(buf: &mut [u8], width: usize, x: usize, y: usize, cx: f32, cy: f32, inner_radius: f32) {
let keep = corner_keep_mask(x, y, cx, cy, inner_radius);
scale_pixel(buf, width, x, y, 1.0 - keep);
}
/// [`round_top_corners`]'s mirror for the bottom two corners - same
/// construction, corner centres `r` *up* from the bottom instead of down
/// from the top. Same anti-aliasing, same reason - see
/// [`blend_corner_pixel`]'s own doc comment. `inner` is the same idea as
/// [`round_top_corners`]' own parameter of the same name - see
/// [`InnerRing`]'s own doc comment. Unlike `round_top_corners`, this
/// function's own outer cut has no `center_row`/`center_col` of its own to
/// default the ring's centre from (nothing has ever needed to shift the
/// *outer* cut here - only `render_border_top`'s own top strip sits above
/// a titlebar that needs one), so `InnerRing`'s fields are the ring's own
/// absolute centre, not an offset from anything.
pub(crate) fn round_bottom_corners(buf: &mut [u8], width: usize, height: usize, radius: u32, inner: Option<InnerRing>) {
let r = (radius as usize).min(width / 2);
if r == 0 {
return;
}
// See `round_top_corners`' matching comment: `r` (the real corner
// radius) must stay unclamped by `height`, or a strip thinner than the
// radius cuts its own separate, too-tight arc instead of continuing the
// titlebar's. `rows` is just how many of that circle's rows this
// buffer actually has room for.
let rows = r.min(height);
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` going negative when `r >
// height` (the strip-thinner-than-radius case above) is just a
// 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;
// See `round_top_corners`'s own `mirror_col` for why this needs to be a
// closure, not a one-off `let`.
let mirror_col = |col: i32| (width - r) as f32 + (radius as i32 - col) as f32;
// See `round_top_corners`'s matching line for why this is `.filter(|ring|
// ring.radius > 0)`, not just `.is_some()`.
let inner = inner.filter(|ring| ring.radius > 0);
for y in (height - rows)..height {
for x in 0..r {
blend_corner_pixel(buf, width, x, y, rf, cy, rf);
if let Some(ring) = &inner {
carve_inner_corner_pixel(buf, width, x, y, ring.center_col as f32, ring.center_row as f32, ring.radius as f32);
}
}
for x in (width - r)..width {
// See `round_top_corners`'s matching comment for why this is
// `width - r`, not `width - r - 1`.
let cx = (width - r) as f32;
blend_corner_pixel(buf, width, x, y, cx, cy, rf);
if let Some(ring) = &inner {
carve_inner_corner_pixel(buf, width, x, y, mirror_col(ring.center_col), ring.center_row as f32, ring.radius as f32);
}
}
}
}
|