feat: add optional background blur via image crate

Gaussian blur applied at texture load time when `background-blur` is
set in the [appearance] section of moongreet.toml. Blur runs once,
result is shared across monitors.
This commit is contained in:
2026-03-28 14:53:16 +01:00
parent 14d6476e5a
commit 293bba32a6
6 changed files with 197 additions and 10 deletions
+35 -5
View File
@@ -6,6 +6,7 @@ use gdk_pixbuf::Pixbuf;
use glib::clone;
use gtk4::prelude::*;
use gtk4::{self as gtk, gio};
use image::imageops;
use std::cell::RefCell;
use std::collections::HashMap;
use std::os::unix::net::UnixStream;
@@ -96,9 +97,10 @@ fn is_valid_username(name: &str) -> bool {
}
/// Load the background image as a shared texture (decode once, reuse everywhere).
pub fn load_background_texture(bg_path: &Path) -> Option<gdk::Texture> {
/// When `blur_radius` is `Some(sigma)` with sigma > 0, a Gaussian blur is applied.
pub fn load_background_texture(bg_path: &Path, blur_radius: Option<f32>) -> Option<gdk::Texture> {
let path_str = bg_path.to_str()?;
if bg_path.starts_with("/dev/moonarch/moongreet") {
let texture = if bg_path.starts_with("/dev/moonarch/moongreet") {
match gio::resources_lookup_data(path_str, gio::ResourceLookupFlags::NONE) {
Ok(bytes) => match gdk::Texture::from_bytes(&bytes) {
Ok(texture) => Some(texture),
@@ -132,9 +134,37 @@ pub fn load_background_texture(bg_path: &Path) -> Option<gdk::Texture> {
None
}
}
}?;
match blur_radius {
Some(sigma) if sigma > 0.0 => Some(apply_blur(&texture, sigma)),
_ => Some(texture),
}
}
/// Apply Gaussian blur to a texture and return a blurred texture.
fn apply_blur(texture: &gdk::Texture, sigma: f32) -> gdk::Texture {
let width = texture.width() as u32;
let height = texture.height() as u32;
let stride = width as usize * 4;
let mut pixel_data = vec![0u8; stride * height as usize];
texture.download(&mut pixel_data, stride);
let img = image::RgbaImage::from_raw(width, height, pixel_data)
.expect("pixel buffer size matches texture dimensions");
let blurred = imageops::blur(&image::DynamicImage::ImageRgba8(img), sigma);
let bytes = glib::Bytes::from(blurred.as_raw());
let mem_texture = gdk::MemoryTexture::new(
width as i32,
height as i32,
gdk::MemoryFormat::B8g8r8a8Premultiplied,
&bytes,
stride,
);
mem_texture.upcast()
}
/// Create a wallpaper-only window for secondary monitors.
pub fn create_wallpaper_window(
texture: &gdk::Texture,
@@ -1628,7 +1658,7 @@ mod tests {
#[test]
fn load_background_texture_missing_file_returns_none() {
let result = load_background_texture(Path::new("/nonexistent/wallpaper.jpg"));
let result = load_background_texture(Path::new("/nonexistent/wallpaper.jpg"), None);
assert!(result.is_none());
}
@@ -1639,7 +1669,7 @@ mod tests {
// Create a sparse file that exceeds MAX_WALLPAPER_FILE_SIZE
let f = std::fs::File::create(&path).unwrap();
f.set_len(MAX_WALLPAPER_FILE_SIZE + 1).unwrap();
let result = load_background_texture(&path);
let result = load_background_texture(&path, None);
assert!(result.is_none());
}
@@ -1650,7 +1680,7 @@ mod tests {
// 0xFF is not valid UTF-8
let non_utf8 = OsStr::from_bytes(&[0xff, 0xfe, 0xfd]);
let path = Path::new(non_utf8);
let result = load_background_texture(path);
let result = load_background_texture(path, None);
assert!(result.is_none());
}
}