use crate::{
ColorRGB, Complex, ImageEffect, Monitor, NEON_PALETTES, NeonColor, get_random_integer,
process_rows_parallel_scoped,
};
use image::RgbImage;
pub struct AuroraParams {
pub density_u: f64,
pub density_w_coeff: f64,
pub density_w4_coeff: f64,
pub inv_w: f64,
}
pub struct AuroraRowState {
pub v: f64,
pub w2: f64,
pub v_density: f64,
pub v_sq: f64,
}
pub struct AuroraGenerator {
pub color_palette: NeonColor,
pub density: f64,
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, ¶ms, &row_state);
if alpha > 0.01 {
let bg = ColorRGB::from_slice(pixel_slice);
let fg = self.color_palette.color_rgb;
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 {
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);
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);
}
}