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 #[arg(short, long, default_value = "mandelbrot.png")]
17 pub out_file: String,
18 #[arg(short, long, default_value_t = 2000)]
20 max_iters: usize,
21 #[arg(short, long, default_value_t = 1e6)]
23 bailout: f64,
24 #[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 #[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 PlottingAlgorithm::Histogram | PlottingAlgorithm::Vanilla => {
57 Box::new(|escape_count, _| escape_count as f64)
58 }
59 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: 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}