wallswitch 0.60.11

randomly selects wallpapers for multiple monitors
Documentation
//! Cosmic Aurora wave generator overlay.
//!
//! This module implements a procedural overlay mimicking polar aurora wave filaments.
//! It processes pixels in parallel across logical CPU cores and blends the glowing waves
//! using gamma-corrected linear color space to prevent dark-boundary artifacts.

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

/// Unified mathematical parameters for wave-frequency calculations in the polar aurora effect.
pub struct AuroraParams {
    /// Horizontal frequency mapping coefficient.
    pub density_u: f64,
    /// Spatial wave scaling coefficient.
    pub density_w_coeff: f64,
    /// Viewport-bound radial scaling coefficient.
    pub density_w4_coeff: f64,
    /// The reciprocal of the physical display width.
    pub inv_w: f64,
}

/// Dynamic calculations computed per physical image row to optimize thread-pool performance.
pub struct AuroraRowState {
    /// Vertical coordinate projection normalized to the viewport.
    pub v: f64,
    /// Cached vertical density scalar projection.
    pub w2: f64,
    /// Cached vertical density factor.
    pub v_density: f64,
    /// Cached square of the normalized vertical coordinate.
    pub v_sq: f64,
}

/// A procedural generator for rendering wave-like Cosmic Aurora overlays.
pub struct AuroraGenerator {
    /// The base color palette selected for the neon glow.
    pub color_palette: NeonColor,
    /// Base density frequency scaling factor.
    pub density: f64,
    /// Density multiplier tailored to the active aspect ratio.
    pub aspect_ratio_density_multiplier: f64,
}

impl ImageEffect for AuroraGenerator {
    fn apply(&self, rgb_img: &mut RgbImage) {
        let (width, height) = rgb_img.dimensions();
        let w_f = width as f64;
        let h_f = height as f64;

        let inv_w = 1.0 / w_f;
        let inv_h = 1.0 / h_f;

        let adjusted_density = self.density * self.aspect_ratio_density_multiplier;

        let params = AuroraParams {
            density_u: adjusted_density * 1.5 * inv_w,
            density_w_coeff: adjusted_density * inv_w,
            density_w4_coeff: adjusted_density * 1.2,
            inv_w,
        };

        let density_v_coeff = self.density * 2.0;
        let density_val = self.density;

        process_rows_parallel_scoped(rgb_img, |y, row_data| {
            let y_f = y as f64;
            let v = y_f * inv_h;

            let row_state = AuroraRowState {
                v,
                w2: (v * density_v_coeff).cos(),
                v_density: v * density_val,
                v_sq: v * v,
            };

            for (x, pixel_slice) in row_data.chunks_exact_mut(3).enumerate() {
                let x_f = x as f64;
                let alpha = Self::calculate_aurora_alpha(x_f, &params, &row_state);

                if alpha > 0.01 {
                    let bg = ColorRGB::from_slice(pixel_slice);
                    let fg = self.color_palette.color_rgb;

                    // Blends natively using gamma-corrected color-space equations
                    let blended = bg.blend(fg, alpha);
                    blended.write_to_slice(pixel_slice);
                }
            }
        });
    }

    fn info(&self) -> String {
        format!(
            "overlay (density = {}), color: {}",
            self.density, self.color_palette
        )
    }
}

impl AuroraGenerator {
    /// Generates a randomized Aurora configuration using the target monitor's dimensions.
    pub fn random(monitor: &Monitor) -> Self {
        let width = monitor.resolution.width;
        let height = monitor.resolution.height;

        let p_idx: usize = get_random_integer(0, NEON_PALETTES.len() - 1);
        let color_palette = NEON_PALETTES[p_idx];
        let density: f64 = get_random_integer(4, 8);

        let aspect_ratio = width as f64 / height as f64;
        let aspect_ratio_density_multiplier = if aspect_ratio > 1.0 {
            aspect_ratio.sqrt()
        } else {
            1.0
        };

        Self {
            color_palette,
            density,
            aspect_ratio_density_multiplier,
        }
    }

    #[inline(always)]
    fn calculate_aurora_alpha(x_f: f64, params: &AuroraParams, row: &AuroraRowState) -> f64 {
        let u = x_f * params.inv_w;

        let w1 = (x_f * params.density_u).sin();
        let w3 = (x_f * params.density_w_coeff + row.v_density).sin();

        let coord = Complex::new(u, row.v);
        let w4 = (coord.abs() * params.density_w4_coeff).cos();

        let val = (w1 + row.w2 + w3 + w4) * 0.25;
        let wave = (val * std::f64::consts::PI).sin() * 0.5 + 0.5;
        let intensity = wave.powi(2);

        // Calculate center distance mapping using unified Complex vector operations
        let center_offset = (coord - Complex::new(0.5, 0.5)) * 2.0;
        let edge_fade = (1.0 - center_offset.abs() * 0.45).clamp(0.0, 1.0);

        intensity * edge_fade * 0.65
    }
}

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

    #[test]
    fn test_aurora_generator_random() {
        let monitor = Monitor::default();
        let aurora = AuroraGenerator::random(&monitor);
        assert!(aurora.density >= 4.0 && aurora.density <= 8.0);
    }
}