wallswitch 0.60.5

randomly selects wallpapers for multiple monitors
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
//! Procedural wallpaper overlay common utilities and math structures.
//!
//! This module provides shared helper functions, coordinate system transformations,
//! blending equations, and escape-time evaluation loops used across all fractal
//! and wave-based procedural generation engines.

use crate::{
    AuroraGenerator, Complex, JuliaGenerator, MandelbrotGenerator, Monitor, NewtonGenerator,
    NovaGenerator, StarfieldGenerator, get_random_integer,
};
use clap::ValueEnum;
use image::RgbImage;
use serde::{Deserialize, Serialize};
use std::f32::consts::LOG2_E;

/// The default minimum iteration limit for escape-time fractal calculation.
pub const MIN_ITERATIONS: u32 = 500;

/// The default maximum iteration limit for escape-time fractal calculation.
pub const MAX_ITERATIONS: u32 = 1200;

/// The number of angular steps used to evaluate structural rotations during optimization.
pub const ROTATION_STEPS: u32 = 16;

/// Trait defining the behavior for any image processing effect.
///
/// This allows different procedural generators to be treated polymorphically,
/// keeping the rendering engine decoupled from specific math implementations.
pub trait ImageEffect {
    /// Applies the procedural effect in-place to a mutable image buffer.
    fn apply(&self, rgb_img: &mut RgbImage);

    /// Returns a formatted string containing specific diagnostic details of the active effect.
    fn info(&self) -> String;
}

/// Represents the supported procedural background overlay effects.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
#[serde(rename_all = "lowercase")]
pub enum ProceduralEffect {
    /// No overlay effect is applied; displays the raw, unaltered wallpaper.
    #[value(name = "none")]
    #[default]
    None,

    /// Julia Set fractal overlay.
    ///
    /// * **Characteristics**: Rendered as thin, sharp, self-similar contour lines forming highly
    ///   symmetrical branching patterns. Depending on the selected complex constant, the lines trace
    ///   intricate shapes resembling swirling clouds, dendritic lace, spiral galaxy arms, leafy
    ///   filaments, or crystalline snowflakes.
    /// * **Creator**: Developed mathematically by the French mathematician Gaston Julia in 1918.
    /// * **Generator function**: Calculated by mapping the convergence boundary under the recursive function:
    ///
    ///   f(z) = z^2 + c
    ///
    ///   where c is a fixed complex constant perturbation and the initial coordinate z_0 varies across the viewport.
    #[value(name = "julia")]
    JuliaSet,

    /// Mandelbrot Set fractal overlay.
    ///
    /// * **Characteristics**: Rendered as thin, sharp, self-similar contour lines tracing the boundary of
    ///   the set. The lines expose highly detailed structural contours, including a main cardioid,
    ///   circular period bulbs, swirling spiral valleys, and repeating miniature copies of
    ///   the entire set connected by thin filaments.
    /// * **Creator**: First visualized and defined by the Polish-born French-American mathematician
    ///   Benoit Mandelbrot in 1980.
    /// * **Generator function**: Modeled using the quadratic recurrence equation starting from the origin:
    ///
    ///   z(n+1) = z(n)^2 + c
    ///
    ///   where z_0 = 0 and the complex parameter c varies across the viewport grid coordinates.
    #[value(name = "mandelbrot")]
    Mandelbrot,

    /// Newton-Raphson Basin of Attraction fractal overlay.
    ///
    /// * **Characteristics**: Symmetrical, kaleidoscope-like mandala structures representing root-finding
    ///   convergence fields across complex space boundaries. It maps the limits of convergence zones
    ///   where points migrate to specific roots of a polynomial equation.
    /// * **Creator**: Formulated based on Sir Isaac Newton's root-approximation methods (1690s) and Arthur
    ///   Cayley's subsequent complex-plane studies (1879).
    /// * **Generator function**: Computed using a relaxed Newton-Raphson recurrence formula:
    ///
    ///   z(n+1) = z(n) - lambda * f(z(n)) / f'(z(n))
    ///
    ///   on the polynomial:
    ///
    ///   f(z) = z^p - 1
    ///
    ///   where p is the integer polynomial power and lambda is a complex relaxation factor.
    #[value(name = "newton")]
    NewtonBasins,

    /// Nova Julia liquid fractal overlay.
    ///
    /// * **Characteristics**: Organic, flowing, fluid-like plumes resembling liquid mercury,
    ///   cosmic nebulae, or dynamic plasma current paths.
    /// * **Creator**: Developed by Paul Derbyshire in the late 1990s as a structural variation and
    ///   relaxation of the classic Newton-Raphson fractal.
    /// * **Generator function**: Evaluated using the relaxed Newton recurrence relation perturbed by a
    ///   dynamic additive complex value:
    ///
    ///   z(n+1) = z(n) - R * (z(n)^p - 1) / (p * z(n)^(p-1)) + c
    ///
    ///   where p is the polynomial exponent, R is a complex relaxation modifier, and c is a fixed
    ///   perturbation coordinate.
    #[value(name = "nova")]
    NovaJulia,

    /// Procedural Cosmic Aurora wave generator.
    ///
    /// * **Characteristics**: Generates glowing, wave-like atmospheric filaments with smooth edge transitions
    ///   mimicking planetary polar auroras.
    /// * **Creator**: Modeled using transcendental multi-frequency wave equations combined with coordinate-space
    ///   decay falloffs.
    /// * **Generator function**: Evaluated using composite sinusoidal waves mapping local coordinate inputs (x, y)
    ///   against continuous densities:
    ///
    ///   alpha = 0.25 * (sin(d_u * x) + cos(d_v * y) + sin(d_w * x + rho) + cos(sqrt(u^2 + v^2) * d_w4))
    ///
    ///   coupled with radial quadratic dampening to fade margins near boundaries:
    ///
    ///   Intensity = sin(alpha * pi)^2 * (1.0 - 0.45 * sqrt(delta_x^2 + delta_y^2))
    #[value(name = "aurora")]
    CosmicAurora,

    /// Procedural Starfield / Bokeh generator.
    ///
    /// * **Characteristics**: Projects soft, circular glowing stars and light orbs of varying intensities
    ///   and high-contrast neon colors.
    /// * **Creator**: Constructed via random coordinate sampling mapped against continuous, non-linear
    ///   light field structures.
    /// * **Generator function**: Calculated using spatial coordinates evaluated against an exponential Gaussian
    ///   falloff curve centered on randomized center points (x_s, y_s):
    ///
    ///   I(d) = I_0 * exp(-d^2 / (2 * sigma^2))
    ///
    ///   where d = sqrt((x - x_s)^2 + (y - y_s)^2) represents the Euclidean distance from the active pixel
    ///   to the stellar epicenter, sigma represents the star radius, and I_0 is the peak scalar intensity.
    #[value(name = "star")]
    Starfield,

    /// Fractal mode selector.
    ///
    /// * **Characteristics**: Randomly selects between the Julia Set, Newton-Raphson Basins, or Nova Julia
    ///   fractal overlays for the active generation cycle.
    #[value(name = "fractal")]
    Fractal,

    /// Fully randomized mode selector.
    ///
    /// * **Characteristics**: Dynamically and independently selects one of the active procedural overlays
    ///   for each physical monitor.
    #[value(name = "random")]
    Random,
}

impl ProceduralEffect {
    /// Returns the user-friendly display name of the active effect.
    pub fn get_name(self) -> &'static str {
        match self {
            Self::None => "None",
            Self::JuliaSet => "Julia Sets",
            Self::Mandelbrot => "Mandelbrot",
            Self::NewtonBasins => "Newton Basins",
            Self::NovaJulia => "Nova Julia",
            Self::CosmicAurora => "Cosmic Aurora",
            Self::Starfield => "Starfield",
            Self::Fractal => "Fractal",
            Self::Random => "Random",
        }
    }

    /// Translates a logical placeholder effect (such as `Random` or `Fractal`) into a concrete, renderable variant.
    ///
    /// In the configuration model, some options act as abstract choices rather than direct visual renderers:
    /// * `Random` rolls a pseudo-random selection among all available generators.
    /// * `Fractal` restricts the selection strictly to mathematical escape-time systems.
    /// * Concrete variants (such as `JuliaSet` or `CosmicAurora`) are returned completely unmodified.
    ///
    /// # Technical Note
    /// To avoid non-deterministic bugs—where the printed console log chooses one random effect
    /// while the background generator chooses another—you must call this method exactly *once*
    /// per execution cycle, storing the returned concrete variant in a variable for all downstream tasks.
    pub fn resolve(self) -> Self {
        match self {
            Self::Random => match get_random_integer(0, 5) {
                // Fractal
                0 => Self::JuliaSet,
                1 => Self::Mandelbrot,
                2 => Self::NewtonBasins,
                3 => Self::NovaJulia,
                // Others
                4 => Self::CosmicAurora,
                _ => Self::Starfield,
            },
            Self::Fractal => match get_random_integer(0, 3) {
                0 => Self::JuliaSet,
                1 => Self::Mandelbrot,
                2 => Self::NewtonBasins,
                _ => Self::NovaJulia,
            },
            concrete => concrete,
        }
    }

    /// Dynamic factory method that constructs and returns the resolved image renderer.
    /// Returns `None` if the selected effect is `None`.
    pub fn get_renderer(self, monitor: &Monitor) -> Option<Box<dyn ImageEffect>> {
        // Match directly on self without calling self.resolve() internally
        match self {
            Self::JuliaSet => Some(Box::new(JuliaGenerator::random(monitor))),
            Self::Mandelbrot => Some(Box::new(MandelbrotGenerator::random(monitor))),
            Self::NewtonBasins => Some(Box::new(NewtonGenerator::random(monitor))),
            Self::NovaJulia => Some(Box::new(NovaGenerator::random(monitor))),
            Self::Starfield => Some(Box::new(StarfieldGenerator::random(monitor))),
            Self::CosmicAurora => Some(Box::new(AuroraGenerator::random(monitor))),
            _ => None, // None, Fractal, or Random (which must be resolved prior to this call)
        }
    }
}

/// Represents a mathematical coordinate preset for procedural fractal effects.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct FractalPreset {
    /// The target focal center coordinate.
    pub center: Complex,
    /// Friendly descriptive name of the structural pattern.
    pub fractal_name: &'static str,
    /// Associated category of procedural effect.
    pub effect_name: ProceduralEffect,
}

impl std::fmt::Display for FractalPreset {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{} ({:+.5} {:+.5}i) under {:?}",
            self.fractal_name, self.center.re, self.center.im, self.effect_name
        )
    }
}

/// Partitions an RGB image buffer into mutable row segments for thread-safe parallel processing.
pub fn partition_rows(rgb_img: &mut RgbImage) -> (Vec<(usize, &mut [u8])>, usize) {
    let (width, _) = rgb_img.dimensions();
    let width_usize = width as usize;
    let row_stride = width_usize * 3;
    let pixels_buffer = rgb_img.as_mut();

    let rows: Vec<(usize, &mut [u8])> = pixels_buffer
        .chunks_exact_mut(row_stride)
        .enumerate()
        .collect();

    (rows, width_usize)
}

/// Applies power-law (Gamma) stretching to enhance the visual contrast of fractal filaments.
#[inline(always)]
pub fn stretch_potential(raw_t: f32) -> f32 {
    raw_t.clamp(0.0, 1.0).powf(0.35)
}

/// Calculates the continuous potential (smooth coloring) value for quadratic escape-time fractals.
///
/// Filters out low escape iterations to guarantee complete transparency in the far exterior.
#[inline]
pub fn calculate_smooth_potential(i: u32, max_iterations: u32, z: Complex) -> f32 {
    if i < max_iterations {
        let mag2 = z.norm_sq();
        let smooth_i = if mag2 > 4.0 {
            let log_zn = (mag2.ln() * 0.5) as f32; // ln(|z_n|)
            let nu = log_zn.ln() * LOG2_E;
            (i as f32 + 1.0 - nu).max(0.0)
        } else {
            i as f32
        };

        // Enforce a minimum escape iteration threshold to keep the outer space 100% transparent
        let min_render_iter = 32.0_f32;
        if smooth_i < min_render_iter {
            return 0.0;
        }

        // Map the active rendering interval linearly into [0.0, 1.0] before stretching
        let normalized = (smooth_i - min_render_iter) / (max_iterations as f32 - min_render_iter);
        stretch_potential(normalized)
    } else {
        1.0 // Set interior remains fully transparent
    }
}

/// Calculates the analytical distance estimator (DEM) to the boundary of the fractal set.
/// Returns a coordinate-independent distance value.
#[inline]
pub fn calculate_distance_estimator(i: u32, max_iterations: u32, z: Complex, dz: Complex) -> f64 {
    if i < max_iterations {
        let z_mag2 = z.norm_sq();
        let dz_mag2 = dz.norm_sq();
        if z_mag2 > 0.0 && dz_mag2 > 0.0 {
            let z_mag = z_mag2.sqrt();
            let dz_mag = dz_mag2.sqrt();
            // Standard DEM formula: d = 2 * |z| * ln(|z|) / |dz|
            return 2.0 * z_mag * z_mag.ln() / dz_mag;
        }
    }
    0.0
}

/// Linearizes sRGB values to perform mathematically correct alpha-blending,
/// preventing dark-boundary artifacts.
#[inline]
pub fn blend_channels_gamma(bg: u8, fg: f32, alpha: f32) -> u8 {
    let bg_f = bg as f32 / 255.0;
    let bg_linear = bg_f * bg_f; // Fast gamma = 2.0 approximation

    let fg_f = fg / 255.0;
    let fg_linear = fg_f * fg_f;

    let blended_linear = bg_linear * (1.0 - alpha) + fg_linear * alpha;

    (blended_linear.sqrt() * 255.0).clamp(0.0, 255.0) as u8 // Re-encode to sRGB
}

/// Standard smoothstep mathematical interpolation function.
#[inline]
pub fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
    let t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
    t * t * (3.0 - 2.0 * t)
}

/// Blends the computed fractal value and vignette shadow onto the active background pixel coordinates.
#[inline]
pub fn blend_and_vignette_pixel(
    row_data: &mut [u8],
    idx: usize,
    fractal_rgb: [f32; 3],
    alpha: f32,
    shadow_alpha: f32,
) {
    let original_r = row_data[idx];
    let original_g = row_data[idx + 1];
    let original_b = row_data[idx + 2];

    // 1. Apply drop shadow (ambient occlusion) to the background
    let (background_r, background_g, background_b) = if shadow_alpha > 0.005 {
        let shadow_factor = 1.0 - shadow_alpha;
        (
            (original_r as f32 * shadow_factor).clamp(0.0, 255.0) as u8,
            (original_g as f32 * shadow_factor).clamp(0.0, 255.0) as u8,
            (original_b as f32 * shadow_factor).clamp(0.0, 255.0) as u8,
        )
    } else {
        (original_r, original_g, original_b)
    };

    // 2. Blend the bright neon on top of the shadowed background
    if alpha > 0.005 {
        let blended_r = blend_channels_gamma(background_r, fractal_rgb[0] * 255.0, alpha);
        let blended_g = blend_channels_gamma(background_g, fractal_rgb[1] * 255.0, alpha);
        let blended_b = blend_channels_gamma(background_b, fractal_rgb[2] * 255.0, alpha);

        row_data[idx] = blended_r;
        row_data[idx + 1] = blended_g;
        row_data[idx + 2] = blended_b;
    } else if shadow_alpha > 0.005 {
        // If only shadow is present, write the shadowed background
        row_data[idx] = background_r;
        row_data[idx + 1] = background_g;
        row_data[idx + 2] = background_b;
    }
}

/// Executes complex escape-time steps for both Julia and Mandelbrot fractal types.
///
/// Unifies the complex escape mathematics, returning the escape count,
/// final coordinate `z`, and boundary derivative `dz`.
#[inline(always)]
pub fn compute_escape_iterations(
    fractal_type: ProceduralEffect,
    init: Complex,
    c: Complex,
    scan_iterations: u32,
) -> (u32, Complex, Complex) {
    let (mut z, param) = if fractal_type == ProceduralEffect::JuliaSet {
        (init, c)
    } else {
        // Cardioid optimization boundary check
        let q = (init.re - 0.25) * (init.re - 0.25) + init.im * init.im;
        if q * (q + (init.re - 0.25)) < 0.25 * init.im * init.im {
            return (
                scan_iterations,
                Complex::new(0.0, 0.0),
                Complex::new(0.0, 0.0),
            );
        }
        // Period-2 bulb check
        if (init.re + 1.0) * (init.re + 1.0) + init.im * init.im < 0.0625 {
            return (
                scan_iterations,
                Complex::new(0.0, 0.0),
                Complex::new(0.0, 0.0),
            );
        }
        (Complex::new(0.0, 0.0), init)
    };

    let mut dz = if fractal_type == ProceduralEffect::JuliaSet {
        Complex::new(1.0, 0.0)
    } else {
        Complex::new(0.0, 0.0)
    };

    let mut i = 0;
    while i < scan_iterations {
        if z.norm_sq() > 4.0 {
            break;
        }

        let add_factor = if fractal_type == ProceduralEffect::JuliaSet {
            Complex::new(0.0, 0.0)
        } else {
            Complex::new(1.0, 0.0)
        };
        // Analytical boundary tracking: dz = 2 * z * dz + step_add
        dz = 2.0 * z * dz + add_factor;
        z = z * z + param;
        i += 1;
    }
    (i, z, dz)
}

/// Returns an iterator over the structural rotation angles as (radians, cosine, sine).
#[inline]
pub fn get_rotation_steps() -> impl Iterator<Item = (f64, f64, f64)> {
    (0..ROTATION_STEPS).map(|step| {
        let angle_deg = (step * 360 / ROTATION_STEPS) as f64;
        let rad = angle_deg.to_radians();
        (rad, rad.cos(), rad.sin())
    })
}

/// Rotates a 2D coordinate using precalculated sine and cosine values.
#[inline(always)]
pub fn rotate_point(x: f64, y: f64, cos_t: f64, sin_t: f64) -> (f64, f64) {
    let rx = x * cos_t + y * sin_t;
    let ry = -x * sin_t + y * cos_t;
    (rx, ry)
}

/// Viewport configuration parameters representing current camera scale and rotation orientation.
pub struct ViewportSpecs {
    /// Focal complex center point.
    pub center: Complex,
    /// Zoom translation scaling index.
    pub zoom: f64,
    /// Cosine of rotation angle.
    pub cos_angle: f64,
    /// Sine of rotation angle.
    pub sin_angle: f64,
    /// Toggle determining whether mapping centers relative to Julia origins.
    pub is_julia: bool,
}

/// Viewport mapper that transforms physical coordinate grids into complex space.
pub struct Viewport {
    pub start_re: f64,
    pub start_im: f64,
    pub dx_re: f64,
    pub dx_im: f64,
    pub dy_re: f64,
    pub dy_im: f64,
}

impl Viewport {
    /// Instantiates a new viewport converter using screen dimensions and camera orientation details.
    pub fn new(width: f64, height: f64, specs: &ViewportSpecs) -> Self {
        let min_dim = width.min(height);
        let scale = specs.zoom / min_dim;

        let cx_off = width / 2.0;
        let cy_off = height / 2.0;

        let dx_re = scale * specs.cos_angle;
        let dx_im = scale * specs.sin_angle;
        let dy_re = -scale * specs.sin_angle;
        let dy_im = scale * specs.cos_angle;

        let v_center = if specs.is_julia {
            Complex::new(0.0, 0.0)
        } else {
            specs.center
        };

        let start_re = v_center.re - cx_off * dx_re - cy_off * dy_re;
        let start_im = v_center.im - cx_off * dx_im - cy_off * dy_im;

        Self {
            start_re,
            start_im,
            dx_re,
            dx_im,
            dy_re,
            dy_im,
        }
    }

    /// Transforms physical screen coordinate coordinates (`x`, `y`) into a raw complex number coordinate.
    #[inline(always)]
    pub fn map(&self, x: f64, y: f64) -> Complex {
        let rx_row = self.start_re + y * self.dy_re;
        let ry_row = self.start_im + y * self.dy_im;
        let rx = rx_row + x * self.dx_re;
        let ry = ry_row + x * self.dx_im;
        Complex::new(rx, ry)
    }
}