Add wallpaper module
- Separate image processing logic - Start cleaning up `main()`
This commit is contained in:
parent
8e52d12226
commit
e83e2df49d
45
src/main.rs
45
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.
|
||||
|
||||
91
src/wallpaper.rs
Normal file
91
src/wallpaper.rs
Normal 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 server’s 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)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user