use crate::effects::partition_rows;
use crate::{NEON_PALETTES, NeonColor, get_random_integer};
use image::RgbImage;
use std::thread;
impl crate::effects::ImageEffect for AuroraGenerator {
fn apply(&self, rgb_img: &mut RgbImage) {
self.apply_effect_in_memory(rgb_img);
}
fn info(&self) -> String {
format!(
"overlay (density = {}), color: {}",
self.density, self.color_palette
)
}
}
pub struct AuroraParams {
pub density_u: f32,
pub density_w_coeff: f32,
pub density_w4_coeff: f32,
pub inv_w: f32,
}
pub struct AuroraRowState {
pub v: f32,
pub w2: f32,
pub v_density: f32,
pub v_sq: f32,
}
pub struct AuroraGenerator {
pub color_palette: NeonColor,
pub density: f32,
pub aspect_ratio_density_multiplier: f32,
}
impl AuroraGenerator {
pub fn random(monitor: &crate::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];
let density: f32 = get_random_integer(4, 8);
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();
let w4 = ((u * u + row.v_sq).sqrt() * params.density_w4_coeff).cos();
let val = (w1 + row.w2 + w3 + w4) * 0.25;
let wave = (val * std::f32::consts::PI).sin() * 0.5 + 0.5;
let intensity = wave.powf(2.0);
let dx = (u - 0.5) * 2.0;
let dy = (row.v - 0.5) * 2.0;
let edge_fade = (1.0 - (dx * dx + dy * dy).sqrt() * 0.45).clamp(0.0, 1.0);
intensity * edge_fade * 0.65
}
pub fn apply_effect_in_memory(&self, rgb_img: &mut RgbImage) {
let (width, height) = rgb_img.dimensions();
let w_f = width as f32;
let h_f = height as f32;
let contrast_color = self.color_palette.to_array();
let (mut rows, width_usize) = partition_rows(rgb_img);
let cores = thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let chunk_size = (rows.len() / cores).max(1);
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;
thread::scope(|scope| {
for chunk in rows.chunks_mut(chunk_size) {
let params_ref = ¶ms;
scope.spawn(move || {
for (y, row_data) in chunk.iter_mut() {
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,
};
for x in 0..width_usize {
let x_f = x as f32;
let alpha = Self::calculate_aurora_alpha(x_f, params_ref, &row_state);
if alpha > 0.01 {
let idx = x * 3;
let original_r = row_data[idx] as f32;
let original_g = row_data[idx + 1] as f32;
let original_b = row_data[idx + 2] as f32;
let r_aurora = contrast_color[0] * 255.0;
let g_aurora = contrast_color[1] * 255.0;
let b_aurora = contrast_color[2] * 255.0;
let bg_r_linear = (original_r / 255.0).powi(2);
let bg_g_linear = (original_g / 255.0).powi(2);
let bg_b_linear = (original_b / 255.0).powi(2);
let aurora_r_linear = (r_aurora / 255.0).powi(2);
let aurora_g_linear = (g_aurora / 255.0).powi(2);
let aurora_b_linear = (b_aurora / 255.0).powi(2);
let blended_r =
(bg_r_linear * (1.0 - alpha) + aurora_r_linear * alpha).sqrt()
* 255.0;
let blended_g =
(bg_g_linear * (1.0 - alpha) + aurora_g_linear * alpha).sqrt()
* 255.0;
let blended_b =
(bg_b_linear * (1.0 - alpha) + aurora_b_linear * alpha).sqrt()
* 255.0;
row_data[idx] = blended_r.clamp(0.0, 255.0) as u8;
row_data[idx + 1] = blended_g.clamp(0.0, 255.0) as u8;
row_data[idx + 2] = blended_b.clamp(0.0, 255.0) as u8;
}
}
}
});
}
});
}
}