Add wallpaper module
- Separate image processing logic - Start cleaning up `main()`
This commit is contained in:
parent
8e52d12226
commit
e83e2df49d
41
src/main.rs
41
src/main.rs
@ -12,6 +12,7 @@ use x11rb::{
|
|||||||
};
|
};
|
||||||
mod args;
|
mod args;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod wallpaper;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
// Parse CLI arguments
|
// Parse CLI arguments
|
||||||
@ -100,33 +101,13 @@ fn main() -> Result<()> {
|
|||||||
let pixmap = conn.generate_id()?;
|
let pixmap = conn.generate_id()?;
|
||||||
conn.create_pixmap(depth, pixmap, root, width, height)?;
|
conn.create_pixmap(depth, pixmap, root, width, height)?;
|
||||||
|
|
||||||
// Load the wallpaper image from disk.
|
// Prepare wallpaper image (scaling + pixel format conversion)
|
||||||
let img = image::open(&image_path)
|
let prepared = wallpaper::prepare_wallpaper(
|
||||||
.with_context(|| format!("Failed to open image: {}", image_path.display()))?;
|
&image_path,
|
||||||
|
wallpaper::ScalingMode::Stretch, // default for now
|
||||||
// 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,
|
|
||||||
width.into(),
|
width.into(),
|
||||||
height.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).
|
// Create a Graphics Context (GC).
|
||||||
// A GC is required by X11 for image uploads, even though most of its
|
// A GC is required by X11 for image uploads, even though most of its
|
||||||
@ -141,13 +122,13 @@ fn main() -> Result<()> {
|
|||||||
ImageFormat::Z_PIXMAP,
|
ImageFormat::Z_PIXMAP,
|
||||||
pixmap, // drawable: Destination pixmap (wallpaper image)
|
pixmap, // drawable: Destination pixmap (wallpaper image)
|
||||||
gc, // Graphics Context (required by X11)
|
gc, // Graphics Context (required by X11)
|
||||||
width,
|
prepared.width as u16,
|
||||||
height, // width/height: size of the uploaded image region in pixels
|
prepared.height as u16, // width/height: size of the uploaded image region in pixels
|
||||||
0,
|
prepared.offset_x as i16,
|
||||||
0, // dst_x/dst_y: Destination offset inside the pixmap (top-left corner)
|
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
|
0, // left_pad: legacy bitmap padding, always 0 for modern images
|
||||||
depth, // depth: must match the root window’s depth (usually 24 or 32)
|
depth, // depth: must match the root window’s depth (usually 24 or 32)
|
||||||
&pixel_data, // data: raw BGRA/BGRX pixel buffer
|
&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.
|
// 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