Add wallpaper module

- Separate image processing logic
- Start cleaning up `main()`
This commit is contained in:
Candifloss 2025-12-17 16:29:01 +05:30
parent 8e52d12226
commit e83e2df49d
2 changed files with 104 additions and 32 deletions

View File

@ -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 servers 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 windows 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 windows 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.

91
src/wallpaper.rs Normal file
View File

@ -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<u8>, // 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 servers native pixel layout.
/// Without this conversion, colors appear incorrect.
fn rgba_to_bgra(mut pixels: Vec<u8>) -> Vec<u8> {
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<PreparedWallpaper> {
// 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)
}