wallswitch 0.60.1

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
use crate::{
    AuroraGenerator, JuliaGenerator, MandelbrotGenerator, Monitor, StarfieldGenerator,
    get_random_integer,
};
use clap::ValueEnum;
use image::RgbImage;
use serde::{Deserialize, Serialize};
use std::f32::consts::LOG2_E;

pub const MIN_ITERATIONS: u32 = 400;
pub const MAX_ITERATIONS: u32 = 990;

/// 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: f(z) = z^2 + c, where c is a fixed complex constant and the initial z varies.
    #[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 infinitely repeating miniature copies of
    /// the entire set connected by thin filaments.
    ///
    /// Creator: First visualized and defined by the Polish-born French-American mathematician
    /// BenoƮt Mandelbrot in 1980.
    ///
    /// Generator function: f(z) = z^2 + c, where the initial z is zero and the complex parameter c varies.
    ///
    /// Z(0) = 0
    ///
    /// Z(n+1) = Z(n)^2 + c, where c = x + y * i
    #[value(name = "mandelbrot")]
    Mandelbrot,

    /// Procedural Starfield / Bokeh generator.
    ///
    /// Characteristics: Projects soft, organic, circular glowing stars and light orbs of varying intensities and neon colors.
    ///
    /// Generator function: Calculated using smooth Gaussian light falloff curves centered on randomized coordinates: I(d) = exp(-d^2 / (2 * r^2)).
    #[value(name = "star")]
    Starfield,

    /// Procedural Cosmic Aurora wave generator.
    ///
    /// Characteristics: Generates glowing, wave-like atmospheric filaments with smooth edge transitions mimicking polar auroras.
    ///
    /// Generator function: Modeled using composite sinusoidal wave mathematics combined with radial coordinate dampening.
    #[value(name = "aurora")]
    CosmicAurora,

    /// Fractal mode selector.
    ///
    /// Characteristics: Randomly selects between the Julia Set or Mandelbrot Set contour line overlays for the 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::Starfield => "Starfield",
            Self::CosmicAurora => "Cosmic Aurora",
            Self::Fractal => "Fractal",
            Self::Random => "Random",
        }
    }

    /// Resolves the effect to a concrete rendering variant (resolving Random or Fractal if selected).
    pub fn resolve(self) -> Self {
        match self {
            Self::Random => match get_random_integer(0, 3) {
                0 => Self::JuliaSet,
                1 => Self::Starfield,
                2 => Self::CosmicAurora,
                _ => Self::Mandelbrot,
            },
            Self::Fractal => match get_random_integer(0, 1) {
                0 => Self::JuliaSet,
                _ => Self::Mandelbrot,
            },
            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 self.resolve() {
            Self::JuliaSet => Some(Box::new(JuliaGenerator::random(monitor))),
            Self::Mandelbrot => Some(Box::new(MandelbrotGenerator::random(monitor))),
            Self::Starfield => Some(Box::new(StarfieldGenerator::random(monitor))),
            Self::CosmicAurora => Some(Box::new(AuroraGenerator::random(monitor))),
            Self::None | Self::Fractal | Self::Random => None,
        }
    }
}

// ==============================================================================
// FRACTAL COORDINATE PRESETS
// ==============================================================================

/// Represents a mathematical coordinate preset for procedural fractal effects.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct FractalPreset {
    pub center_re: f64,
    pub center_im: f64,
    pub fractal_name: &'static str,
    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_re: f64, z_im: f64) -> f32 {
    if i < max_iterations {
        let mag2 = z_re * z_re + z_im * z_im;
        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_re: f64,
    z_im: f64,
    dz_re: f64,
    dz_im: f64,
) -> f64 {
    if i < max_iterations {
        let z_mag2 = z_re * z_re + z_im * z_im;
        let dz_mag2 = dz_re * dz_re + dz_im * dz_im;
        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 the standard dark-boundary artifacts of linear byte blending.
#[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,
/// applying the soft floating drop-shadow.
#[inline]
pub fn blend_and_vignette_pixel(
    row_data: &mut [u8],
    idx: usize,
    fractal_rgb: [f32; 3],
    alpha: f32,
    shadow_alpha: f32,
    dx_vignette: f32,
    dy_vignette_sq: f32,
) {
    // Soft Vignette Calculation
    let dist = (dx_vignette * dx_vignette + dy_vignette_sq).sqrt();
    let vignette = (1.0 - dist * 0.4).clamp(0.1, 1.0);

    let original_r = row_data[idx];
    let original_g = row_data[idx + 1];
    let original_b = row_data[idx + 2];

    // 1. Aplica a sombra suave (Ambient Occlusion) diretamente ao fundo primeiro
    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. Mescla o neon brilhante por cima do fundo jĆ” sombreado
    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 as f32 * vignette).clamp(0.0, 255.0) as u8;
        row_data[idx + 1] = (blended_g as f32 * vignette).clamp(0.0, 255.0) as u8;
        row_data[idx + 2] = (blended_b as f32 * vignette).clamp(0.0, 255.0) as u8;
    } else if shadow_alpha > 0.005 {
        // Se houver apenas a sombra (borda externa), aplica a vinheta sobre ela
        row_data[idx] = (background_r as f32 * vignette).clamp(0.0, 255.0) as u8;
        row_data[idx + 1] = (background_g as f32 * vignette).clamp(0.0, 255.0) as u8;
        row_data[idx + 2] = (background_b as f32 * vignette).clamp(0.0, 255.0) as u8;
    } else {
        // Pixel de fundo intacto
        row_data[idx] = (original_r as f32 * vignette).clamp(0.0, 255.0) as u8;
        row_data[idx + 1] = (original_g as f32 * vignette).clamp(0.0, 255.0) as u8;
        row_data[idx + 2] = (original_b as f32 * vignette).clamp(0.0, 255.0) as u8;
    }
}

/// Executes complex escape-time steps for both Julia and Mandelbrot fractal types.
#[inline(always)]
pub fn compute_escape_iterations(
    fractal_type: ProceduralEffect,
    rx: f64,
    ry: f64,
    c_re: f64,
    c_im: f64,
    scan_iterations: u32,
) -> (u32, f64, f64, f64, f64) {
    let (mut z_re, mut z_im, param_re, param_im) = if fractal_type == ProceduralEffect::JuliaSet {
        (rx, ry, c_re, c_im)
    } else {
        let q = (rx - 0.25) * (rx - 0.25) + ry * ry;
        if q * (q + (rx - 0.25)) < 0.25 * ry * ry {
            return (scan_iterations, 0.0, 0.0, 0.0, 0.0);
        }
        if (rx + 1.0) * (rx + 1.0) + ry * ry < 0.0625 {
            return (scan_iterations, 0.0, 0.0, 0.0, 0.0);
        }
        (0.0, 0.0, rx, ry)
    };

    let (mut dz_re, mut dz_im) = if fractal_type == ProceduralEffect::JuliaSet {
        (1.0, 0.0)
    } else {
        (0.0, 0.0)
    };

    let mut i = 0;
    while i < scan_iterations {
        let re2 = z_re * z_re;
        let im2 = z_im * z_im;
        if re2 + im2 > 4.0 {
            break;
        }

        let next_dz_re = 2.0 * (z_re * dz_re - z_im * dz_im)
            + if fractal_type == ProceduralEffect::JuliaSet {
                0.0
            } else {
                1.0
            };
        let next_dz_im = 2.0 * (z_re * dz_im + z_im * dz_re);
        dz_re = next_dz_re;
        dz_im = next_dz_im;

        z_im = 2.0 * z_re * z_im + param_im;
        z_re = re2 - im2 + param_re;
        i += 1;
    }
    (i, z_re, z_im, dz_re, dz_im)
}

/// 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)
}

pub struct ViewportSpecs {
    pub center_re: f64,
    pub center_im: f64,
    pub zoom: f64,
    pub cos_angle: f64,
    pub sin_angle: f64,
    pub is_julia: bool,
}

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 {
    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_re, v_center_im) = if specs.is_julia {
            (0.0, 0.0)
        } else {
            (specs.center_re, specs.center_im)
        };

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

    #[inline(always)]
    pub fn map(&self, x: f64, y: f64) -> (f64, f64) {
        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;
        (rx, ry)
    }
}