wallswitch 0.60.9

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;
use std::f32::consts;

impl ImageEffect for AuroraGenerator {
    /// Renders the wave-like aurora in-place over the active background memory buffer.
    fn apply(&self, rgb_img: &mut RgbImage) {
        let (width, height) = rgb_img.dimensions();
        let w_f = width as f32;
        let h_f = height as f32;

        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 f32;
            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,
            };

            // Process pixels using 3-channel (RGB) chunks
            for (x, pixel_slice) in row_data.chunks_exact_mut(3).enumerate() {
                let x_f = x as f32;
                let alpha = Self::calculate_aurora_alpha(x_f, &params, &row_state);

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

                    // Conversion and computation in linear color space
                    let bg_linear = pixel_color.squared();
                    let aurora_linear = self.color_palette.color_rgb.squared();

                    // Linear interpolation adjusted for the by-value model:
                    // from bg_linear at alpha = 0.0 to aurora_linear at alpha = 1.0
                    let blended_linear = bg_linear.lerp(aurora_linear, alpha);

                    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 (density = {}), color: {}",
            self.density, self.color_palette
        )
    }
}

/// Parameters for wave-frequency math operations in the Cosmic Aurora effect.
pub struct AuroraParams {
    /// Density coefficient for horizontal frequency mapping.
    pub density_u: f32,
    /// Density coefficient for wave scaling.
    pub density_w_coeff: f32,
    /// Exponent coefficient for radial math calculations.
    pub density_w4_coeff: f32,
    /// Inverted width of the image.
    pub inv_w: f32,
}

/// Dynamic calculations computed per image row to optimize thread execution.
pub struct AuroraRowState {
    /// Vertical viewport parameter.
    pub v: f32,
    /// Cached cosine calculation for density.
    pub w2: f32,
    /// Cached vertical density scalar.
    pub v_density: f32,
    /// Cached squared vertical offset coordinate.
    pub v_sq: f32,
}

/// 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: f32,
    /// Density multiplier tailored to the active aspect ratio.
    pub aspect_ratio_density_multiplier: f32,
}

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

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

        // Base density scalar
        let density: f32 = get_random_integer(4, 8);

        // Adjust horizontal density to prevent stretching on wider/narrower displays
        let aspect_ratio = width as f32 / height as f32;
        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: f32, params: &AuroraParams, row: &AuroraRowState) -> f32 {
        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();

        // Map the coordinates to complex space to compute Euclidean norm cleanly via abs()
        let coord = Complex::new(u as f64, row.v as f64);
        let w4 = (coord.abs() as f32 * params.density_w4_coeff).cos();

        let val = (w1 + row.w2 + w3 + w4) * 0.25;

        // Smooth, anti-aliasing sine wave mapping instead of narrow, noisy power spikes
        let wave = (val * consts::PI).sin() * 0.5 + 0.5;
        let intensity = wave.powi(2); // Ambient curtain light falloff

        // Center offset represented inside a Complex coordinate
        let center_offset = Complex::new(((u - 0.5) * 2.0) as f64, ((row.v - 0.5) * 2.0) as f64);
        let edge_fade = (1.0 - center_offset.abs() as f32 * 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);
    }
}