use crate::effects::{
FractalPreset, MAX_ITERATIONS, MIN_ITERATIONS, ProceduralEffect, Viewport, ViewportSpecs,
color_distance_estimator, compute_escape_iterations, get_rotation_phasors, partition_rows,
};
use crate::{Complex, ImageEffect, NEON_PALETTES, NeonColor, get_random_integer};
use image::RgbImage;
use std::thread;
impl ImageEffect for JuliaGenerator {
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 specs = ViewportSpecs {
center: self.preset.center,
zoom: self.zoom,
cos_angle: self.cos_angle,
sin_angle: self.sin_angle,
is_julia: true,
};
let viewport = Viewport::new(w_f, h_f, &specs);
let scan_iterations = self.scan_iterations;
let center = self.preset.center;
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 min_dim = w_f.min(h_f);
let scale = self.zoom / min_dim;
thread::scope(|scope| {
let viewport_ref = &viewport;
let color_palette = self.color_palette;
for chunk in rows.chunks_mut(chunk_size) {
scope.spawn(move || {
for (y, row_data) in chunk.iter_mut() {
let y_f = *y as f64;
for x in 0..width_usize {
let x_f = x as f64;
let z_init = viewport_ref.map(x_f, y_f);
let (i, z, dz) = compute_escape_iterations(
ProceduralEffect::JuliaSet,
z_init,
center,
scan_iterations,
);
let (fractal_rgb, alpha, s_alpha) = color_distance_estimator(
i,
scan_iterations,
z,
dz,
scale,
color_palette,
);
let idx = x * 3;
crate::effects::blend_and_vignette_pixel(
row_data,
idx,
fractal_rgb,
alpha,
s_alpha,
);
}
}
});
}
});
}
fn info(&self) -> String {
format!(
"fractal [{}]\n\
f(z) = z^2 + c, where c = {:8.5} {} {:7.5}i (iter = {:4}, zoom = {:.2}), color: {}",
self.preset.fractal_name,
self.preset.center.re,
if self.preset.center.im >= 0.0 {
"+"
} else {
"-"
},
self.preset.center.im.abs(),
self.scan_iterations,
self.zoom,
self.color_palette
)
}
}
pub struct JuliaGenerator {
pub preset: FractalPreset,
pub scan_iterations: u32,
pub color_palette: NeonColor,
pub zoom: f64,
pub cos_angle: f64,
pub sin_angle: f64,
}
impl Default for JuliaGenerator {
fn default() -> Self {
Self {
preset: FractalPreset {
center: Complex::new(-0.7, 0.27015),
fractal_name: "Classic dendrite",
effect_name: ProceduralEffect::JuliaSet,
},
scan_iterations: get_random_integer(MIN_ITERATIONS, MAX_ITERATIONS),
color_palette: NEON_PALETTES[5],
zoom: 3.0,
cos_angle: 1.0,
sin_angle: 0.0,
}
}
}
impl JuliaGenerator {
pub fn random(monitor: &crate::Monitor) -> Self {
let width = monitor.resolution.width as u32;
let height = monitor.resolution.height as u32;
let presets = [
FractalPreset {
center: Complex::new(-0.4, 0.6),
fractal_name: "Classic cloud swirls",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.8, 0.156),
fractal_name: "Detailed spirals",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.7269, 0.1889),
fractal_name: "Lace structures",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.75, 0.11),
fractal_name: "Feathery branches",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.1, 0.651),
fractal_name: "Cosmic dust style",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(0.355, 0.355),
fractal_name: "Spiral galaxy arms",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.4, -0.59),
fractal_name: "Swirling vortexes",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.54, 0.54),
fractal_name: "Ornamental lace borders",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.835, -0.2321),
fractal_name: "Lightning rods",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.77269, 0.12428),
fractal_name: "Coral reefs",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.51251, 0.5213),
fractal_name: "Fine lace filaments",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.55, 0.55),
fractal_name: "Intricate leaf outlines",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.624, 0.435),
fractal_name: "Crystalline snowflake patterns",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.12, 0.85),
fractal_name: "Flowing plasma plumes",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.391, -0.587),
fractal_name: "Swirling storm clouds",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.73, 0.21),
fractal_name: "Feathery dendritic lace",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.81, 0.2),
fractal_name: "Spiral galaxy filaments",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.68, 0.34),
fractal_name: "Delicate coral spirals",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.76, 0.08),
fractal_name: "Lightning tree branches",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(0.285, 0.01),
fractal_name: "Cosmic galaxy vortex swirls",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.8, 0.17),
fractal_name: "Spidery lace denderites",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.7269, -0.1889),
fractal_name: "Conjugate lace structures",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.835, 0.2321),
fractal_name: "Conjugate lightning rods",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.75, 0.05),
fractal_name: "Dense branching coral reef",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.70176, 0.3842),
fractal_name: "Conjugate dragon-like curves",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.8, 0.16),
fractal_name: "Deep sea coral spirals",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.722, 0.246),
fractal_name: "Dendritic pine branch variation",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.11, 0.655),
fractal_name: "Triple helix rotational cosmic swirls",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.52519, 0.5215),
fractal_name: "Intertwined Gothic Cathedral window arches",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(0.28, 0.008),
fractal_name: "Centrifugal pinwheel galaxy vortex generator",
effect_name: ProceduralEffect::JuliaSet,
},
FractalPreset {
center: Complex::new(-0.83, -0.232),
fractal_name: "Sharp crystalline glacial ice column needles",
effect_name: ProceduralEffect::JuliaSet,
},
];
let p_idx: usize = get_random_integer(0, NEON_PALETTES.len() - 1);
let color_palette = NEON_PALETTES[p_idx];
let angle_degrees: f64 = get_random_integer(0, 359);
let radians = angle_degrees.to_radians();
let preset_idx: usize = get_random_integer(0, presets.len() - 1);
let selected_preset = presets[preset_idx];
let mut julia = Self {
preset: selected_preset,
scan_iterations: get_random_integer(MIN_ITERATIONS, MAX_ITERATIONS),
color_palette,
zoom: 3.0,
cos_angle: radians.cos(),
sin_angle: radians.sin(),
};
julia.optimize_fit(width, height);
julia
}
pub fn optimize_fit(&mut self, width: u32, height: u32) {
let w_f = width as f64;
let h_f = height as f64;
let min_dim = w_f.min(h_f);
let c_abs = self.preset.center.norm_sq().sqrt();
let r_bound = (1.0 + (1.0 + 4.0 * c_abs).sqrt()) / 2.0;
let search_limit = r_bound * 1.2;
let steps = 128;
let inv_steps_minus_1 = 1.0 / (steps - 1) as f64;
let range = 2.0 * search_limit;
let scan_iterations = self.scan_iterations;
let mut active_points = Vec::with_capacity(steps * steps / 2);
for step_y in 0..steps {
let ry = -search_limit + (step_y as f64 * inv_steps_minus_1) * range;
for step_x in 0..steps {
let rx = -search_limit + (step_x as f64 * inv_steps_minus_1) * range;
let (i, _, _) = compute_escape_iterations(
ProceduralEffect::JuliaSet,
Complex::new(rx, ry),
self.preset.center,
scan_iterations,
);
if i > 3 && i < scan_iterations {
active_points.push(Complex::new(rx, ry));
}
}
}
if !active_points.is_empty() {
let mut best_zoom = f64::MAX;
let mut best_cos = self.cos_angle;
let mut best_sin = self.sin_angle;
for phasor in get_rotation_phasors() {
let mut max_cx_abs = 0.0_f64;
let mut max_cy_abs = 0.0_f64;
let inverse_phasor = phasor.conj();
for &point in &active_points {
let rotated = point * inverse_phasor;
max_cx_abs = max_cx_abs.max(rotated.re.abs());
max_cy_abs = max_cy_abs.max(rotated.im.abs());
}
let zoom_x = 2.0 * max_cx_abs * min_dim / w_f;
let zoom_y = 2.0 * max_cy_abs * min_dim / h_f;
let required_zoom = zoom_x.max(zoom_y);
if required_zoom < best_zoom {
best_zoom = required_zoom;
best_cos = phasor.re;
best_sin = phasor.im;
}
}
self.zoom = best_zoom * 1.10;
self.cos_angle = best_cos;
self.sin_angle = best_sin;
} else {
self.zoom = 2.0 * r_bound * 1.10;
}
}
}
#[cfg(test)]
mod tests_julia {
use super::*;
use crate::core::Monitor;
#[test]
fn test_julia_generation_random() {
let monitor = Monitor::default();
let julia = JuliaGenerator::random(&monitor);
assert!(julia.zoom > 0.0);
assert_eq!(julia.preset.effect_name, ProceduralEffect::JuliaSet);
}
}