wallswitch 0.60.9

randomly selects wallpapers for multiple monitors
Documentation
//! Procedural Starfield / Bokeh overlay generator.
//!
//! This module projects soft, circular glowing stars and light orbs of varying sizes,
//! intensities, and high-contrast neon colors over active background canvases.
//! Rendering and blending equations are processed in parallel using a gamma-corrected
//! linear color space model to prevent gray-fringe artifacts.

use crate::effects::ImageEffect;
use crate::{
    ColorRGB, Complex, NEON_PALETTES, NeonColor, get_random_integer, process_rows_parallel_scoped,
};
use image::RgbImage;

/// The minimum and maximum limits for the randomized star count.
pub const STAR_RANGE: [usize; 2] = [60, 120];

/// Represents a single procedurally generated star element.
pub struct Star {
    /// The coordinate position of the star center in complex space.
    pub position: Complex,
    /// The physical radius of the star.
    pub radius: f32,
    /// The visual peak intensity.
    pub intensity: f32,
    /// The neon color palette assigned to the star.
    pub color_palette: NeonColor,
}

/// A procedural generator for rendering cosmic starfields and bokeh onto image backgrounds.
pub struct StarfieldGenerator {
    /// The collection of active star elements.
    pub stars: Vec<Star>,
}

impl ImageEffect for StarfieldGenerator {
    /// Blends the generated starfield overlay onto the image buffer in parallel.
    fn apply(&self, rgb_img: &mut RgbImage) {
        let contrast_color = ColorRGB::new(0.64, 0.75, 0.85);

        process_rows_parallel_scoped(rgb_img, |y, row_data| {
            let y_f = y as f32;

            let mut active_stars = Vec::with_capacity(16);
            for star in &self.stars {
                // Component 'im' represents 'y'
                let dy = star.position.im as f32 - y_f;
                let limit = star.radius * 2.0;
                if dy.abs() < limit {
                    let dy_sq = dy * dy;
                    let star_radius_sq = star.radius * star.radius;
                    active_stars.push((star, dy_sq, star_radius_sq));
                }
            }

            // Use chunks_exact_mut to eliminate explicit index calculations
            for (x, pixel_slice) in row_data.chunks_exact_mut(3).enumerate() {
                let x_f = x as f32;

                let mut r_contrib = 0.0;
                let mut g_contrib = 0.0;
                let mut b_contrib = 0.0;
                let mut total_alpha = 0.0;

                for &(star, dy_sq, star_radius_sq) in &active_stars {
                    if let Some((r, g, b, alpha)) = Self::calculate_star_influence(
                        star,
                        x_f,
                        dy_sq,
                        star_radius_sq,
                        contrast_color,
                    ) {
                        r_contrib += r;
                        g_contrib += g;
                        b_contrib += b;
                        total_alpha += alpha;
                    }
                }

                if total_alpha > 0.001 {
                    // Load background pixel mapping [0..255] u8 range to [0.0..1.0] linear scale
                    let mut pixel_color = ColorRGB::from_slice(pixel_slice);

                    let alpha_clamp = total_alpha.min(0.95);

                    // Compute weighted foreground color components
                    let fg_color = ColorRGB::new(
                        r_contrib / total_alpha,
                        g_contrib / total_alpha,
                        b_contrib / total_alpha,
                    );

                    // Component-wise blending and gamma correction using ColorRGB operations
                    let bg_linear = pixel_color.squared();
                    let fg_linear = fg_color.squared();

                    // Blends such that fg_linear is returned at alpha_clamp = 1.0 (standard lerp)
                    let blended_linear = bg_linear.lerp(fg_linear, alpha_clamp);

                    pixel_color = blended_linear.sqrt();

                    // Persist processed color channels back to the image buffer
                    pixel_color.write_to_slice(pixel_slice);
                }
            }
        });
    }

    /// Returns a formatting diagnostic string about the active generator.
    fn info(&self) -> String {
        format!("overlay ({} stars)", self.stars.len())
    }
}

impl StarfieldGenerator {
    /// Generates a randomized field of stars based on the monitor's physical dimensions.
    pub fn random(monitor: &crate::Monitor) -> Self {
        let width = monitor.resolution.width as u32;
        let height = monitor.resolution.height as u32;

        let count = get_random_integer(STAR_RANGE[0], STAR_RANGE[1]);
        Self::new(count, width, height)
    }

    /// Generates a randomized field of stars using the centralized high-contrast neon colors.
    pub fn new(count: usize, width: u32, height: u32) -> Self {
        let mut stars = Vec::with_capacity(count);

        for _ in 0..count {
            let x: f64 = get_random_integer(0, width);
            let y: f64 = get_random_integer(0, height);
            let radius: f32 = get_random_integer(5, 45);
            let intensity = get_random_integer::<_, f32>(30, 95) / 100.0;

            // Pick a randomized high-visibility color palette for each star
            let p_idx: usize = get_random_integer(0, NEON_PALETTES.len() - 1);
            let color_palette = NEON_PALETTES[p_idx];

            stars.push(Star {
                position: Complex::new(x, y),
                radius,
                intensity,
                color_palette,
            });
        }

        Self { stars }
    }

    #[inline(always)]
    fn calculate_star_influence(
        star: &Star,
        x_f: f32,
        dy_sq: f32,
        star_radius_sq: f32,
        contrast_color: ColorRGB,
    ) -> Option<(f32, f32, f32, f32)> {
        // Component 're' represents 'x'
        let dx = star.position.re as f32 - x_f;
        let dist_sq = dx * dx + dy_sq;

        if dist_sq < star_radius_sq * 4.0 {
            let factor = (-dist_sq / (2.0 * star_radius_sq)).exp();
            let alpha = factor * star.intensity;

            // Retrieve ColorRGB from the selected NeonColor
            let star_rgb = star.color_palette.color_rgb;

            // Perform vector operations directly on ColorRGB.
            // Using 0.25 interpolates such that 25% of other (contrast_color) is blended.
            let blended = star_rgb.lerp(contrast_color, 0.25).scale(alpha);

            Some((blended.red, blended.green, blended.blue, alpha))
        } else {
            None
        }
    }
}

#[cfg(test)]
mod tests_star {
    use super::*;
    use crate::core::Monitor;

    #[test]
    fn test_star_generation() {
        let monitor = Monitor::default();
        let starfield = StarfieldGenerator::random(&monitor);
        assert!(!starfield.stars.is_empty());
    }
}