Skip to main content

mandelbruhst_cli/
opts.rs

1use crate::config::Configuration;
2use crate::mandelbrot::Complex;
3use crate::mandelbrot::{generate_escape_counts, generate_hist_counts, normalise_escape_counts};
4use crate::palette::ColorPalette;
5use anyhow::Result;
6use clap::{Parser, Subcommand, ValueEnum};
7
8static PALETTE_HELP: &str = "color palette to use in output image; 
9defaults include electric, warm, and greyscale;
10palettes can be added in ~/.config/mandelbrot-rs/config.ron";
11
12#[derive(Parser, Debug)]
13#[command(author, version, about, long_about = None)]
14pub struct Cli {
15    /// filepath to save the output image to
16    #[arg(short, long, default_value = "mandelbrot.png")]
17    pub out_file: String,
18    /// number of iterations to perform before deciding if a point is in the set
19    #[arg(short, long, default_value_t = 2000)]
20    max_iters: usize,
21    /// bailout radius for iterations
22    #[arg(short, long, default_value_t = 1e6)]
23    bailout: f64,
24    /// resolution of the output image.
25    #[arg(short, long, value_enum, default_value_t = Resolution::High)]
26    pub resolution: Resolution,
27    #[arg(
28        short,
29        long,
30        value_enum,
31        default_value = "electric",
32        help = PALETTE_HELP)]
33    pub palette: String,
34    /// algorithm to plot the image using
35    #[arg(short, long, value_enum, default_value_t = PlottingAlgorithm::Histogram)]
36    algorithm: PlottingAlgorithm,
37    #[command(subcommand)]
38    command: Commands,
39}
40
41impl Cli {
42    pub fn get_hue_array(&self) -> Result<Vec<Vec<f64>>> {
43        let (width, height): (usize, usize) = self.resolution.to_dimensions();
44        let config: Configuration = confy::load("mandelbrot-rs", "config")?;
45        let (centre, zoom) = match &self.command {
46            &Commands::Centre { x, y, zoom } => (Complex::new(x, y), zoom as f64),
47            Commands::CentreString { name } => {
48                let centre = config.get_named_point(&name)?;
49                (centre.point, centre.zoom as f64)
50            }
51        };
52        let (x_range, y_range) = get_intervals(centre, zoom);
53
54        let post_fn: Box<dyn Fn(usize, Complex) -> f64 + std::marker::Sync> = match self.algorithm {
55            // for non-smooth, return identity
56            PlottingAlgorithm::Histogram | PlottingAlgorithm::Vanilla => {
57                Box::new(|escape_count, _| escape_count as f64)
58            }
59            // for smooth, do some cool maths shit
60            PlottingAlgorithm::Smooth | PlottingAlgorithm::SmoothHistogram => {
61                Box::new(|escape_count, escape_val| {
62                    if escape_count < self.max_iters {
63                        let nu = (escape_val.abs_value_sq().ln() / 2.).log2();
64                        (escape_count + 1) as f64 - nu
65                    } else {
66                        self.max_iters as f64
67                    }
68                })
69            }
70        };
71
72        let escape_counts = generate_escape_counts(
73            &x_range,
74            &y_range,
75            width,
76            height,
77            self.max_iters,
78            self.bailout,
79            post_fn,
80        );
81
82        Ok(match self.algorithm {
83            PlottingAlgorithm::Vanilla | PlottingAlgorithm::Smooth => {
84                normalise_escape_counts(&escape_counts, self.max_iters)
85            }
86            PlottingAlgorithm::Histogram | PlottingAlgorithm::SmoothHistogram => {
87                generate_hist_counts(&escape_counts, self.max_iters, width * height)
88            }
89        })
90    }
91
92    pub fn get_palette(&self) -> Result<ColorPalette> {
93        let config: Configuration = confy::load("mandelbrot-rs", "config.ron")?;
94        config.get_palette(&self.palette).map(|p| p.clone())
95    }
96}
97
98#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
99pub enum Resolution {
100    Low,
101    Med,
102    High,
103}
104
105impl Resolution {
106    pub fn to_dimensions(self) -> (usize, usize) {
107        match self {
108            Resolution::Low => (320, 180),
109            Resolution::Med => (960, 540),
110            Resolution::High => (1920, 1080),
111        }
112    }
113}
114
115#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
116pub enum PlottingAlgorithm {
117    Vanilla,
118    Smooth,
119    Histogram,
120    SmoothHistogram,
121}
122
123#[derive(Subcommand, Debug)]
124enum Commands {
125    Centre {
126        x: f64,
127        y: f64,
128        #[arg(short, long, default_value_t = 8)]
129        zoom: usize,
130    },
131    CentreString {
132        /// name of corresponding NamedPoint in config.yaml
133        name: String,
134    },
135}
136
137#[derive(Debug)]
138pub struct Interval {
139    pub lower: f64,
140    pub upper: f64,
141}
142
143impl Interval {
144    pub fn lerp(&self, frac: f64) -> f64 {
145        self.lower + (self.upper - self.lower) * frac
146    }
147}
148
149fn get_intervals(centre: Complex, zoom: f64) -> (Interval, Interval) {
150    return (
151        Interval {
152            lower: centre.re - 16. / zoom,
153            upper: centre.re + 16. / zoom,
154        },
155        Interval {
156            lower: centre.im - 9. / zoom,
157            upper: centre.im + 9. / zoom,
158        },
159    );
160}