From e83e2df49d9cafe218bb303c1afd676f1a0e2524 Mon Sep 17 00:00:00 2001 From: candifloss Date: Wed, 17 Dec 2025 16:29:01 +0530 Subject: [PATCH] Add wallpaper module - Separate image processing logic - Start cleaning up `main()` --- src/main.rs | 45 +++++++----------------- src/wallpaper.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 32 deletions(-) create mode 100644 src/wallpaper.rs diff --git a/src/main.rs b/src/main.rs index 2b6f913..fef7247 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use x11rb::{ }; mod args; mod config; +mod wallpaper; fn main() -> Result<()> { // Parse CLI arguments @@ -100,33 +101,13 @@ fn main() -> Result<()> { let pixmap = conn.generate_id()?; conn.create_pixmap(depth, pixmap, root, width, height)?; - // Load the wallpaper image from disk. - let img = image::open(&image_path) - .with_context(|| format!("Failed to open image: {}", image_path.display()))?; - - // Convert the image into RGBA8 (4 bytes per pixel). - // Predictable, CPU-friendly format to work on. - let img = img.to_rgba8(); - - // Resize the image to exactly match the screen size. - // This uses a simple "fill" strategy (no aspect ratio preservation). - let img = image::imageops::resize( - &img, + // Prepare wallpaper image (scaling + pixel format conversion) + let prepared = wallpaper::prepare_wallpaper( + &image_path, + wallpaper::ScalingMode::Stretch, // default for now width.into(), height.into(), - image::imageops::FilterType::Lanczos3, - ); - - // Extract raw pixel bytes from the image buffer. - let mut pixel_data = img.into_raw(); - - // Image libraries produce RGBA, but X11 TrueColor visuals typically use - // BGRX/BGRA byte order on little-endian systems. - // Swap red and blue channels to match the server’s native pixel layout. - // Without this conversion, colors appear incorrect. - for pixel in pixel_data.chunks_exact_mut(4) { - pixel.swap(0, 2); // Swap Red and Blue channels - } + )?; // Create a Graphics Context (GC). // A GC is required by X11 for image uploads, even though most of its @@ -141,13 +122,13 @@ fn main() -> Result<()> { ImageFormat::Z_PIXMAP, pixmap, // drawable: Destination pixmap (wallpaper image) gc, // Graphics Context (required by X11) - width, - height, // width/height: size of the uploaded image region in pixels - 0, - 0, // dst_x/dst_y: Destination offset inside the pixmap (top-left corner) - 0, // left_pad: legacy bitmap padding, always 0 for modern images - depth, // depth: must match the root window’s depth (usually 24 or 32) - &pixel_data, // data: raw BGRA/BGRX pixel buffer + prepared.width as u16, + prepared.height as u16, // width/height: size of the uploaded image region in pixels + prepared.offset_x as i16, + prepared.offset_y as i16, // dst_x/dst_y: Destination offset inside the pixmap (top-left corner) + 0, // left_pad: legacy bitmap padding, always 0 for modern images + depth, // depth: must match the root window’s depth (usually 24 or 32) + &prepared.pixels, // data: raw BGRA/BGRX pixel buffer )?; // Grab and lock the X server to prevent other clients from observing or reacting to intermediate state. diff --git a/src/wallpaper.rs b/src/wallpaper.rs new file mode 100644 index 0000000..ba4d99e --- /dev/null +++ b/src/wallpaper.rs @@ -0,0 +1,91 @@ +use anyhow::{Context, Result}; +use image::{RgbaImage, imageops}; +use std::path::Path; + +/// Supported wallpaper scaling modes. +/// +/// Only `Stretch` is implemented for now. +/// Others are defined so the public API does not need to change later. +#[derive(Debug, Clone, Copy)] +pub enum ScalingMode { + Stretch, + // Fill, + // Fit, +} + +/// Result of preparing a wallpaper image for the screen. +/// +/// This struct contains everything needed to upload the image +/// into an X11 pixmap using `put_image`. +pub struct PreparedWallpaper { + pub pixels: Vec, // BGRA / BGRX pixel buffer + pub width: u32, + pub height: u32, // Image dimensions (height, width) + pub offset_x: i32, + pub offset_y: i32, // Image offset (x,y) from top-left of pixmap +} + +/// Convert RGBA byte order to BGRA. +/// +/// Image libraries produce RGBA, but X11 TrueColor visuals typically use +/// BGRX/BGRA byte order on little-endian systems. +/// Swap red and blue channels to match the server’s native pixel layout. +/// Without this conversion, colors appear incorrect. +fn rgba_to_bgra(mut pixels: Vec) -> Vec { + for px in pixels.chunks_exact_mut(4) { + px.swap(0, 2); // R <-> B + } + pixels +} + +/// Stretch mode: +/// - Image is resized to exactly match screen dimensions +/// - Aspect ratio is not preserved +/// - Entire screen is covered +fn prepare_stretch(img: &RgbaImage, screen_width: u32, screen_height: u32) -> PreparedWallpaper { + // Resize image to exactly fill the screen + let resized = imageops::resize( + img, + screen_width, + screen_height, + imageops::FilterType::Lanczos3, + ); + + // Convert RGBA -> BGRA for X11 TrueColor visuals + let pixels = rgba_to_bgra(resized.into_raw()); + + PreparedWallpaper { + pixels, + width: screen_width, + height: screen_height, + offset_x: 0, + offset_y: 0, + } +} + +/// Load, scale, and prepare a wallpaper image for display. +/// +/// This function: +/// - Loads the image from disk +/// - Converts it into a predictable pixel format +/// - Applies the requested scaling mode +/// - Converts pixel layout to match X11 expectations +/// +/// No X11-specific code lives here. +pub fn prepare_wallpaper( + image_path: &Path, + mode: ScalingMode, + screen_width: u32, + screen_height: u32, +) -> Result { + // Load image from disk + let img = image::open(image_path) + .with_context(|| format!("Failed to open image: {}", image_path.display()))? + .to_rgba8(); + + let prepared = match mode { + ScalingMode::Stretch => prepare_stretch(&img, screen_width, screen_height), + }; + + Ok(prepared) +}