Rust · 3465 bytes Raw Blame History
1 //! Background image loading and processing for the greeter
2 //!
3 //! Loads background images, scales to fit screen, applies blur and brightness adjustments.
4
5 use anyhow::{Context, Result};
6 use image::{imageops, RgbaImage};
7
8 /// Load and process a background image for the greeter
9 pub fn load_blurred_background(
10 path: &str,
11 width: u32,
12 height: u32,
13 blur_radius: f32,
14 brightness: f32,
15 ) -> Result<RgbaImage> {
16 let img = image::open(path)
17 .with_context(|| format!("Failed to open background image: {}", path))?
18 .to_rgba8();
19
20 // Scale to screen size (cover mode - fills screen, may crop)
21 let scaled = scale_to_cover(&img, width, height);
22
23 // Apply gaussian blur
24 let blurred = imageops::blur(&scaled, blur_radius);
25
26 // Adjust brightness (typically darken for better text contrast)
27 let adjusted = adjust_brightness(&blurred, brightness);
28
29 Ok(adjusted)
30 }
31
32 /// Generate a solid color fallback background
33 pub fn solid_background(width: u32, height: u32, r: u8, g: u8, b: u8) -> RgbaImage {
34 RgbaImage::from_fn(width, height, |_, _| image::Rgba([r, g, b, 255]))
35 }
36
37 /// Scale image to cover the target dimensions (may crop)
38 fn scale_to_cover(img: &RgbaImage, target_w: u32, target_h: u32) -> RgbaImage {
39 let (src_w, src_h) = img.dimensions();
40
41 // Calculate scale to cover entire target
42 let scale = (target_w as f32 / src_w as f32).max(target_h as f32 / src_h as f32);
43
44 let new_w = (src_w as f32 * scale).ceil() as u32;
45 let new_h = (src_h as f32 * scale).ceil() as u32;
46
47 let resized = imageops::resize(img, new_w, new_h, imageops::FilterType::Lanczos3);
48
49 // Crop to center
50 let x = (new_w.saturating_sub(target_w)) / 2;
51 let y = (new_h.saturating_sub(target_h)) / 2;
52
53 imageops::crop_imm(&resized, x, y, target_w, target_h).to_image()
54 }
55
56 /// Adjust image brightness by a factor (0.0-1.0 darkens, >1.0 brightens)
57 fn adjust_brightness(img: &RgbaImage, factor: f32) -> RgbaImage {
58 let mut result = img.clone();
59 for pixel in result.pixels_mut() {
60 pixel[0] = (pixel[0] as f32 * factor).min(255.0) as u8;
61 pixel[1] = (pixel[1] as f32 * factor).min(255.0) as u8;
62 pixel[2] = (pixel[2] as f32 * factor).min(255.0) as u8;
63 // Alpha unchanged
64 }
65 result
66 }
67
68 /// Convert RGBA image to BGRA for Cairo/X11 (little-endian ARGB32)
69 pub fn rgba_to_bgra(img: &RgbaImage) -> Vec<u8> {
70 let (width, height) = img.dimensions();
71 let stride = (width * 4) as usize;
72 let mut data = vec![0u8; stride * height as usize];
73
74 for (y, row) in img.rows().enumerate() {
75 for (x, pixel) in row.enumerate() {
76 let offset = y * stride + x * 4;
77 // RGBA -> BGRA
78 data[offset] = pixel[2]; // B
79 data[offset + 1] = pixel[1]; // G
80 data[offset + 2] = pixel[0]; // R
81 data[offset + 3] = pixel[3]; // A
82 }
83 }
84
85 data
86 }
87
88 /// Render background image to Cairo context
89 pub fn render_to_cairo(
90 ctx: &cairo::Context,
91 img: &RgbaImage,
92 ) -> Result<()> {
93 let (width, height) = img.dimensions();
94 let bgra_data = rgba_to_bgra(img);
95
96 let surface = cairo::ImageSurface::create_for_data(
97 bgra_data,
98 cairo::Format::ARgb32,
99 width as i32,
100 height as i32,
101 (width * 4) as i32,
102 )
103 .context("Failed to create Cairo surface from background")?;
104
105 ctx.set_source_surface(&surface, 0.0, 0.0)?;
106 ctx.paint()?;
107
108 Ok(())
109 }
110